Copy smearing method to BatteryStatsHelper
am: 5dad8068e2 -s ours
Change-Id: I07d9a563f224d739e734af92ae5078d8c6f41100
diff --git a/Android.bp b/Android.bp
index 81d6dab..33f3213 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12,8 +12,48 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+// ==== c++ proto device library ==============================
+cc_library {
+ name: "libplatformprotos",
+ host_supported: true,
+ // b/34740546, work around clang-tidy segmentation fault.
+ tidy_checks: ["-modernize*"],
+ proto: {
+ export_proto_headers: true,
+ include_dirs: ["external/protobuf/src"],
+ },
+
+ target: {
+ host: {
+ proto: {
+ type: "full",
+ },
+ srcs: [
+ "core/proto/**/*.proto",
+ "libs/incident/**/*.proto",
+ ],
+ },
+ android: {
+ proto: {
+ type: "lite",
+ },
+ // We only build the protos that are optimized for the lite
+ // runtime, as well as the only protos that are actually
+ // needed by the device.
+ srcs: [
+ "core/proto/android/service/graphicsstats.proto",
+ ],
+ shared: {
+ enabled: false,
+ },
+ },
+ },
+}
+
subdirs = [
+ "core/jni",
"libs/*",
+ "media/*",
"tools/*",
"native/android",
"native/graphics/jni",
diff --git a/Android.mk b/Android.mk
index 3f2ae83..00dc784 100644
--- a/Android.mk
+++ b/Android.mk
@@ -114,6 +114,8 @@
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/timezone/ICallback.aidl \
+ core/java/android/app/timezone/IRulesManager.aidl \
core/java/android/app/usage/ICacheQuotaService.aidl \
core/java/android/app/usage/IStorageStatsManager.aidl \
core/java/android/app/usage/IUsageStatsManager.aidl \
@@ -157,6 +159,7 @@
core/java/android/content/ISyncServiceAdapter.aidl \
core/java/android/content/ISyncStatusObserver.aidl \
core/java/android/content/om/IOverlayManager.aidl \
+ core/java/android/content/pm/IDexModuleRegisterCallback.aidl \
core/java/android/content/pm/ILauncherApps.aidl \
core/java/android/content/pm/IOnAppsChangedListener.aidl \
core/java/android/content/pm/IOnPermissionsChangeListener.aidl \
@@ -215,6 +218,9 @@
core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl \
core/java/android/hardware/location/IContextHubCallback.aidl \
core/java/android/hardware/location/IContextHubService.aidl \
+ core/java/android/hardware/radio/IRadioService.aidl \
+ core/java/android/hardware/radio/ITuner.aidl \
+ core/java/android/hardware/radio/ITunerCallback.aidl \
core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl \
core/java/android/hardware/usb/IUsbManager.aidl \
core/java/android/net/ICaptivePortal.aidl \
@@ -277,6 +283,17 @@
core/java/android/service/carrier/ICarrierService.aidl \
core/java/android/service/carrier/ICarrierMessagingCallback.aidl \
core/java/android/service/carrier/ICarrierMessagingService.aidl \
+ core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl \
+ core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl \
+ core/java/android/service/euicc/IEraseSubscriptionsCallback.aidl \
+ core/java/android/service/euicc/IEuiccService.aidl \
+ core/java/android/service/euicc/IGetDefaultDownloadableSubscriptionListCallback.aidl \
+ core/java/android/service/euicc/IGetDownloadableSubscriptionMetadataCallback.aidl \
+ core/java/android/service/euicc/IGetEidCallback.aidl \
+ core/java/android/service/euicc/IGetEuiccInfoCallback.aidl \
+ core/java/android/service/euicc/IGetEuiccProfileInfoListCallback.aidl \
+ core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl \
+ core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl \
core/java/android/service/gatekeeper/IGateKeeperService.aidl \
core/java/android/service/notification/INotificationListener.aidl \
core/java/android/service/notification/IStatusBarNotificationHolder.aidl \
@@ -368,6 +385,7 @@
core/java/com/android/internal/appwidget/IAppWidgetHost.aidl \
core/java/com/android/internal/backup/IBackupTransport.aidl \
core/java/com/android/internal/backup/IObbBackupService.aidl \
+ core/java/com/android/internal/car/ICarServiceHelper.aidl \
core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl \
core/java/com/android/internal/policy/IKeyguardDrawnCallback.aidl \
core/java/com/android/internal/policy/IKeyguardDismissCallback.aidl \
@@ -482,6 +500,12 @@
telecomm/java/com/android/internal/telecom/IInCallService.aidl \
telecomm/java/com/android/internal/telecom/ITelecomService.aidl \
telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl \
+ telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl \
+ telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl \
+ telephony/java/android/telephony/mbms/IDownloadCallback.aidl \
+ telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl \
+ telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl \
+ telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl \
telephony/java/com/android/ims/internal/IImsCallSession.aidl \
telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl \
telephony/java/com/android/ims/internal/IImsConfig.aidl \
@@ -506,6 +530,7 @@
telephony/java/com/android/ims/internal/uce/presence/IPresenceService.aidl \
telephony/java/com/android/ims/internal/uce/presence/IPresenceListener.aidl \
telephony/java/com/android/ims/ImsConfigListener.aidl \
+ telephony/java/com/android/internal/telephony/IApnSourceService.aidl \
telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl \
telephony/java/com/android/internal/telephony/IMms.aidl \
telephony/java/com/android/internal/telephony/IOnSubscriptionsChangedListener.aidl \
@@ -516,6 +541,7 @@
telephony/java/com/android/internal/telephony/ITelephony.aidl \
telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl \
telephony/java/com/android/internal/telephony/IWapPushManager.aidl \
+ telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl \
wifi/java/android/net/wifi/IWifiManager.aidl \
wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl \
wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl \
@@ -541,6 +567,15 @@
LOCAL_AIDL_INCLUDES += system/update_engine/binder_bindings
+LOCAL_AIDL_INCLUDES += frameworks/base/lowpan/java
+LOCAL_SRC_FILES += \
+ lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl \
+ lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl \
+ lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl \
+ lowpan/java/android/net/lowpan/ILowpanInterface.aidl \
+ lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl \
+ lowpan/java/android/net/lowpan/ILowpanManager.aidl
+
# FRAMEWORKS_BASE_JAVA_SRC_DIRS comes from build/core/pathmap.mk
LOCAL_AIDL_INCLUDES += \
$(FRAMEWORKS_BASE_JAVA_SRC_DIRS) \
@@ -572,7 +607,10 @@
android.hardware.thermal-V1.0-java-constants \
android.hardware.tv.input-V1.0-java-constants \
android.hardware.usb-V1.0-java-constants \
+ android.hardware.usb-V1.1-java-constants \
android.hardware.vibrator-V1.0-java-constants \
+ android.hardware.vibrator-V1.1-java-constants \
+ android.hardware.wifi-V1.0-java-constants \
# Loaded with System.loadLibrary by android.view.textclassifier
LOCAL_REQUIRED_MODULES += libtextclassifier
@@ -605,6 +643,12 @@
include $(CLEAR_VARS)
aidl_files := \
+ frameworks/base/telephony/java/android/telephony/mbms/DownloadRequest.aidl \
+ frameworks/base/telephony/java/android/telephony/mbms/DownloadStatus.aidl \
+ frameworks/base/telephony/java/android/telephony/mbms/FileInfo.aidl \
+ frameworks/base/telephony/java/android/telephony/mbms/FileServiceInfo.aidl \
+ frameworks/base/telephony/java/android/telephony/mbms/ServiceInfo.aidl \
+ frameworks/base/telephony/java/android/telephony/mbms/StreamingServiceInfo.aidl \
frameworks/base/telephony/java/android/telephony/ServiceState.aidl \
frameworks/base/telephony/java/android/telephony/SubscriptionInfo.aidl \
frameworks/base/telephony/java/android/telephony/CellInfo.aidl \
@@ -612,6 +656,9 @@
frameworks/base/telephony/java/android/telephony/IccOpenLogicalChannelResponse.aidl \
frameworks/base/telephony/java/android/telephony/NeighboringCellInfo.aidl \
frameworks/base/telephony/java/android/telephony/ModemActivityInfo.aidl \
+ frameworks/base/telephony/java/android/telephony/UiccAccessRule.aidl \
+ frameworks/base/telephony/java/android/telephony/euicc/DownloadableSubscription.aidl \
+ frameworks/base/telephony/java/android/telephony/euicc/EuiccInfo.aidl \
frameworks/base/location/java/android/location/Location.aidl \
frameworks/base/location/java/android/location/Address.aidl \
frameworks/base/location/java/android/location/Criteria.aidl \
@@ -665,6 +712,7 @@
frameworks/base/core/java/android/print/PrinterInfo.aidl \
frameworks/base/core/java/android/print/PrintJobId.aidl \
frameworks/base/core/java/android/printservice/recommendation/RecommendationInfo.aidl \
+ frameworks/base/core/java/android/hardware/radio/RadioManager.aidl \
frameworks/base/core/java/android/hardware/usb/UsbDevice.aidl \
frameworks/base/core/java/android/hardware/usb/UsbInterface.aidl \
frameworks/base/core/java/android/hardware/usb/UsbEndpoint.aidl \
@@ -724,6 +772,14 @@
frameworks/base/core/java/android/view/textservice/SuggestionsInfo.aidl \
frameworks/base/core/java/android/service/carrier/CarrierIdentifier.aidl \
frameworks/base/core/java/android/service/carrier/MessagePdu.aidl \
+ frameworks/base/core/java/android/service/euicc/DeleteResult.aidl \
+ frameworks/base/core/java/android/service/euicc/DownloadResult.aidl \
+ frameworks/base/core/java/android/service/euicc/EraseResult.aidl \
+ frameworks/base/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.aidl \
+ frameworks/base/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.aidl \
+ frameworks/base/core/java/android/service/euicc/GetEuiccProfileInfoListResult.aidl \
+ frameworks/base/core/java/android/service/euicc/SwitchResult.aidl \
+ frameworks/base/core/java/android/service/euicc/UpdateNicknameResult.aidl \
frameworks/base/core/java/android/service/notification/Adjustment.aidl \
frameworks/base/core/java/android/service/notification/Condition.aidl \
frameworks/base/core/java/android/service/notification/SnoozeCriterion.aidl \
@@ -834,7 +890,6 @@
include libcore/Docs.mk
non_base_dirs := \
- ../opt/telephony/src/java/android/provider \
../opt/telephony/src/java/android/telephony \
../opt/telephony/src/java/android/telephony/gsm \
../opt/net/voip/src/java/android/net/rtp \
@@ -1450,35 +1505,6 @@
include $(BUILD_JAVA_LIBRARY)
-# ==== c++ proto device library ==============================
-include $(CLEAR_VARS)
-LOCAL_MODULE := libplatformprotos
-# b/34740546, work around clang-tidy segmentation fault.
-LOCAL_TIDY_CHECKS := -modernize*
-LOCAL_PROTOC_OPTIMIZE_TYPE := lite
-LOCAL_PROTOC_FLAGS := \
- --include_source_info \
- -Iexternal/protobuf/src
-LOCAL_SRC_FILES := \
- $(call all-proto-files-under, core/proto) \
- $(call all-proto-files-under, libs/incident/proto)
-include $(BUILD_STATIC_LIBRARY)
-
-# ==== c++ proto host library ==============================
-include $(CLEAR_VARS)
-LOCAL_MODULE := libplatformprotos
-# b/34740546, work around clang-tidy segmentation fault.
-LOCAL_TIDY_CHECKS := -modernize*
-LOCAL_PROTOC_OPTIMIZE_TYPE := full
-LOCAL_PROTOC_FLAGS := \
- --include_source_info \
- -Iexternal/protobuf/src
-LOCAL_SRC_FILES := \
- $(call all-proto-files-under, core/proto) \
- $(call all-proto-files-under, libs/incident/proto)
-include $(BUILD_HOST_SHARED_LIBRARY)
-
-
# ==== java proto host library ==============================
include $(CLEAR_VARS)
LOCAL_MODULE := platformprotos
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 75de4a7..50a5974 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,14 +1,7 @@
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
- -fw core/java/android/animation/
- core/java/android/hardware/usb/
- core/java/android/print/
- core/java/android/printservice/
- core/java/android/text/
- core/java/android/transition/
- core/java/android/view/
- core/java/android/widget/
- core/tests/coretests/src/android/print/
+ -fw core/java/android/
+ core/tests/coretests/src/android/
packages/PrintRecommendationService/
packages/PrintSpooler/
services/print/
diff --git a/apct-tests/perftests/core/src/android/os/SharedPreferencesTest.java b/apct-tests/perftests/core/src/android/os/SharedPreferencesTest.java
new file mode 100644
index 0000000..099134f
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/SharedPreferencesTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.os;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class SharedPreferencesTest {
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Test
+ public void timeCachedGetSharedPreferences() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final Context context = InstrumentationRegistry.getTargetContext();
+ // Do the real work once as we're only interested in cache-hit performance
+ SharedPreferences prefs = context.getSharedPreferences("test", Context.MODE_PRIVATE);
+ while (state.keepRunning()) {
+ prefs = context.getSharedPreferences("test", Context.MODE_PRIVATE);
+ }
+ }
+}
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTest.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTest.java
index f56c763..e89157b 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTest.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTest.java
@@ -56,20 +56,8 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
public class UserLifecycleTest {
- private final int TIMEOUT_REMOVE_USER_MS = 4 * 1000; // 4 sec
- private final int CHECK_USER_REMOVED_INTERVAL_MS = 200; // 0.2 sec
-
- private final int TIMEOUT_USER_START_SEC = 4; // 4 sec
-
- private final int TIMEOUT_USER_SWITCH_SEC = 8; // 8 sec
-
- private final int TIMEOUT_USER_STOP_SEC = 1; // 1 sec
-
- private final int TIMEOUT_MANAGED_PROFILE_UNLOCK_SEC = 2; // 2 sec
-
- private final int TIMEOUT_LOCKED_BOOT_COMPLETE_MS = 5 * 1000; // 5 sec
-
- private final int TIMEOUT_EPHERMAL_USER_STOP_SEC = 6; // 6 sec
+ private final int TIMEOUT_IN_SECOND = 10;
+ private final int CHECK_USER_REMOVED_INTERVAL_MS = 200;
private UserManager mUm;
private ActivityManager mAm;
@@ -109,7 +97,7 @@
final CountDownLatch latch = new CountDownLatch(1);
registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch, userInfo.id);
mIam.startUserInBackground(userInfo.id);
- latch.await(TIMEOUT_USER_START_SEC, TimeUnit.SECONDS);
+ latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
mState.pauseTiming();
removeUser(userInfo.id);
@@ -142,10 +130,10 @@
final CountDownLatch latch = new CountDownLatch(1);
registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch, userInfo.id);
mIam.startUserInBackground(userInfo.id);
- latch.await(TIMEOUT_USER_START_SEC, TimeUnit.SECONDS);
+ latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
mState.resumeTiming();
- stopUser(userInfo.id);
+ stopUser(userInfo.id, false);
mState.pauseTiming();
removeUser(userInfo.id);
@@ -164,7 +152,7 @@
mState.resumeTiming();
mAm.switchUser(userInfo.id);
- latch.await(TIMEOUT_LOCKED_BOOT_COMPLETE_MS, TimeUnit.SECONDS);
+ latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
mState.pauseTiming();
switchUser(startUser);
@@ -184,7 +172,7 @@
mState.resumeTiming();
mIam.startUserInBackground(userInfo.id);
- latch.await(TIMEOUT_MANAGED_PROFILE_UNLOCK_SEC, TimeUnit.SECONDS);
+ latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
mState.pauseTiming();
removeUser(userInfo.id);
@@ -215,10 +203,30 @@
mState.resumeTiming();
mAm.switchUser(startUser);
- latch.await(TIMEOUT_EPHERMAL_USER_STOP_SEC, TimeUnit.SECONDS);
+ latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
mState.pauseTiming();
- switchLatch.await(TIMEOUT_USER_SWITCH_SEC, TimeUnit.SECONDS);
+ switchLatch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
+ removeUser(userInfo.id);
+ mState.resumeTiming();
+ }
+ }
+
+ @Test
+ public void managedProfileStoppedPerf() throws Exception {
+ while (mState.keepRunning()) {
+ mState.pauseTiming();
+ final UserInfo userInfo = mUm.createProfileForUser("TestUser",
+ UserInfo.FLAG_MANAGED_PROFILE, mAm.getCurrentUser());
+ final CountDownLatch latch = new CountDownLatch(1);
+ registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch, userInfo.id);
+ mIam.startUserInBackground(userInfo.id);
+ latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
+ mState.resumeTiming();
+
+ stopUser(userInfo.id, true);
+
+ mState.pauseTiming();
removeUser(userInfo.id);
mState.resumeTiming();
}
@@ -228,12 +236,12 @@
final CountDownLatch latch = new CountDownLatch(1);
registerUserSwitchObserver(latch, null, userId);
mAm.switchUser(userId);
- latch.await(TIMEOUT_USER_SWITCH_SEC, TimeUnit.SECONDS);
+ latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
}
- private void stopUser(int userId) throws Exception {
+ private void stopUser(int userId, boolean force) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
- mIam.stopUser(userId, false /* force */, new IStopUserCallback.Stub() {
+ mIam.stopUser(userId, force /* force */, new IStopUserCallback.Stub() {
@Override
public void userStopped(int userId) throws RemoteException {
latch.countDown();
@@ -243,7 +251,7 @@
public void userStopAborted(int userId) throws RemoteException {
}
});
- latch.await(TIMEOUT_USER_STOP_SEC, TimeUnit.SECONDS);
+ latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
}
private void registerUserSwitchObserver(final CountDownLatch switchLatch,
@@ -283,9 +291,10 @@
try {
mUm.removeUser(userId);
final long startTime = System.currentTimeMillis();
+ final long timeoutInMs = TIMEOUT_IN_SECOND * 1000;
while (mUm.getUserInfo(userId) != null &&
- System.currentTimeMillis() - startTime < TIMEOUT_REMOVE_USER_MS) {
- Thread.sleep(CHECK_USER_REMOVED_INTERVAL_MS);
+ System.currentTimeMillis() - startTime < timeoutInMs) {
+ TimeUnit.MILLISECONDS.sleep(CHECK_USER_REMOVED_INTERVAL_MS);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
diff --git a/api/current.txt b/api/current.txt
index ac97b54..a71c63a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6087,6 +6087,17 @@
method public void onDetached();
}
+ public final class WallpaperColors implements android.os.Parcelable {
+ ctor public WallpaperColors(android.os.Parcel);
+ ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>);
+ ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean);
+ method public int describeContents();
+ method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors();
+ method public boolean supportsDarkText();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR;
+ }
+
public final class WallpaperInfo implements android.os.Parcelable {
ctor public WallpaperInfo(android.content.Context, android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public int describeContents();
@@ -6109,6 +6120,8 @@
}
public class WallpaperManager {
+ method public void addOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener);
+ method public void addOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener, android.os.Handler);
method public void clear() throws java.io.IOException;
method public void clear(int) throws java.io.IOException;
method public void clearWallpaperOffsets(android.os.IBinder);
@@ -6123,6 +6136,7 @@
method public android.graphics.drawable.Drawable getDrawable();
method public android.graphics.drawable.Drawable getFastDrawable();
method public static android.app.WallpaperManager getInstance(android.content.Context);
+ method public android.app.WallpaperColors getWallpaperColors(int);
method public android.os.ParcelFileDescriptor getWallpaperFile(int);
method public int getWallpaperId(int);
method public android.app.WallpaperInfo getWallpaperInfo();
@@ -6131,6 +6145,7 @@
method public boolean isWallpaperSupported();
method public android.graphics.drawable.Drawable peekDrawable();
method public android.graphics.drawable.Drawable peekFastDrawable();
+ method public void removeOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener);
method public void sendWallpaperCommand(android.os.IBinder, java.lang.String, int, int, int, android.os.Bundle);
method public void setBitmap(android.graphics.Bitmap) throws java.io.IOException;
method public int setBitmap(android.graphics.Bitmap, android.graphics.Rect, boolean) throws java.io.IOException;
@@ -6155,6 +6170,10 @@
field public static final java.lang.String WALLPAPER_PREVIEW_META_DATA = "android.wallpaper.preview";
}
+ public static abstract interface WallpaperManager.OnColorsChangedListener {
+ method public abstract void onColorsChanged(android.app.WallpaperColors, int);
+ }
+
}
package android.app.admin {
@@ -10693,6 +10712,7 @@
field public static final java.lang.String FEATURE_NFC_HOST_CARD_EMULATION = "android.hardware.nfc.hce";
field public static final java.lang.String FEATURE_NFC_HOST_CARD_EMULATION_NFCF = "android.hardware.nfc.hcef";
field public static final java.lang.String FEATURE_OPENGLES_EXTENSION_PACK = "android.hardware.opengles.aep";
+ field public static final java.lang.String FEATURE_PC = "android.hardware.type.pc";
field public static final java.lang.String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
field public static final java.lang.String FEATURE_PRINTING = "android.software.print";
field public static final java.lang.String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape";
@@ -10734,6 +10754,7 @@
field public static final java.lang.String FEATURE_WIFI = "android.hardware.wifi";
field public static final java.lang.String FEATURE_WIFI_AWARE = "android.hardware.wifi.aware";
field public static final java.lang.String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
+ field public static final java.lang.String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint";
field public static final int GET_ACTIVITIES = 1; // 0x1
field public static final int GET_CONFIGURATIONS = 16384; // 0x4000
field public static final deprecated int GET_DISABLED_COMPONENTS = 512; // 0x200
@@ -37562,10 +37583,12 @@
method public int getDesiredMinimumHeight();
method public int getDesiredMinimumWidth();
method public android.view.SurfaceHolder getSurfaceHolder();
+ method public void invalidateColors();
method public boolean isPreview();
method public boolean isVisible();
method public void onApplyWindowInsets(android.view.WindowInsets);
method public android.os.Bundle onCommand(java.lang.String, int, int, int, android.os.Bundle, boolean);
+ method public android.app.WallpaperColors onComputeWallpaperColors();
method public void onCreate(android.view.SurfaceHolder);
method public void onDesiredSizeChanged(int, int);
method public void onDestroy();
@@ -38650,7 +38673,8 @@
public static final class Call.RttCall {
method public int getRttAudioMode();
- method public java.lang.String read();
+ method public java.lang.String read() throws java.io.IOException;
+ method public java.lang.String readImmediately() throws java.io.IOException;
method public void setRttMode(int);
method public void write(java.lang.String) throws java.io.IOException;
field public static final int RTT_MODE_FULL = 1; // 0x1
@@ -43912,6 +43936,7 @@
field public static final int KEYBOARD_TAP = 3; // 0x3
field public static final int LONG_PRESS = 0; // 0x0
field public static final int VIRTUAL_KEY = 1; // 0x1
+ field public static final int VIRTUAL_KEY_RELEASE = 7; // 0x7
}
public class InflateException extends java.lang.RuntimeException {
@@ -43940,6 +43965,7 @@
method public android.os.Vibrator getVibrator();
method public boolean[] hasKeys(int...);
method public boolean hasMicrophone();
+ method public boolean isEnabled();
method public boolean isVirtual();
method public boolean supportsSource(int);
method public void writeToParcel(android.os.Parcel, int);
@@ -46929,6 +46955,7 @@
}
public final class AccessibilityManager {
+ method public void addAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
method public boolean addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
method public void addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, android.os.Handler);
method public boolean addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
@@ -46939,6 +46966,7 @@
method public void interrupt();
method public boolean isEnabled();
method public boolean isTouchExplorationEnabled();
+ method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
method public boolean removeAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
method public boolean removeTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
@@ -47255,6 +47283,13 @@
method public void setToIndex(int);
}
+ public abstract class AccessibilityRequestPreparer {
+ ctor public AccessibilityRequestPreparer(android.view.View, int);
+ method public android.view.View getView();
+ method public abstract void onPrepareExtraData(int, java.lang.String, android.os.Bundle, android.os.Message);
+ field public static final int REQUEST_TYPE_EXTRA_DATA = 1; // 0x1
+ }
+
public final class AccessibilityWindowInfo implements android.os.Parcelable {
method public int describeContents();
method public android.view.accessibility.AccessibilityNodeInfo getAnchor();
@@ -48776,6 +48811,7 @@
method public void goBack();
method public void goBackOrForward(int);
method public void goForward();
+ method public static void initSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean>);
method public void invokeZoomPicker();
method public boolean isPrivateBrowsingEnabled();
method public void loadData(java.lang.String, java.lang.String, java.lang.String);
@@ -48820,6 +48856,7 @@
method public static void setWebContentsDebuggingEnabled(boolean);
method public void setWebViewClient(android.webkit.WebViewClient);
method public deprecated boolean showFindDialog(java.lang.String, boolean);
+ method public static void shutdownSafeBrowsing();
method public void stopLoading();
method public void zoomBy(float);
method public boolean zoomIn();
@@ -69809,6 +69846,7 @@
public abstract class SSLSocketFactory extends javax.net.SocketFactory {
ctor public SSLSocketFactory();
method public abstract java.net.Socket createSocket(java.net.Socket, java.lang.String, int, boolean) throws java.io.IOException;
+ method public java.net.Socket createSocket(java.net.Socket, java.io.InputStream, boolean) throws java.io.IOException;
method public static synchronized javax.net.SocketFactory getDefault();
method public abstract java.lang.String[] getDefaultCipherSuites();
method public abstract java.lang.String[] getSupportedCipherSuites();
diff --git a/api/system-current.txt b/api/system-current.txt
index a2c2771..fbe2109 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6297,6 +6297,17 @@
method public void setPersistentVrModeEnabled(boolean);
}
+ public final class WallpaperColors implements android.os.Parcelable {
+ ctor public WallpaperColors(android.os.Parcel);
+ ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>);
+ ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean);
+ method public int describeContents();
+ method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors();
+ method public boolean supportsDarkText();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR;
+ }
+
public final class WallpaperInfo implements android.os.Parcelable {
ctor public WallpaperInfo(android.content.Context, android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public int describeContents();
@@ -6319,6 +6330,8 @@
}
public class WallpaperManager {
+ method public void addOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener);
+ method public void addOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener, android.os.Handler);
method public void clear() throws java.io.IOException;
method public void clear(int) throws java.io.IOException;
method public void clearWallpaper();
@@ -6335,6 +6348,7 @@
method public android.graphics.drawable.Drawable getDrawable();
method public android.graphics.drawable.Drawable getFastDrawable();
method public static android.app.WallpaperManager getInstance(android.content.Context);
+ method public android.app.WallpaperColors getWallpaperColors(int);
method public android.os.ParcelFileDescriptor getWallpaperFile(int);
method public int getWallpaperId(int);
method public android.app.WallpaperInfo getWallpaperInfo();
@@ -6343,6 +6357,7 @@
method public boolean isWallpaperSupported();
method public android.graphics.drawable.Drawable peekDrawable();
method public android.graphics.drawable.Drawable peekFastDrawable();
+ method public void removeOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener);
method public void sendWallpaperCommand(android.os.IBinder, java.lang.String, int, int, int, android.os.Bundle);
method public void setBitmap(android.graphics.Bitmap) throws java.io.IOException;
method public int setBitmap(android.graphics.Bitmap, android.graphics.Rect, boolean) throws java.io.IOException;
@@ -6370,6 +6385,10 @@
field public static final java.lang.String WALLPAPER_PREVIEW_META_DATA = "android.wallpaper.preview";
}
+ public static abstract interface WallpaperManager.OnColorsChangedListener {
+ method public abstract void onColorsChanged(android.app.WallpaperColors, int);
+ }
+
}
package android.app.admin {
@@ -6660,6 +6679,7 @@
field public static final java.lang.String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
field public static final java.lang.String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
field public static final java.lang.String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
+ field public static final java.lang.String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
field public static final java.lang.String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
field public static final java.lang.String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
field public static final java.lang.String DELEGATION_BLOCK_UNINSTALL = "delegation-block-uninstall";
@@ -11316,6 +11336,7 @@
method public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(android.content.Intent, int);
method public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentServices(android.content.Intent, int);
method public abstract java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ 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 abstract deprecated void removePackageFromPreferred(java.lang.String);
method public abstract void removePermission(java.lang.String);
@@ -11394,6 +11415,7 @@
field public static final java.lang.String FEATURE_NFC_HOST_CARD_EMULATION = "android.hardware.nfc.hce";
field public static final java.lang.String FEATURE_NFC_HOST_CARD_EMULATION_NFCF = "android.hardware.nfc.hcef";
field public static final java.lang.String FEATURE_OPENGLES_EXTENSION_PACK = "android.hardware.opengles.aep";
+ field public static final java.lang.String FEATURE_PC = "android.hardware.type.pc";
field public static final java.lang.String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
field public static final java.lang.String FEATURE_PRINTING = "android.software.print";
field public static final java.lang.String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape";
@@ -11436,6 +11458,7 @@
field public static final java.lang.String FEATURE_WIFI = "android.hardware.wifi";
field public static final java.lang.String FEATURE_WIFI_AWARE = "android.hardware.wifi.aware";
field public static final java.lang.String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
+ field public static final java.lang.String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint";
field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20
field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4
field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40
@@ -11535,6 +11558,11 @@
field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
}
+ public static abstract class PackageManager.DexModuleRegisterCallback {
+ ctor public PackageManager.DexModuleRegisterCallback();
+ method public abstract void onDexModuleRegistered(java.lang.String, boolean, java.lang.String);
+ }
+
public static class PackageManager.NameNotFoundException extends android.util.AndroidException {
ctor public PackageManager.NameNotFoundException();
ctor public PackageManager.NameNotFoundException(java.lang.String);
@@ -40774,10 +40802,12 @@
method public int getDesiredMinimumHeight();
method public int getDesiredMinimumWidth();
method public android.view.SurfaceHolder getSurfaceHolder();
+ method public void invalidateColors();
method public boolean isPreview();
method public boolean isVisible();
method public void onApplyWindowInsets(android.view.WindowInsets);
method public android.os.Bundle onCommand(java.lang.String, int, int, int, android.os.Bundle, boolean);
+ method public android.app.WallpaperColors onComputeWallpaperColors();
method public void onCreate(android.view.SurfaceHolder);
method public void onDesiredSizeChanged(int, int);
method public void onDestroy();
@@ -41887,7 +41917,8 @@
public static final class Call.RttCall {
method public int getRttAudioMode();
- method public java.lang.String read();
+ method public java.lang.String read() throws java.io.IOException;
+ method public java.lang.String readImmediately() throws java.io.IOException;
method public void setRttMode(int);
method public void write(java.lang.String) throws java.io.IOException;
field public static final int RTT_MODE_FULL = 1; // 0x1
@@ -44358,6 +44389,7 @@
method public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(android.content.Intent, int);
method public java.util.List<android.content.pm.ResolveInfo> queryIntentServices(android.content.Intent, int);
method public java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
method public void removePackageFromPreferred(java.lang.String);
method public void removePermission(java.lang.String);
@@ -47451,6 +47483,7 @@
field public static final int KEYBOARD_TAP = 3; // 0x3
field public static final int LONG_PRESS = 0; // 0x0
field public static final int VIRTUAL_KEY = 1; // 0x1
+ field public static final int VIRTUAL_KEY_RELEASE = 7; // 0x7
}
public class InflateException extends java.lang.RuntimeException {
@@ -47479,6 +47512,7 @@
method public android.os.Vibrator getVibrator();
method public boolean[] hasKeys(int...);
method public boolean hasMicrophone();
+ method public boolean isEnabled();
method public boolean isVirtual();
method public boolean supportsSource(int);
method public void writeToParcel(android.os.Parcel, int);
@@ -50471,6 +50505,7 @@
}
public final class AccessibilityManager {
+ method public void addAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
method public boolean addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
method public void addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, android.os.Handler);
method public boolean addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
@@ -50482,6 +50517,7 @@
method public static boolean isAccessibilityButtonSupported();
method public boolean isEnabled();
method public boolean isTouchExplorationEnabled();
+ method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
method public boolean removeAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
method public boolean removeTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
@@ -50798,6 +50834,13 @@
method public void setToIndex(int);
}
+ public abstract class AccessibilityRequestPreparer {
+ ctor public AccessibilityRequestPreparer(android.view.View, int);
+ method public android.view.View getView();
+ method public abstract void onPrepareExtraData(int, java.lang.String, android.os.Bundle, android.os.Message);
+ field public static final int REQUEST_TYPE_EXTRA_DATA = 1; // 0x1
+ }
+
public final class AccessibilityWindowInfo implements android.os.Parcelable {
method public int describeContents();
method public android.view.accessibility.AccessibilityNodeInfo getAnchor();
@@ -52413,6 +52456,7 @@
method public void goBack();
method public void goBackOrForward(int);
method public void goForward();
+ method public static void initSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean>);
method public void invokeZoomPicker();
method public boolean isPrivateBrowsingEnabled();
method public void loadData(java.lang.String, java.lang.String, java.lang.String);
@@ -52457,6 +52501,7 @@
method public static void setWebContentsDebuggingEnabled(boolean);
method public void setWebViewClient(android.webkit.WebViewClient);
method public deprecated boolean showFindDialog(java.lang.String, boolean);
+ method public static void shutdownSafeBrowsing();
method public void stopLoading();
method public void zoomBy(float);
method public boolean zoomIn();
@@ -52644,8 +52689,10 @@
method public abstract java.lang.String findAddress(java.lang.String);
method public abstract void freeMemoryForTests();
method public abstract java.lang.String getDefaultUserAgent(android.content.Context);
+ method public abstract void initSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean>);
method public abstract android.net.Uri[] parseFileChooserResult(int, android.content.Intent);
method public abstract void setWebContentsDebuggingEnabled(boolean);
+ method public abstract void shutdownSafeBrowsing();
}
public class WebViewFragment extends android.app.Fragment {
@@ -73717,6 +73764,7 @@
public abstract class SSLSocketFactory extends javax.net.SocketFactory {
ctor public SSLSocketFactory();
method public abstract java.net.Socket createSocket(java.net.Socket, java.lang.String, int, boolean) throws java.io.IOException;
+ method public java.net.Socket createSocket(java.net.Socket, java.io.InputStream, boolean) throws java.io.IOException;
method public static synchronized javax.net.SocketFactory getDefault();
method public abstract java.lang.String[] getDefaultCipherSuites();
method public abstract java.lang.String[] getSupportedCipherSuites();
diff --git a/api/test-current.txt b/api/test-current.txt
index 9b5b1a2..96bda4f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5986,6 +5986,7 @@
method public void destroy();
method public android.view.accessibility.AccessibilityEvent executeAndWaitForEvent(java.lang.Runnable, android.app.UiAutomation.AccessibilityEventFilter, long) throws java.util.concurrent.TimeoutException;
method public android.os.ParcelFileDescriptor executeShellCommand(java.lang.String);
+ method public android.os.ParcelFileDescriptor[] executeShellCommandRw(java.lang.String);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
@@ -6107,6 +6108,17 @@
method public void onDetached();
}
+ public final class WallpaperColors implements android.os.Parcelable {
+ ctor public WallpaperColors(android.os.Parcel);
+ ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>);
+ ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean);
+ method public int describeContents();
+ method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors();
+ method public boolean supportsDarkText();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR;
+ }
+
public final class WallpaperInfo implements android.os.Parcelable {
ctor public WallpaperInfo(android.content.Context, android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public int describeContents();
@@ -6129,6 +6141,8 @@
}
public class WallpaperManager {
+ method public void addOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener);
+ method public void addOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener, android.os.Handler);
method public void clear() throws java.io.IOException;
method public void clear(int) throws java.io.IOException;
method public void clearWallpaperOffsets(android.os.IBinder);
@@ -6143,6 +6157,7 @@
method public android.graphics.drawable.Drawable getDrawable();
method public android.graphics.drawable.Drawable getFastDrawable();
method public static android.app.WallpaperManager getInstance(android.content.Context);
+ method public android.app.WallpaperColors getWallpaperColors(int);
method public android.os.ParcelFileDescriptor getWallpaperFile(int);
method public int getWallpaperId(int);
method public android.app.WallpaperInfo getWallpaperInfo();
@@ -6151,6 +6166,7 @@
method public boolean isWallpaperSupported();
method public android.graphics.drawable.Drawable peekDrawable();
method public android.graphics.drawable.Drawable peekFastDrawable();
+ method public void removeOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener);
method public void sendWallpaperCommand(android.os.IBinder, java.lang.String, int, int, int, android.os.Bundle);
method public void setBitmap(android.graphics.Bitmap) throws java.io.IOException;
method public int setBitmap(android.graphics.Bitmap, android.graphics.Rect, boolean) throws java.io.IOException;
@@ -6175,6 +6191,10 @@
field public static final java.lang.String WALLPAPER_PREVIEW_META_DATA = "android.wallpaper.preview";
}
+ public static abstract interface WallpaperManager.OnColorsChangedListener {
+ method public abstract void onColorsChanged(android.app.WallpaperColors, int);
+ }
+
}
package android.app.admin {
@@ -10733,6 +10753,7 @@
field public static final java.lang.String FEATURE_NFC_HOST_CARD_EMULATION = "android.hardware.nfc.hce";
field public static final java.lang.String FEATURE_NFC_HOST_CARD_EMULATION_NFCF = "android.hardware.nfc.hcef";
field public static final java.lang.String FEATURE_OPENGLES_EXTENSION_PACK = "android.hardware.opengles.aep";
+ field public static final java.lang.String FEATURE_PC = "android.hardware.type.pc";
field public static final java.lang.String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
field public static final java.lang.String FEATURE_PRINTING = "android.software.print";
field public static final java.lang.String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape";
@@ -10774,6 +10795,7 @@
field public static final java.lang.String FEATURE_WIFI = "android.hardware.wifi";
field public static final java.lang.String FEATURE_WIFI_AWARE = "android.hardware.wifi.aware";
field public static final java.lang.String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
+ field public static final java.lang.String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint";
field public static final int GET_ACTIVITIES = 1; // 0x1
field public static final int GET_CONFIGURATIONS = 16384; // 0x4000
field public static final deprecated int GET_DISABLED_COMPONENTS = 512; // 0x200
@@ -32596,7 +32618,9 @@
public final class PrintManager {
method public java.util.List<android.print.PrintJob> getPrintJobs();
+ method public java.util.List<android.printservice.PrintServiceInfo> getPrintServices(int);
method public android.print.PrintJob print(java.lang.String, android.print.PrintDocumentAdapter, android.print.PrintAttributes);
+ field public static final int ALL_SERVICES = 3; // 0x3
}
public final class PrinterCapabilitiesInfo implements android.os.Parcelable {
@@ -32726,6 +32750,13 @@
field public static final java.lang.String SERVICE_META_DATA = "android.printservice";
}
+ public final class PrintServiceInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.content.ComponentName getComponentName();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.printservice.PrintServiceInfo> CREATOR;
+ }
+
public abstract class PrinterDiscoverySession {
ctor public PrinterDiscoverySession();
method public final void addPrinters(java.util.List<android.print.PrinterInfo>);
@@ -34989,6 +35020,7 @@
field public static final java.lang.String DEFAULT_INPUT_METHOD = "default_input_method";
field public static final deprecated java.lang.String DEVELOPMENT_SETTINGS_ENABLED = "development_settings_enabled";
field public static final deprecated java.lang.String DEVICE_PROVISIONED = "device_provisioned";
+ field public static final java.lang.String DISABLED_PRINT_SERVICES = "disabled_print_services";
field public static final java.lang.String ENABLED_ACCESSIBILITY_SERVICES = "enabled_accessibility_services";
field public static final java.lang.String ENABLED_INPUT_METHODS = "enabled_input_methods";
field public static final java.lang.String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages";
@@ -37758,10 +37790,12 @@
method public int getDesiredMinimumHeight();
method public int getDesiredMinimumWidth();
method public android.view.SurfaceHolder getSurfaceHolder();
+ method public void invalidateColors();
method public boolean isPreview();
method public boolean isVisible();
method public void onApplyWindowInsets(android.view.WindowInsets);
method public android.os.Bundle onCommand(java.lang.String, int, int, int, android.os.Bundle, boolean);
+ method public android.app.WallpaperColors onComputeWallpaperColors();
method public void onCreate(android.view.SurfaceHolder);
method public void onDesiredSizeChanged(int, int);
method public void onDestroy();
@@ -38846,7 +38880,8 @@
public static final class Call.RttCall {
method public int getRttAudioMode();
- method public java.lang.String read();
+ method public java.lang.String read() throws java.io.IOException;
+ method public java.lang.String readImmediately() throws java.io.IOException;
method public void setRttMode(int);
method public void write(java.lang.String) throws java.io.IOException;
field public static final int RTT_MODE_FULL = 1; // 0x1
@@ -38966,6 +39001,7 @@
method public final int getState();
method public final android.telecom.StatusHints getStatusHints();
method public final android.telecom.Connection.VideoProvider getVideoProvider();
+ method public void handleRttUpgradeResponse(android.telecom.Connection.RttTextStream);
method public final boolean isRingbackRequested();
method public void onAbort();
method public void onAnswer(int);
@@ -38982,14 +39018,20 @@
method public void onReject(java.lang.String);
method public void onSeparate();
method public void onShowIncomingCallUi();
+ method public void onStartRtt(android.telecom.Connection.RttTextStream);
method public void onStateChanged(int);
method public void onStopDtmfTone();
+ method public void onStopRtt();
method public void onUnhold();
method public static java.lang.String propertiesToString(int);
method public final void putExtras(android.os.Bundle);
method public final void removeExtras(java.util.List<java.lang.String>);
method public final void removeExtras(java.lang.String...);
method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
+ method public final void sendRemoteRttRequest();
+ method public final void sendRttInitiationFailure(int);
+ method public final void sendRttInitiationSuccess();
+ method public final void sendRttSessionRemotelyTerminated();
method public final void setActive();
method public final void setAddress(android.net.Uri, int);
method public final void setAudioModeIsVoip(boolean);
@@ -39043,6 +39085,7 @@
field public static final java.lang.String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20
field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
+ field public static final int PROPERTY_IS_RTT = 256; // 0x100
field public static final int PROPERTY_SELF_MANAGED = 128; // 0x80
field public static final int STATE_ACTIVE = 4; // 0x4
field public static final int STATE_DIALING = 3; // 0x3
@@ -39062,6 +39105,12 @@
field public static final int SESSION_MODIFY_REQUEST_TIMED_OUT = 4; // 0x4
}
+ public static final class Connection.RttTextStream {
+ method public java.lang.String read() throws java.io.IOException;
+ method public java.lang.String readImmediately() throws java.io.IOException;
+ method public void write(java.lang.String) throws java.io.IOException;
+ }
+
public static abstract class Connection.VideoProvider {
ctor public Connection.VideoProvider();
method public void changeCameraCapabilities(android.telecom.VideoProfile.CameraCapabilities);
@@ -39102,7 +39151,9 @@
method public android.telecom.PhoneAccountHandle getAccountHandle();
method public android.net.Uri getAddress();
method public android.os.Bundle getExtras();
+ method public android.telecom.Connection.RttTextStream getRttTextStream();
method public int getVideoState();
+ method public boolean isRequestingRtt();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.telecom.ConnectionRequest> CREATOR;
}
@@ -44278,6 +44329,7 @@
field public static final int KEYBOARD_TAP = 3; // 0x3
field public static final int LONG_PRESS = 0; // 0x0
field public static final int VIRTUAL_KEY = 1; // 0x1
+ field public static final int VIRTUAL_KEY_RELEASE = 7; // 0x7
}
public class InflateException extends java.lang.RuntimeException {
@@ -44289,6 +44341,8 @@
public final class InputDevice implements android.os.Parcelable {
method public int describeContents();
+ method public void disable();
+ method public void enable();
method public int getControllerNumber();
method public java.lang.String getDescriptor();
method public static android.view.InputDevice getDevice(int);
@@ -44306,6 +44360,7 @@
method public android.os.Vibrator getVibrator();
method public boolean[] hasKeys(int...);
method public boolean hasMicrophone();
+ method public boolean isEnabled();
method public boolean isVirtual();
method public boolean supportsSource(int);
method public void writeToParcel(android.os.Parcel, int);
@@ -44830,10 +44885,13 @@
public final class KeyboardShortcutGroup implements android.os.Parcelable {
ctor public KeyboardShortcutGroup(java.lang.CharSequence, java.util.List<android.view.KeyboardShortcutInfo>);
ctor public KeyboardShortcutGroup(java.lang.CharSequence);
+ ctor public KeyboardShortcutGroup(java.lang.CharSequence, java.util.List<android.view.KeyboardShortcutInfo>, boolean);
+ ctor public KeyboardShortcutGroup(java.lang.CharSequence, boolean);
method public void addItem(android.view.KeyboardShortcutInfo);
method public int describeContents();
method public java.util.List<android.view.KeyboardShortcutInfo> getItems();
method public java.lang.CharSequence getLabel();
+ method public boolean isSystemGroup();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.view.KeyboardShortcutGroup> CREATOR;
}
@@ -47310,6 +47368,7 @@
}
public final class AccessibilityManager {
+ method public void addAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
method public boolean addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
method public void addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, android.os.Handler);
method public boolean addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
@@ -47320,6 +47379,7 @@
method public void interrupt();
method public boolean isEnabled();
method public boolean isTouchExplorationEnabled();
+ method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
method public boolean removeAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
method public boolean removeTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
@@ -47637,6 +47697,13 @@
method public void setToIndex(int);
}
+ public abstract class AccessibilityRequestPreparer {
+ ctor public AccessibilityRequestPreparer(android.view.View, int);
+ method public android.view.View getView();
+ method public abstract void onPrepareExtraData(int, java.lang.String, android.os.Bundle, android.os.Message);
+ field public static final int REQUEST_TYPE_EXTRA_DATA = 1; // 0x1
+ }
+
public final class AccessibilityWindowInfo implements android.os.Parcelable {
method public int describeContents();
method public android.view.accessibility.AccessibilityNodeInfo getAnchor();
@@ -49159,6 +49226,7 @@
method public void goBack();
method public void goBackOrForward(int);
method public void goForward();
+ method public static void initSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean>);
method public void invokeZoomPicker();
method public boolean isPrivateBrowsingEnabled();
method public void loadData(java.lang.String, java.lang.String, java.lang.String);
@@ -49203,6 +49271,7 @@
method public static void setWebContentsDebuggingEnabled(boolean);
method public void setWebViewClient(android.webkit.WebViewClient);
method public deprecated boolean showFindDialog(java.lang.String, boolean);
+ method public static void shutdownSafeBrowsing();
method public void stopLoading();
method public void zoomBy(float);
method public boolean zoomIn();
@@ -70207,6 +70276,7 @@
public abstract class SSLSocketFactory extends javax.net.SocketFactory {
ctor public SSLSocketFactory();
method public abstract java.net.Socket createSocket(java.net.Socket, java.lang.String, int, boolean) throws java.io.IOException;
+ method public java.net.Socket createSocket(java.net.Socket, java.io.InputStream, boolean) throws java.io.IOException;
method public static synchronized javax.net.SocketFactory getDefault();
method public abstract java.lang.String[] getDefaultCipherSuites();
method public abstract java.lang.String[] getSupportedCipherSuites();
diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk
index 0e2c13e..7ab402a 100644
--- a/cmds/bootanimation/Android.mk
+++ b/cmds/bootanimation/Android.mk
@@ -1,15 +1,50 @@
+bootanimation_CommonCFlags = -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
+bootanimation_CommonCFlags += -Wall -Werror -Wunused -Wunreachable-code
+
+
+# bootanimation executable
+# =========================================================
+
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
bootanimation_main.cpp \
audioplay.cpp \
+
+LOCAL_CFLAGS += ${bootanimation_CommonCFlags}
+
+LOCAL_SHARED_LIBRARIES := \
+ libOpenSLES \
+ libandroidfw \
+ libbase \
+ libbinder \
+ libbootanimation \
+ libcutils \
+ liblog \
+ libutils \
+
+LOCAL_MODULE:= bootanimation
+
+LOCAL_INIT_RC := bootanim.rc
+
+ifdef TARGET_32_BIT_SURFACEFLINGER
+LOCAL_32_BIT_ONLY := true
+endif
+
+include $(BUILD_EXECUTABLE)
+
+
+# libbootanimation
+# ===========================================================
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libbootanimation
+LOCAL_CFLAGS += ${bootanimation_CommonCFlags}
+
+LOCAL_SRC_FILES:= \
BootAnimation.cpp
-LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
-
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
-
LOCAL_C_INCLUDES += \
external/tinyalsa/include \
frameworks/wilhelm/include
@@ -25,16 +60,11 @@
libEGL \
libGLESv1_CM \
libgui \
- libOpenSLES \
libtinyalsa \
libbase
-LOCAL_MODULE:= bootanimation
-
-LOCAL_INIT_RC := bootanim.rc
-
ifdef TARGET_32_BIT_SURFACEFLINGER
LOCAL_32_BIT_ONLY := true
endif
-include $(BUILD_EXECUTABLE)
+include ${BUILD_SHARED_LIBRARY}
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 7394490..6b2de4b 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -62,7 +62,6 @@
#include <EGL/eglext.h>
#include "BootAnimation.h"
-#include "audioplay.h"
namespace android {
@@ -92,26 +91,18 @@
static const int TEXT_CENTER_VALUE = INT_MAX;
static const int TEXT_MISSING_VALUE = INT_MIN;
static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
-static const char PLAY_SOUND_PROP_NAME[] = "persist.sys.bootanim.play_sound";
static const int ANIM_ENTRY_NAME_MAX = 256;
static constexpr size_t TEXT_POS_LEN_MAX = 16;
-static const char BOOT_COMPLETED_PROP_NAME[] = "sys.boot_completed";
-static const char BOOTREASON_PROP_NAME[] = "ro.boot.bootreason";
-// bootreasons list in "system/core/bootstat/bootstat.cpp".
-static const std::vector<std::string> PLAY_SOUND_BOOTREASON_BLACKLIST {
- "kernel_panic",
- "Panic",
- "Watchdog",
-};
// ---------------------------------------------------------------------------
-BootAnimation::BootAnimation() : Thread(false), mClockEnabled(true), mTimeIsAccurate(false),
- mTimeFormat12Hour(false), mTimeCheckThread(NULL) {
+BootAnimation::BootAnimation(InitCallback initCallback,
+ PlayPartCallback partCallback)
+ : Thread(false), mClockEnabled(true), mTimeIsAccurate(false),
+ mTimeFormat12Hour(false), mTimeCheckThread(NULL),
+ mInitCallback(initCallback), mPlayPartCallback(partCallback) {
mSession = new SurfaceComposerClient();
- // If the system has already booted, the animation is not being used for a boot.
- mSystemBoot = !android::base::GetBoolProperty(BOOT_COMPLETED_PROP_NAME, false);
std::string powerCtl = android::base::GetProperty("sys.powerctl", "");
if (powerCtl.empty()) {
mShuttingDown = false;
@@ -142,7 +133,6 @@
// might be blocked on a condition variable that will never be updated.
kill( getpid(), SIGKILL );
requestExit();
- audioplay::destroy();
}
status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
@@ -158,10 +148,6 @@
asset->close();
delete asset;
- // ensure we can call getPixels(). No need to call unlock, since the
- // bitmap will go out of scope when we return from this method.
- bitmap.lockPixels();
-
const int w = bitmap.width();
const int h = bitmap.height();
const void* p = bitmap.getPixels();
@@ -216,10 +202,6 @@
// the packed resource can be released.
delete map;
- // ensure we can call getPixels(). No need to call unlock, since the
- // bitmap will go out of scope when we return from this method.
- bitmap.lockPixels();
-
const int w = bitmap.width();
const int h = bitmap.height();
const void* p = bitmap.getPixels();
@@ -712,7 +694,6 @@
return false;
}
- Animation::Part* partWithAudio = NULL;
ZipEntryRO entry;
char name[ANIM_ENTRY_NAME_MAX];
while ((entry = zip->nextEntry(cookie)) != NULL) {
@@ -747,7 +728,6 @@
// a part may have at most one audio file
part.audioData = (uint8_t *)map->getDataPtr();
part.audioLength = map->getDataLength();
- partWithAudio = ∂
} else if (leaf == "trim.txt") {
part.trimData.setTo((char const*)map->getDataPtr(),
map->getDataLength());
@@ -797,13 +777,8 @@
}
}
- // Create and initialize audioplay if there is a wav file in any of the animations.
- // Do it on a separate thread so we don't hold up the animation intro.
- if (partWithAudio != NULL) {
- ALOGD("found audio.wav, creating playback engine");
- mInitAudioThread = new InitAudioThread(partWithAudio->audioData,
- partWithAudio->audioLength);
- mInitAudioThread->run("BootAnimation::InitAudioThread", PRIORITY_NORMAL);
+ if (mInitCallback != nullptr) {
+ mInitCallback(animation.parts);
}
zip->endIteration(cookie);
@@ -876,11 +851,6 @@
mTimeCheckThread = nullptr;
}
- // We should have joined mInitAudioThread thread in playAnimation
- if (mInitAudioThread != nullptr) {
- mInitAudioThread = nullptr;
- }
-
releaseAnimation(animation);
if (clockFontInitialized) {
@@ -917,14 +887,8 @@
if(exitPending() && !part.playUntilComplete)
break;
- // only play audio file the first time we animate the part
- if (r == 0 && part.audioData && playSoundsAllowed()) {
- ALOGD("playing clip for part%d, size=%d", (int) i, part.audioLength);
- // Block until the audio engine is finished initializing.
- if (mInitAudioThread != nullptr) {
- mInitAudioThread->join();
- }
- audioplay::playClip(part.audioData, part.audioLength);
+ if (mPlayPartCallback != nullptr) {
+ mPlayPartCallback(i, part, r);
}
glClearColor(
@@ -1013,10 +977,6 @@
}
}
- // we've finally played everything we're going to play
- audioplay::setPlaying(false);
- audioplay::destroy();
-
return true;
}
@@ -1062,32 +1022,6 @@
return animation;
}
-bool BootAnimation::playSoundsAllowed() const {
- // Only play sounds for system boots, not runtime restarts.
- if (!mSystemBoot) {
- return false;
- }
- if (mShuttingDown) { // no audio while shutting down
- return false;
- }
- // Read the system property to see if we should play the sound.
- // If it's not present, default to allowed.
- if (!property_get_bool(PLAY_SOUND_PROP_NAME, 1)) {
- return false;
- }
-
- // Don't play sounds if this is a reboot due to an error.
- char bootreason[PROPERTY_VALUE_MAX];
- if (property_get(BOOTREASON_PROP_NAME, bootreason, nullptr) > 0) {
- for (const auto& str : PLAY_SOUND_BOOTREASON_BLACKLIST) {
- if (strcasecmp(str.c_str(), bootreason) == 0) {
- return false;
- }
- }
- }
- return true;
-}
-
bool BootAnimation::updateIsTimeAccurate() {
static constexpr long long MAX_TIME_IN_PAST = 60000LL * 60LL * 24LL * 30LL; // 30 days
static constexpr long long MAX_TIME_IN_FUTURE = 60000LL * 90LL; // 90 minutes
@@ -1219,17 +1153,6 @@
return NO_ERROR;
}
-BootAnimation::InitAudioThread::InitAudioThread(uint8_t* exampleAudioData, int exampleAudioLength)
- : Thread(false),
- mExampleAudioData(exampleAudioData),
- mExampleAudioLength(exampleAudioLength) {}
-
-bool BootAnimation::InitAudioThread::threadLoop() {
- audioplay::create(mExampleAudioData, mExampleAudioLength);
- // Exit immediately
- return false;
-}
-
// ---------------------------------------------------------------------------
}
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index 181ef1c..3ebe7d6 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -39,44 +39,6 @@
class BootAnimation : public Thread, public IBinder::DeathRecipient
{
public:
- BootAnimation();
-
- sp<SurfaceComposerClient> session() const;
-
-private:
- virtual bool threadLoop();
- virtual status_t readyToRun();
- virtual void onFirstRef();
- virtual void binderDied(const wp<IBinder>& who);
-
- bool updateIsTimeAccurate();
-
- class TimeCheckThread : public Thread {
- public:
- TimeCheckThread(BootAnimation* bootAnimation);
- virtual ~TimeCheckThread();
- private:
- virtual status_t readyToRun();
- virtual bool threadLoop();
- bool doThreadLoop();
- void addTimeDirWatch();
-
- int mInotifyFd;
- int mSystemWd;
- int mTimeWd;
- BootAnimation* mBootAnimation;
- };
-
- class InitAudioThread : public Thread {
- public:
- InitAudioThread(uint8_t* exampleAudioData, int mExampleAudioLength);
- private:
- virtual bool threadLoop();
-
- uint8_t* mExampleAudioData;
- int mExampleAudioLength;
- };
-
struct Texture {
GLint w;
GLint h;
@@ -131,6 +93,49 @@
Font clockFont;
};
+ // Callback will be called during initialization after we have loaded
+ // the animation and be provided with all parts in animation.
+ typedef std::function<void(const Vector<Animation::Part>& parts)> InitCallback;
+
+ // Callback will be called while animation is playing before each part is
+ // played. It will be provided with the part and play count for it.
+ // It will be provided with the partNumber for the part about to be played,
+ // as well as a reference to the part itself. It will also be provided with
+ // which play of that part is about to start, some parts are repeated
+ // multiple times.
+ typedef std::function<void(int partNumber, const Animation::Part& part, int playNumber)>
+ PlayPartCallback;
+
+ // Callbacks may be null and will be called from this class's internal
+ // thread.
+ BootAnimation(InitCallback initCallback, PlayPartCallback partCallback);
+
+ sp<SurfaceComposerClient> session() const;
+
+private:
+ virtual bool threadLoop();
+ virtual status_t readyToRun();
+ virtual void onFirstRef();
+ virtual void binderDied(const wp<IBinder>& who);
+
+ bool updateIsTimeAccurate();
+
+ class TimeCheckThread : public Thread {
+ public:
+ TimeCheckThread(BootAnimation* bootAnimation);
+ virtual ~TimeCheckThread();
+ private:
+ virtual status_t readyToRun();
+ virtual bool threadLoop();
+ bool doThreadLoop();
+ void addTimeDirWatch();
+
+ int mInotifyFd;
+ int mSystemWd;
+ int mTimeWd;
+ BootAnimation* mBootAnimation;
+ };
+
status_t initTexture(Texture* texture, AssetManager& asset, const char* name);
status_t initTexture(FileMap* map, int* width, int* height);
status_t initFont(Font* font, const char* fallback);
@@ -144,7 +149,6 @@
void releaseAnimation(Animation*) const;
bool parseAnimationDesc(Animation&);
bool preloadZip(Animation &animation);
- bool playSoundsAllowed() const;
void checkExit();
@@ -162,12 +166,12 @@
bool mClockEnabled;
bool mTimeIsAccurate;
bool mTimeFormat12Hour;
- bool mSystemBoot;
bool mShuttingDown;
String8 mZipFileName;
SortedVector<String8> mLoadedFiles;
sp<TimeCheckThread> mTimeCheckThread = nullptr;
- sp<InitAudioThread> mInitAudioThread = nullptr;
+ InitCallback mInitCallback = nullptr;
+ PlayPartCallback mPlayPartCallback = nullptr;
};
// ---------------------------------------------------------------------------
diff --git a/cmds/bootanimation/bootanimation_main.cpp b/cmds/bootanimation/bootanimation_main.cpp
index 3689d5e..c11d905 100644
--- a/cmds/bootanimation/bootanimation_main.cpp
+++ b/cmds/bootanimation/bootanimation_main.cpp
@@ -27,13 +27,77 @@
#include <utils/Log.h>
#include <utils/SystemClock.h>
#include <utils/threads.h>
+#include <android-base/properties.h>
#include "BootAnimation.h"
+#include "audioplay.h"
using namespace android;
// ---------------------------------------------------------------------------
+namespace {
+
+// Create a typedef for readability.
+typedef android::BootAnimation::Animation Animation;
+
+static const char PLAY_SOUND_PROP_NAME[] = "persist.sys.bootanim.play_sound";
+static const char BOOT_COMPLETED_PROP_NAME[] = "sys.boot_completed";
+static const char POWER_CTL_PROP_NAME[] = "sys.powerctl";
+static const char BOOTREASON_PROP_NAME[] = "ro.boot.bootreason";
+static const std::vector<std::string> PLAY_SOUND_BOOTREASON_BLACKLIST {
+ "kernel_panic",
+ "Panic",
+ "Watchdog",
+};
+
+class InitAudioThread : public Thread {
+public:
+ InitAudioThread(uint8_t* exampleAudioData, int exampleAudioLength)
+ : Thread(false),
+ mExampleAudioData(exampleAudioData),
+ mExampleAudioLength(exampleAudioLength) {}
+private:
+ virtual bool threadLoop() {
+ audioplay::create(mExampleAudioData, mExampleAudioLength);
+ // Exit immediately
+ return false;
+ }
+
+ uint8_t* mExampleAudioData;
+ int mExampleAudioLength;
+};
+
+bool playSoundsAllowed() {
+ // Only play sounds for system boots, not runtime restarts.
+ if (android::base::GetBoolProperty(BOOT_COMPLETED_PROP_NAME, false)) {
+ return false;
+ }
+ // no audio while shutting down
+ if (!android::base::GetProperty(POWER_CTL_PROP_NAME, "").empty()) {
+ return false;
+ }
+ // Read the system property to see if we should play the sound.
+ // If it's not present, default to allowed.
+ if (!property_get_bool(PLAY_SOUND_PROP_NAME, 1)) {
+ return false;
+ }
+
+ // Don't play sounds if this is a reboot due to an error.
+ char bootreason[PROPERTY_VALUE_MAX];
+ if (property_get(BOOTREASON_PROP_NAME, bootreason, nullptr) > 0) {
+ for (const auto& str : PLAY_SOUND_BOOTREASON_BLACKLIST) {
+ if (strcasecmp(str.c_str(), bootreason) == 0) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace
+
+
int main()
{
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
@@ -71,10 +135,50 @@
ALOGI("Waiting for SurfaceFlinger took %" PRId64 " ms", totalWaited);
}
+ // TODO: Move audio code to a new class that just exports the callbacks.
+ sp<InitAudioThread> initAudioThread = nullptr;
+
+ auto initCallback = [&](const Vector<Animation::Part>& parts) {
+ const Animation::Part* partWithAudio = nullptr;
+ for (const Animation::Part& part : parts) {
+ if (part.audioData != nullptr) {
+ partWithAudio = ∂
+ }
+ }
+
+ if (partWithAudio == nullptr) {
+ return;
+ }
+
+ ALOGD("found audio.wav, creating playback engine");
+ initAudioThread = new InitAudioThread(partWithAudio->audioData,
+ partWithAudio->audioLength);
+ initAudioThread->run("BootAnimation::InitAudioThread", PRIORITY_NORMAL);
+
+ };
+
+ auto partCallback = [&](int partNumber, const Animation::Part& part,
+ int playNumber) {
+ // only play audio file the first time we animate the part
+ if (playNumber == 0 && part.audioData && playSoundsAllowed()) {
+ ALOGD("playing clip for part%d, size=%d",
+ partNumber, part.audioLength);
+ // Block until the audio engine is finished initializing.
+ if (initAudioThread != nullptr) {
+ initAudioThread->join();
+ }
+ audioplay::playClip(part.audioData, part.audioLength);
+ }
+ };
+
// create the boot animation object
- sp<BootAnimation> boot = new BootAnimation();
+ sp<BootAnimation> boot = new BootAnimation(initCallback, partCallback);
IPCThreadState::self()->joinThreadPool();
+
+ // we've finally played everything we're going to play
+ audioplay::setPlaying(false);
+ audioplay::destroy();
}
return 0;
}
diff --git a/cmds/hid/README.md b/cmds/hid/README.md
new file mode 100644
index 0000000..7e22d08
--- /dev/null
+++ b/cmds/hid/README.md
@@ -0,0 +1,145 @@
+# Usage
+## Two options to use the hid command:
+### 1. Interactive through stdin:
+type `hid -` into the terminal, then type/paste commands to send to the binary.
+Use Ctrl+D to signal end of stream to the binary (EOF).
+
+This mode can be also used from an app to send HID events.
+For an example, see the cts test case at: [InputTestCase.java][2]
+
+When using another program to control hid in interactive mode, registering a
+new input device (for example, a bluetooth joystick) should be the first step.
+After the device is added, you need to wait for the _onInputDeviceAdded_
+(see [InputDeviceListener][1]) notification before issuing commands
+to the device.
+Failure to do so will cause missed events and inconsistent behaviour.
+In the current implementation of the hid command, the hid binary will wait
+for the file descriptor to the uhid node to send the UHID_START and UHID_OPEN
+signals before returning. However, this is not sufficient. These signals
+only notify the readiness of the kernel driver,
+but do not take into account the inputflinger framework.
+
+
+### 2. Using a file as an input:
+type `hid <filename>`, and the file will be used an an input to the binary.
+You must add a sufficient delay after a "register" command to ensure device
+is ready. The interactive mode is the recommended method of communicating
+with the hid binary.
+
+All of the input commands should be in pseudo-JSON format as documented below.
+See examples [here][3].
+
+The file can have multiple commands one after the other (which is not strictly
+legal JSON format, as this would imply multiple root elements).
+
+## Command description
+
+1. `register`
+Register a new uhid device
+
+| Field | Type | Description |
+|:-------------:|:-------------:|:--------------------------|
+| id | integer | Device id |
+| command | string | Must be set to "register" |
+| name | string | Device name |
+| vid | 16-bit integer| Vendor id |
+| pid | 16-bit integer| Product id |
+| descriptor | byte array | USB HID report descriptor |
+
+Device ID is used for matching the subsequent commands to a specific device
+to avoid ambiguity when multiple devices are registered.
+
+USB HID report descriptor should be generated according the the USB HID spec
+and can be checked by reverse parsing using a variety of tools, for example
+[usbdescreqparser][5].
+
+Example:
+```json
+{
+ "id": 1,
+ "command": "register",
+ "name": "Odie (Test)",
+ "vid": 0x18d1,
+ "pid": 0x2c40,
+ "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x09, 0x0a, 0x01, 0x00,
+ 0x0a, 0x02, 0x00, 0x0a, 0x04, 0x00, 0x0a, 0x05, 0x00, 0x0a, 0x07, 0x00, 0x0a, 0x08, 0x00,
+ 0x0a, 0x0e, 0x00, 0x0a, 0x0f, 0x00, 0x0a, 0x0d, 0x00, 0x05, 0x0c, 0x0a, 0x24, 0x02, 0x0a,
+ 0x23, 0x02, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x0b, 0x81, 0x02, 0x75, 0x01, 0x95,
+ 0x01, 0x81, 0x03, 0x05, 0x01, 0x75, 0x04, 0x95, 0x01, 0x25, 0x07, 0x46, 0x3b, 0x01, 0x66,
+ 0x14, 0x00, 0x09, 0x39, 0x81, 0x42, 0x66, 0x00, 0x00, 0x09, 0x01, 0xa1, 0x00, 0x09, 0x30,
+ 0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x05, 0x02, 0x09, 0xc5, 0x09, 0xc4, 0x15, 0x00, 0x26,
+ 0xff, 0x00, 0x35, 0x00, 0x46, 0xff, 0x00, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0xc0, 0x85,
+ 0x02, 0x05, 0x08, 0x0a, 0x01, 0x00, 0x0a, 0x02, 0x00, 0x0a, 0x03, 0x00, 0x0a, 0x04, 0x00,
+ 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x91, 0x02, 0x75, 0x04, 0x95, 0x01, 0x91,
+ 0x03, 0xc0, 0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x03, 0x05, 0x01, 0x09, 0x06, 0xa1,
+ 0x02, 0x05, 0x06, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x01, 0x81,
+ 0x02, 0x06, 0xbc, 0xff, 0x0a, 0xad, 0xbd, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0xc0, 0xc0]
+}
+```
+2. `delay`
+Add a delay to command processing
+
+| Field | Type | Description |
+|:-------------:|:-------------:|:-------------------------- |
+| id | integer | Device id |
+| command | string | Must be set to "delay" |
+| duration | integer | Delay in milliseconds |
+
+Example:
+```json
+{
+ "id": 1,
+ "command": "delay",
+ "duration": 10
+}
+```
+
+3. `report`
+Send a report to the HID device
+
+| Field | Type | Description |
+|:-------------:|:-------------:|:-------------------------- |
+| id | integer | Device id |
+| command | string | Must be set to "report" |
+| report | byte array | Report data to send |
+
+Example:
+```json
+{
+ "id": 1,
+ "command": "report",
+ "report": [0x01, 0x01, 0x80, 0x7f, 0x7f, 0x7f, 0x7f, 0x00, 0x00]
+}
+```
+
+### Sending a joystick button press event
+To send a button press event on a joystick device:
+1. Register the joystick device
+2. Send button down event with coordinates ABS_X, ABS_Y, ABS_Z, and ABS_RZ
+at the center of the range. If the coordinates are not centered, this event
+will generate a motion event within the input framework, in addition to the
+button press event. The range can be determined from the uhid report descriptor.
+3. Send the button up event with the same coordinates as in 2.
+4. Check that the button press event was received.
+
+### Notes
+1. As soon as EOF is reached (either in interactive mode, or in file mode),
+the device that was created will be unregistered. There is no
+explicit command for unregistering a device.
+2. The linux input subsystem does not generate events for those values
+that remain unchanged. For example, if there are two events sent to the driver,
+and both events have the same value of ABS_X, then ABS_X coordinate
+will not be reported.
+3. The description of joystick actions is available [here][6].
+4. Joysticks are split axes. When an analog stick is in a resting state,
+the reported coordinates are at the center of the range.
+5. The `getevent` utility can used to print out the key events
+for debugging purposes.
+
+
+[1]: https://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener.html
+[2]: ../../../../cts/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
+[3]: ../../../../cts/tests/tests/hardware/res/raw/
+[4]: https://developer.android.com/training/game-controllers/controller-input.html#button
+[5]: http://eleccelerator.com/usbdescreqparser/
+[6]: https://developer.android.com/training/game-controllers/controller-input.html
\ No newline at end of file
diff --git a/cmds/hid/jni/Android.mk b/cmds/hid/jni/Android.mk
index d41d39d..86f4e01 100644
--- a/cmds/hid/jni/Android.mk
+++ b/cmds/hid/jni/Android.mk
@@ -6,14 +6,9 @@
com_android_commands_hid_Device.cpp
LOCAL_C_INCLUDES := \
- $(JNI_H_INCLUDE) \
- frameworks/base/core/jni
+ $(JNI_H_INCLUDE)
-LOCAL_SHARED_LIBRARIES := \
- libandroid_runtime \
- liblog \
- libnativehelper \
- libutils
+LOCAL_LDLIBS += -landroid -llog -lnativehelper
LOCAL_MODULE := libhidcommand_jni
LOCAL_MODULE_TAGS := optional
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp
index 1ea33ce..107dc86 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.cpp
+++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp
@@ -26,17 +26,17 @@
#include <memory>
#include <unistd.h>
-#include <android_runtime/AndroidRuntime.h>
-#include <android_runtime/Log.h>
-#include <android_os_MessageQueue.h>
-#include <core_jni_helpers.h>
#include <jni.h>
#include <JNIHelp.h>
#include <ScopedPrimitiveArray.h>
#include <ScopedUtfChars.h>
-#include <utils/Log.h>
-#include <utils/Looper.h>
-#include <utils/StrongPointer.h>
+#include <android/looper.h>
+#include <android/log.h>
+
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
+#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
+#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
namespace android {
namespace uhid {
@@ -56,59 +56,67 @@
static void checkAndClearException(JNIEnv* env, const char* methodName) {
if (env->ExceptionCheck()) {
- ALOGE("An exception was thrown by callback '%s'.", methodName);
- LOGE_EX(env);
+ LOGE("An exception was thrown by callback '%s'.", methodName);
env->ExceptionClear();
}
}
DeviceCallback::DeviceCallback(JNIEnv* env, jobject callback) :
- mCallbackObject(env->NewGlobalRef(callback)) { }
+ mCallbackObject(env->NewGlobalRef(callback)) {
+ env->GetJavaVM(&mJavaVM);
+ }
DeviceCallback::~DeviceCallback() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = getJNIEnv();
env->DeleteGlobalRef(mCallbackObject);
}
void DeviceCallback::onDeviceError() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = getJNIEnv();
env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceError);
checkAndClearException(env, "onDeviceError");
}
void DeviceCallback::onDeviceOpen() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = getJNIEnv();
env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOpen);
checkAndClearException(env, "onDeviceOpen");
}
+JNIEnv* DeviceCallback::getJNIEnv() {
+ JNIEnv* env;
+ mJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
+ return env;
+}
+
Device* Device::open(int32_t id, const char* name, int32_t vid, int32_t pid,
std::unique_ptr<uint8_t[]> descriptor, size_t descriptorSize,
- std::unique_ptr<DeviceCallback> callback, sp<Looper> looper) {
+ std::unique_ptr<DeviceCallback> callback) {
int fd = ::open(UHID_PATH, O_RDWR | O_CLOEXEC);
if (fd < 0) {
- ALOGE("Failed to open uhid: %s", strerror(errno));
+ LOGE("Failed to open uhid: %s", strerror(errno));
return nullptr;
}
struct uhid_event ev;
memset(&ev, 0, sizeof(ev));
- ev.type = UHID_CREATE;
- strncpy((char*)ev.u.create.name, name, UHID_MAX_NAME_LENGTH);
- ev.u.create.rd_data = descriptor.get();
- ev.u.create.rd_size = descriptorSize;
- ev.u.create.bus = BUS_BLUETOOTH;
- ev.u.create.vendor = vid;
- ev.u.create.product = pid;
- ev.u.create.version = 0;
- ev.u.create.country = 0;
+ ev.type = UHID_CREATE2;
+ strncpy((char*)ev.u.create2.name, name, UHID_MAX_NAME_LENGTH);
+ memcpy(&ev.u.create2.rd_data, descriptor.get(),
+ descriptorSize * sizeof(ev.u.create2.rd_data[0]));
+ ev.u.create2.rd_size = descriptorSize;
+ ev.u.create2.bus = BUS_BLUETOOTH;
+ ev.u.create2.vendor = vid;
+ ev.u.create2.product = pid;
+ ev.u.create2.version = 0;
+ ev.u.create2.country = 0;
errno = 0;
ssize_t ret = TEMP_FAILURE_RETRY(::write(fd, &ev, sizeof(ev)));
if (ret < 0 || ret != sizeof(ev)) {
::close(fd);
- ALOGE("Failed to create uhid node: %s", strerror(errno));
+ LOGE("Failed to create uhid node: %s", strerror(errno));
return nullptr;
}
@@ -116,20 +124,30 @@
ret = TEMP_FAILURE_RETRY(::read(fd, &ev, sizeof(ev)));
if (ret < 0 || ev.type != UHID_START) {
::close(fd);
- ALOGE("uhid node failed to start: %s", strerror(errno));
+ LOGE("uhid node failed to start: %s", strerror(errno));
return nullptr;
}
-
- return new Device(id, fd, std::move(callback), looper);
+ return new Device(id, fd, std::move(callback));
}
-Device::Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback, sp<Looper> looper) :
- mId(id), mFd(fd), mDeviceCallback(std::move(callback)), mLooper(looper) {
- looper->addFd(fd, 0, Looper::EVENT_INPUT, handleLooperEvents, reinterpret_cast<void*>(this));
+Device::Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback) :
+ mId(id), mFd(fd), mDeviceCallback(std::move(callback)) {
+ ALooper* aLooper = ALooper_forThread();
+ if (aLooper == NULL) {
+ LOGE("Could not get ALooper, ALooper_forThread returned NULL");
+ aLooper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
+ }
+ ALooper_addFd(aLooper, fd, 0, ALOOPER_EVENT_INPUT, handleLooperEvents,
+ reinterpret_cast<void*>(this));
}
Device::~Device() {
- mLooper->removeFd(mFd);
+ ALooper* looper = ALooper_forThread();
+ if (looper != NULL) {
+ ALooper_removeFd(looper, mFd);
+ } else {
+ LOGE("Could not remove fd, ALooper_forThread() returned NULL!");
+ }
struct uhid_event ev;
memset(&ev, 0, sizeof(ev));
ev.type = UHID_DESTROY;
@@ -141,25 +159,25 @@
void Device::sendReport(uint8_t* report, size_t reportSize) {
struct uhid_event ev;
memset(&ev, 0, sizeof(ev));
- ev.type = UHID_INPUT;
- ev.u.input.size = reportSize;
- memcpy(&ev.u.input.data, report, reportSize);
+ ev.type = UHID_INPUT2;
+ ev.u.input2.size = reportSize;
+ memcpy(&ev.u.input2.data, report, reportSize);
ssize_t ret = TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev)));
if (ret < 0 || ret != sizeof(ev)) {
- ALOGE("Failed to send hid event: %s", strerror(errno));
+ LOGE("Failed to send hid event: %s", strerror(errno));
}
}
int Device::handleEvents(int events) {
- if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
- ALOGE("uhid node was closed or an error occurred. events=0x%x", events);
+ if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
+ LOGE("uhid node was closed or an error occurred. events=0x%x", events);
mDeviceCallback->onDeviceError();
return 0;
}
struct uhid_event ev;
ssize_t ret = TEMP_FAILURE_RETRY(::read(mFd, &ev, sizeof(ev)));
if (ret < 0) {
- ALOGE("Failed to read from uhid node: %s", strerror(errno));
+ LOGE("Failed to read from uhid node: %s", strerror(errno));
mDeviceCallback->onDeviceError();
return 0;
}
@@ -184,7 +202,7 @@
}
static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid, jint pid,
- jbyteArray rawDescriptor, jobject queue, jobject callback) {
+ jbyteArray rawDescriptor, jobject callback) {
ScopedUtfChars name(env, rawName);
if (name.c_str() == nullptr) {
return 0;
@@ -194,20 +212,21 @@
std::unique_ptr<uint8_t[]> desc = getData(env, rawDescriptor, size);
std::unique_ptr<uhid::DeviceCallback> cb(new uhid::DeviceCallback(env, callback));
- sp<Looper> looper = android_os_MessageQueue_getMessageQueue(env, queue)->getLooper();
uhid::Device* d = uhid::Device::open(
id, reinterpret_cast<const char*>(name.c_str()), vid, pid,
- std::move(desc), size, std::move(cb), std::move(looper));
+ std::move(desc), size, std::move(cb));
return reinterpret_cast<jlong>(d);
}
-static void sendReport(JNIEnv* env, jclass /* clazz */, jlong ptr,jbyteArray rawReport) {
+static void sendReport(JNIEnv* env, jclass /* clazz */, jlong ptr, jbyteArray rawReport) {
size_t size;
std::unique_ptr<uint8_t[]> report = getData(env, rawReport, size);
uhid::Device* d = reinterpret_cast<uhid::Device*>(ptr);
if (d) {
d->sendReport(report.get(), size);
+ } else {
+ LOGE("Could not send report, Device* is null!");
}
}
@@ -220,7 +239,7 @@
static JNINativeMethod sMethods[] = {
{ "nativeOpenDevice",
- "(Ljava/lang/String;III[BLandroid/os/MessageQueue;"
+ "(Ljava/lang/String;III[B"
"Lcom/android/commands/hid/Device$DeviceCallback;)J",
reinterpret_cast<void*>(openDevice) },
{ "nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport) },
@@ -228,11 +247,21 @@
};
int register_com_android_commands_hid_Device(JNIEnv* env) {
- jclass clazz = FindClassOrDie(env, "com/android/commands/hid/Device$DeviceCallback");
+ jclass clazz = env->FindClass("com/android/commands/hid/Device$DeviceCallback");
+ if (clazz == NULL) {
+ LOGE("Unable to find class 'DeviceCallback'");
+ return JNI_ERR;
+ }
uhid::gDeviceCallbackClassInfo.onDeviceOpen =
- GetMethodIDOrDie(env, clazz, "onDeviceOpen", "()V");
- uhid::gDeviceCallbackClassInfo.onDeviceError=
- GetMethodIDOrDie(env, clazz, "onDeviceError", "()V");
+ env->GetMethodID(clazz, "onDeviceOpen", "()V");
+ uhid::gDeviceCallbackClassInfo.onDeviceError =
+ env->GetMethodID(clazz, "onDeviceError", "()V");
+ if (uhid::gDeviceCallbackClassInfo.onDeviceOpen == NULL ||
+ uhid::gDeviceCallbackClassInfo.onDeviceError == NULL) {
+ LOGE("Unable to obtain onDeviceOpen or onDeviceError methods");
+ return JNI_ERR;
+ }
+
return jniRegisterNativeMethods(env, "com/android/commands/hid/Device",
sMethods, NELEM(sMethods));
}
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h
index 6c5899e..149456d 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.h
+++ b/cmds/hid/jni/com_android_commands_hid_Device.h
@@ -17,8 +17,6 @@
#include <memory>
#include <jni.h>
-#include <utils/Looper.h>
-#include <utils/StrongPointer.h>
namespace android {
namespace uhid {
@@ -32,16 +30,18 @@
void onDeviceError();
private:
+ JNIEnv* getJNIEnv();
jobject mCallbackObject;
+ JavaVM* mJavaVM;
};
class Device {
public:
static Device* open(int32_t id, const char* name, int32_t vid, int32_t pid,
std::unique_ptr<uint8_t[]> descriptor, size_t descriptorSize,
- std::unique_ptr<DeviceCallback> callback, sp<Looper> looper);
+ std::unique_ptr<DeviceCallback> callback);
- Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback, sp<Looper> looper);
+ Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback);
~Device();
void sendReport(uint8_t* report, size_t reportSize);
@@ -53,7 +53,6 @@
int32_t mId;
int mFd;
std::unique_ptr<DeviceCallback> mDeviceCallback;
- sp<Looper> mLooper;
};
diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java
index dbe883b..8c52a8e 100644
--- a/cmds/hid/src/com/android/commands/hid/Device.java
+++ b/cmds/hid/src/com/android/commands/hid/Device.java
@@ -29,22 +29,14 @@
public class Device {
private static final String TAG = "HidDevice";
- // Minimum amount of time to wait before sending input events to a device. Even though we're
- // guaranteed that the device has been created and opened by the input system, there's still a
- // window in which the system hasn't started reading events out of it. If a stream of events
- // begins in during this window (like a button down event) and *then* we start reading, we're
- // liable to ignore the whole stream.
- private static final int MIN_WAIT_FOR_FIRST_EVENT = 150;
-
private static final int MSG_OPEN_DEVICE = 1;
private static final int MSG_SEND_REPORT = 2;
private static final int MSG_CLOSE_DEVICE = 3;
-
private final int mId;
private final HandlerThread mThread;
private final DeviceHandler mHandler;
- private long mEventTime;
+ private long mTimeToSend;
private final Object mCond = new Object();
@@ -53,7 +45,7 @@
}
private static native long nativeOpenDevice(String name, int id, int vid, int pid,
- byte[] descriptor, MessageQueue queue, DeviceCallback callback);
+ byte[] descriptor, DeviceCallback callback);
private static native void nativeSendReport(long ptr, byte[] data);
private static native void nativeCloseDevice(long ptr);
@@ -74,22 +66,22 @@
args.arg2 = descriptor;
args.arg3 = report;
mHandler.obtainMessage(MSG_OPEN_DEVICE, args).sendToTarget();
- mEventTime = SystemClock.uptimeMillis() + MIN_WAIT_FOR_FIRST_EVENT;
+ mTimeToSend = SystemClock.uptimeMillis();
}
public void sendReport(byte[] report) {
Message msg = mHandler.obtainMessage(MSG_SEND_REPORT, report);
- mHandler.sendMessageAtTime(msg, mEventTime);
+ // if two messages are sent at identical time, they will be processed in order received
+ mHandler.sendMessageAtTime(msg, mTimeToSend);
}
public void addDelay(int delay) {
- mEventTime += delay;
+ mTimeToSend = Math.max(SystemClock.uptimeMillis(), mTimeToSend) + delay;
}
public void close() {
Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE);
- msg.setAsynchronous(true);
- mHandler.sendMessageAtTime(msg, mEventTime + 1);
+ mHandler.sendMessageAtTime(msg, Math.max(SystemClock.uptimeMillis(), mTimeToSend) + 1);
try {
synchronized (mCond) {
mCond.wait();
@@ -111,8 +103,7 @@
case MSG_OPEN_DEVICE:
SomeArgs args = (SomeArgs) msg.obj;
mPtr = nativeOpenDevice((String) args.arg1, args.argi1, args.argi2, args.argi3,
- (byte[]) args.arg2, getLooper().myQueue(), new DeviceCallback());
- nativeSendReport(mPtr, (byte[]) args.arg3);
+ (byte[]) args.arg2, new DeviceCallback());
pauseEvents();
break;
case MSG_SEND_REPORT:
@@ -155,6 +146,7 @@
}
public void onDeviceError() {
+ Log.e(TAG, "Device error occurred, closing /dev/uhid");
Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE);
msg.setAsynchronous(true);
msg.sendToTarget();
diff --git a/cmds/hid/src/com/android/commands/hid/Hid.java b/cmds/hid/src/com/android/commands/hid/Hid.java
index 976a782..234e47f 100644
--- a/cmds/hid/src/com/android/commands/hid/Hid.java
+++ b/cmds/hid/src/com/android/commands/hid/Hid.java
@@ -16,7 +16,6 @@
package com.android.commands.hid;
-import android.os.SystemClock;
import android.util.JsonReader;
import android.util.JsonToken;
import android.util.Log;
@@ -91,7 +90,6 @@
}
}
-
private void process(Event e) {
final int index = mDevices.indexOfKey(e.getId());
if (index >= 0) {
@@ -101,10 +99,16 @@
} else if (Event.COMMAND_REPORT.equals(e.getCommand())) {
d.sendReport(e.getReport());
} else {
- error("Unknown command \"" + e.getCommand() + "\". Ignoring event.");
+ if (Event.COMMAND_REGISTER.equals(e.getCommand())) {
+ error("Device id=" + e.getId() + " is already registered. Ignoring event.");
+ } else {
+ error("Unknown command \"" + e.getCommand() + "\". Ignoring event.");
+ }
}
- } else {
+ } else if (Event.COMMAND_REGISTER.equals(e.getCommand())) {
registerDevice(e);
+ } else {
+ Log.e(TAG, "Unknown device id specified. Ignoring event.");
}
}
@@ -124,7 +128,6 @@
}
private static void error(String msg, Exception e) {
- System.out.println(msg);
Log.e(TAG, msg);
if (e != null) {
Log.e(TAG, Log.getStackTraceString(e));
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 5fedc9e..607e6e0 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -33,17 +33,24 @@
#include <ui/DisplayInfo.h>
#include <ui/PixelFormat.h>
+#include <system/graphics.h>
+
// TODO: Fix Skia.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#include <SkImageEncoder.h>
#include <SkData.h>
+#include <SkColorSpace.h>
#pragma GCC diagnostic pop
using namespace android;
static uint32_t DEFAULT_DISPLAY_ID = ISurfaceComposer::eDisplayIdMain;
+#define COLORSPACE_UNKNOWN 0
+#define COLORSPACE_SRGB 1
+#define COLORSPACE_DISPLAY_P3 2
+
static void usage(const char* pname)
{
fprintf(stderr,
@@ -67,6 +74,31 @@
}
}
+static sk_sp<SkColorSpace> dataSpaceToColorSpace(android_dataspace d)
+{
+ switch (d) {
+ case HAL_DATASPACE_V0_SRGB:
+ return SkColorSpace::MakeSRGB();
+ case HAL_DATASPACE_DISPLAY_P3:
+ return SkColorSpace::MakeRGB(
+ SkColorSpace::kSRGB_RenderTargetGamma, SkColorSpace::kDCIP3_D65_Gamut);
+ default:
+ return nullptr;
+ }
+}
+
+static uint32_t dataSpaceToInt(android_dataspace d)
+{
+ switch (d) {
+ case HAL_DATASPACE_V0_SRGB:
+ return COLORSPACE_SRGB;
+ case HAL_DATASPACE_DISPLAY_P3:
+ return COLORSPACE_DISPLAY_P3;
+ default:
+ return COLORSPACE_UNKNOWN;
+ }
+}
+
static status_t notifyMediaScanner(const char* fileName) {
String8 cmd("am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://");
String8 fileUrl("\"");
@@ -139,6 +171,7 @@
void const* base = NULL;
uint32_t w, s, h, f;
+ android_dataspace d;
size_t size = 0;
// Maps orientations from DisplayInfo to ISurfaceComposer
@@ -177,13 +210,15 @@
h = screenshot.getHeight();
s = screenshot.getStride();
f = screenshot.getFormat();
+ d = screenshot.getDataSpace();
size = screenshot.getSize();
}
if (base != NULL) {
if (png) {
const SkImageInfo info =
- SkImageInfo::Make(w, h, flinger2skia(f), kPremul_SkAlphaType);
+ SkImageInfo::Make(w, h, flinger2skia(f), kPremul_SkAlphaType,
+ dataSpaceToColorSpace(d));
SkPixmap pixmap(info, base, s * bytesPerPixel(f));
struct FDWStream final : public SkWStream {
size_t fBytesWritten = 0;
@@ -200,9 +235,11 @@
notifyMediaScanner(fn);
}
} else {
+ uint32_t c = dataSpaceToInt(d);
write(fd, &w, 4);
write(fd, &h, 4);
write(fd, &f, 4);
+ write(fd, &c, 4);
size_t Bpp = bytesPerPixel(f);
for (size_t y=0 ; y<h ; y++) {
write(fd, base, w*Bpp);
diff --git a/cmds/uiautomator/instrumentation/Android.mk b/cmds/uiautomator/instrumentation/Android.mk
index 0c93b4c..e6cbdb4 100644
--- a/cmds/uiautomator/instrumentation/Android.mk
+++ b/cmds/uiautomator/instrumentation/Android.mk
@@ -22,6 +22,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, testrunner-src) \
$(call all-java-files-under, ../library/core-src)
LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
LOCAL_MODULE := uiautomator-instrumentation
# TODO: change this to 18 when it's available
LOCAL_SDK_VERSION := current
diff --git a/cmds/uiautomator/library/Android.mk b/cmds/uiautomator/library/Android.mk
index e70bd11..4bf856f 100644
--- a/cmds/uiautomator/library/Android.mk
+++ b/cmds/uiautomator/library/Android.mk
@@ -64,10 +64,9 @@
LOCAL_MODULE := android_uiautomator
LOCAL_JAVA_LIBRARIES := $(uiautomator.core_java_libraries)
LOCAL_SOURCE_FILES_ALL_GENERATED := true
-include $(BUILD_STATIC_JAVA_LIBRARY)
# Make sure to run droiddoc first to generate the stub source files.
-$(full_classes_compiled_jar) : $(uiautomator_stubs_stamp)
-$(built_dex_intermediate) : $(uiautomator_stubs_stamp)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(uiautomator_stubs_stamp)
+include $(BUILD_STATIC_JAVA_LIBRARY)
###############################################
# API check
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 38ce427..00d6657 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -473,9 +473,13 @@
if (mReversing) {
// Between start() and first frame, mLastEventId would be unset (i.e. -1)
mLastEventId = mLastEventId == -1 ? mEvents.size() : mLastEventId;
- for (int j = mLastEventId - 1; j >= 0; j--) {
- AnimationEvent event = mEvents.get(j);
+ while (mLastEventId > 0) {
+ mLastEventId = mLastEventId - 1;
+ AnimationEvent event = mEvents.get(mLastEventId);
Animator anim = event.mNode.mAnimation;
+ if (mNodeMap.get(anim).mEnded) {
+ continue;
+ }
if (event.mEvent == AnimationEvent.ANIMATION_END) {
anim.reverse();
} else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
@@ -487,9 +491,15 @@
}
}
} else {
- for (int j = mLastEventId + 1; j < mEvents.size(); j++) {
- AnimationEvent event = mEvents.get(j);
+ while (mLastEventId < mEvents.size() - 1) {
+ // Avoid potential reentrant loop caused by child animators manipulating
+ // AnimatorSet's lifecycle (i.e. not a recommended approach).
+ mLastEventId = mLastEventId + 1;
+ AnimationEvent event = mEvents.get(mLastEventId);
Animator anim = event.mNode.mAnimation;
+ if (mNodeMap.get(anim).mEnded) {
+ continue;
+ }
if (event.mEvent == AnimationEvent.ANIMATION_START) {
anim.start();
} else if (event.mEvent == AnimationEvent.ANIMATION_END && anim.isStarted()) {
@@ -663,6 +673,10 @@
* <p>Starting this <code>AnimatorSet</code> will, in turn, start the animations for which
* it is responsible. The details of when exactly those animations are started depends on
* the dependency relationships that have been set up between the animations.
+ *
+ * <b>Note:</b> Manipulating AnimatorSet's lifecycle in the child animators' listener callbacks
+ * will lead to undefined behaviors. Also, AnimatorSet will ignore any seeking in the child
+ * animators once {@link #start()} is called.
*/
@SuppressWarnings("unchecked")
@Override
@@ -1653,6 +1667,8 @@
int childrenSize = parent.mChildNodes.size();
for (int i = 0; i < childrenSize; i++) {
Node child = parent.mChildNodes.get(i);
+ child.mTotalDuration = child.mAnimation.getTotalDuration(); // Update cached duration.
+
int index = visited.indexOf(child);
if (index >= 0) {
// Child has been visited, cycle found. Mark all the nodes in the cycle.
@@ -1679,9 +1695,8 @@
child.mStartTime = parent.mEndTime;
}
- long duration = child.mAnimation.getTotalDuration();
- child.mEndTime = duration == DURATION_INFINITE ?
- DURATION_INFINITE : child.mStartTime + duration;
+ child.mEndTime = child.mTotalDuration == DURATION_INFINITE
+ ? DURATION_INFINITE : child.mStartTime + child.mTotalDuration;
}
}
updatePlayTime(child, visited);
diff --git a/core/java/android/annotation/IntDef.java b/core/java/android/annotation/IntDef.java
index dd712a6..3f9064e 100644
--- a/core/java/android/annotation/IntDef.java
+++ b/core/java/android/annotation/IntDef.java
@@ -19,7 +19,7 @@
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
-import static java.lang.annotation.RetentionPolicy.CLASS;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
/**
* Denotes that the annotated element of integer type, represents
@@ -48,7 +48,7 @@
*
* @hide
*/
-@Retention(CLASS)
+@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface IntDef {
/** Defines the constant prefix for this element */
diff --git a/core/java/android/annotation/StringDef.java b/core/java/android/annotation/StringDef.java
index 8c8d5d8..d5157c3 100644
--- a/core/java/android/annotation/StringDef.java
+++ b/core/java/android/annotation/StringDef.java
@@ -19,7 +19,7 @@
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
-import static java.lang.annotation.RetentionPolicy.CLASS;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
/**
* Denotes that the annotated String element, represents a logical
@@ -43,7 +43,7 @@
*
* @hide
*/
-@Retention(CLASS)
+@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface StringDef {
/** Defines the allowed constants for this element */
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f398c8d..199e856 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -649,6 +649,9 @@
*/
public static final int COMPAT_MODE_TOGGLE = 2;
+ private static final boolean DEVELOPMENT_FORCE_LOW_RAM =
+ SystemProperties.getBoolean("debug.force_low_ram", false);
+
/** @hide */
public static class StackId {
/** Invalid stack ID. */
@@ -1108,7 +1111,20 @@
/** @hide */
public static boolean isLowRamDeviceStatic() {
- return RoSystemProperties.CONFIG_LOW_RAM;
+ return RoSystemProperties.CONFIG_LOW_RAM ||
+ (Build.IS_DEBUGGABLE && DEVELOPMENT_FORCE_LOW_RAM);
+ }
+
+ /**
+ * Returns true if this is a small battery device. Exactly whether a device is considered to be
+ * small battery is ultimately up to the device configuration, but currently it generally means
+ * something in the class of a device with 1000 mAh or less. This is mostly intended to be used
+ * to determine whether certain features should be altered to account for a drastically smaller
+ * battery.
+ * @hide
+ */
+ public static boolean isSmallBatteryDevice() {
+ return RoSystemProperties.CONFIG_SMALL_BATTERY;
}
/**
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 01e4cceb..df55080 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -29,8 +29,8 @@
import android.content.ContentProvider;
import android.content.Context;
import android.content.IContentProvider;
-import android.content.Intent;
import android.content.IIntentReceiver;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
@@ -91,6 +91,7 @@
import android.provider.Downloads;
import android.provider.FontsContract;
import android.provider.Settings;
+import android.renderscript.RenderScriptCacheDir;
import android.security.NetworkSecurityPolicy;
import android.security.net.config.NetworkSecurityConfigProvider;
import android.util.AndroidRuntimeException;
@@ -114,7 +115,6 @@
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import android.renderscript.RenderScriptCacheDir;
import android.webkit.WebView;
import com.android.internal.annotations.GuardedBy;
@@ -128,8 +128,21 @@
import com.android.internal.util.FastPrintWriter;
import com.android.org.conscrypt.OpenSSLSocketImpl;
import com.android.org.conscrypt.TrustedCertificateStore;
+
+import dalvik.system.BaseDexClassLoader;
+import dalvik.system.CloseGuard;
+import dalvik.system.VMDebug;
+import dalvik.system.VMRuntime;
+
import com.google.android.collect.Lists;
+import libcore.io.DropBox;
+import libcore.io.EventLogger;
+import libcore.io.IoUtils;
+import libcore.net.event.NetworkEventDispatcher;
+
+import org.apache.harmony.dalvik.ddmc.DdmVmInternal;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
@@ -146,16 +159,6 @@
import java.util.Objects;
import java.util.TimeZone;
-import libcore.io.DropBox;
-import libcore.io.EventLogger;
-import libcore.io.IoUtils;
-import libcore.net.event.NetworkEventDispatcher;
-import dalvik.system.BaseDexClassLoader;
-import dalvik.system.CloseGuard;
-import dalvik.system.VMDebug;
-import dalvik.system.VMRuntime;
-import org.apache.harmony.dalvik.ddmc.DdmVmInternal;
-
final class RemoteServiceException extends AndroidRuntimeException {
public RemoteServiceException(String msg) {
super(msg);
@@ -2887,6 +2890,9 @@
TAG, "Handling launch of " + r);
// Initialize before creating the activity
+ if (!ThreadedRenderer.sRendererDisabled) {
+ GraphicsEnvironment.earlyInitEGL();
+ }
WindowManagerGlobal.initialize();
Activity a = performLaunchActivity(r, customIntent);
@@ -5372,26 +5378,45 @@
WindowManagerGlobal.getInstance().trimMemory(level);
}
- private void setupGraphicsSupport(Context context, File cacheDir) {
- if (Process.isIsolated()) {
- // Isolated processes aren't going to do UI.
- return;
- }
+ private void setupGraphicsSupport(Context context) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setupGraphicsSupport");
- try {
- int uid = Process.myUid();
- String[] packages = getPackageManager().getPackagesForUid(uid);
- if (packages != null) {
- ThreadedRenderer.setupDiskCache(cacheDir);
- RenderScriptCacheDir.setupDiskCache(cacheDir);
- GraphicsEnvironment.setupGraphicsEnvironment(context);
+ // The system package doesn't have real data directories, so don't set up cache paths.
+ if (!"android".equals(context.getPackageName())) {
+ // This cache location probably points at credential-encrypted
+ // storage which may not be accessible yet; assign it anyway instead
+ // of pointing at device-encrypted storage.
+ final File cacheDir = context.getCacheDir();
+ if (cacheDir != null) {
+ // Provide a usable directory for temporary files
+ System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
+ } else {
+ Log.v(TAG, "Unable to initialize \"java.io.tmpdir\" property "
+ + "due to missing cache directory");
}
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+ // Setup a location to store generated/compiled graphics code.
+ final Context deviceContext = context.createDeviceProtectedStorageContext();
+ final File codeCacheDir = deviceContext.getCodeCacheDir();
+ if (codeCacheDir != null) {
+ try {
+ int uid = Process.myUid();
+ String[] packages = getPackageManager().getPackagesForUid(uid);
+ if (packages != null) {
+ ThreadedRenderer.setupDiskCache(cacheDir);
+ RenderScriptCacheDir.setupDiskCache(cacheDir);
+ }
+ } catch (RemoteException e) {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ Log.w(TAG, "Unable to use shader/script cache: missing code-cache directory");
+ }
}
+
+ GraphicsEnvironment.chooseDriver(context);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
private void updateDefaultDensity() {
@@ -5478,15 +5503,6 @@
android.ddm.DdmHandleAppName.setAppName(data.processName,
UserHandle.myUserId());
- if (data.persistent) {
- // Persistent processes on low-memory devices do not get to
- // use hardware accelerated drawing, since this can add too much
- // overhead to the process.
- if (!ActivityManager.isHighEndGfx()) {
- ThreadedRenderer.disable(false);
- }
- }
-
if (mProfiler.profileFd != null) {
mProfiler.startProfiling();
}
@@ -5673,27 +5689,8 @@
updateLocaleListFromAppContext(appContext,
mResourcesManager.getConfiguration().getLocales());
- if (!Process.isIsolated() && !"android".equals(appContext.getPackageName())) {
- // This cache location probably points at credential-encrypted
- // storage which may not be accessible yet; assign it anyway instead
- // of pointing at device-encrypted storage.
- final File cacheDir = appContext.getCacheDir();
- if (cacheDir != null) {
- // Provide a usable directory for temporary files
- System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
- } else {
- Log.v(TAG, "Unable to initialize \"java.io.tmpdir\" property "
- + "due to missing cache directory");
- }
-
- // Setup a location to store generated/compiled graphics code.
- final Context deviceContext = appContext.createDeviceProtectedStorageContext();
- final File codeCacheDir = deviceContext.getCodeCacheDir();
- if (codeCacheDir != null) {
- setupGraphicsSupport(appContext, codeCacheDir);
- } else {
- Log.e(TAG, "Unable to setupGraphicsSupport due to missing code-cache directory");
- }
+ if (!Process.isIsolated()) {
+ setupGraphicsSupport(appContext);
}
// If we use profiles, setup the dex reporter to notify package manager
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index e5c4208..acceed0 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -78,6 +78,10 @@
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.provider.Settings;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructStat;
import android.util.ArrayMap;
import android.util.IconDrawableFactory;
import android.util.LauncherIcons;
@@ -2674,4 +2678,78 @@
throw e.rethrowAsRuntimeException();
}
}
+
+ private static class DexModuleRegisterResult {
+ final String dexModulePath;
+ final boolean success;
+ final String message;
+
+ private DexModuleRegisterResult(String dexModulePath, boolean success, String message) {
+ this.dexModulePath = dexModulePath;
+ this.success = success;
+ this.message = message;
+ }
+ }
+
+ private static class DexModuleRegisterCallbackDelegate
+ extends android.content.pm.IDexModuleRegisterCallback.Stub
+ implements Handler.Callback {
+ private static final int MSG_DEX_MODULE_REGISTERED = 1;
+ private final DexModuleRegisterCallback callback;
+ private final Handler mHandler;
+
+ DexModuleRegisterCallbackDelegate(@NonNull DexModuleRegisterCallback callback) {
+ this.callback = callback;
+ mHandler = new Handler(Looper.getMainLooper(), this);
+ }
+
+ @Override
+ public void onDexModuleRegistered(@NonNull String dexModulePath, boolean success,
+ @Nullable String message)throws RemoteException {
+ mHandler.obtainMessage(MSG_DEX_MODULE_REGISTERED,
+ new DexModuleRegisterResult(dexModulePath, success, message)).sendToTarget();
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (msg.what != MSG_DEX_MODULE_REGISTERED) {
+ return false;
+ }
+ DexModuleRegisterResult result = (DexModuleRegisterResult)msg.obj;
+ callback.onDexModuleRegistered(result.dexModulePath, result.success, result.message);
+ return true;
+ }
+ }
+
+ @Override
+ public void registerDexModule(@NonNull String dexModule,
+ @Nullable DexModuleRegisterCallback callback) {
+ // Check if this is a shared module by looking if the others can read it.
+ boolean isSharedModule = false;
+ try {
+ StructStat stat = Os.stat(dexModule);
+ if ((OsConstants.S_IROTH & stat.st_mode) != 0) {
+ isSharedModule = true;
+ }
+ } catch (ErrnoException e) {
+ callback.onDexModuleRegistered(dexModule, false,
+ "Could not get stat the module file: " + e.getMessage());
+ return;
+ }
+
+ // Module path is ok.
+ // Create the callback delegate to be passed to package manager service.
+ DexModuleRegisterCallbackDelegate callbackDelegate = null;
+ if (callback != null) {
+ callbackDelegate = new DexModuleRegisterCallbackDelegate(callback);
+ }
+
+ // Invoke the package manager service.
+ try {
+ mPM.registerDexModule(mContext.getPackageName(), dexModule,
+ isSharedModule, callbackDelegate);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 268a105b..8c64129 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -63,7 +63,6 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
-import android.os.UserManager;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
import android.system.ErrnoException;
@@ -90,6 +89,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteOrder;
+import java.util.ArrayList;
import java.util.Objects;
class ReceiverRestrictedContext extends ContextWrapper {
@@ -368,27 +368,26 @@
// STOPSHIP: fix buggy apps
if (SystemProperties.getBoolean("fw.ignore_buggy", false)) return false;
if ("com.google.android.tts".equals(getApplicationInfo().packageName)) return true;
- if ("com.breel.geswallpapers".equals(getApplicationInfo().packageName)) return true;
return false;
}
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
- checkMode(mode);
- if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
- if (isCredentialProtectedStorage()
- && !getSystemService(StorageManager.class).isUserKeyUnlocked(
- UserHandle.myUserId())
- && !isBuggy()) {
- throw new IllegalStateException("SharedPreferences in credential encrypted "
- + "storage are not available until after user is unlocked");
- }
- }
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
if (sp == null) {
+ checkMode(mode);
+ if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
+ if (isCredentialProtectedStorage()
+ && !getSystemService(StorageManager.class).isUserKeyUnlocked(
+ UserHandle.myUserId())
+ && !isBuggy()) {
+ throw new IllegalStateException("SharedPreferences in credential encrypted "
+ + "storage are not available until after user is unlocked");
+ }
+ }
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
@@ -419,6 +418,26 @@
return packagePrefs;
}
+ @Override
+ public void reloadSharedPreferences() {
+ // Build the list of all per-context impls (i.e. caches) we know about
+ ArrayList<SharedPreferencesImpl> spImpls = new ArrayList<>();
+ synchronized (ContextImpl.class) {
+ final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
+ for (int i = 0; i < cache.size(); i++) {
+ final SharedPreferencesImpl sp = cache.valueAt(i);
+ if (sp != null) {
+ spImpls.add(sp);
+ }
+ }
+ }
+
+ // Issue the reload outside the cache lock
+ for (int i = 0; i < spImpls.size(); i++) {
+ spImpls.get(i).startReloadIfChangedUnexpectedly();
+ }
+ }
+
/**
* Try our best to migrate all files from source to target that match
* requested prefix.
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index c2fd007..66dc6a1 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -60,114 +60,6 @@
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
-final class FragmentState implements Parcelable {
- final String mClassName;
- final int mIndex;
- final boolean mFromLayout;
- final int mFragmentId;
- final int mContainerId;
- final String mTag;
- final boolean mRetainInstance;
- final boolean mDetached;
- final Bundle mArguments;
- final boolean mHidden;
-
- Bundle mSavedFragmentState;
-
- Fragment mInstance;
-
- public FragmentState(Fragment frag) {
- mClassName = frag.getClass().getName();
- mIndex = frag.mIndex;
- mFromLayout = frag.mFromLayout;
- mFragmentId = frag.mFragmentId;
- mContainerId = frag.mContainerId;
- mTag = frag.mTag;
- mRetainInstance = frag.mRetainInstance;
- mDetached = frag.mDetached;
- mArguments = frag.mArguments;
- mHidden = frag.mHidden;
- }
-
- public FragmentState(Parcel in) {
- mClassName = in.readString();
- mIndex = in.readInt();
- mFromLayout = in.readInt() != 0;
- mFragmentId = in.readInt();
- mContainerId = in.readInt();
- mTag = in.readString();
- mRetainInstance = in.readInt() != 0;
- mDetached = in.readInt() != 0;
- mArguments = in.readBundle();
- mHidden = in.readInt() != 0;
- mSavedFragmentState = in.readBundle();
- }
-
- public Fragment instantiate(FragmentHostCallback host, FragmentContainer container,
- Fragment parent, FragmentManagerNonConfig childNonConfig) {
- if (mInstance == null) {
- final Context context = host.getContext();
- if (mArguments != null) {
- mArguments.setClassLoader(context.getClassLoader());
- }
-
- if (container != null) {
- mInstance = container.instantiate(context, mClassName, mArguments);
- } else {
- mInstance = Fragment.instantiate(context, mClassName, mArguments);
- }
-
- if (mSavedFragmentState != null) {
- mSavedFragmentState.setClassLoader(context.getClassLoader());
- mInstance.mSavedFragmentState = mSavedFragmentState;
- }
- mInstance.setIndex(mIndex, parent);
- mInstance.mFromLayout = mFromLayout;
- mInstance.mRestored = true;
- mInstance.mFragmentId = mFragmentId;
- mInstance.mContainerId = mContainerId;
- mInstance.mTag = mTag;
- mInstance.mRetainInstance = mRetainInstance;
- mInstance.mDetached = mDetached;
- mInstance.mHidden = mHidden;
- mInstance.mFragmentManager = host.mFragmentManager;
- if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
- "Instantiated fragment " + mInstance);
- }
- mInstance.mChildNonConfig = childNonConfig;
- return mInstance;
- }
-
- public int describeContents() {
- return 0;
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mClassName);
- dest.writeInt(mIndex);
- dest.writeInt(mFromLayout ? 1 : 0);
- dest.writeInt(mFragmentId);
- dest.writeInt(mContainerId);
- dest.writeString(mTag);
- dest.writeInt(mRetainInstance ? 1 : 0);
- dest.writeInt(mDetached ? 1 : 0);
- dest.writeBundle(mArguments);
- dest.writeInt(mHidden ? 1 : 0);
- dest.writeBundle(mSavedFragmentState);
- }
-
- public static final Parcelable.Creator<FragmentState> CREATOR
- = new Parcelable.Creator<FragmentState>() {
- public FragmentState createFromParcel(Parcel in) {
- return new FragmentState(in);
- }
-
- public FragmentState[] newArray(int size) {
- return new FragmentState[size];
- }
- };
-}
-
/**
* A Fragment is a piece of an application's user interface or behavior
* that can be placed in an {@link Activity}. Interaction with fragments
@@ -984,7 +876,7 @@
/**
* Return true if the fragment is currently visible to the user. This means
- * it: (1) has been added, (2) has its view attached to the window, and
+ * it: (1) has been added, (2) has its view attached to the window, and
* (3) is not hidden.
*/
final public boolean isVisible() {
@@ -1463,7 +1355,7 @@
* declaration for the styleable used here is:</p>
*
* {@sample development/samples/ApiDemos/res/values/attrs.xml fragment_arguments}
- *
+ *
* <p>The fragment can then be declared within its activity's content layout
* through a tag like this:</p>
*
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 3e26e0f..9fb9c00 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -670,7 +670,7 @@
int mNextFragmentIndex = 0;
SparseArray<Fragment> mActive;
- ArrayList<Fragment> mAdded;
+ final ArrayList<Fragment> mAdded = new ArrayList<>();
ArrayList<BackStackRecord> mBackStack;
ArrayList<Fragment> mCreatedMenus;
@@ -925,7 +925,7 @@
@Override
public List<Fragment> getFragments() {
- if (mAdded == null) {
+ if (mAdded.isEmpty()) {
return Collections.EMPTY_LIST;
}
synchronized (mAdded) {
@@ -988,15 +988,17 @@
}
}
- if (mAdded != null) {
- N = mAdded.size();
- if (N > 0) {
- writer.print(prefix); writer.println("Added Fragments:");
- for (int i=0; i<N; i++) {
- Fragment f = mAdded.get(i);
- writer.print(prefix); writer.print(" #"); writer.print(i);
- writer.print(": "); writer.println(f.toString());
- }
+ N = mAdded.size();
+ if (N > 0) {
+ writer.print(prefix);
+ writer.println("Added Fragments:");
+ for (int i = 0; i < N; i++) {
+ Fragment f = mAdded.get(i);
+ writer.print(prefix);
+ writer.print(" #");
+ writer.print(i);
+ writer.print(": ");
+ writer.println(f.toString());
}
}
@@ -1602,14 +1604,12 @@
boolean loadersRunning = false;
// Must add them in the proper order. mActive fragments may be out of order
- if (mAdded != null) {
- final int numAdded = mAdded.size();
- for (int i = 0; i < numAdded; i++) {
- Fragment f = mAdded.get(i);
- moveFragmentToExpectedState(f);
- if (f.mLoaderManager != null) {
- loadersRunning |= f.mLoaderManager.hasRunningLoaders();
- }
+ final int numAdded = mAdded.size();
+ for (int i = 0; i < numAdded; i++) {
+ Fragment f = mAdded.get(i);
+ moveFragmentToExpectedState(f);
+ if (f.mLoaderManager != null) {
+ loadersRunning |= f.mLoaderManager.hasRunningLoaders();
}
}
@@ -1675,9 +1675,6 @@
}
public void addFragment(Fragment fragment, boolean moveToStateNow) {
- if (mAdded == null) {
- mAdded = new ArrayList<Fragment>();
- }
if (DEBUG) Log.v(TAG, "add: " + fragment);
makeActive(fragment);
if (!fragment.mDetached) {
@@ -1713,10 +1710,8 @@
throw new IllegalStateException("Fragment not added: " + fragment);
}
}
- if (mAdded != null) {
- synchronized (mAdded) {
- mAdded.remove(fragment);
- }
+ synchronized (mAdded) {
+ mAdded.remove(fragment);
}
if (fragment.mHasMenu && fragment.mMenuVisible) {
mNeedMenuInvalidate = true;
@@ -1764,11 +1759,9 @@
fragment.mDetached = true;
if (fragment.mAdded) {
// We are not already in back stack, so need to remove the fragment.
- if (mAdded != null) {
- if (DEBUG) Log.v(TAG, "remove from detach: " + fragment);
- synchronized (mAdded) {
- mAdded.remove(fragment);
- }
+ if (DEBUG) Log.v(TAG, "remove from detach: " + fragment);
+ synchronized (mAdded) {
+ mAdded.remove(fragment);
}
if (fragment.mHasMenu && fragment.mMenuVisible) {
mNeedMenuInvalidate = true;
@@ -1783,9 +1776,6 @@
if (fragment.mDetached) {
fragment.mDetached = false;
if (!fragment.mAdded) {
- if (mAdded == null) {
- mAdded = new ArrayList<Fragment>();
- }
if (mAdded.contains(fragment)) {
throw new IllegalStateException("Fragment already added: " + fragment);
}
@@ -1802,13 +1792,11 @@
}
public Fragment findFragmentById(int id) {
- if (mAdded != null) {
- // First look through added fragments.
- for (int i=mAdded.size()-1; i>=0; i--) {
- Fragment f = mAdded.get(i);
- if (f != null && f.mFragmentId == id) {
- return f;
- }
+ // First look through added fragments.
+ for (int i = mAdded.size() - 1; i >= 0; i--) {
+ Fragment f = mAdded.get(i);
+ if (f != null && f.mFragmentId == id) {
+ return f;
}
}
if (mActive != null) {
@@ -1824,7 +1812,7 @@
}
public Fragment findFragmentByTag(String tag) {
- if (mAdded != null && tag != null) {
+ if (tag != null) {
// First look through added fragments.
for (int i=mAdded.size()-1; i>=0; i--) {
Fragment f = mAdded.get(i);
@@ -1844,7 +1832,7 @@
}
return null;
}
-
+
public Fragment findFragmentByWho(String who) {
if (mActive != null && who != null) {
for (int i=mActive.size()-1; i>=0; i--) {
@@ -2164,9 +2152,7 @@
} else {
mTmpAddedFragments.clear();
}
- if (mAdded != null) {
- mTmpAddedFragments.addAll(mAdded);
- }
+ mTmpAddedFragments.addAll(mAdded);
Fragment oldPrimaryNav = getPrimaryNavigationFragment();
for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
final BackStackRecord record = records.get(recordNum);
@@ -2406,7 +2392,7 @@
}
// We want to leave the fragment in the started state
final int state = Math.min(mCurState, Fragment.STARTED);
- final int numAdded = mAdded == null ? 0 : mAdded.size();
+ final int numAdded = mAdded.size();
for (int i = 0; i < numAdded; i++) {
Fragment fragment = mAdded.get(i);
if (fragment.mState < state) {
@@ -2754,23 +2740,21 @@
BackStackState[] backStack = null;
// Build list of currently added fragments.
- if (mAdded != null) {
- N = mAdded.size();
- if (N > 0) {
- added = new int[N];
- for (int i=0; i<N; i++) {
- added[i] = mAdded.get(i).mIndex;
- if (added[i] < 0) {
- throwException(new IllegalStateException(
- "Failure saving state: active " + mAdded.get(i)
- + " has cleared index: " + added[i]));
- }
- if (DEBUG) Log.v(TAG, "saveAllState: adding fragment #" + i
- + ": " + mAdded.get(i));
+ N = mAdded.size();
+ if (N > 0) {
+ added = new int[N];
+ for (int i=0; i<N; i++) {
+ added[i] = mAdded.get(i).mIndex;
+ if (added[i] < 0) {
+ throwException(new IllegalStateException(
+ "Failure saving state: active " + mAdded.get(i)
+ + " has cleared index: " + added[i]));
}
+ if (DEBUG) Log.v(TAG, "saveAllState: adding fragment #" + i
+ + ": " + mAdded.get(i));
}
}
-
+
// Now save back stack.
if (mBackStack != null) {
N = mBackStack.size();
@@ -2876,8 +2860,8 @@
}
// Build the list of currently added fragments.
+ mAdded.clear();
if (fms.mAdded != null) {
- mAdded = new ArrayList<Fragment>(fms.mAdded.length);
for (int i=0; i<fms.mAdded.length; i++) {
Fragment f = mActive.get(fms.mAdded[i]);
if (f == null) {
@@ -2893,8 +2877,6 @@
mAdded.add(f);
}
}
- } else {
- mAdded = null;
}
// Build the back stack.
@@ -2970,7 +2952,7 @@
public void noteStateNotSaved() {
mSavedNonConfig = null;
mStateSaved = false;
- final int addedCount = mAdded == null ? 0 : mAdded.size();
+ final int addedCount = mAdded.size();
for (int i = 0; i < addedCount; i++) {
Fragment fragment = mAdded.get(i);
if (fragment != null) {
@@ -3047,9 +3029,6 @@
*/
@Deprecated
public void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) {
- if (mAdded == null) {
- return;
- }
for (int i = mAdded.size() - 1; i >= 0; --i) {
final Fragment f = mAdded.get(i);
if (f != null) {
@@ -3060,9 +3039,6 @@
public void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode,
Configuration newConfig) {
- if (mAdded == null) {
- return;
- }
for (int i = mAdded.size() - 1; i >= 0; --i) {
final Fragment f = mAdded.get(i);
if (f != null) {
@@ -3076,9 +3052,6 @@
*/
@Deprecated
public void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
- if (mAdded == null) {
- return;
- }
for (int i = mAdded.size() - 1; i >= 0; --i) {
final Fragment f = mAdded.get(i);
if (f != null) {
@@ -3089,9 +3062,6 @@
public void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode,
Configuration newConfig) {
- if (mAdded == null) {
- return;
- }
for (int i = mAdded.size() - 1; i >= 0; --i) {
final Fragment f = mAdded.get(i);
if (f != null) {
@@ -3101,34 +3071,28 @@
}
public void dispatchConfigurationChanged(Configuration newConfig) {
- if (mAdded != null) {
- for (int i=0; i<mAdded.size(); i++) {
- Fragment f = mAdded.get(i);
- if (f != null) {
- f.performConfigurationChanged(newConfig);
- }
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ f.performConfigurationChanged(newConfig);
}
}
}
public void dispatchLowMemory() {
- if (mAdded != null) {
- for (int i=0; i<mAdded.size(); i++) {
- Fragment f = mAdded.get(i);
- if (f != null) {
- f.performLowMemory();
- }
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ f.performLowMemory();
}
}
}
public void dispatchTrimMemory(int level) {
- if (mAdded != null) {
- for (int i=0; i<mAdded.size(); i++) {
- Fragment f = mAdded.get(i);
- if (f != null) {
- f.performTrimMemory(level);
- }
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ f.performTrimMemory(level);
}
}
}
@@ -3136,21 +3100,19 @@
public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) {
boolean show = false;
ArrayList<Fragment> newMenus = null;
- if (mAdded != null) {
- for (int i=0; i<mAdded.size(); i++) {
- Fragment f = mAdded.get(i);
- if (f != null) {
- if (f.performCreateOptionsMenu(menu, inflater)) {
- show = true;
- if (newMenus == null) {
- newMenus = new ArrayList<Fragment>();
- }
- newMenus.add(f);
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ if (f.performCreateOptionsMenu(menu, inflater)) {
+ show = true;
+ if (newMenus == null) {
+ newMenus = new ArrayList<Fragment>();
}
+ newMenus.add(f);
}
}
}
-
+
if (mCreatedMenus != null) {
for (int i=0; i<mCreatedMenus.size(); i++) {
Fragment f = mCreatedMenus.get(i);
@@ -3167,13 +3129,11 @@
public boolean dispatchPrepareOptionsMenu(Menu menu) {
boolean show = false;
- if (mAdded != null) {
- for (int i=0; i<mAdded.size(); i++) {
- Fragment f = mAdded.get(i);
- if (f != null) {
- if (f.performPrepareOptionsMenu(menu)) {
- show = true;
- }
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ if (f.performPrepareOptionsMenu(menu)) {
+ show = true;
}
}
}
@@ -3181,13 +3141,11 @@
}
public boolean dispatchOptionsItemSelected(MenuItem item) {
- if (mAdded != null) {
- for (int i=0; i<mAdded.size(); i++) {
- Fragment f = mAdded.get(i);
- if (f != null) {
- if (f.performOptionsItemSelected(item)) {
- return true;
- }
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ if (f.performOptionsItemSelected(item)) {
+ return true;
}
}
}
@@ -3195,13 +3153,11 @@
}
public boolean dispatchContextItemSelected(MenuItem item) {
- if (mAdded != null) {
- for (int i=0; i<mAdded.size(); i++) {
- Fragment f = mAdded.get(i);
- if (f != null) {
- if (f.performContextItemSelected(item)) {
- return true;
- }
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ if (f.performContextItemSelected(item)) {
+ return true;
}
}
}
@@ -3209,12 +3165,10 @@
}
public void dispatchOptionsMenuClosed(Menu menu) {
- if (mAdded != null) {
- for (int i=0; i<mAdded.size(); i++) {
- Fragment f = mAdded.get(i);
- if (f != null) {
- f.performOptionsMenuClosed(menu);
- }
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ f.performOptionsMenuClosed(menu);
}
}
}
diff --git a/core/java/android/app/FragmentState.java b/core/java/android/app/FragmentState.java
new file mode 100644
index 0000000..a61da3bb
--- /dev/null
+++ b/core/java/android/app/FragmentState.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+final class FragmentState implements Parcelable {
+ final String mClassName;
+ final int mIndex;
+ final boolean mFromLayout;
+ final int mFragmentId;
+ final int mContainerId;
+ final String mTag;
+ final boolean mRetainInstance;
+ final boolean mDetached;
+ final Bundle mArguments;
+ final boolean mHidden;
+
+ Bundle mSavedFragmentState;
+
+ Fragment mInstance;
+
+ FragmentState(Fragment frag) {
+ mClassName = frag.getClass().getName();
+ mIndex = frag.mIndex;
+ mFromLayout = frag.mFromLayout;
+ mFragmentId = frag.mFragmentId;
+ mContainerId = frag.mContainerId;
+ mTag = frag.mTag;
+ mRetainInstance = frag.mRetainInstance;
+ mDetached = frag.mDetached;
+ mArguments = frag.mArguments;
+ mHidden = frag.mHidden;
+ }
+
+ FragmentState(Parcel in) {
+ mClassName = in.readString();
+ mIndex = in.readInt();
+ mFromLayout = in.readInt() != 0;
+ mFragmentId = in.readInt();
+ mContainerId = in.readInt();
+ mTag = in.readString();
+ mRetainInstance = in.readInt() != 0;
+ mDetached = in.readInt() != 0;
+ mArguments = in.readBundle();
+ mHidden = in.readInt() != 0;
+ mSavedFragmentState = in.readBundle();
+ }
+
+ public Fragment instantiate(FragmentHostCallback host, FragmentContainer container,
+ Fragment parent, FragmentManagerNonConfig childNonConfig) {
+ if (mInstance == null) {
+ final Context context = host.getContext();
+ if (mArguments != null) {
+ mArguments.setClassLoader(context.getClassLoader());
+ }
+
+ if (container != null) {
+ mInstance = container.instantiate(context, mClassName, mArguments);
+ } else {
+ mInstance = Fragment.instantiate(context, mClassName, mArguments);
+ }
+
+ if (mSavedFragmentState != null) {
+ mSavedFragmentState.setClassLoader(context.getClassLoader());
+ mInstance.mSavedFragmentState = mSavedFragmentState;
+ }
+ mInstance.setIndex(mIndex, parent);
+ mInstance.mFromLayout = mFromLayout;
+ mInstance.mRestored = true;
+ mInstance.mFragmentId = mFragmentId;
+ mInstance.mContainerId = mContainerId;
+ mInstance.mTag = mTag;
+ mInstance.mRetainInstance = mRetainInstance;
+ mInstance.mDetached = mDetached;
+ mInstance.mHidden = mHidden;
+ mInstance.mFragmentManager = host.mFragmentManager;
+
+ if (FragmentManagerImpl.DEBUG) {
+ Log.v(FragmentManagerImpl.TAG, "Instantiated fragment " + mInstance);
+ }
+ }
+ mInstance.mChildNonConfig = childNonConfig;
+ return mInstance;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mClassName);
+ dest.writeInt(mIndex);
+ dest.writeInt(mFromLayout ? 1 : 0);
+ dest.writeInt(mFragmentId);
+ dest.writeInt(mContainerId);
+ dest.writeString(mTag);
+ dest.writeInt(mRetainInstance ? 1 : 0);
+ dest.writeInt(mDetached ? 1 : 0);
+ dest.writeBundle(mArguments);
+ dest.writeInt(mHidden ? 1 : 0);
+ dest.writeBundle(mSavedFragmentState);
+ }
+
+ public static final Parcelable.Creator<FragmentState> CREATOR =
+ new Parcelable.Creator<FragmentState>() {
+ @Override
+ public FragmentState createFromParcel(Parcel in) {
+ return new FragmentState(in);
+ }
+
+ @Override
+ public FragmentState[] newArray(int size) {
+ return new FragmentState[size];
+ }
+ };
+}
\ No newline at end of file
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 7640e75..b26117d 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -42,7 +42,8 @@
WindowContentFrameStats getWindowContentFrameStats(int windowId);
void clearWindowAnimationFrameStats();
WindowAnimationFrameStats getWindowAnimationFrameStats();
- void executeShellCommand(String command, in ParcelFileDescriptor fd);
+ void executeShellCommand(String command, in ParcelFileDescriptor sink,
+ in ParcelFileDescriptor source);
void grantRuntimePermission(String packageName, String permission, int userId);
void revokeRuntimePermission(String packageName, String permission, int userId);
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 75a5bf7..411d0e2 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -22,6 +22,7 @@
import android.app.IWallpaperManagerCallback;
import android.app.WallpaperInfo;
import android.content.ComponentName;
+import android.app.WallpaperColors;
/** @hide */
interface IWallpaperManager {
@@ -135,4 +136,23 @@
* wallpaper content has changed.
*/
boolean setLockWallpaperCallback(IWallpaperManagerCallback cb);
+
+ /**
+ * Returns the colors used by the lock screen or system wallpaper.
+ *
+ * @param which either {@link WallpaperManager#FLAG_LOCK}
+ * or {@link WallpaperManager#FLAG_SYSTEM}
+ * @return colors of chosen wallpaper
+ */
+ WallpaperColors getWallpaperColors(int which);
+
+ /**
+ * Register a callback to receive color updates
+ */
+ void registerWallpaperColorsCallback(IWallpaperManagerCallback cb);
+
+ /**
+ * Unregister a callback that was receiving color updates
+ */
+ void unregisterWallpaperColorsCallback(IWallpaperManagerCallback cb);
}
diff --git a/core/java/android/app/IWallpaperManagerCallback.aidl b/core/java/android/app/IWallpaperManagerCallback.aidl
index 991b2bc..0cfbaef 100644
--- a/core/java/android/app/IWallpaperManagerCallback.aidl
+++ b/core/java/android/app/IWallpaperManagerCallback.aidl
@@ -16,6 +16,8 @@
package android.app;
+import android.app.WallpaperColors;
+
/**
* Callback interface used by IWallpaperManager to send asynchronous
* notifications back to its clients. Note that this is a
@@ -28,4 +30,10 @@
* Called when the wallpaper has changed
*/
void onWallpaperChanged();
+
+ /**
+ * Called when wallpaper colors change
+ */
+ void onWallpaperColorsChanged(in WallpaperColors colors, int which);
+
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index dbea349..467fc95 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -816,7 +816,7 @@
* returned. If the timeout expires, a null object is returned.
*
* @param monitor The ActivityMonitor to wait for.
- * @param timeOut The timeout value in secs.
+ * @param timeOut The timeout value in milliseconds.
*
* @return The Activity object that matched the monitor.
*/
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 2a29616..16b21f1 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -29,11 +29,12 @@
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.persistentdata.IPersistentDataBlockService;
import android.util.Log;
import android.view.IOnKeyguardExitResult;
import android.view.IWindowManager;
@@ -41,6 +42,7 @@
import android.view.WindowManagerGlobal;
import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.widget.LockPatternUtils;
import java.util.List;
@@ -74,6 +76,13 @@
"android.app.action.CONFIRM_DEVICE_CREDENTIAL_WITH_USER";
/**
+ * Intent used to prompt user for factory reset credentials.
+ * @hide
+ */
+ public static final String ACTION_CONFIRM_FRP_CREDENTIAL =
+ "android.app.action.CONFIRM_FRP_CREDENTIAL";
+
+ /**
* A CharSequence dialog title to show to the user when used with a
* {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}.
* @hide
@@ -88,6 +97,23 @@
public static final String EXTRA_DESCRIPTION = "android.app.extra.DESCRIPTION";
/**
+ * A CharSequence description to show to the user on the alternate button when used with
+ * {@link #ACTION_CONFIRM_FRP_CREDENTIAL}.
+ * @hide
+ */
+ public static final String EXTRA_ALTERNATE_BUTTON_LABEL =
+ "android.app.extra.ALTERNATE_BUTTON_LABEL";
+
+ /**
+ * Result code returned by the activity started by
+ * {@link #createConfirmFactoryResetCredentialIntent} indicating that the user clicked the
+ * alternate button.
+ *
+ * @hide
+ */
+ public static final int RESULT_ALTERNATE = 1;
+
+ /**
* Get an intent to prompt the user to confirm credentials (pin, pattern or password)
* for the current user of the device. The caller is expected to launch this activity using
* {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
@@ -130,6 +156,63 @@
return intent;
}
+ /**
+ * Get an intent to prompt the user to confirm credentials (pin, pattern or password)
+ * for the previous owner of the device. The caller is expected to launch this activity using
+ * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
+ * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
+ *
+ * @param alternateButtonLabel if not empty, a button is provided with the given label. Upon
+ * clicking this button, the activity returns
+ * {@link #RESULT_ALTERNATE}
+ *
+ * @return the intent for launching the activity or null if the credential of the previous
+ * owner can not be verified (e.g. because there was none, or the device does not support
+ * verifying credentials after a factory reset, or device setup has already been completed).
+ *
+ * @hide
+ */
+ public Intent createConfirmFactoryResetCredentialIntent(
+ CharSequence title, CharSequence description, CharSequence alternateButtonLabel) {
+ if (!LockPatternUtils.frpCredentialEnabled()) {
+ Log.w(TAG, "Factory reset credentials not supported.");
+ return null;
+ }
+
+ // Cannot verify credential if the device is provisioned
+ if (Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
+ Log.e(TAG, "Factory reset credential cannot be verified after provisioning.");
+ return null;
+ }
+
+ // Make sure we have a credential
+ try {
+ IPersistentDataBlockService pdb = IPersistentDataBlockService.Stub.asInterface(
+ ServiceManager.getService(Context.PERSISTENT_DATA_BLOCK_SERVICE));
+ if (pdb == null) {
+ Log.e(TAG, "No persistent data block service");
+ return null;
+ }
+ if (!pdb.hasFrpCredentialHandle()) {
+ Log.i(TAG, "The persistent data block does not have a factory reset credential.");
+ return null;
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ Intent intent = new Intent(ACTION_CONFIRM_FRP_CREDENTIAL);
+ intent.putExtra(EXTRA_TITLE, title);
+ intent.putExtra(EXTRA_DESCRIPTION, description);
+ intent.putExtra(EXTRA_ALTERNATE_BUTTON_LABEL, alternateButtonLabel);
+
+ // explicitly set the package for security
+ intent.setPackage(getSettingsPackageForIntent(intent));
+
+ return intent;
+ }
+
private String getSettingsPackageForIntent(Intent intent) {
List<ResolveInfo> resolveInfos = mContext.getPackageManager()
.queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY);
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 0708b0b..46f115f 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -448,10 +448,13 @@
}
}
+ // Prepend the shared libraries, maintaining their original order where possible.
if (sharedLibraries != null) {
+ int index = 0;
for (String lib : sharedLibraries) {
if (!outZipPaths.contains(lib)) {
- outZipPaths.add(0, lib);
+ outZipPaths.add(index, lib);
+ index++;
}
}
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index ad6b454..4f9ed83 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1171,7 +1171,7 @@
*/
public static final int GROUP_ALERT_CHILDREN = 2;
- private int mGroupAlertBehavior = GROUP_ALERT_CHILDREN;
+ private int mGroupAlertBehavior = GROUP_ALERT_ALL;
/**
* If this notification is being shown as a badge, always show as a number.
@@ -2745,6 +2745,11 @@
private int mBackgroundColor = COLOR_INVALID;
private int mForegroundColor = COLOR_INVALID;
private int mBackgroundColorHint = COLOR_INVALID;
+ /**
+ * A temporary location where actions are stored. If != null the view originally has action
+ * but doesn't have any for this inflation.
+ */
+ private ArrayList<Action> mOriginalActions;
private boolean mRebuildStyledRemoteViews;
/**
@@ -4057,7 +4062,53 @@
contentView.setViewLayoutMarginEndDimen(R.id.line1, endMargin);
contentView.setViewLayoutMarginEndDimen(R.id.text, endMargin);
contentView.setViewLayoutMarginEndDimen(R.id.progress, endMargin);
+ // Bind the reply action
+ Action action = findReplyAction();
+ contentView.setViewVisibility(R.id.reply_icon_action, action != null
+ ? View.VISIBLE
+ : View.GONE);
+
+ if (action != null) {
+ int contrastColor = resolveContrastColor();
+ contentView.setDrawableParameters(R.id.reply_icon_action,
+ true /* targetBackground */,
+ -1,
+ contrastColor,
+ PorterDuff.Mode.SRC_ATOP, -1);
+ int iconColor = NotificationColorUtil.isColorLight(contrastColor)
+ ? Color.BLACK : Color.WHITE;
+ contentView.setDrawableParameters(R.id.reply_icon_action,
+ false /* targetBackground */,
+ -1,
+ iconColor,
+ PorterDuff.Mode.SRC_ATOP, -1);
+ contentView.setOnClickPendingIntent(R.id.right_icon,
+ action.actionIntent);
+ contentView.setOnClickPendingIntent(R.id.reply_icon_action,
+ action.actionIntent);
+ contentView.setRemoteInputs(R.id.right_icon, action.mRemoteInputs);
+ contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs);
+
+ }
}
+ contentView.setViewVisibility(R.id.right_icon_container, mN.mLargeIcon != null
+ ? View.VISIBLE
+ : View.GONE);
+ }
+
+ private Action findReplyAction() {
+ ArrayList<Action> actions = mActions;
+ if (mOriginalActions != null) {
+ actions = mOriginalActions;
+ }
+ int numActions = actions.size();
+ for (int i = 0; i < numActions; i++) {
+ Action action = actions.get(i);
+ if (hasValidRemoteInput(action)) {
+ return action;
+ }
+ }
+ return null;
}
private void bindNotificationHeader(RemoteViews contentView, boolean ambient) {
@@ -5607,10 +5658,11 @@
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
if (increasedHeight) {
- ArrayList<Action> actions = mBuilder.mActions;
+ mBuilder.mOriginalActions = mBuilder.mActions;
mBuilder.mActions = new ArrayList<>();
RemoteViews remoteViews = makeBigContentView();
- mBuilder.mActions = actions;
+ mBuilder.mActions = mBuilder.mOriginalActions;
+ mBuilder.mOriginalActions = null;
return remoteViews;
}
return super.makeContentView(increasedHeight);
@@ -5894,10 +5946,11 @@
return mBuilder.applyStandardTemplate(mBuilder.getBaseLayoutResource(),
mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
} else {
- ArrayList<Action> actions = mBuilder.mActions;
+ mBuilder.mOriginalActions = mBuilder.mActions;
mBuilder.mActions = new ArrayList<>();
RemoteViews remoteViews = makeBigContentView();
- mBuilder.mActions = actions;
+ mBuilder.mActions = mBuilder.mOriginalActions;
+ mBuilder.mOriginalActions = null;
return remoteViews;
}
}
@@ -6727,8 +6780,8 @@
// Need to clone customContent before adding, because otherwise it can no longer be
// parceled independently of remoteViews.
customContent = customContent.clone();
- remoteViews.removeAllViews(R.id.notification_main_column);
- remoteViews.addView(R.id.notification_main_column, customContent);
+ remoteViews.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress);
+ remoteViews.addView(R.id.notification_main_column, customContent, 0 /* index */);
}
// also update the end margin if there is an image
int endMargin = R.dimen.notification_content_margin_end;
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 11d9b5e..042eece 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -102,6 +102,7 @@
FLAG_NO_CREATE,
FLAG_CANCEL_CURRENT,
FLAG_UPDATE_CURRENT,
+ FLAG_IMMUTABLE,
Intent.FILL_IN_ACTION,
Intent.FILL_IN_DATA,
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index fb8bd39..4a09214 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -94,6 +94,7 @@
public static final int CAMERA_LAUNCH_SOURCE_WIGGLE = 0;
public static final int CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = 1;
+ public static final int CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = 2;
private Context mContext;
private IStatusBarService mService;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index fcf1931..9a019b8 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -22,6 +22,7 @@
import android.app.admin.IDevicePolicyManager;
import android.app.job.IJobScheduler;
import android.app.job.JobScheduler;
+import android.app.timezone.RulesManager;
import android.app.trust.TrustManager;
import android.app.usage.IStorageStatsManager;
import android.app.usage.IUsageStatsManager;
@@ -126,6 +127,7 @@
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.euicc.EuiccManager;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -492,6 +494,13 @@
return new TelecomManager(ctx.getOuterContext());
}});
+ registerService(Context.EUICC_SERVICE, EuiccManager.class,
+ new CachedServiceFetcher<EuiccManager>() {
+ @Override
+ public EuiccManager createService(ContextImpl ctx) {
+ return new EuiccManager(ctx.getOuterContext());
+ }});
+
registerService(Context.UI_MODE_SERVICE, UiModeManager.class,
new CachedServiceFetcher<UiModeManager>() {
@Override
@@ -793,7 +802,7 @@
registerService(Context.RADIO_SERVICE, RadioManager.class,
new CachedServiceFetcher<RadioManager>() {
@Override
- public RadioManager createService(ContextImpl ctx) {
+ public RadioManager createService(ContextImpl ctx) throws ServiceNotFoundException {
return new RadioManager(ctx);
}});
@@ -863,6 +872,13 @@
return new VrManager(IVrManager.Stub.asInterface(b));
}
});
+
+ registerService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, RulesManager.class,
+ new CachedServiceFetcher<RulesManager>() {
+ @Override
+ public RulesManager createService(ContextImpl ctx) {
+ return new RulesManager(ctx.getOuterContext());
+ }});
}
/**
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 18e7599..c99de5d 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -975,11 +975,11 @@
}
/**
- * Executes a shell command. This method returs a file descriptor that points
+ * Executes a shell command. This method returns a file descriptor that points
* to the standard output stream. The command execution is similar to running
* "adb shell <command>" from a host connected to the device.
* <p>
- * <strong>Note:</strong> It is your responsibility to close the retunred file
+ * <strong>Note:</strong> It is your responsibility to close the returned file
* descriptor once you are done reading.
* </p>
*
@@ -1000,7 +1000,7 @@
sink = pipe[1];
// Calling out without a lock held.
- mUiAutomationConnection.executeShellCommand(command, sink);
+ mUiAutomationConnection.executeShellCommand(command, sink, null);
} catch (IOException ioe) {
Log.e(LOG_TAG, "Error executing shell command!", ioe);
} catch (RemoteException re) {
@@ -1012,6 +1012,59 @@
return source;
}
+ /**
+ * Executes a shell command. This method returns two file descriptors,
+ * one that points to the standard output stream (element at index 0), and one that points
+ * to the standard input stream (element at index 1). The command execution is similar
+ * to running "adb shell <command>" from a host connected to the device.
+ * <p>
+ * <strong>Note:</strong> It is your responsibility to close the returned file
+ * descriptors once you are done reading/writing.
+ * </p>
+ *
+ * @param command The command to execute.
+ * @return File descriptors (out, in) to the standard output/input streams.
+ *
+ * @hide
+ */
+ @TestApi
+ public ParcelFileDescriptor[] executeShellCommandRw(String command) {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+
+ ParcelFileDescriptor source_read = null;
+ ParcelFileDescriptor sink_read = null;
+
+ ParcelFileDescriptor source_write = null;
+ ParcelFileDescriptor sink_write = null;
+
+ try {
+ ParcelFileDescriptor[] pipe_read = ParcelFileDescriptor.createPipe();
+ source_read = pipe_read[0];
+ sink_read = pipe_read[1];
+
+ ParcelFileDescriptor[] pipe_write = ParcelFileDescriptor.createPipe();
+ source_write = pipe_write[0];
+ sink_write = pipe_write[1];
+
+ // Calling out without a lock held.
+ mUiAutomationConnection.executeShellCommand(command, sink_read, source_write);
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "Error executing shell command!", ioe);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error executing shell command!", re);
+ } finally {
+ IoUtils.closeQuietly(sink_read);
+ IoUtils.closeQuietly(source_write);
+ }
+
+ ParcelFileDescriptor[] result = new ParcelFileDescriptor[2];
+ result[0] = source_read;
+ result[1] = sink_write;
+ return result;
+ }
+
private static float getDegreesForRotation(int value) {
switch (value) {
case Surface.ROTATION_90: {
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 9960df6..5e414b8 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -36,9 +36,11 @@
import android.view.WindowContentFrameStats;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.IAccessibilityManager;
+import android.util.Log;
import libcore.io.IoUtils;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -55,6 +57,8 @@
*/
public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
+ private static final String TAG = "UiAutomationConnection";
+
private static final int INITIAL_FROZEN_ROTATION_UNSPECIFIED = -1;
private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
@@ -267,47 +271,95 @@
}
}
+ public class Repeater implements Runnable {
+ // Continuously read readFrom and write back to writeTo until EOF is encountered
+ private final InputStream readFrom;
+ private final OutputStream writeTo;
+ public Repeater (InputStream readFrom, OutputStream writeTo) {
+ this.readFrom = readFrom;
+ this.writeTo = writeTo;
+ }
+ @Override
+ public void run() {
+ try {
+ final byte[] buffer = new byte[8192];
+ int readByteCount;
+ while (true) {
+ readByteCount = readFrom.read(buffer);
+ if (readByteCount < 0) {
+ break;
+ }
+ writeTo.write(buffer, 0, readByteCount);
+ writeTo.flush();
+ }
+ } catch (IOException ioe) {
+ throw new RuntimeException("Error while reading/writing ", ioe);
+ } finally {
+ IoUtils.closeQuietly(readFrom);
+ IoUtils.closeQuietly(writeTo);
+ }
+ }
+ }
+
@Override
- public void executeShellCommand(final String command, final ParcelFileDescriptor sink)
- throws RemoteException {
+ public void executeShellCommand(final String command, final ParcelFileDescriptor sink,
+ final ParcelFileDescriptor source) throws RemoteException {
synchronized (mLock) {
throwIfCalledByNotTrustedUidLocked();
throwIfShutdownLocked();
throwIfNotConnectedLocked();
}
+ final java.lang.Process process;
- Thread streamReader = new Thread() {
+ try {
+ process = Runtime.getRuntime().exec(command);
+ } catch (IOException exc) {
+ throw new RuntimeException("Error running shell command '" + command + "'", exc);
+ }
+
+ // Read from process and write to pipe
+ final Thread readFromProcess;
+ if (sink != null) {
+ InputStream sink_in = process.getInputStream();;
+ OutputStream sink_out = new FileOutputStream(sink.getFileDescriptor());
+
+ readFromProcess = new Thread(new Repeater(sink_in, sink_out));
+ readFromProcess.start();
+ } else {
+ readFromProcess = null;
+ }
+
+ // Read from pipe and write to process
+ final Thread writeToProcess;
+ if (source != null) {
+ OutputStream source_out = process.getOutputStream();
+ InputStream source_in = new FileInputStream(source.getFileDescriptor());
+
+ writeToProcess = new Thread(new Repeater(source_in, source_out));
+ writeToProcess.start();
+ } else {
+ writeToProcess = null;
+ }
+
+ Thread cleanup = new Thread(new Runnable() {
+ @Override
public void run() {
- InputStream in = null;
- OutputStream out = null;
- java.lang.Process process = null;
-
try {
- process = Runtime.getRuntime().exec(command);
-
- in = process.getInputStream();
- out = new FileOutputStream(sink.getFileDescriptor());
-
- final byte[] buffer = new byte[8192];
- while (true) {
- final int readByteCount = in.read(buffer);
- if (readByteCount < 0) {
- break;
- }
- out.write(buffer, 0, readByteCount);
+ if (writeToProcess != null) {
+ writeToProcess.join();
}
- } catch (IOException ioe) {
- throw new RuntimeException("Error running shell command", ioe);
- } finally {
- if (process != null) {
- process.destroy();
+ if (readFromProcess != null) {
+ readFromProcess.join();
}
- IoUtils.closeQuietly(out);
- IoUtils.closeQuietly(sink);
+ } catch (InterruptedException exc) {
+ Log.e(TAG, "At least one of the threads was interrupted");
}
- };
- };
- streamReader.start();
+ IoUtils.closeQuietly(sink);
+ IoUtils.closeQuietly(source);
+ process.destroy();
+ }
+ });
+ cleanup.start();
}
@Override
diff --git a/core/java/android/app/WallpaperColors.aidl b/core/java/android/app/WallpaperColors.aidl
new file mode 100644
index 0000000..f5edff2
--- /dev/null
+++ b/core/java/android/app/WallpaperColors.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+parcelable WallpaperColors;
\ No newline at end of file
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
new file mode 100644
index 0000000..b3c70a4
--- /dev/null
+++ b/core/java/android/app/WallpaperColors.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.app;
+
+import android.graphics.Color;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class containing information about the colors of a wallpaper.
+ */
+public final class WallpaperColors implements Parcelable {
+
+ private static final float BRIGHT_LUMINANCE = 0.9f;
+ private final List<Pair<Color, Integer>> mColors;
+ private final boolean mSupportsDarkText;
+
+ public WallpaperColors(Parcel parcel) {
+ mColors = new ArrayList<>();
+ int count = parcel.readInt();
+ for (int i=0; i < count; i++) {
+ Color color = Color.valueOf(parcel.readInt());
+ int weight = parcel.readInt();
+ mColors.add(new Pair<>(color, weight));
+ }
+ mSupportsDarkText = parcel.readBoolean();
+ }
+
+ /**
+ * Wallpaper color details containing a list of colors and their weights,
+ * as if it were an histogram.
+ * This list can be extracted from a bitmap by the Palette API.
+ *
+ * Dark text support will be calculated internally based on the histogram.
+ *
+ * @param colors list of pairs where each pair contains a color
+ * and number of occurrences/influence.
+ */
+ public WallpaperColors(List<Pair<Color, Integer>> colors) {
+ this(colors, calculateDarkTextSupport(colors));
+ }
+
+ /**
+ * Wallpaper color details containing a list of colors and their weights,
+ * as if it were an histogram.
+ * Explicit dark text support.
+ *
+ * @param colors list of pairs where each pair contains a color
+ * and number of occurrences/influence.
+ * @param supportsDarkText can have dark text on top or not
+ */
+ public WallpaperColors(List<Pair<Color, Integer>> colors, boolean supportsDarkText) {
+ if (colors == null)
+ colors = new ArrayList<>();
+ mColors = colors;
+ mSupportsDarkText = supportsDarkText;
+ }
+
+ public static final Creator<WallpaperColors> CREATOR = new Creator<WallpaperColors>() {
+ @Override
+ public WallpaperColors createFromParcel(Parcel in) {
+ return new WallpaperColors(in);
+ }
+
+ @Override
+ public WallpaperColors[] newArray(int size) {
+ return new WallpaperColors[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ int count = mColors.size();
+ dest.writeInt(count);
+ for (Pair<Color, Integer> color : mColors) {
+ dest.writeInt(color.first.toArgb());
+ dest.writeInt(color.second);
+ }
+ dest.writeBoolean(mSupportsDarkText);
+ }
+
+ /**
+ * List of colors with their occurrences. The bigger the int, the more relevant the color.
+ * @return list of colors paired with their weights.
+ */
+ public List<Pair<Color, Integer>> getColors() {
+ return mColors;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ WallpaperColors other = (WallpaperColors) o;
+ return mColors.equals(other.mColors) && mSupportsDarkText == other.mSupportsDarkText;
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * mColors.hashCode() + (mSupportsDarkText ? 1 : 0);
+ }
+
+ /**
+ * Whether or not dark text is legible on top of this wallpaper.
+ *
+ * @return true if dark text is supported
+ */
+ public boolean supportsDarkText() {
+ return mSupportsDarkText;
+ }
+
+ private static boolean calculateDarkTextSupport(List<Pair<Color, Integer>> colors) {
+ if (colors == null) {
+ return false;
+ }
+
+ Pair<Color, Integer> mainColor = null;
+
+ for (Pair<Color, Integer> color : colors) {
+ if (mainColor == null) {
+ mainColor = color;
+ } else if (color.second > mainColor.second) {
+ mainColor = color;
+ }
+ }
+ return mainColor != null &&
+ mainColor.first.luminance() > BRIGHT_LUMINANCE;
+ }
+}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index a850423..dfcbfc4 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -17,6 +17,8 @@
package android.app;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RawRes;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
@@ -52,13 +54,13 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
-import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.view.WindowManagerGlobal;
import libcore.io.IoUtils;
@@ -71,6 +73,8 @@
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -271,15 +275,20 @@
}
}
- static class Globals extends IWallpaperManagerCallback.Stub {
+ private static class Globals extends IWallpaperManagerCallback.Stub {
private final IWallpaperManager mService;
+ private boolean mColorCallbackRegistered;
+ private final ArrayList<Pair<OnColorsChangedListener, Handler>> mColorListeners =
+ new ArrayList<>();
private Bitmap mCachedWallpaper;
private int mCachedWallpaperUserId;
private Bitmap mDefaultWallpaper;
+ private Handler mMainLooperHandler;
Globals(Looper looper) {
IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
mService = IWallpaperManager.Stub.asInterface(b);
+ mMainLooperHandler = new Handler(looper);
forgetLoadedWallpaper();
}
@@ -292,6 +301,88 @@
forgetLoadedWallpaper();
}
+ /**
+ * Start listening to wallpaper color events.
+ * Will be called whenever someone changes their wallpaper or if a live wallpaper
+ * changes its colors.
+ * @param callback Listener
+ * @param handler Thread to call it from. Main thread if null.
+ */
+ public void addOnColorsChangedListener(@NonNull OnColorsChangedListener callback,
+ @Nullable Handler handler) {
+ synchronized (this) {
+ if (!mColorCallbackRegistered) {
+ try {
+ mService.registerWallpaperColorsCallback(this);
+ mColorCallbackRegistered = true;
+ } catch (RemoteException e) {
+ // Failed, service is gone
+ Log.w(TAG, "Can't register for color updates", e);
+ }
+ }
+ mColorListeners.add(new Pair<>(callback, handler));
+ }
+ }
+
+ /**
+ * Stop listening to wallpaper color events.
+ *
+ * @param callback listener
+ */
+ public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener callback) {
+ synchronized (this) {
+ mColorListeners.removeIf(pair -> pair.first == callback);
+
+ if (mColorListeners.size() == 0 && mColorCallbackRegistered) {
+ mColorCallbackRegistered = false;
+ try {
+ mService.unregisterWallpaperColorsCallback(this);
+ } catch (RemoteException e) {
+ // Failed, service is gone
+ Log.w(TAG, "Can't unregister color updates", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onWallpaperColorsChanged(WallpaperColors colors, int which) {
+ synchronized (this) {
+ for (Pair<OnColorsChangedListener, Handler> listener : mColorListeners) {
+ Handler handler = listener.second;
+ if (listener.second == null) {
+ handler = mMainLooperHandler;
+ }
+ handler.post(() -> {
+ // Dealing with race conditions between posting a callback and
+ // removeOnColorsChangedListener being called.
+ boolean stillExists;
+ synchronized (sGlobals) {
+ stillExists = mColorListeners.contains(listener);
+ }
+ if (stillExists) {
+ listener.first.onColorsChanged(colors, which);
+ }
+ });
+ }
+ }
+ }
+
+ WallpaperColors getWallpaperColors(int which) {
+ synchronized (this) {
+ if (which != FLAG_LOCK && which != FLAG_SYSTEM)
+ throw new IllegalArgumentException(
+ "which should be either FLAG_LOCK or FLAG_SYSTEM");
+
+ try {
+ return mService.getWallpaperColors(which);
+ } catch (RemoteException e) {
+ // Can't get colors, connection lost.
+ }
+ return null;
+ }
+ }
+
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
@SetWallpaperFlags int which) {
return peekWallpaperBitmap(context, returnDefault, which, context.getUserId());
@@ -747,6 +838,45 @@
}
/**
+ * Registers a listener to get notified when the wallpaper colors change.
+ * Callback might be called from an arbitrary background thread.
+ *
+ * @param listener A listener to register
+ */
+ public void addOnColorsChangedListener(@NonNull OnColorsChangedListener listener) {
+ sGlobals.addOnColorsChangedListener(listener, null);
+ }
+
+ /**
+ * Registers a listener to get notified when the wallpaper colors change
+ * @param listener A listener to register
+ * @param handler Where to call it from. Will be called from the main thread
+ * if null.
+ */
+ public void addOnColorsChangedListener(@NonNull OnColorsChangedListener listener,
+ @NonNull Handler handler) {
+ sGlobals.addOnColorsChangedListener(listener, handler);
+ }
+
+ /**
+ * Stop listening to color updates.
+ * @param callback A callback to unsubscribe
+ */
+ public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener callback) {
+ sGlobals.removeOnColorsChangedListener(callback);
+ }
+
+ /**
+ * Get the primary colors of a wallpaper
+ * @param which wallpaper type. Must be either {@link #FLAG_SYSTEM} or
+ * {@link #FLAG_LOCK}
+ * @return a list of colors ordered by priority
+ */
+ public @Nullable WallpaperColors getWallpaperColors(int which) {
+ return sGlobals.getWallpaperColors(which);
+ }
+
+ /**
* Version of {@link #getWallpaperFile(int)} that can access the wallpaper data
* for a given user. The caller must hold the INTERACT_ACROSS_USERS_FULL
* permission to access another user's wallpaper data.
@@ -1737,5 +1867,26 @@
public void onWallpaperChanged() throws RemoteException {
mLatch.countDown();
}
+
+ @Override
+ public void onWallpaperColorsChanged(WallpaperColors colors, int which)
+ throws RemoteException {
+ sGlobals.onWallpaperColorsChanged(colors, which);
+ }
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when colors change on a wallpaper.
+ */
+ public interface OnColorsChangedListener {
+ /**
+ * Called when colors change.
+ * A {@link android.app.WallpaperColors} object containing a simplified
+ * color histogram will be given.
+ *
+ * @param colors Wallpaper color info
+ * @param which A combination of {@link #FLAG_LOCK} and {@link #FLAG_SYSTEM}
+ */
+ void onColorsChanged(WallpaperColors colors, int which);
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 01c4656..ff9425e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -264,6 +264,26 @@
= "android.app.action.PROVISION_MANAGED_DEVICE";
/**
+ * Activity action: launch when user provisioning completed, i.e.
+ * {@link #getUserProvisioningState()} returns one of the complete state.
+ *
+ * <p> Please note that the API behavior is not necessarily consistent across various releases,
+ * and devices, as it's contract between SetupWizard and ManagedProvisioning. The default
+ * implementation is that ManagedProvisioning launches SetupWizard in NFC provisioning only.
+ *
+ * <p> The activity must be protected by permission
+ * {@link android.Manifest.permission#BIND_DEVICE_ADMIN}, and the process must hold
+ * {@link android.Manifest.permission#DISPATCH_PROVISIONING_MESSAGE} to be launched.
+ * Only one {@link ComponentName} in the entire system should be enabled, and the rest of the
+ * components are not started by this intent.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @SystemApi
+ public static final String ACTION_STATE_USER_SETUP_COMPLETE =
+ "android.app.action.STATE_USER_SETUP_COMPLETE";
+
+ /**
* Activity action: Starts the provisioning flow which sets up a managed device.
*
* <p>During device owner provisioning, a device admin app is downloaded and set as the owner of
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index c99a1e4..266fa7e 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -18,7 +18,6 @@
import android.os.PooledStringWriter;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.service.autofill.FillContext;
import android.service.autofill.FillRequest;
import android.text.TextUtils;
import android.util.Log;
@@ -31,7 +30,6 @@
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import java.util.ArrayList;
@@ -39,18 +37,18 @@
import java.util.List;
/**
- * Assist data automatically created by the platform's implementation of Assist and Autofill.
+ * Assist data automatically created by the platform's implementation of assist and autofill.
*
- * <p>The structure is used for Assist purposes when created by
+ * <p>The structure is used for assist purposes when created by
* {@link android.app.Activity#onProvideAssistData}, {@link View#onProvideStructure(ViewStructure)},
* or {@link View#onProvideVirtualStructure(ViewStructure)}.
*
- * <p>The structure is used for Autofill purposes when created by
+ * <p>The structure is used for autofill purposes when created by
* {@link View#onProvideAutofillStructure(ViewStructure, int)},
* or {@link View#onProvideAutofillVirtualStructure(ViewStructure, int)}.
*
- * <p>For performance reasons, some properties of the Assist data might be available just for Assist
- * or Autofill purposes; in those case, the property availability will be document in its javadoc.
+ * <p>For performance reasons, some properties of the assist data might be available just for assist
+ * or autofill purposes; in those case, the property availability will be document in its javadoc.
*/
public class AssistStructure implements Parcelable {
static final String TAG = "AssistStructure";
@@ -958,10 +956,10 @@
/**
* Gets the id that can be used to autofill the view contents.
*
- * <p>It's relevant set when the {@link AssistStructure} is used for Autofill purposes.
+ * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes.
*
* @return id that can be used to autofill the view contents, or {@code null} if the
- * structure was created for Assist purposes.
+ * structure was created for assist purposes.
*/
@Nullable public AutofillId getAutofillId() {
return mAutofillId;
@@ -970,10 +968,10 @@
/**
* Gets the the type of value that can be used to autofill the view contents.
*
- * <p>It's only relevant when the {@link AssistStructure} is used for Autofill purposes.
+ * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes.
*
* @return autofill type as defined by {@link View#getAutofillType()},
- * or {@link View#AUTOFILL_TYPE_NONE} if the structure was created for Assist purposes.
+ * or {@link View#AUTOFILL_TYPE_NONE} if the structure was created for assist purposes.
*/
public @View.AutofillType int getAutofillType() {
return mAutofillType;
@@ -983,11 +981,11 @@
* Describes the content of a view so that a autofill service can fill in the appropriate
* data.
*
- * <p>It's only relevant when the {@link AssistStructure} is used for Autofill purposes,
+ * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
* not for Assist - see {@link View#getAutofillHints()} for more info.
*
* @return The autofill hints for this view, or {@code null} if the structure was created
- * for Assist purposes.
+ * for assist purposes.
*/
@Nullable public String[] getAutofillHints() {
return mAutofillHints;
@@ -996,11 +994,11 @@
/**
* Gets the the value of this view.
*
- * <p>It's only relevant when the {@link AssistStructure} is used for Autofill purposes,
- * not for Assist purposes.
+ * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+ * not for assist purposes.
*
* @return the autofill value of this view, or {@code null} if the structure was created
- * for Assist purposes.
+ * for assist purposes.
*/
@Nullable public AutofillValue getAutofillValue() {
return mAutofillValue;
@@ -1017,11 +1015,11 @@
* <p>Typically used by nodes whose {@link View#getAutofillType()} is a list to indicate
* the meaning of each possible value in the list.
*
- * <p>It's relevant when the {@link AssistStructure} is used for Autofill purposes, not
- * for Assist purposes.
+ * <p>It's relevant when the {@link AssistStructure} is used for autofill purposes, not
+ * for assist purposes.
*
* @return the options that can be used to autofill this view, or {@code null} if the
- * structure was created for Assist purposes.
+ * structure was created for assist purposes.
*/
@Nullable public CharSequence[] getAutofillOptions() {
return mAutofillOptions;
@@ -1106,8 +1104,8 @@
* or scaling. The returned Matrix object is owned by ViewNode; do not modify it.
* Returns null if there is no transformation applied to the view.
*
- * <p>It's only relevant when the {@link AssistStructure} is used for Assist purposes,
- * not for Autofill purposes.
+ * <p>It's only relevant when the {@link AssistStructure} is used for assist purposes,
+ * not for autofill purposes.
*/
public Matrix getTransformation() {
return mMatrix;
@@ -1118,8 +1116,8 @@
* characterstics, as set by {@link ViewStructure#setElevation
* ViewStructure.setElevation(float)}.
*
- * <p>It's only relevant when the {@link AssistStructure} is used for Assist purposes,
- * not for Autofill purposes.
+ * <p>It's only relevant when the {@link AssistStructure} is used for assist purposes,
+ * not for autofill purposes.
*/
public float getElevation() {
return mElevation;
@@ -1130,8 +1128,8 @@
* of the view's contents, as set by {@link ViewStructure#setAlpha
* ViewStructure.setAlpha(float)}.
*
- * <p>It's only relevant when the {@link AssistStructure} is used for Assist purposes,
- * not for Autofill purposes.
+ * <p>It's only relevant when the {@link AssistStructure} is used for assist purposes,
+ * not for autofill purposes.
*/
public float getAlpha() {
return mAlpha;
@@ -1260,7 +1258,7 @@
* document.
*
* <strong>WARNING:</strong> a {@link android.service.autofill.AutofillService} should only
- * use this domain for Autofill purposes when it trusts the app generating it (i.e., the app
+ * use this domain for autofill purposes when it trusts the app generating it (i.e., the app
* defined by {@link AssistStructure#getActivityComponent()}).
*
* @return domain-only part of the document. For example, if the full URL is
@@ -1273,11 +1271,11 @@
/**
* Returns the HTML properties associated with this view.
*
- * <p>It's only relevant when the {@link AssistStructure} is used for Autofill purposes,
- * not for Assist purposes.
+ * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+ * not for assist purposes.
*
* @return the HTML properties associated with this view, or {@code null} if the
- * structure was created for Assist purposes.
+ * structure was created for assist purposes.
*/
@Nullable public HtmlInfo getHtmlInfo() {
return mHtmlInfo;
@@ -1301,8 +1299,8 @@
/**
* If {@link #getText()} is non-null, this is where the current selection starts.
*
- * <p>It's only relevant when the {@link AssistStructure} is used for Assist purposes,
- * not for Autofill purposes.
+ * <p>It's only relevant when the {@link AssistStructure} is used for assist purposes,
+ * not for autofill purposes.
*/
public int getTextSelectionStart() {
return mText != null ? mText.mTextSelectionStart : -1;
@@ -1313,8 +1311,8 @@
* If there is no selection, returns the same value as {@link #getTextSelectionStart()},
* indicating the cursor position.
*
- * <p>It's only relevant when the {@link AssistStructure} is used for Assist purposes,
- * not for Autofill purposes.
+ * <p>It's only relevant when the {@link AssistStructure} is used for assist purposes,
+ * not for autofill purposes.
*/
public int getTextSelectionEnd() {
return mText != null ? mText.mTextSelectionEnd : -1;
@@ -1337,8 +1335,8 @@
* Note that the text may also contain style spans that modify the color of specific
* parts of the text.
*
- * <p>It's only relevant when the {@link AssistStructure} is used for Assist purposes,
- * not for Autofill purposes.
+ * <p>It's only relevant when the {@link AssistStructure} is used for assist purposes,
+ * not for autofill purposes.
*/
public int getTextBackgroundColor() {
return mText != null ? mText.mTextBackgroundColor : TEXT_COLOR_UNDEFINED;
@@ -1350,8 +1348,8 @@
* Note that the text may also contain style spans that modify the size of specific
* parts of the text.
*
- * <p>It's only relevant when the {@link AssistStructure} is used for Assist purposes,
- * not for Autofill purposes.
+ * <p>It's only relevant when the {@link AssistStructure} is used for assist purposes,
+ * not for autofill purposes.
*/
public float getTextSize() {
return mText != null ? mText.mTextSize : 0;
@@ -1365,8 +1363,8 @@
* Note that the text may also contain style spans that modify the style of specific
* parts of the text.
*
- * <p>It's only relevant when the {@link AssistStructure} is used for Assist purposes,
- * not for Autofill purposes.
+ * <p>It's only relevant when the {@link AssistStructure} is used for assist purposes,
+ * not for autofill purposes.
*/
public int getTextStyle() {
return mText != null ? mText.mTextStyle : 0;
@@ -1378,8 +1376,8 @@
* into the text string where that line starts. May return null if there is no line
* information.
*
- * <p>It's only relevant when the {@link AssistStructure} is used for Assist purposes,
- * not for Autofill purposes.
+ * <p>It's only relevant when the {@link AssistStructure} is used for assist purposes,
+ * not for autofill purposes.
*/
public int[] getTextLineCharOffsets() {
return mText != null ? mText.mLineCharOffsets : null;
@@ -1391,8 +1389,8 @@
* where that text appears in the view. May return null if there is no line
* information.
*
- * <p>It's only relevant when the {@link AssistStructure} is used for Assist purposes,
- * not for Autofill purposes.
+ * <p>It's only relevant when the {@link AssistStructure} is used for assist purposes,
+ * not for autofill purposes.
*/
public int[] getTextLineBaselines() {
return mText != null ? mText.mLineBaselines : null;
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 5f92af9..42e6147 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -942,6 +942,11 @@
long ident = Binder.clearCallingIdentity();
if (DEBUG) Log.v(TAG, "doRestore() invoked");
+
+ // Ensure that any side-effect SharedPreferences writes have landed *before*
+ // we may be about to rewrite the file out from underneath
+ waitForSharedPrefs();
+
BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
try {
BackupAgent.this.onRestore(input, appVersionCode, newState);
@@ -952,8 +957,8 @@
Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
throw ex;
} finally {
- // Ensure that any side-effect SharedPreferences writes have landed
- waitForSharedPrefs();
+ // And bring live SharedPreferences instances up to date
+ reloadSharedPreferences();
Binder.restoreCallingIdentity(ident);
try {
@@ -1053,6 +1058,8 @@
} finally {
// Ensure that any side-effect SharedPreferences writes have landed
waitForSharedPrefs();
+ // And bring live SharedPreferences instances up to date
+ reloadSharedPreferences();
Binder.restoreCallingIdentity(ident);
try {
diff --git a/core/java/android/app/timezone/Callback.java b/core/java/android/app/timezone/Callback.java
new file mode 100644
index 0000000..b51e5ba
--- /dev/null
+++ b/core/java/android/app/timezone/Callback.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.timezone;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Callback interface for receiving information about an async time zone operation.
+ * The methods will be called on your application's main thread.
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public abstract class Callback {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SUCCESS, ERROR_UNKNOWN_FAILURE, ERROR_INSTALL_BAD_DISTRO_STRUCTURE,
+ ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION, ERROR_INSTALL_RULES_TOO_OLD,
+ ERROR_INSTALL_VALIDATION_ERROR})
+ public @interface AsyncResultCode {}
+
+ /**
+ * Indicates that an operation succeeded.
+ */
+ public static final int SUCCESS = 0;
+
+ /**
+ * Indicates an install / uninstall did not fully succeed for an unknown reason.
+ */
+ public static final int ERROR_UNKNOWN_FAILURE = 1;
+
+ /**
+ * Indicates an install failed because of a structural issue with the provided distro,
+ * e.g. it wasn't in the right format or the contents were structured incorrectly.
+ */
+ public static final int ERROR_INSTALL_BAD_DISTRO_STRUCTURE = 2;
+
+ /**
+ * Indicates an install failed because of a versioning issue with the provided distro,
+ * e.g. it was created for a different version of Android.
+ */
+ public static final int ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION = 3;
+
+ /**
+ * Indicates an install failed because the rules provided are too old for the device,
+ * e.g. the Android device shipped with a newer rules version.
+ */
+ public static final int ERROR_INSTALL_RULES_TOO_OLD = 4;
+
+ /**
+ * Indicates an install failed because the distro contents failed validation.
+ */
+ public static final int ERROR_INSTALL_VALIDATION_ERROR = 5;
+
+ /**
+ * Reports the result of an async time zone operation.
+ */
+ public abstract void onFinished(@AsyncResultCode int status);
+}
diff --git a/core/java/android/app/timezone/DistroFormatVersion.java b/core/java/android/app/timezone/DistroFormatVersion.java
new file mode 100644
index 0000000..e879e8f
--- /dev/null
+++ b/core/java/android/app/timezone/DistroFormatVersion.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.timezone;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Versioning information about a distro's format or a device's supported format.
+ *
+ * <p>The following properties are included:
+ * <dl>
+ * <dt>majorVersion</dt>
+ * <dd>the major distro format version. Major versions differences are not compatible - e.g.
+ * 2 is not compatible with 1 or 3.</dd>
+ * <dt>minorVersion</dt>
+ * <dd>the minor distro format version. Minor versions should be backwards compatible iff the
+ * major versions match exactly, i.e. version 2.2 will be compatible with 2.1 devices but not
+ * 2.3 devices.</dd>
+ * </dl>
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public final class DistroFormatVersion implements Parcelable {
+
+ private final int mMajorVersion;
+ private final int mMinorVersion;
+
+ public DistroFormatVersion(int majorVersion, int minorVersion) {
+ mMajorVersion = Utils.validateVersion("major", majorVersion);
+ mMinorVersion = Utils.validateVersion("minor", minorVersion);
+ }
+
+ public static final Creator<DistroFormatVersion> CREATOR = new Creator<DistroFormatVersion>() {
+ public DistroFormatVersion createFromParcel(Parcel in) {
+ int majorVersion = in.readInt();
+ int minorVersion = in.readInt();
+ return new DistroFormatVersion(majorVersion, minorVersion);
+ }
+
+ public DistroFormatVersion[] newArray(int size) {
+ return new DistroFormatVersion[size];
+ }
+ };
+
+ public int getMajorVersion() {
+ return mMajorVersion;
+ }
+
+ public int getMinorVersion() {
+ return mMinorVersion;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mMajorVersion);
+ out.writeInt(mMinorVersion);
+ }
+
+ /**
+ * If this object describes a device's supported version and the parameter describes a distro's
+ * version, this method returns whether the device would accept the distro.
+ */
+ public boolean supports(DistroFormatVersion distroFormatVersion) {
+ return mMajorVersion == distroFormatVersion.mMajorVersion
+ && mMinorVersion <= distroFormatVersion.mMinorVersion;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ DistroFormatVersion that = (DistroFormatVersion) o;
+
+ if (mMajorVersion != that.mMajorVersion) {
+ return false;
+ }
+ return mMinorVersion == that.mMinorVersion;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mMajorVersion;
+ result = 31 * result + mMinorVersion;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "DistroFormatVersion{"
+ + "mMajorVersion=" + mMajorVersion
+ + ", mMinorVersion=" + mMinorVersion
+ + '}';
+ }
+}
diff --git a/core/java/android/app/timezone/DistroRulesVersion.java b/core/java/android/app/timezone/DistroRulesVersion.java
new file mode 100644
index 0000000..5503ce1
--- /dev/null
+++ b/core/java/android/app/timezone/DistroRulesVersion.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.timezone;
+
+import static android.app.timezone.Utils.validateRulesVersion;
+import static android.app.timezone.Utils.validateVersion;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Versioning information about a set of time zone rules.
+ *
+ * <p>The following properties are included:
+ * <dl>
+ * <dt>rulesVersion</dt>
+ * <dd>the IANA rules version. e.g. "2017a"</dd>
+ * <dt>revision</dt>
+ * <dd>the revision for the rules. Allows there to be several revisions for a given IANA rules
+ * release. Numerically higher is newer.</dd>
+ * </dl>
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public final class DistroRulesVersion implements Parcelable {
+
+ private final String mRulesVersion;
+ private final int mRevision;
+
+ public DistroRulesVersion(String rulesVersion, int revision) {
+ mRulesVersion = validateRulesVersion("rulesVersion", rulesVersion);
+ mRevision = validateVersion("revision", revision);
+ }
+
+ public static final Creator<DistroRulesVersion> CREATOR = new Creator<DistroRulesVersion>() {
+ public DistroRulesVersion createFromParcel(Parcel in) {
+ String rulesVersion = in.readString();
+ int revision = in.readInt();
+ return new DistroRulesVersion(rulesVersion, revision);
+ }
+
+ public DistroRulesVersion[] newArray(int size) {
+ return new DistroRulesVersion[size];
+ }
+ };
+
+ public String getRulesVersion() {
+ return mRulesVersion;
+ }
+
+ public int getRevision() {
+ return mRevision;
+ }
+
+ /**
+ * Returns true if this DistroRulesVersion is older than the one supplied. It returns false if
+ * it is the same or newer. This method compares the {@code rulesVersion} and the
+ * {@code revision}.
+ */
+ public boolean isOlderThan(DistroRulesVersion distroRulesVersion) {
+ int rulesComparison = mRulesVersion.compareTo(distroRulesVersion.mRulesVersion);
+ if (rulesComparison < 0) {
+ return true;
+ }
+ if (rulesComparison > 0) {
+ return false;
+ }
+ return mRevision < distroRulesVersion.mRevision;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mRulesVersion);
+ out.writeInt(mRevision);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ DistroRulesVersion that = (DistroRulesVersion) o;
+
+ if (mRevision != that.mRevision) {
+ return false;
+ }
+ return mRulesVersion.equals(that.mRulesVersion);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mRulesVersion.hashCode();
+ result = 31 * result + mRevision;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "DistroRulesVersion{"
+ + "mRulesVersion='" + mRulesVersion + '\''
+ + ", mRevision='" + mRevision + '\''
+ + '}';
+ }
+}
diff --git a/core/java/android/app/timezone/ICallback.aidl b/core/java/android/app/timezone/ICallback.aidl
new file mode 100644
index 0000000..519ef1a
--- /dev/null
+++ b/core/java/android/app/timezone/ICallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.timezone;
+
+/**
+ * Callback interface for a timezone updater to receive information about the success or failure of
+ * an installation/uninstallation attempt.
+ *
+ * {@hide}
+ */
+oneway interface ICallback {
+ void onFinished(int error);
+}
\ No newline at end of file
diff --git a/core/java/android/app/timezone/IRulesManager.aidl b/core/java/android/app/timezone/IRulesManager.aidl
new file mode 100644
index 0000000..40f3fd2
--- /dev/null
+++ b/core/java/android/app/timezone/IRulesManager.aidl
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.timezone;
+
+import android.app.timezone.ICallback;
+import android.app.timezone.RulesState;
+import android.os.ParcelFileDescriptor;
+
+ /**
+ * Interface to the TimeZone Rules Manager Service.
+ *
+ * <p>This interface is only intended for system apps to call. They should use the
+ * {@link android.app.timezone.RulesManager} class rather than going through this
+ * Binder interface directly. See {@link android.app.timezone.RulesManager} for more complete
+ * documentation.
+ *
+ * {@hide}
+ */
+interface IRulesManager {
+
+ /**
+ * Returns information about the current time zone rules state such as the IANA version of
+ * the system and any currently installed distro. This method is intended to allow clients to
+ * determine if the current state can be improved; for example by passing the information to a
+ * server that may provide a new distro for download.
+ */
+ RulesState getRulesState();
+
+ /**
+ * Requests installation of the supplied distro. The distro must have been checked for integrity
+ * by the caller or have been received via a trusted mechanism.
+ *
+ * @param distroFileDescriptor the file descriptor for the distro
+ * @param checkToken an optional token provided if the install was triggered in response to a
+ * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+ * @param callback the {@link ICallback} to receive callbacks related to the
+ * installation
+ * @return zero if the installation will be attempted; nonzero on error
+ */
+ int requestInstall(in ParcelFileDescriptor distroFileDescriptor, in byte[] checkToken,
+ ICallback callback);
+
+ /**
+ * Requests uninstallation of the currently installed distro (leaving the device with no
+ * distro installed).
+ *
+ * @param checkToken an optional token provided if the uninstall was triggered in response to a
+ * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+ * @param callback the {@link ICallback} to receive callbacks related to the
+ * uninstall
+ * @return zero if the uninstallation will be attempted; nonzero on error
+ */
+ int requestUninstall(in byte[] checkToken, ICallback callback);
+
+ /**
+ * Requests the system does not modify the currently installed time zone distro, if any. This
+ * method records the fact that a time zone check operation triggered by the system is now
+ * complete and there was nothing to do. The token passed should be the one presented when the
+ * check was triggered.
+ *
+ * <p>Note: Passing {@code success == false} may result in more checks being triggered. Clients
+ * should be careful not to pass false if the failure is unlikely to resolve by itself.
+ *
+ * @param checkToken an optional token provided if the install was triggered in response to a
+ * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+ * @param success true if the check was successful, false if it was not successful but may
+ * succeed if it is retried
+ */
+ void requestNothing(in byte[] token, boolean success);
+}
diff --git a/core/java/android/app/timezone/RulesManager.java b/core/java/android/app/timezone/RulesManager.java
new file mode 100644
index 0000000..649d894
--- /dev/null
+++ b/core/java/android/app/timezone/RulesManager.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.timezone;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * The interface through which a time zone update application interacts with the Android system
+ * to handle time zone rule updates.
+ *
+ * <p>This interface is intended for use with the default APK-based time zone rules update
+ * application but it can also be used by OEMs if that mechanism is turned off using configuration.
+ * All callers must possess the {@link android.Manifest.permission#UPDATE_TIME_ZONE_RULES} system
+ * permission.
+ *
+ * <p>When using the default mechanism, when properly configured the Android system will send a
+ * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent with a
+ * {@link RulesUpdaterContract#EXTRA_CHECK_TOKEN} extra to the time zone rules updater application
+ * when it detects that it or the OEM's APK containing time zone rules data has been modified. The
+ * updater application is then responsible for calling one of
+ * {@link #requestInstall(ParcelFileDescriptor, byte[], Callback)},
+ * {@link #requestUninstall(byte[], Callback)} or
+ * {@link #requestNothing(byte[], boolean)}, indicating, respectively, whether a new time zone rules
+ * distro should be installed, the current distro should be uninstalled, or there is nothing to do
+ * (or that the correct operation could not be determined due to an error). In each case the updater
+ * must pass the {@link RulesUpdaterContract#EXTRA_CHECK_TOKEN} value it received from the intent
+ * back so the system in the {@code checkToken} parameter.
+ *
+ * <p>If OEMs want to handle their own time zone rules updates, perhaps via a server-side component
+ * rather than an APK, then they should disable the default triggering mechanism in config and are
+ * responsible for triggering their own update checks / installs / uninstalls. In this case the
+ * "check token" parameter can be left null and there is never any need to call
+ * {@link #requestNothing(byte[], boolean)}.
+ *
+ * <p>OEMs should not mix the default mechanism and their own as this could lead to conflicts and
+ * unnecessary checks being triggered.
+ *
+ * <p>Applications obtain this using {@link android.app.Activity#getSystemService(String)} with
+ * {@link Context#TIME_ZONE_RULES_MANAGER_SERVICE}.
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public final class RulesManager {
+ private static final String TAG = "timezone.RulesManager";
+ private static final boolean DEBUG = false;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SUCCESS, ERROR_UNKNOWN_FAILURE, ERROR_OPERATION_IN_PROGRESS})
+ public @interface ResultCode {}
+
+ /**
+ * Indicates that an operation succeeded.
+ */
+ public static final int SUCCESS = 0;
+
+ /**
+ * Indicates that an install/uninstall cannot be initiated because there is one already in
+ * progress.
+ */
+ public static final int ERROR_OPERATION_IN_PROGRESS = 1;
+
+ /**
+ * Indicates an install / uninstall did not fully succeed for an unknown reason.
+ */
+ public static final int ERROR_UNKNOWN_FAILURE = 2;
+
+ private final Context mContext;
+ private final IRulesManager mIRulesManager;
+
+ public RulesManager(Context context) {
+ mContext = context;
+ mIRulesManager = IRulesManager.Stub.asInterface(
+ ServiceManager.getService(Context.TIME_ZONE_RULES_MANAGER_SERVICE));
+ }
+
+ /**
+ * Returns information about the current time zone rules state such as the IANA version of
+ * the system and any currently installed distro. This method is intended to allow clients to
+ * determine if the current state can be improved; for example by passing the information to a
+ * server that may provide a new distro for download.
+ */
+ public RulesState getRulesState() {
+ try {
+ logDebug("sIRulesManager.getRulesState()");
+ RulesState rulesState = mIRulesManager.getRulesState();
+ logDebug("sIRulesManager.getRulesState() returned " + rulesState);
+ return rulesState;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Requests installation of the supplied distro. The distro must have been checked for integrity
+ * by the caller or have been received via a trusted mechanism.
+ *
+ * @param distroFileDescriptor the file descriptor for the distro
+ * @param checkToken an optional token provided if the install was triggered in response to a
+ * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+ * @param callback the {@link Callback} to receive callbacks related to the installation
+ * @return {@link #SUCCESS} if the installation will be attempted
+ */
+ @ResultCode
+ public int requestInstall(
+ ParcelFileDescriptor distroFileDescriptor, byte[] checkToken, Callback callback)
+ throws IOException {
+
+ ICallback iCallback = new CallbackWrapper(mContext, callback);
+ try {
+ logDebug("sIRulesManager.requestInstall()");
+ return mIRulesManager.requestInstall(distroFileDescriptor, checkToken, iCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Requests uninstallation of the currently installed distro (leaving the device with no
+ * distro installed).
+ *
+ * @param checkToken an optional token provided if the uninstall was triggered in response to a
+ * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+ * @param callback the {@link Callback} to receive callbacks related to the uninstall
+ * @return {@link #SUCCESS} if the uninstallation will be attempted
+ */
+ @ResultCode
+ public int requestUninstall(byte[] checkToken, Callback callback) {
+ ICallback iCallback = new CallbackWrapper(mContext, callback);
+ try {
+ logDebug("sIRulesManager.requestUninstall()");
+ return mIRulesManager.requestUninstall(checkToken, iCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /*
+ * We wrap incoming binder calls with a private class implementation that
+ * redirects them into main-thread actions. This serializes the backup
+ * progress callbacks nicely within the usual main-thread lifecycle pattern.
+ */
+ private class CallbackWrapper extends ICallback.Stub {
+ final Handler mHandler;
+ final Callback mCallback;
+
+ CallbackWrapper(Context context, Callback callback) {
+ mCallback = callback;
+ mHandler = new Handler(context.getMainLooper());
+ }
+
+ // Binder calls into this object just enqueue on the main-thread handler
+ @Override
+ public void onFinished(int status) {
+ logDebug("mCallback.onFinished(status), status=" + status);
+ mHandler.post(() -> mCallback.onFinished(status));
+ }
+ }
+
+ /**
+ * Requests the system does not modify the currently installed time zone distro, if any. This
+ * method records the fact that a time zone check operation triggered by the system is now
+ * complete and there was nothing to do. The token passed should be the one presented when the
+ * check was triggered.
+ *
+ * <p>Note: Passing {@code success == false} may result in more checks being triggered. Clients
+ * should be careful not to pass false if the failure is unlikely to resolve by itself.
+ *
+ * @param checkToken an optional token provided if the install was triggered in response to a
+ * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+ * @param succeeded true if the check was successful, false if it was not successful but may
+ * succeed if it is retried
+ */
+ public void requestNothing(byte[] checkToken, boolean succeeded) {
+ try {
+ logDebug("sIRulesManager.requestNothing() with token=" + Arrays.toString(checkToken));
+ mIRulesManager.requestNothing(checkToken, succeeded);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ static void logDebug(String msg) {
+ if (DEBUG) {
+ Log.v(TAG, msg);
+ }
+ }
+}
diff --git a/core/java/android/app/timezone/RulesState.aidl b/core/java/android/app/timezone/RulesState.aidl
new file mode 100644
index 0000000..f789120
--- /dev/null
+++ b/core/java/android/app/timezone/RulesState.aidl
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+parcelable RulesState;
\ No newline at end of file
diff --git a/core/java/android/app/timezone/RulesState.java b/core/java/android/app/timezone/RulesState.java
new file mode 100644
index 0000000..33f4e80
--- /dev/null
+++ b/core/java/android/app/timezone/RulesState.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.timezone;
+
+import static android.app.timezone.Utils.validateConditionalNull;
+import static android.app.timezone.Utils.validateNotNull;
+import static android.app.timezone.Utils.validateRulesVersion;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Description of the state of time zone rules on a device.
+ *
+ * <p>The following properties are included:
+ * <dl>
+ * <dt>systemRulesVersion</dt>
+ * <dd>the IANA rules version that shipped with the OS. Always present. e.g. "2017a".</dd>
+ * <dt>distroFormatVersionSupported</dt>
+ * <dd>the distro format version supported by this device. Always present.</dd>
+ * <dt>operationInProgress</dt>
+ * <dd>{@code true} if there is an install / uninstall operation currently happening.</dd>
+ * <dt>stagedOperationType</dt>
+ * <dd>one of {@link #STAGED_OPERATION_UNKNOWN}, {@link #STAGED_OPERATION_NONE},
+ * {@link #STAGED_OPERATION_UNINSTALL} and {@link #STAGED_OPERATION_INSTALL} indicating whether
+ * there is a currently staged time zone distro operation. {@link #STAGED_OPERATION_UNKNOWN} is
+ * used when {@link #isOperationInProgress()} is {@code true}. Staged operations currently
+ * require a reboot to become active.</dd>
+ * <dt>stagedDistroRulesVersion</dt>
+ * <dd>[present if distroStagedState == STAGED_STATE_INSTALL], the rules version of the distro
+ * currently staged for installation.</dd>
+ * <dt>distroStatus</dt>
+ * <dd>{@link #DISTRO_STATUS_INSTALLED} if there is a time zone distro installed and active,
+ * {@link #DISTRO_STATUS_NONE} if there is no active installed distro.
+ * {@link #DISTRO_STATUS_UNKNOWN} is used when {@link #isOperationInProgress()} is {@code true}.
+ * </dd>
+ * <dt>installedDistroRulesVersion</dt>
+ * <dd>[present if distroStatus == {@link #DISTRO_STATUS_INSTALLED}], the rules version of the
+ * installed and active distro.</dd>
+ * </dl>
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public final class RulesState implements Parcelable {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ STAGED_OPERATION_UNKNOWN,
+ STAGED_OPERATION_NONE,
+ STAGED_OPERATION_UNINSTALL,
+ STAGED_OPERATION_INSTALL })
+ private @interface StagedOperationType {}
+
+ /** Staged state could not be determined. */
+ public static final int STAGED_OPERATION_UNKNOWN = 0;
+ /** Nothing is staged. */
+ public static final int STAGED_OPERATION_NONE = 1;
+ /** An uninstall is staged. */
+ public static final int STAGED_OPERATION_UNINSTALL = 2;
+ /** An install is staged. */
+ public static final int STAGED_OPERATION_INSTALL = 3;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DISTRO_STATUS_UNKNOWN,
+ DISTRO_STATUS_NONE,
+ DISTRO_STATUS_INSTALLED })
+ private @interface DistroStatus {}
+
+ /** The current distro status could not be determined. */
+ public static final int DISTRO_STATUS_UNKNOWN = 0;
+ /** There is no active installed time zone distro. */
+ public static final int DISTRO_STATUS_NONE = 1;
+ /** The is an active, installed time zone distro. */
+ public static final int DISTRO_STATUS_INSTALLED = 2;
+
+ private static final byte BYTE_FALSE = 0;
+ private static final byte BYTE_TRUE = 1;
+
+ private final String mSystemRulesVersion;
+ private final DistroFormatVersion mDistroFormatVersionSupported;
+ private final boolean mOperationInProgress;
+ @StagedOperationType private final int mStagedOperationType;
+ @Nullable private final DistroRulesVersion mStagedDistroRulesVersion;
+ @DistroStatus private final int mDistroStatus;
+ @Nullable private final DistroRulesVersion mInstalledDistroRulesVersion;
+
+ public RulesState(String systemRulesVersion, DistroFormatVersion distroFormatVersionSupported,
+ boolean operationInProgress,
+ @StagedOperationType int stagedOperationType,
+ @Nullable DistroRulesVersion stagedDistroRulesVersion,
+ @DistroStatus int distroStatus,
+ @Nullable DistroRulesVersion installedDistroRulesVersion) {
+ this.mSystemRulesVersion = validateRulesVersion("systemRulesVersion", systemRulesVersion);
+ this.mDistroFormatVersionSupported =
+ validateNotNull("distroFormatVersionSupported", distroFormatVersionSupported);
+ this.mOperationInProgress = operationInProgress;
+
+ if (operationInProgress && stagedOperationType != STAGED_OPERATION_UNKNOWN) {
+ throw new IllegalArgumentException(
+ "stagedOperationType != STAGED_OPERATION_UNKNOWN");
+ }
+ this.mStagedOperationType = validateStagedOperation(stagedOperationType);
+ this.mStagedDistroRulesVersion = validateConditionalNull(
+ mStagedOperationType == STAGED_OPERATION_INSTALL /* requireNotNull */,
+ "stagedDistroRulesVersion", stagedDistroRulesVersion);
+
+ if (operationInProgress && distroStatus != DISTRO_STATUS_UNKNOWN) {
+ throw new IllegalArgumentException("distroInstalled != DISTRO_STATUS_UNKNOWN");
+ }
+ this.mDistroStatus = validateDistroStatus(distroStatus);
+ this.mInstalledDistroRulesVersion = validateConditionalNull(
+ mDistroStatus == DISTRO_STATUS_INSTALLED/* requireNotNull */,
+ "installedDistroRulesVersion", installedDistroRulesVersion);
+ }
+
+ public String getSystemRulesVersion() {
+ return mSystemRulesVersion;
+ }
+
+ public boolean isOperationInProgress() {
+ return mOperationInProgress;
+ }
+
+ public @StagedOperationType int getStagedOperationType() {
+ return mStagedOperationType;
+ }
+
+ /**
+ * Returns the staged rules version when {@link #getStagedOperationType()} is
+ * {@link #STAGED_OPERATION_INSTALL}.
+ */
+ public @Nullable DistroRulesVersion getStagedDistroRulesVersion() {
+ return mStagedDistroRulesVersion;
+ }
+
+ public @DistroStatus int getDistroStatus() {
+ return mDistroStatus;
+ }
+
+ /**
+ * Returns the installed rules version when {@link #getDistroStatus()} is
+ * {@link #DISTRO_STATUS_INSTALLED}.
+ */
+ public @Nullable DistroRulesVersion getInstalledDistroRulesVersion() {
+ return mInstalledDistroRulesVersion;
+ }
+
+ /**
+ * Returns true if a distro in the specified format is supported on this device.
+ */
+ public boolean isDistroFormatVersionSupported(DistroFormatVersion distroFormatVersion) {
+ return mDistroFormatVersionSupported.supports(distroFormatVersion);
+ }
+
+ /**
+ * Returns true if the distro IANA rules version supplied is newer or the same as the version in
+ * the system image data files.
+ */
+ public boolean isSystemVersionOlderThan(DistroRulesVersion distroRulesVersion) {
+ return mSystemRulesVersion.compareTo(distroRulesVersion.getRulesVersion()) < 0;
+ }
+
+ public boolean isDistroInstalled() {
+ return mDistroStatus == DISTRO_STATUS_INSTALLED;
+ }
+
+ /**
+ * Returns true if the rules version supplied is newer than the one currently installed. If
+ * there is no installed distro this method throws IllegalStateException.
+ */
+ public boolean isInstalledDistroOlderThan(DistroRulesVersion distroRulesVersion) {
+ if (mOperationInProgress) {
+ throw new IllegalStateException("Distro state not known: operation in progress.");
+ }
+ if (!isDistroInstalled()) {
+ throw new IllegalStateException("No distro installed.");
+ }
+ return mInstalledDistroRulesVersion.isOlderThan(distroRulesVersion);
+ }
+
+ public static final Parcelable.Creator<RulesState> CREATOR =
+ new Parcelable.Creator<RulesState>() {
+ public RulesState createFromParcel(Parcel in) {
+ return RulesState.createFromParcel(in);
+ }
+
+ public RulesState[] newArray(int size) {
+ return new RulesState[size];
+ }
+ };
+
+ private static RulesState createFromParcel(Parcel in) {
+ String systemRulesVersion = in.readString();
+ DistroFormatVersion distroFormatVersionSupported = in.readParcelable(null);
+ boolean operationInProgress = in.readByte() == BYTE_TRUE;
+ int distroStagedState = in.readByte();
+ DistroRulesVersion stagedDistroRulesVersion = in.readParcelable(null);
+ int installedDistroStatus = in.readByte();
+ DistroRulesVersion installedDistroRulesVersion = in.readParcelable(null);
+ return new RulesState(systemRulesVersion, distroFormatVersionSupported, operationInProgress,
+ distroStagedState, stagedDistroRulesVersion,
+ installedDistroStatus, installedDistroRulesVersion);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mSystemRulesVersion);
+ out.writeParcelable(mDistroFormatVersionSupported, 0);
+ out.writeByte(mOperationInProgress ? BYTE_TRUE : BYTE_FALSE);
+ out.writeByte((byte) mStagedOperationType);
+ out.writeParcelable(mStagedDistroRulesVersion, 0);
+ out.writeByte((byte) mDistroStatus);
+ out.writeParcelable(mInstalledDistroRulesVersion, 0);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ RulesState that = (RulesState) o;
+
+ if (mOperationInProgress != that.mOperationInProgress) {
+ return false;
+ }
+ if (mStagedOperationType != that.mStagedOperationType) {
+ return false;
+ }
+ if (mDistroStatus != that.mDistroStatus) {
+ return false;
+ }
+ if (!mSystemRulesVersion.equals(that.mSystemRulesVersion)) {
+ return false;
+ }
+ if (!mDistroFormatVersionSupported.equals(that.mDistroFormatVersionSupported)) {
+ return false;
+ }
+ if (mStagedDistroRulesVersion != null ? !mStagedDistroRulesVersion
+ .equals(that.mStagedDistroRulesVersion) : that.mStagedDistroRulesVersion != null) {
+ return false;
+ }
+ return mInstalledDistroRulesVersion != null ? mInstalledDistroRulesVersion
+ .equals(that.mInstalledDistroRulesVersion)
+ : that.mInstalledDistroRulesVersion == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mSystemRulesVersion.hashCode();
+ result = 31 * result + mDistroFormatVersionSupported.hashCode();
+ result = 31 * result + (mOperationInProgress ? 1 : 0);
+ result = 31 * result + mStagedOperationType;
+ result = 31 * result + (mStagedDistroRulesVersion != null ? mStagedDistroRulesVersion
+ .hashCode()
+ : 0);
+ result = 31 * result + mDistroStatus;
+ result = 31 * result + (mInstalledDistroRulesVersion != null ? mInstalledDistroRulesVersion
+ .hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "RulesState{"
+ + "mSystemRulesVersion='" + mSystemRulesVersion + '\''
+ + ", mDistroFormatVersionSupported=" + mDistroFormatVersionSupported
+ + ", mOperationInProgress=" + mOperationInProgress
+ + ", mStagedOperationType=" + mStagedOperationType
+ + ", mStagedDistroRulesVersion=" + mStagedDistroRulesVersion
+ + ", mDistroStatus=" + mDistroStatus
+ + ", mInstalledDistroRulesVersion=" + mInstalledDistroRulesVersion
+ + '}';
+ }
+
+ private static int validateStagedOperation(int stagedOperationType) {
+ if (stagedOperationType < STAGED_OPERATION_UNKNOWN
+ || stagedOperationType > STAGED_OPERATION_INSTALL) {
+ throw new IllegalArgumentException("Unknown operation type=" + stagedOperationType);
+ }
+ return stagedOperationType;
+ }
+
+ private static int validateDistroStatus(int distroStatus) {
+ if (distroStatus < DISTRO_STATUS_UNKNOWN || distroStatus > DISTRO_STATUS_INSTALLED) {
+ throw new IllegalArgumentException("Unknown distro status=" + distroStatus);
+ }
+ return distroStatus;
+ }
+}
diff --git a/core/java/android/app/timezone/RulesUpdaterContract.java b/core/java/android/app/timezone/RulesUpdaterContract.java
new file mode 100644
index 0000000..4e77818
--- /dev/null
+++ b/core/java/android/app/timezone/RulesUpdaterContract.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.timezone;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Constants related to the contract between the Android system and the privileged time zone updater
+ * application.
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public final class RulesUpdaterContract {
+
+ /**
+ * The system permission possessed by the Android system that allows it to trigger time zone
+ * update checks. The updater should be configured to require this permission when registering
+ * for {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intents.
+ */
+ public static final String TRIGGER_TIME_ZONE_RULES_CHECK_PERMISSION =
+ android.Manifest.permission.TRIGGER_TIME_ZONE_RULES_CHECK;
+
+ /**
+ * The system permission possessed by the time zone rules updater app that allows it to update
+ * device time zone rules. The Android system requires this permission for calls made to
+ * {@link RulesManager}.
+ */
+ public static final String UPDATE_TIME_ZONE_RULES_PERMISSION =
+ android.Manifest.permission.UPDATE_TIME_ZONE_RULES;
+
+ /**
+ * The action of the intent that the Android system will broadcast. The intent will be targeted
+ * at the configured updater application's package meaning the term "broadcast" only loosely
+ * applies.
+ */
+ public static final String ACTION_TRIGGER_RULES_UPDATE_CHECK =
+ "android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK";
+
+ /**
+ * The extra containing the {@code byte[]} that should be passed to
+ * {@link RulesManager#requestInstall(ParcelFileDescriptor, byte[], Callback)},
+ * {@link RulesManager#requestUninstall(byte[], Callback)} and
+ * {@link RulesManager#requestNothing(byte[], boolean)} methods when the
+ * {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intent has been processed.
+ */
+ public static final String EXTRA_CHECK_TOKEN =
+ "android.intent.extra.timezone.CHECK_TOKEN";
+
+ /**
+ * Creates an intent that would trigger a time zone rules update check.
+ */
+ public static Intent createUpdaterIntent(String updaterPackageName) {
+ Intent intent = new Intent(RulesUpdaterContract.ACTION_TRIGGER_RULES_UPDATE_CHECK);
+ intent.setPackage(updaterPackageName);
+ intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+ return intent;
+ }
+
+ /**
+ * Broadcasts an {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intent with the
+ * {@link #EXTRA_CHECK_TOKEN} that triggers an update check, including the required receiver
+ * permission.
+ */
+ public static void sendBroadcast(Context context, String updaterAppPackageName,
+ byte[] checkTokenBytes) {
+ Intent intent = createUpdaterIntent(updaterAppPackageName);
+ intent.putExtra(EXTRA_CHECK_TOKEN, checkTokenBytes);
+ context.sendBroadcast(intent, RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION);
+ }
+}
diff --git a/core/java/android/app/timezone/Utils.java b/core/java/android/app/timezone/Utils.java
new file mode 100644
index 0000000..8dd3fb7
--- /dev/null
+++ b/core/java/android/app/timezone/Utils.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.timezone;
+
+/**
+ * Shared code for android.app.timezone classes.
+ */
+final class Utils {
+ private Utils() {}
+
+ static int validateVersion(String type, int version) {
+ if (version < 0 || version > 999) {
+ throw new IllegalArgumentException("Invalid " + type + " version=" + version);
+ }
+ return version;
+ }
+
+ static String validateRulesVersion(String type, String rulesVersion) {
+ validateNotNull(type, rulesVersion);
+
+ if (rulesVersion.isEmpty()) {
+ throw new IllegalArgumentException(type + " must not be empty");
+ }
+ return rulesVersion;
+ }
+
+ /** Validates that {@code object} is not null. Always returns {@code object}. */
+ static <T> T validateNotNull(String type, T object) {
+ if (object == null) {
+ throw new NullPointerException(type + " == null");
+ }
+ return object;
+ }
+
+ /**
+ * If {@code requireNotNull} is {@code true} calls {@link #validateNotNull(String, Object)},
+ * and {@link #validateNull(String, Object)} otherwise. Returns {@code object}.
+ */
+ static <T> T validateConditionalNull(boolean requireNotNull, String type, T object) {
+ if (requireNotNull) {
+ return validateNotNull(type, object);
+ } else {
+ return validateNull(type, object);
+ }
+ }
+
+ /** Validates that {@code object} is null. Always returns null. */
+ static <T> T validateNull(String type, T object) {
+ if (object != null) {
+ throw new IllegalArgumentException(type + " != null");
+ }
+ return null;
+ }
+}
diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl
index a10de45..6d65e3e 100644
--- a/core/java/android/app/trust/ITrustManager.aidl
+++ b/core/java/android/app/trust/ITrustManager.aidl
@@ -34,4 +34,6 @@
boolean isDeviceLocked(int userId);
boolean isDeviceSecure(int userId);
boolean isTrustUsuallyManaged(int userId);
+ void unlockedByFingerprintForUser(int userId);
+ void clearAllFingerprints();
}
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index 54a7e5c..852cb8e 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -27,8 +27,6 @@
import android.os.RemoteException;
import android.util.ArrayMap;
-import com.android.internal.widget.LockPatternUtils;
-
/**
* See {@link com.android.server.trust.TrustManagerService}
* @hide
@@ -187,6 +185,32 @@
}
}
+ /**
+ * Updates the trust state for the user due to the user unlocking via fingerprint.
+ * Should only be called if user authenticated via fingerprint and bouncer can be skipped.
+ * @param userId
+ */
+ @RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE)
+ public void unlockedByFingerprintForUser(int userId) {
+ try {
+ mService.unlockedByFingerprintForUser(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Clears authenticated fingerprints for all users.
+ */
+ @RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE)
+ public void clearAllFingerprints() {
+ try {
+ mService.clearAllFingerprints();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index e8ad69d..7ff37d2 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1658,7 +1658,7 @@
*/
public BluetoothGatt connectGatt(Context context, boolean autoConnect,
BluetoothGattCallback callback, int transport) {
- return (connectGatt(context, autoConnect,callback, TRANSPORT_AUTO, PHY_LE_1M_MASK));
+ return (connectGatt(context, autoConnect,callback, transport, PHY_LE_1M_MASK));
}
/**
@@ -1682,7 +1682,7 @@
*/
public BluetoothGatt connectGatt(Context context, boolean autoConnect,
BluetoothGattCallback callback, int transport, int phy) {
- return connectGatt(context, autoConnect,callback, TRANSPORT_AUTO, PHY_LE_1M_MASK, null);
+ return connectGatt(context, autoConnect,callback, transport, phy, null);
}
/**
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 8519dba..57b954f 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -768,6 +768,28 @@
}
/**
+ * Force SCO audio to be opened regardless any other restrictions
+ *
+ * @param forced Whether or not SCO audio connection should be forced:
+ * True to force SCO audio
+ * False to use SCO audio in normal manner
+ * @hide
+ */
+ public void setForceScoAudio(boolean forced) {
+ if (VDBG) log("setForceScoAudio " + String.valueOf(forced));
+ if (mService != null && isEnabled()) {
+ try {
+ mService.setForceScoAudio(forced);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ }
+
+ /**
* Check if Bluetooth SCO audio is connected.
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java
index e3288f3..a5a0243 100644
--- a/core/java/android/bluetooth/BluetoothInputDevice.java
+++ b/core/java/android/bluetooth/BluetoothInputDevice.java
@@ -97,6 +97,12 @@
public static final String ACTION_VIRTUAL_UNPLUG_STATUS =
"android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS";
+ /**
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_IDLE_TIME_CHANGED =
+ "android.bluetooth.input.profile.action.IDLE_TIME_CHANGED";
/**
* Return codes for the connect and disconnect Bluez / Dbus calls.
@@ -200,6 +206,11 @@
*/
public static final String EXTRA_VIRTUAL_UNPLUG_STATUS = "android.bluetooth.BluetoothInputDevice.extra.VIRTUAL_UNPLUG_STATUS";
+ /**
+ * @hide
+ */
+ public static final String EXTRA_IDLE_TIME = "android.bluetooth.BluetoothInputDevice.extra.IDLE_TIME";
+
private Context mContext;
private ServiceListener mServiceListener;
private BluetoothAdapter mAdapter;
@@ -659,6 +670,56 @@
if (mService == null) Log.w(TAG, "Proxy not attached to service");
return false;
}
+
+ /**
+ * Send Get_Idle_Time command to the connected HID input device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param device Remote Bluetooth Device
+ * @return false on immediate error,
+ * true otherwise
+ * @hide
+ */
+ public boolean getIdleTime(BluetoothDevice device) {
+ if (DBG) log("getIdletime(" + device + ")");
+ if (mService != null && isEnabled() && isValidDevice(device)) {
+ try {
+ return mService.getIdleTime(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
+
+ /**
+ * Send Set_Idle_Time command to the connected HID input device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param device Remote Bluetooth Device
+ * @param idleTime Idle time to be set on HID Device
+ * @return false on immediate error,
+ * true otherwise
+ * @hide
+ */
+ public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
+ if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
+ if (mService != null && isEnabled() && isValidDevice(device)) {
+ try {
+ return mService.setIdleTime(device, idleTime);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
+
private static void log(String msg) {
Log.d(TAG, msg);
}
diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl
index dde0ac6..92ab8da 100755
--- a/core/java/android/bluetooth/IBluetoothHeadset.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -52,6 +52,7 @@
boolean disconnectAudio();
void setAudioRouteAllowed(boolean allowed);
boolean getAudioRouteAllowed();
+ void setForceScoAudio(boolean forced);
boolean startScoUsingVirtualVoiceCall(in BluetoothDevice device);
boolean stopScoUsingVirtualVoiceCall(in BluetoothDevice device);
oneway void phoneStateChanged(int numActive, int numHeld, int callState, String number, int type);
diff --git a/core/java/android/bluetooth/IBluetoothInputDevice.aidl b/core/java/android/bluetooth/IBluetoothInputDevice.aidl
index 1ebb9ca..5bd3f78 100644
--- a/core/java/android/bluetooth/IBluetoothInputDevice.aidl
+++ b/core/java/android/bluetooth/IBluetoothInputDevice.aidl
@@ -56,4 +56,12 @@
* @hide
*/
boolean sendData(in BluetoothDevice device, String report);
+ /**
+ * @hide
+ */
+ boolean getIdleTime(in BluetoothDevice device);
+ /**
+ * @hide
+ */
+ boolean setIdleTime(in BluetoothDevice device, byte idleTime);
}
diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
index 8891d2e..cf8f08f 100644
--- a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
+++ b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
@@ -26,8 +26,8 @@
*/
public final class PeriodicAdvertisingParameters implements Parcelable {
- private static final int INTERVAL_MAX = 80;
- private static final int INTERVAL_MIN = 65519;
+ private static final int INTERVAL_MIN = 80;
+ private static final int INTERVAL_MAX = 65519;
private final boolean includeTxPower;
private final int interval;
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 5bc1f18..9323261 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -43,6 +43,7 @@
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
+import libcore.io.IoUtils;
/**
* Representation of a clipped data on the clipboard.
@@ -335,46 +336,46 @@
// If this Item has a URI value, try using that.
Uri uri = getUri();
if (uri != null) {
-
// First see if the URI can be opened as a plain text stream
// (of any sub-type). If so, this is the best textual
// representation for it.
+ final ContentResolver resolver = context.getContentResolver();
+ AssetFileDescriptor descr = null;
FileInputStream stream = null;
+ InputStreamReader reader = null;
try {
- // Ask for a stream of the desired type.
- AssetFileDescriptor descr = context.getContentResolver()
- .openTypedAssetFileDescriptor(uri, "text/*", null);
- stream = descr.createInputStream();
- InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
-
- // Got it... copy the stream into a local string and return it.
- StringBuilder builder = new StringBuilder(128);
- char[] buffer = new char[8192];
- int len;
- while ((len=reader.read(buffer)) > 0) {
- builder.append(buffer, 0, len);
+ try {
+ // Ask for a stream of the desired type.
+ descr = resolver.openTypedAssetFileDescriptor(uri, "text/*", null);
+ } catch (SecurityException e) {
+ Log.w("ClipData", "Failure opening stream", e);
+ } catch (FileNotFoundException|RuntimeException e) {
+ // Unable to open content URI as text... not really an
+ // error, just something to ignore.
}
- return builder.toString();
-
- } catch (SecurityException e) {
- Log.w("ClipData", "Failure opening stream", e);
-
- } catch (FileNotFoundException e) {
- // Unable to open content URI as text... not really an
- // error, just something to ignore.
-
- } catch (IOException e) {
- // Something bad has happened.
- Log.w("ClipData", "Failure loading text", e);
- return e.toString();
-
- } finally {
- if (stream != null) {
+ if (descr != null) {
try {
- stream.close();
+ stream = descr.createInputStream();
+ reader = new InputStreamReader(stream, "UTF-8");
+
+ // Got it... copy the stream into a local string and return it.
+ final StringBuilder builder = new StringBuilder(128);
+ char[] buffer = new char[8192];
+ int len;
+ while ((len=reader.read(buffer)) > 0) {
+ builder.append(buffer, 0, len);
+ }
+ return builder.toString();
} catch (IOException e) {
+ // Something bad has happened.
+ Log.w("ClipData", "Failure loading text", e);
+ return e.toString();
}
}
+ } finally {
+ IoUtils.closeQuietly(descr);
+ IoUtils.closeQuietly(stream);
+ IoUtils.closeQuietly(reader);
}
// If we couldn't open the URI as a stream, use the URI itself as a textual
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index a04a7b2..f8c139f 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -524,7 +524,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mCloseGuard.warnIfOpen();
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
close();
} finally {
super.finalize();
@@ -579,7 +582,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mCloseGuard.warnIfOpen();
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
close();
} finally {
super.finalize();
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 309827c..9ccc552 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -2938,7 +2938,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mCloseGuard.warnIfOpen();
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
close();
} finally {
super.finalize();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index db80c72..2f79538 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -34,7 +34,6 @@
import android.annotation.UserIdInt;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
-import android.app.Notification;
import android.app.VrManager;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -814,6 +813,9 @@
*/
public abstract boolean deleteSharedPreferences(String name);
+ /** @hide */
+ public abstract void reloadSharedPreferences();
+
/**
* Open a private file associated with this Context's application package
* for reading.
@@ -2675,8 +2677,8 @@
/**
* Similar to {@link #startService(Intent)}, but with an implicit promise that the
- * Service will call {@link android.app.Service#startForeground(int, Notification)
- * startForeground(int, Notification)} once it begins running. The service is given
+ * Service will call {@link android.app.Service#startForeground(int, android.app.Notification)
+ * startForeground(int, android.app.Notification)} once it begins running. The service is given
* an amount of time comparable to the ANR interval to do this, otherwise the system
* will automatically stop the service and declare the app ANR.
*
@@ -2697,7 +2699,7 @@
* or the service can not be found.
*
* @see #stopService
- * @see android.app.Service#startForeground(int, Notification)
+ * @see android.app.Service#startForeground(int, android.app.Notification)
*/
@Nullable
public abstract ComponentName startForegroundService(Intent service);
@@ -2885,6 +2887,7 @@
STORAGE_SERVICE,
STORAGE_STATS_SERVICE,
WALLPAPER_SERVICE,
+ TIME_ZONE_RULES_MANAGER_SERVICE,
VIBRATOR_SERVICE,
//@hide: STATUS_BAR_SERVICE,
CONNECTIVITY_SERVICE,
@@ -3525,6 +3528,17 @@
/**
* Use with {@link #getSystemService} to retrieve a
+ * {@link android.telephony.euicc.EuiccManager} to manage the device eUICC (embedded SIM).
+ *
+ * @see #getSystemService
+ * @see android.telephony.euicc.EuiccManager
+ * TODO(b/35851809): Unhide this API.
+ * @hide
+ */
+ public static final String EUICC_SERVICE = "euicc_service";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.text.ClipboardManager} for accessing and modifying
* {@link android.content.ClipboardManager} for accessing and modifying
* the contents of the global clipboard.
@@ -3978,6 +3992,15 @@
public static final String VR_SERVICE = "vrmanager";
/**
+ * Use with {@link #getSystemService} to retrieve an
+ * {@link android.app.timezone.ITimeZoneRulesManager}.
+ * @hide
+ *
+ * @see #getSystemService
+ */
+ public static final String TIME_ZONE_RULES_MANAGER_SERVICE = "timezone";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index e127ca3..20fafb2 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -19,7 +19,6 @@
import android.annotation.SystemApi;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
-import android.app.Notification;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
@@ -173,6 +172,12 @@
return mBase.getSharedPreferences(file, mode);
}
+ /** @hide */
+ @Override
+ public void reloadSharedPreferences() {
+ mBase.reloadSharedPreferences();
+ }
+
@Override
public boolean moveSharedPreferencesFrom(Context sourceContext, String name) {
return mBase.moveSharedPreferencesFrom(sourceContext, name);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 1d879e9..852e5f7 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4765,10 +4765,21 @@
/** {@hide} */
public static final String EXTRA_REASON = "android.intent.extra.REASON";
- /** {@hide} */
+ /**
+ * {@hide}
+ * This extra will be send together with {@link #ACTION_FACTORY_RESET}
+ */
public static final String EXTRA_WIPE_EXTERNAL_STORAGE = "android.intent.extra.WIPE_EXTERNAL_STORAGE";
/**
+ * {@hide}
+ * This extra will be set to true when the user choose to wipe the data on eSIM during factory
+ * reset for the device with eSIM. This extra will be sent together with
+ * {@link #ACTION_FACTORY_RESET}
+ */
+ public static final String EXTRA_WIPE_ESIMS = "com.android.internal.intent.extra.WIPE_ESIMS";
+
+ /**
* Optional {@link android.app.PendingIntent} extra used to deliver the result of the SIM
* activation request.
* TODO: Add information about the structure and response data used with the pending intent.
diff --git a/core/java/android/content/SyncAdaptersCache.java b/core/java/android/content/SyncAdaptersCache.java
index ddbdb8a..ccd7994 100644
--- a/core/java/android/content/SyncAdaptersCache.java
+++ b/core/java/android/content/SyncAdaptersCache.java
@@ -20,6 +20,7 @@
import android.content.pm.XmlSerializerAndParser;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.SparseArray;
@@ -63,7 +64,7 @@
sa.getString(com.android.internal.R.styleable.SyncAdapter_contentAuthority);
final String accountType =
sa.getString(com.android.internal.R.styleable.SyncAdapter_accountType);
- if (authority == null || accountType == null) {
+ if (TextUtils.isEmpty(authority) || TextUtils.isEmpty(accountType)) {
return null;
}
final boolean userVisible =
diff --git a/core/java/android/content/pm/IDexModuleRegisterCallback.aidl b/core/java/android/content/pm/IDexModuleRegisterCallback.aidl
new file mode 100644
index 0000000..4af6999
--- /dev/null
+++ b/core/java/android/content/pm/IDexModuleRegisterCallback.aidl
@@ -0,0 +1,28 @@
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.content.pm;
+
+import android.os.Bundle;
+
+/**
+ * Callback for registering a dex module with the Package Manager.
+ *
+ * @hide
+ */
+oneway interface IDexModuleRegisterCallback {
+ void onDexModuleRegistered(in String dexModulePath, in boolean success, in String message);
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 2ebfa8f..e800e88 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -25,6 +25,7 @@
import android.content.pm.ChangedPackages;
import android.content.pm.InstantAppInfo;
import android.content.pm.FeatureInfo;
+import android.content.pm.IDexModuleRegisterCallback;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageDeleteObserver;
@@ -475,12 +476,44 @@
String loaderIsa);
/**
+ * Register an application dex module with the package manager.
+ * The package manager will keep track of the given module for future optimizations.
+ *
+ * Dex module optimizations will disable the classpath checking at runtime. The client bares
+ * the responsibility to ensure that the static assumptions on classes in the optimized code
+ * hold at runtime (e.g. there's no duplicate classes in the classpath).
+ *
+ * Note that the package manager already keeps track of dex modules loaded with
+ * {@link dalvik.system.DexClassLoader} and {@link dalvik.system.PathClassLoader}.
+ * This can be called for an eager registration.
+ *
+ * The call might take a while and the results will be posted on the main thread, using
+ * the given callback.
+ *
+ * If the module is intended to be shared with other apps, make sure that the file
+ * permissions allow for it.
+ * If at registration time the permissions allow for others to read it, the module would
+ * be marked as a shared module which might undergo a different optimization strategy.
+ * (usually shared modules will generated larger optimizations artifacts,
+ * taking more disk space).
+ *
+ * @param packageName the package name to which the dex module belongs
+ * @param dexModulePath the absolute path of the dex module.
+ * @param isSharedModule whether or not the module is intended to be used by other apps.
+ * @param callback if not null,
+ * {@link android.content.pm.IDexModuleRegisterCallback.IDexModuleRegisterCallback#onDexModuleRegistered}
+ * will be called once the registration finishes.
+ */
+ oneway void registerDexModule(in String packageName, in String dexModulePath,
+ in boolean isSharedModule, IDexModuleRegisterCallback callback);
+
+ /**
* Ask the package manager to perform a dex-opt for the given reason. The package
* manager will map the reason to a compiler filter according to the current system
* configuration.
*/
boolean performDexOpt(String packageName, boolean checkProfiles,
- int compileReason, boolean force);
+ int compileReason, boolean force, boolean bootComplete);
/**
* Ask the package manager to perform a dex-opt with the given compiler filter.
@@ -489,7 +522,7 @@
* definite state.
*/
boolean performDexOptMode(String packageName, boolean checkProfiles,
- String targetCompilerFilter, boolean force);
+ String targetCompilerFilter, boolean force, boolean bootComplete);
/**
* Ask the package manager to perform a dex-opt with the given compiler filter on the
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 7f3f35f..5eca57b 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -24,6 +24,7 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.app.ActivityManager;
+import android.app.AppGlobals;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager.InstallReason;
@@ -1355,6 +1356,16 @@
* if unavailable.
*/
public @Nullable Bitmap getAppIcon() {
+ if (appIcon == null) {
+ // Icon may have been omitted for calls that return bulk session
+ // lists, so try fetching the specific icon.
+ try {
+ appIcon = AppGlobals.getPackageManager().getPackageInstaller()
+ .getSessionInfo(sessionId).appIcon;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
return appIcon;
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index be2cd10..6bc7d42 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1898,6 +1898,14 @@
public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version";
/**
+ * The device includes broadcast radio tuner.
+ *
+ * @hide FutureFeature
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_RADIO = "android.hardware.radio";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device includes an accelerometer.
*/
@@ -2031,6 +2039,15 @@
"android.hardware.telephony.carrierlock";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+ * supports embedded subscriptions on eUICCs.
+ * TODO(b/35851809): Make this public.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports connecting to USB devices
* as the USB host.
@@ -2267,6 +2284,13 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports Wi-Fi Passpoint.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: This is a device dedicated to showing UI
* on a vehicle headunit. A headunit here is defined to be inside a
* vehicle that may or may not be moving. A headunit uses either a
@@ -2312,6 +2336,17 @@
public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: This is a device dedicated to be primarily used
+ * with keyboard, mouse or touchpad. This includes traditional desktop
+ * computers, laptops and variants such as convertibles or detachables.
+ * Due to the larger screen, the device will most likely use the
+ * {@link #FEATURE_FREEFORM_WINDOW_MANAGEMENT} feature as well.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_PC = "android.hardware.type.pc";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
* The device supports printing.
*/
@@ -5731,4 +5766,48 @@
* @hide
*/
public abstract String getInstantAppAndroidId(String packageName, @NonNull UserHandle user);
+
+ /**
+ * Callback use to notify the callers of module registration that the operation
+ * has finished.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static abstract class DexModuleRegisterCallback {
+ public abstract void onDexModuleRegistered(String dexModulePath, boolean success,
+ String message);
+ }
+
+ /**
+ * Register an application dex module with the package manager.
+ * The package manager will keep track of the given module for future optimizations.
+ *
+ * Dex module optimizations will disable the classpath checking at runtime. The client bares
+ * the responsibility to ensure that the static assumptions on classes in the optimized code
+ * hold at runtime (e.g. there's no duplicate classes in the classpath).
+ *
+ * Note that the package manager already keeps track of dex modules loaded with
+ * {@link dalvik.system.DexClassLoader} and {@link dalvik.system.PathClassLoader}.
+ * This can be called for an eager registration.
+ *
+ * The call might take a while and the results will be posted on the main thread, using
+ * the given callback.
+ *
+ * If the module is intended to be shared with other apps, make sure that the file
+ * permissions allow for it.
+ * If at registration time the permissions allow for others to read it, the module would
+ * be marked as a shared module which might undergo a different optimization strategy.
+ * (usually shared modules will generated larger optimizations artifacts,
+ * taking more disk space).
+ *
+ * @param dexModulePath the absolute path of the dex module.
+ * @param callback if not null, {@link DexModuleRegisterCallback#onDexModuleRegistered} will
+ * be called once the registration finishes.
+ *
+ * @hide
+ */
+ @SystemApi
+ public abstract void registerDexModule(String dexModulePath,
+ @Nullable DexModuleRegisterCallback callback);
}
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 4cee2df..99700df 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -18,7 +18,11 @@
import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.pm.PackageManager.ComponentInfoFlags;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManager.PackageInfoFlags;
+import android.content.pm.PackageManager.ResolveInfoFlags;
import android.os.Bundle;
import android.util.SparseArray;
@@ -133,16 +137,40 @@
public abstract boolean isPermissionsReviewRequired(String packageName, int userId);
/**
- * Gets all of the information we know about a particular package.
- *
- * @param packageName The package name to find.
- * @param userId The user under which to check.
- *
- * @return An {@link ApplicationInfo} containing information about the
- * package, or {@code null} if no application exists with that
- * package name.
+ * Retrieve all of the information we know about a particular package/application.
+ * @param filterCallingUid The results will be filtered in the context of this UID instead
+ * of the calling UID.
+ * @see PackageManager#getPackageInfo(String, int)
*/
- public abstract ApplicationInfo getApplicationInfo(String packageName, int userId);
+ public abstract PackageInfo getPackageInfo(String packageName,
+ @PackageInfoFlags int flags, int filterCallingUid, int userId);
+
+ /**
+ * Retrieve all of the information we know about a particular package/application.
+ * @param filterCallingUid The results will be filtered in the context of this UID instead
+ * of the calling UID.
+ * @see PackageManager#getApplicationInfo(String, int)
+ */
+ public abstract ApplicationInfo getApplicationInfo(String packageName,
+ @ApplicationInfoFlags int flags, int filterCallingUid, int userId);
+
+ /**
+ * Retrieve all of the information we know about a particular activity class.
+ * @param filterCallingUid The results will be filtered in the context of this UID instead
+ * of the calling UID.
+ * @see PackageManager#getActivityInfo(ComponentName, int)
+ */
+ public abstract ActivityInfo getActivityInfo(ComponentName component,
+ @ComponentInfoFlags int flags, int filterCallingUid, int userId);
+
+ /**
+ * Retrieve all activities that can be performed for the given intent.
+ * @param filterCallingUid The results will be filtered in the context of this UID instead
+ * of the calling UID.
+ * @see PackageManager#queryIntentActivities(Intent, int)
+ */
+ public abstract List<ResolveInfo> queryIntentActivities(Intent intent,
+ @ResolveInfoFlags int flags, int filterCallingUid, int userId);
/**
* Interface to {@link com.android.server.pm.PackageManagerService#getHomeActivitiesAsUser}.
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 417a95f..88c1627 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -16,7 +16,10 @@
package android.content.res;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.Display;
import android.view.DisplayInfo;
import com.android.internal.util.XmlUtils;
@@ -42,12 +45,6 @@
import java.util.ArrayList;
import java.util.Locale;
-import static android.view.Surface.ROTATION_UNDEFINED;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_90;
-import static android.view.Surface.ROTATION_180;
-import static android.view.Surface.ROTATION_270;
-
/**
* This class describes all device configuration information that can
* impact the resources the application retrieves. This includes both
@@ -603,13 +600,6 @@
*/
public int orientation;
- /**
- * The rotation used at the time orientation was determined.
- * TODO(b/36812336): Move rotation out of {@link Configuration}.
- * {@hide}
- */
- private int rotation;
-
/** Constant for {@link #uiMode}: bits that encode the mode type. */
public static final int UI_MODE_TYPE_MASK = 0x0f;
/** Constant for {@link #uiMode}: a {@link #UI_MODE_TYPE_MASK}
@@ -897,7 +887,6 @@
navigation = o.navigation;
navigationHidden = o.navigationHidden;
orientation = o.orientation;
- rotation = o.rotation;
screenLayout = o.screenLayout;
colorMode = o.colorMode;
uiMode = o.uiMode;
@@ -1001,14 +990,6 @@
case ORIENTATION_PORTRAIT: sb.append(" port"); break;
default: sb.append(" orien="); sb.append(orientation); break;
}
- switch (rotation) {
- case ROTATION_UNDEFINED: sb.append(" ?rotation"); break;
- case ROTATION_0: sb.append(" rot0"); break;
- case ROTATION_90: sb.append(" rot90"); break;
- case ROTATION_180: sb.append(" rot180"); break;
- case ROTATION_270: sb.append(" rot270"); break;
- default: sb.append(" rot="); sb.append(rotation); break;
- }
switch ((uiMode&UI_MODE_TYPE_MASK)) {
case UI_MODE_TYPE_UNDEFINED: sb.append(" ?uimode"); break;
case UI_MODE_TYPE_NORMAL: /* normal is not interesting to print */ break;
@@ -1096,7 +1077,6 @@
navigation = NAVIGATION_UNDEFINED;
navigationHidden = NAVIGATIONHIDDEN_UNDEFINED;
orientation = ORIENTATION_UNDEFINED;
- rotation = ROTATION_UNDEFINED;
screenLayout = SCREENLAYOUT_UNDEFINED;
colorMode = COLOR_MODE_UNDEFINED;
uiMode = UI_MODE_TYPE_UNDEFINED;
@@ -1205,11 +1185,6 @@
changed |= ActivityInfo.CONFIG_ORIENTATION;
orientation = delta.orientation;
}
- if (delta.rotation != ROTATION_UNDEFINED
- && rotation != delta.rotation) {
- changed |= ActivityInfo.CONFIG_ORIENTATION;
- rotation = delta.rotation;
- }
if (((delta.screenLayout & SCREENLAYOUT_SIZE_MASK) != SCREENLAYOUT_SIZE_UNDEFINED)
&& (delta.screenLayout & SCREENLAYOUT_SIZE_MASK)
@@ -1404,10 +1379,6 @@
&& orientation != delta.orientation) {
changed |= ActivityInfo.CONFIG_ORIENTATION;
}
- if ((compareUndefined || delta.rotation != ROTATION_UNDEFINED)
- && rotation != delta.rotation) {
- changed |= ActivityInfo.CONFIG_ORIENTATION;
- }
if ((compareUndefined || getScreenLayoutNoDirection(delta.screenLayout) !=
(SCREENLAYOUT_SIZE_UNDEFINED | SCREENLAYOUT_LONG_UNDEFINED))
&& getScreenLayoutNoDirection(screenLayout) !=
@@ -1544,7 +1515,6 @@
dest.writeInt(navigation);
dest.writeInt(navigationHidden);
dest.writeInt(orientation);
- dest.writeInt(rotation);
dest.writeInt(screenLayout);
dest.writeInt(colorMode);
dest.writeInt(uiMode);
@@ -1581,7 +1551,6 @@
navigation = source.readInt();
navigationHidden = source.readInt();
orientation = source.readInt();
- rotation = source.readInt();
screenLayout = source.readInt();
colorMode = source.readInt();
uiMode = source.readInt();
@@ -1666,8 +1635,6 @@
if (n != 0) return n;
n = this.orientation - that.orientation;
if (n != 0) return n;
- n = this.rotation - that.rotation;
- if (n != 0) return n;
n = this.colorMode - that.colorMode;
if (n != 0) return n;
n = this.screenLayout - that.screenLayout;
@@ -1799,24 +1766,6 @@
/**
* @hide
*
- * Setter for orientation converts from {@link Surface} values to internal representation.
- */
- public void setRotation(int rotation) {
- this.rotation = rotation;
- }
-
- /**
- * @hide
- *
- * Getter for orientation. Converts from internal representation to {@link Surface} values.
- */
- public int getRotation() {
- return rotation != ROTATION_UNDEFINED ? rotation : ROTATION_0;
- }
-
- /**
- * @hide
- *
* Clears the locale without changing layout direction.
*/
public void clearLocales() {
@@ -2051,23 +2000,6 @@
break;
}
- switch (config.rotation) {
- case ROTATION_0:
- parts.add("rot0");
- break;
- case ROTATION_90:
- parts.add("rot90");
- break;
- case ROTATION_180:
- parts.add("rot180");
- break;
- case ROTATION_270:
- parts.add("rot270");
- break;
- default:
- break;
- }
-
switch (config.uiMode & Configuration.UI_MODE_TYPE_MASK) {
case Configuration.UI_MODE_TYPE_APPLIANCE:
parts.add("appliance");
@@ -2262,10 +2194,6 @@
delta.orientation = change.orientation;
}
- if (base.rotation != change.rotation) {
- base.rotation = change.rotation;
- }
-
if ((base.screenLayout & SCREENLAYOUT_SIZE_MASK) !=
(change.screenLayout & SCREENLAYOUT_SIZE_MASK)) {
delta.screenLayout |= change.screenLayout & SCREENLAYOUT_SIZE_MASK;
@@ -2337,7 +2265,6 @@
private static final String XML_ATTR_NAVIGATION = "nav";
private static final String XML_ATTR_NAVIGATION_HIDDEN = "navHid";
private static final String XML_ATTR_ORIENTATION = "ori";
- private static final String XML_ATTR_ROTATION = "rot";
private static final String XML_ATTR_SCREEN_LAYOUT = "scrLay";
private static final String XML_ATTR_COLOR_MODE = "clrMod";
private static final String XML_ATTR_UI_MODE = "ui";
@@ -2397,8 +2324,6 @@
DENSITY_DPI_UNDEFINED);
configOut.appBounds =
Rect.unflattenFromString(XmlUtils.readStringAttribute(parser, XML_ATTR_APP_BOUNDS));
- configOut.rotation = XmlUtils.readIntAttribute(parser, XML_ATTR_ROTATION,
- ROTATION_UNDEFINED);
// For persistence, we don't care about assetsSeq, so do not read it out.
}
@@ -2475,10 +2400,6 @@
config.appBounds.flattenToString());
}
- if (config.rotation != ROTATION_UNDEFINED) {
- XmlUtils.writeIntAttribute(xml, XML_ATTR_ROTATION, config.rotation);
- }
-
// For persistence, we do not care about assetsSeq, so do not write it out.
}
}
diff --git a/core/java/android/database/sqlite/SQLiteGlobal.java b/core/java/android/database/sqlite/SQLiteGlobal.java
index 5ff199a..922d11b 100644
--- a/core/java/android/database/sqlite/SQLiteGlobal.java
+++ b/core/java/android/database/sqlite/SQLiteGlobal.java
@@ -61,6 +61,8 @@
public static int getDefaultPageSize() {
synchronized (sLock) {
if (sDefaultPageSize == 0) {
+ // If there is an issue accessing /data, something is so seriously
+ // wrong that we just let the IllegalArgumentException propagate.
sDefaultPageSize = new StatFs("/data").getBlockSize();
}
return SystemProperties.getInt("debug.sqlite.pagesize", sDefaultPageSize);
diff --git a/core/java/android/hardware/SensorAdditionalInfo.java b/core/java/android/hardware/SensorAdditionalInfo.java
index ea1d01b..0c6a415 100644
--- a/core/java/android/hardware/SensorAdditionalInfo.java
+++ b/core/java/android/hardware/SensorAdditionalInfo.java
@@ -189,6 +189,16 @@
*/
public static final int TYPE_MAGNETIC_FIELD_CALIBRATION = 0x30004;
+ /**
+ * Custom sensor info: array of float values interpreted by sensor based on the type
+ * Any type between TYPE_CUSTOM_INFO <= info_type < TYPE_DEBUG_INFO may be
+ * used to send custom sensor info.
+ * @hide
+ */
+ public static final int TYPE_CUSTOM_INFO = 0x10000000;
+ /** @hide */
+ public static final int TYPE_DEBUG_INFO = 0x40000000;
+
SensorAdditionalInfo(
Sensor aSensor, int aType, int aSerial, int [] aIntValues, float [] aFloatValues) {
sensor = aSensor;
@@ -211,4 +221,13 @@
null, TYPE_LOCAL_GEOMAGNETIC_FIELD, 0,
null, new float[] { strength, declination, inclination});
}
+ /** @hide */
+ public static SensorAdditionalInfo createCustomInfo(Sensor aSensor, int type, float [] data) {
+ if (type < TYPE_CUSTOM_INFO || type >= TYPE_DEBUG_INFO || aSensor == null) {
+ throw new IllegalArgumentException("invalid parameter(s): type: " + type +
+ "; sensor: " + aSensor);
+ }
+
+ return new SensorAdditionalInfo(aSensor, type, 0, null, data);
+ }
}
diff --git a/core/java/android/hardware/SensorDirectChannel.java b/core/java/android/hardware/SensorDirectChannel.java
index bd7f9cf..36607c9 100644
--- a/core/java/android/hardware/SensorDirectChannel.java
+++ b/core/java/android/hardware/SensorDirectChannel.java
@@ -212,7 +212,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mCloseGuard.warnIfOpen();
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
close();
} finally {
super.finalize();
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 0dab5d7..607788d 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -68,7 +68,7 @@
long nativeInstance, int channelHandle, int sensorHandle, int rate);
private static native int nativeSetOperationParameter(
- long nativeInstance, int type, float[] floatValues, int[] intValues);
+ long nativeInstance, int handle, int type, float[] floatValues, int[] intValues);
private static final Object sLock = new Object();
@GuardedBy("sLock")
@@ -956,7 +956,9 @@
}
protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) {
+ int handle = -1;
+ if (parameter.sensor != null) handle = parameter.sensor.getHandle();
return nativeSetOperationParameter(
- mNativeInstance, parameter.type, parameter.floatValues, parameter.intValues) == 0;
+ mNativeInstance, handle, parameter.type, parameter.floatValues, parameter.intValues) == 0;
}
}
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index dbe1394..00e047d 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -1112,6 +1112,8 @@
return ImageFormat.DEPTH_POINT_CLOUD;
case HAL_PIXEL_FORMAT_Y16:
return ImageFormat.DEPTH16;
+ case HAL_PIXEL_FORMAT_RAW16:
+ return ImageFormat.RAW_DEPTH;
case ImageFormat.JPEG:
throw new IllegalArgumentException(
"ImageFormat.JPEG is an unknown internal format");
@@ -1179,6 +1181,8 @@
return HAL_PIXEL_FORMAT_BLOB;
case ImageFormat.DEPTH16:
return HAL_PIXEL_FORMAT_Y16;
+ case ImageFormat.RAW_DEPTH:
+ return HAL_PIXEL_FORMAT_RAW16;
default:
return format;
}
@@ -1220,6 +1224,7 @@
return HAL_DATASPACE_V0_JFIF;
case ImageFormat.DEPTH_POINT_CLOUD:
case ImageFormat.DEPTH16:
+ case ImageFormat.RAW_DEPTH:
return HAL_DATASPACE_DEPTH;
default:
return HAL_DATASPACE_UNKNOWN;
@@ -1609,6 +1614,8 @@
return "DEPTH16";
case ImageFormat.DEPTH_POINT_CLOUD:
return "DEPTH_POINT_CLOUD";
+ case ImageFormat.RAW_DEPTH:
+ return "RAW_DEPTH";
case ImageFormat.PRIVATE:
return "PRIVATE";
default:
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 876125e..0b998e5 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -17,11 +17,10 @@
package android.hardware.display;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.hardware.display.DisplayManager.DisplayListener;
-import android.media.projection.MediaProjection;
import android.media.projection.IMediaProjection;
+import android.media.projection.MediaProjection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -31,8 +30,8 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
-import android.view.DisplayAdjustments;
import android.view.Display;
+import android.view.DisplayAdjustments;
import android.view.DisplayInfo;
import android.view.Surface;
diff --git a/core/java/android/hardware/display/DisplayViewport.java b/core/java/android/hardware/display/DisplayViewport.java
index bb41b45..b4955ea 100644
--- a/core/java/android/hardware/display/DisplayViewport.java
+++ b/core/java/android/hardware/display/DisplayViewport.java
@@ -67,6 +67,9 @@
uniqueId = viewport.uniqueId;
}
+ /**
+ * Creates a copy of this DisplayViewport.
+ */
public DisplayViewport makeCopy() {
DisplayViewport dv = new DisplayViewport();
dv.copyFrom(this);
@@ -84,29 +87,29 @@
}
DisplayViewport other = (DisplayViewport) o;
- return valid == other.valid &&
- displayId == other.displayId &&
- orientation == other.orientation &&
- logicalFrame.equals(other.logicalFrame) &&
- physicalFrame.equals(other.physicalFrame) &&
- deviceWidth == other.deviceWidth &&
- deviceHeight == other.deviceHeight &&
- TextUtils.equals(uniqueId, other.uniqueId);
+ return valid == other.valid
+ && displayId == other.displayId
+ && orientation == other.orientation
+ && logicalFrame.equals(other.logicalFrame)
+ && physicalFrame.equals(other.physicalFrame)
+ && deviceWidth == other.deviceWidth
+ && deviceHeight == other.deviceHeight
+ && TextUtils.equals(uniqueId, other.uniqueId);
}
@Override
public int hashCode() {
- final int prime = 31;
- int result = 1;
- result += prime * result + (valid ? 1 : 0);
- result += prime * result + displayId;
- result += prime * result + orientation;
- result += prime * result + logicalFrame.hashCode();
- result += prime * result + physicalFrame.hashCode();
- result += prime * result + deviceWidth;
- result += prime * result + deviceHeight;
- result += prime * result + uniqueId.hashCode();
- return result;
+ final int prime = 31;
+ int result = 1;
+ result += prime * result + (valid ? 1 : 0);
+ result += prime * result + displayId;
+ result += prime * result + orientation;
+ result += prime * result + logicalFrame.hashCode();
+ result += prime * result + physicalFrame.hashCode();
+ result += prime * result + deviceWidth;
+ result += prime * result + deviceHeight;
+ result += prime * result + uniqueId.hashCode();
+ return result;
}
// For debugging purposes.
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index bdb278b..4586316 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -34,6 +34,11 @@
InputDevice getInputDevice(int deviceId);
int[] getInputDeviceIds();
+ // Enable/disable input device.
+ boolean isInputDeviceEnabled(int deviceId);
+ void enableInputDevice(int deviceId);
+ void disableInputDevice(int deviceId);
+
// Reports whether the hardware supports the given keys; returns true if successful
boolean hasKeys(int deviceId, int sourceMask, in int[] keyCodes, out boolean[] keyExists);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 5149e93..4898c1a 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -16,8 +16,6 @@
package android.hardware.input;
-import com.android.internal.os.SomeArgs;
-
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
@@ -32,10 +30,10 @@
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.Vibrator;
-import android.os.VibrationEffect;
import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.Log;
@@ -47,6 +45,8 @@
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.internal.os.SomeArgs;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -319,6 +319,62 @@
}
/**
+ * Returns true if an input device is enabled. Should return true for most
+ * situations. Some system apps may disable an input device, for
+ * example to prevent unwanted touch events.
+ *
+ * @param id The input device Id.
+ *
+ * @hide
+ */
+ public boolean isInputDeviceEnabled(int id) {
+ try {
+ return mIm.isInputDeviceEnabled(id);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not check enabled status of input device with id = " + id);
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Enables an InputDevice.
+ * <p>
+ * Requires {@link android.Manifest.permissions.DISABLE_INPUT_DEVICE}.
+ * </p>
+ *
+ * @param id The input device Id.
+ *
+ * @hide
+ */
+ public void enableInputDevice(int id) {
+ try {
+ mIm.enableInputDevice(id);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not enable input device with id = " + id);
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Disables an InputDevice.
+ * <p>
+ * Requires {@link android.Manifest.permissions.DISABLE_INPUT_DEVICE}.
+ * </p>
+ *
+ * @param id The input device Id.
+ *
+ * @hide
+ */
+ public void disableInputDevice(int id) {
+ try {
+ mIm.disableInputDevice(id);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not disable input device with id = " + id);
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Registers an input device listener to receive notifications about when
* input devices are added, removed or changed.
*
diff --git a/core/java/android/hardware/radio/IRadioService.aidl b/core/java/android/hardware/radio/IRadioService.aidl
new file mode 100644
index 0000000..9a157d1
--- /dev/null
+++ b/core/java/android/hardware/radio/IRadioService.aidl
@@ -0,0 +1,31 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.RadioManager;
+
+/**
+ * API to the broadcast radio service.
+ *
+ * {@hide}
+ */
+interface IRadioService {
+ ITuner openTuner(int moduleId, in RadioManager.BandConfig bandConfig, boolean withAudio,
+ in ITunerCallback callback);
+}
diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl
new file mode 100644
index 0000000..1fc71e0
--- /dev/null
+++ b/core/java/android/hardware/radio/ITuner.aidl
@@ -0,0 +1,89 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.hardware.radio.RadioManager;
+
+/** {@hide} */
+interface ITuner {
+ void close();
+
+ /**
+ * @throws IllegalArgumentException if config is not valid or null
+ */
+ void setConfiguration(in RadioManager.BandConfig config);
+
+ RadioManager.BandConfig getConfiguration();
+
+ /**
+ * @throws IllegalStateException if tuner was opened without audio
+ */
+ void setMuted(boolean mute);
+
+ boolean isMuted();
+
+ /**
+ * @throws IllegalStateException if called out of sequence
+ */
+ void step(boolean directionDown, boolean skipSubChannel);
+
+ /**
+ * @throws IllegalStateException if called out of sequence
+ */
+ void scan(boolean directionDown, boolean skipSubChannel);
+
+ /**
+ * @throws IllegalArgumentException if invalid arguments are passed
+ * @throws IllegalStateException if called out of sequence
+ */
+ void tune(int channel, int subChannel);
+
+ /**
+ * @throws IllegalStateException if called out of sequence
+ */
+ void cancel();
+
+ RadioManager.ProgramInfo getProgramInformation();
+
+ /**
+ * @returns {@code true} if the scan was properly scheduled,
+ * {@code false} if the scan feature is unavailable
+ */
+ boolean startBackgroundScan();
+
+ /**
+ * @returns the list, or null if scan is in progress
+ * @throws IllegalArgumentException if invalid arguments are passed
+ * @throws IllegalStateException if the scan has not been started, client may
+ * call startBackgroundScan to fix this.
+ */
+ List<RadioManager.ProgramInfo> getProgramList(String filter);
+
+ /**
+ * @throws IllegalStateException if the switch is not supported at current
+ * configuration.
+ */
+ boolean isAnalogForced();
+
+ /**
+ * @throws IllegalStateException if the switch is not supported at current
+ * configuration.
+ */
+ void setAnalogForced(boolean isForced);
+
+ boolean isAntennaConnected();
+}
diff --git a/core/java/android/hardware/radio/ITunerCallback.aidl b/core/java/android/hardware/radio/ITunerCallback.aidl
new file mode 100644
index 0000000..b32c683
--- /dev/null
+++ b/core/java/android/hardware/radio/ITunerCallback.aidl
@@ -0,0 +1,26 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.hardware.radio.RadioManager;
+
+/** {@hide} */
+oneway interface ITunerCallback {
+ void onError(int status);
+ void onConfigurationChanged(in RadioManager.BandConfig config);
+ void onProgramInfoChanged(in RadioManager.ProgramInfo info);
+}
diff --git a/core/java/android/hardware/radio/RadioManager.aidl b/core/java/android/hardware/radio/RadioManager.aidl
new file mode 100644
index 0000000..0f0ec84
--- /dev/null
+++ b/core/java/android/hardware/radio/RadioManager.aidl
@@ -0,0 +1,23 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+/** @hide */
+parcelable RadioManager.ProgramInfo;
+
+/** @hide */
+parcelable RadioManager.BandConfig;
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 403b13c..20292f7 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -16,12 +16,21 @@
package android.hardware.radio;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Log;
+
import java.util.List;
import java.util.Arrays;
@@ -35,6 +44,7 @@
@SystemApi
@SystemService(Context.RADIO_SERVICE)
public class RadioManager {
+ private static final String TAG = "RadioManager";
/** Method return status: successful operation */
public static final int STATUS_OK = 0;
@@ -110,10 +120,12 @@
private final int mNumAudioSources;
private final boolean mIsCaptureSupported;
private final BandDescriptor[] mBands;
+ private final boolean mIsBgScanSupported;
+ private final String mVendorExension;
ModuleProperties(int id, int classId, String implementor, String product, String version,
String serial, int numTuners, int numAudioSources, boolean isCaptureSupported,
- BandDescriptor[] bands) {
+ BandDescriptor[] bands, boolean isBgScanSupported, String vendorExension) {
mId = id;
mClassId = classId;
mImplementor = implementor;
@@ -124,6 +136,8 @@
mNumAudioSources = numAudioSources;
mIsCaptureSupported = isCaptureSupported;
mBands = bands;
+ mIsBgScanSupported = isBgScanSupported;
+ mVendorExension = vendorExension;
}
@@ -198,6 +212,35 @@
return mIsCaptureSupported;
}
+ /**
+ * {@code true} if the module supports background scanning. At the given time it may not
+ * be available though, see {@link RadioTuner#startBackgroundScan()}.
+ *
+ * @return {@code true} if background scanning is supported (not necessary available
+ * at a given time), {@code false} otherwise.
+ *
+ * @hide FutureFeature
+ */
+ public boolean isBackgroundScanningSupported() {
+ return mIsBgScanSupported;
+ }
+
+ /**
+ * Opaque vendor-specific string, passed from HAL without changes.
+ * Format of this string can vary across vendors.
+ *
+ * It may be used for extra features, that's not supported by a platform,
+ * for example: "preset-slots=6;ultra-hd-capable=false".
+ *
+ * Client application MUST verify vendor/product name from the
+ * ModuleProperties class before doing any interpretation of this value.
+ *
+ * @hide FutureFeature
+ */
+ public @NonNull String getVendorExension() {
+ return mVendorExension == null ? "" : mVendorExension;
+ }
+
/** List of descriptors for all bands supported by this module.
* @return an array of {@link BandDescriptor}.
*/
@@ -220,6 +263,8 @@
for (int i = 0; i < tmp.length; i++) {
mBands[i] = (BandDescriptor) tmp[i];
}
+ mIsBgScanSupported = in.readInt() == 1;
+ mVendorExension = in.readString();
}
public static final Parcelable.Creator<ModuleProperties> CREATOR
@@ -245,6 +290,8 @@
dest.writeInt(mNumAudioSources);
dest.writeInt(mIsCaptureSupported ? 1 : 0);
dest.writeParcelableArray(mBands, flags);
+ dest.writeInt(mIsBgScanSupported ? 1 : 0);
+ dest.writeString(mVendorExension);
}
@Override
@@ -260,6 +307,7 @@
+ ", mNumTuners=" + mNumTuners
+ ", mNumAudioSources=" + mNumAudioSources
+ ", mIsCaptureSupported=" + mIsCaptureSupported
+ + ", mIsBgScanSupported=" + mIsBgScanSupported
+ ", mBands=" + Arrays.toString(mBands) + "]";
}
@@ -277,6 +325,8 @@
result = prime * result + mNumAudioSources;
result = prime * result + (mIsCaptureSupported ? 1 : 0);
result = prime * result + Arrays.hashCode(mBands);
+ result = prime * result + (mIsBgScanSupported ? 1 : 0);
+ result = prime * result + ((mVendorExension == null) ? 0 : mVendorExension.hashCode());
return result;
}
@@ -319,6 +369,10 @@
return false;
if (!Arrays.equals(mBands, other.getBands()))
return false;
+ if (mIsBgScanSupported != other.isBackgroundScanningSupported())
+ return false;
+ if (!TextUtils.equals(mVendorExension, other.mVendorExension))
+ return false;
return true;
}
}
@@ -334,6 +388,9 @@
private final int mSpacing;
BandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing) {
+ if (type != BAND_AM && type != BAND_FM && type != BAND_FM_HD && type != BAND_AM_HD) {
+ throw new IllegalArgumentException("Unsupported band: " + type);
+ }
mRegion = region;
mType = type;
mLowerLimit = lowerLimit;
@@ -387,10 +444,28 @@
mSpacing = in.readInt();
}
+ private static int lookupTypeFromParcel(Parcel in) {
+ int pos = in.dataPosition();
+ in.readInt(); // skip region
+ int type = in.readInt();
+ in.setDataPosition(pos);
+ return type;
+ }
+
public static final Parcelable.Creator<BandDescriptor> CREATOR
= new Parcelable.Creator<BandDescriptor>() {
public BandDescriptor createFromParcel(Parcel in) {
- return new BandDescriptor(in);
+ int type = lookupTypeFromParcel(in);
+ switch (type) {
+ case BAND_FM:
+ case BAND_FM_HD:
+ return new FmBandDescriptor(in);
+ case BAND_AM:
+ case BAND_AM_HD:
+ return new AmBandDescriptor(in);
+ default:
+ throw new IllegalArgumentException("Unsupported band: " + type);
+ }
}
public BandDescriptor[] newArray(int size) {
@@ -719,7 +794,17 @@
public static final Parcelable.Creator<BandConfig> CREATOR
= new Parcelable.Creator<BandConfig>() {
public BandConfig createFromParcel(Parcel in) {
- return new BandConfig(in);
+ int type = BandDescriptor.lookupTypeFromParcel(in);
+ switch (type) {
+ case BAND_FM:
+ case BAND_FM_HD:
+ return new FmBandConfig(in);
+ case BAND_AM:
+ case BAND_AM_HD:
+ return new AmBandConfig(in);
+ default:
+ throw new IllegalArgumentException("Unsupported band: " + type);
+ }
}
public BandConfig[] newArray(int size) {
@@ -757,8 +842,9 @@
if (!(obj instanceof BandConfig))
return false;
BandConfig other = (BandConfig) obj;
- if (mDescriptor != other.getDescriptor())
- return false;
+ BandDescriptor otherDesc = other.getDescriptor();
+ if ((mDescriptor == null) != (otherDesc == null)) return false;
+ if (mDescriptor != null && !mDescriptor.equals(otherDesc)) return false;
return true;
}
}
@@ -1138,23 +1224,32 @@
* {@link RadioTuner#getProgramInformation(RadioManager.ProgramInfo[])} */
public static class ProgramInfo implements Parcelable {
+ // sourced from hardware/interfaces/broadcastradio/1.1/types.hal
+ private final static int FLAG_LIVE = 1 << 0;
+ private final static int FLAG_MUTED = 1 << 1;
+
private final int mChannel;
private final int mSubChannel;
private final boolean mTuned;
private final boolean mStereo;
private final boolean mDigital;
+ private final int mFlags;
private final int mSignalStrength;
private final RadioMetadata mMetadata;
+ private final String mVendorExension;
ProgramInfo(int channel, int subChannel, boolean tuned, boolean stereo,
- boolean digital, int signalStrength, RadioMetadata metadata) {
+ boolean digital, int signalStrength, RadioMetadata metadata, int flags,
+ String vendorExension) {
mChannel = channel;
mSubChannel = subChannel;
mTuned = tuned;
mStereo = stereo;
mDigital = digital;
+ mFlags = flags;
mSignalStrength = signalStrength;
mMetadata = metadata;
+ mVendorExension = vendorExension;
}
/** Main channel expressed in units according to band type.
@@ -1188,6 +1283,30 @@
public boolean isDigital() {
return mDigital;
}
+
+ /**
+ * {@code true} if the program is currently playing live stream.
+ * This may result in a slightly altered reception parameters,
+ * usually targetted at reduced latency.
+ *
+ * @hide FutureFeature
+ */
+ public boolean isLive() {
+ return (mFlags & FLAG_LIVE) != 0;
+ }
+
+ /**
+ * {@code true} if radio stream is not playing, ie. due to bad reception
+ * conditions or buffering. In this state volume knob MAY be disabled to
+ * prevent user increasing volume too much.
+ * It does NOT mean the user has muted audio.
+ *
+ * @hide FutureFeature
+ */
+ public boolean isMuted() {
+ return (mFlags & FLAG_MUTED) != 0;
+ }
+
/** Signal strength indicator from 0 (no signal) to 100 (excellent)
* @return the signal strength indication.
*/
@@ -1202,6 +1321,22 @@
return mMetadata;
}
+ /**
+ * Opaque vendor-specific string, passed from HAL without changes.
+ * Format of this string can vary across vendors.
+ *
+ * It may be used for extra features, that's not supported by a platform,
+ * for example: "paid-service=true;bitrate=320kbps".
+ *
+ * Client application MUST verify vendor/product name from the
+ * ModuleProperties class before doing any interpretation of this value.
+ *
+ * @hide FutureFeature
+ */
+ public @NonNull String getVendorExension() {
+ return mVendorExension == null ? "" : mVendorExension;
+ }
+
private ProgramInfo(Parcel in) {
mChannel = in.readInt();
mSubChannel = in.readInt();
@@ -1214,6 +1349,8 @@
} else {
mMetadata = null;
}
+ mFlags = in.readInt();
+ mVendorExension = in.readString();
}
public static final Parcelable.Creator<ProgramInfo> CREATOR
@@ -1241,6 +1378,8 @@
dest.writeByte((byte)1);
mMetadata.writeToParcel(dest, flags);
}
+ dest.writeInt(mFlags);
+ dest.writeString(mVendorExension);
}
@Override
@@ -1252,7 +1391,7 @@
public String toString() {
return "ProgramInfo [mChannel=" + mChannel + ", mSubChannel=" + mSubChannel
+ ", mTuned=" + mTuned + ", mStereo=" + mStereo + ", mDigital=" + mDigital
- + ", mSignalStrength=" + mSignalStrength
+ + ", mFlags=" + mFlags + ", mSignalStrength=" + mSignalStrength
+ ((mMetadata == null) ? "" : (", mMetadata=" + mMetadata.toString()))
+ "]";
}
@@ -1266,8 +1405,10 @@
result = prime * result + (mTuned ? 1 : 0);
result = prime * result + (mStereo ? 1 : 0);
result = prime * result + (mDigital ? 1 : 0);
+ result = prime * result + mFlags;
result = prime * result + mSignalStrength;
result = prime * result + ((mMetadata == null) ? 0 : mMetadata.hashCode());
+ result = prime * result + ((mVendorExension == null) ? 0 : mVendorExension.hashCode());
return result;
}
@@ -1288,6 +1429,8 @@
return false;
if (mDigital != other.isDigital())
return false;
+ if (mFlags != other.mFlags)
+ return false;
if (mSignalStrength != other.getSignalStrength())
return false;
if (mMetadata == null) {
@@ -1295,6 +1438,8 @@
return false;
} else if (!mMetadata.equals(other.getMetadata()))
return false;
+ if (!TextUtils.equals(mVendorExension, other.mVendorExension))
+ return false;
return true;
}
}
@@ -1331,23 +1476,47 @@
public RadioTuner openTuner(int moduleId, BandConfig config, boolean withAudio,
RadioTuner.Callback callback, Handler handler) {
if (callback == null) {
- return null;
+ throw new IllegalArgumentException("callback must not be empty");
}
- RadioModule module = new RadioModule(moduleId, config, withAudio, callback, handler);
- if (module != null) {
- if (!module.initCheck()) {
- module = null;
+
+ if (mService != null) {
+ Log.d(TAG, "Opening tuner...");
+
+ ITuner tuner;
+ ITunerCallback halCallback = new TunerCallbackAdapter(callback, handler);
+ try {
+ tuner = mService.openTuner(moduleId, config, withAudio, halCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
+ if (tuner == null) {
+ Log.e(TAG, "Failed to open tuner");
+ return null;
+ }
+ return new TunerAdapter(tuner);
}
+
+ RadioModule module = new RadioModule(moduleId, config, withAudio, callback, handler);
+ if (!module.initCheck()) {
+ Log.e(TAG, "Failed to open tuner");
+ module = null;
+ }
+
return (RadioTuner)module;
}
- private final Context mContext;
+ @NonNull private final Context mContext;
+ // TODO(b/36863239): NonNull when transitioned from native service
+ @Nullable private final IRadioService mService;
/**
* @hide
*/
- public RadioManager(Context context) {
+ public RadioManager(@NonNull Context context) throws ServiceNotFoundException {
mContext = context;
+
+ boolean isServiceJava = SystemProperties.getBoolean("config.enable_java_radio", false);
+ mService = isServiceJava ? IRadioService.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.RADIO_SERVICE)) : null;
}
}
diff --git a/core/java/android/hardware/radio/RadioModule.java b/core/java/android/hardware/radio/RadioModule.java
index 8964893..c0df0f3 100644
--- a/core/java/android/hardware/radio/RadioModule.java
+++ b/core/java/android/hardware/radio/RadioModule.java
@@ -79,8 +79,14 @@
public native int getProgramInformation(RadioManager.ProgramInfo[] info);
+ public native boolean startBackgroundScan();
+
public native @NonNull List<RadioManager.ProgramInfo> getProgramList(@Nullable String filter);
+ public native boolean isAnalogForced();
+
+ public native void setAnalogForced(boolean isForced);
+
public native boolean isAntennaConnected();
public native boolean hasControl();
diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java
index c8034eb..6e1232d 100644
--- a/core/java/android/hardware/radio/RadioTuner.java
+++ b/core/java/android/hardware/radio/RadioTuner.java
@@ -212,6 +212,23 @@
public abstract int getProgramInformation(RadioManager.ProgramInfo[] info);
/**
+ * Initiates a background scan to update internally cached program list.
+ *
+ * It may not be necessary to initiate the scan explicitly - the scan MAY be performed on boot.
+ *
+ * The operation is asynchronous and {@link Callback} backgroundScanComplete or onError will
+ * be called if the return value of this call was {@code true}. As result of this call
+ * programListChanged may be triggered (if the scanned list differs).
+ *
+ * @return {@code true} if the scan was properly scheduled, {@code false} if the scan feature
+ * is unavailable; ie. temporarily due to ongoing foreground playback in single-tuner device
+ * or permanently if the feature is not supported
+ * (see ModuleProperties#isBackgroundScanningSupported()).
+ * @hide FutureFeature
+ */
+ public abstract boolean startBackgroundScan();
+
+ /**
* Get the list of discovered radio stations.
*
* To get the full list, set filter to null or empty string. Otherwise, client application
@@ -219,13 +236,40 @@
*
* @param filter vendor-specific selector for radio stations.
* @return a list of radio stations.
- * @throws IllegalStateException if the scan is in progress or has not been started.
+ * @throws IllegalStateException if the scan is in progress or has not been started,
+ * startBackgroundScan() call may fix it.
* @throws IllegalArgumentException if the filter argument is not valid.
* @hide FutureFeature
*/
public abstract @NonNull List<RadioManager.ProgramInfo> getProgramList(@Nullable String filter);
/**
+ * Checks, if the analog playback is forced, see setAnalogForced.
+ *
+ * @throws IllegalStateException if the switch is not supported at current
+ * configuration.
+ * @return {@code true} if analog is forced, {@code false} otherwise.
+ * @hide FutureFeature
+ */
+ public abstract boolean isAnalogForced();
+
+ /**
+ * Forces the analog playback for the supporting radio technology.
+ *
+ * User may disable digital playback for FM HD Radio or hybrid FM/DAB with
+ * this option. This is purely user choice, ie. does not reflect digital-
+ * analog handover managed from the HAL implementation side.
+ *
+ * Some radio technologies may not support this, ie. DAB.
+ *
+ * @param isForced {@code true} to force analog, {@code false} for a default behaviour.
+ * @throws IllegalStateException if the switch is not supported at current
+ * configuration.
+ * @hide FutureFeature
+ */
+ public abstract void setAnalogForced(boolean isForced);
+
+ /**
* Get current antenna connection state for current configuration.
* Only valid if a configuration has been applied.
* @return {@code true} if the antenna is connected, {@code false} otherwise.
@@ -317,6 +361,32 @@
* with control set to {@code true}.
*/
public void onControlChanged(boolean control) {}
+
+ /**
+ * onBackgroundScanAvailabilityChange() is called when background scan
+ * feature becomes available or not.
+ *
+ * @param isAvailable true, if the tuner turned temporarily background-
+ * capable, false in the other case.
+ * @hide FutureFeature
+ */
+ public void onBackgroundScanAvailabilityChange(boolean isAvailable) {}
+
+ /**
+ * Called when a background scan completes successfully.
+ *
+ * @hide FutureFeature
+ */
+ public void onBackgroundScanComplete() {}
+
+ /**
+ * Called when available program list changed.
+ *
+ * Use getProgramList() to get the actual list.
+ *
+ * @hide FutureFeature
+ */
+ public void onProgramListChanged() {}
}
}
diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java
new file mode 100644
index 0000000..a457494
--- /dev/null
+++ b/core/java/android/hardware/radio/TunerAdapter.java
@@ -0,0 +1,233 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Implements the RadioTuner interface by forwarding calls to radio service.
+ */
+class TunerAdapter extends RadioTuner {
+ private static final String TAG = "radio.TunerAdapter";
+
+ @NonNull private final ITuner mTuner;
+ private boolean mIsClosed = false;
+
+ TunerAdapter(ITuner tuner) {
+ if (tuner == null) {
+ throw new NullPointerException();
+ }
+ mTuner = tuner;
+ }
+
+ @Override
+ public void close() {
+ synchronized (mTuner) {
+ if (mIsClosed) {
+ Log.d(TAG, "Tuner is already closed");
+ return;
+ }
+ mIsClosed = true;
+ }
+ try {
+ mTuner.close();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception trying to close tuner", e);
+ }
+ }
+
+ @Override
+ public int setConfiguration(RadioManager.BandConfig config) {
+ try {
+ mTuner.setConfiguration(config);
+ return RadioManager.STATUS_OK;
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Can't set configuration", e);
+ return RadioManager.STATUS_BAD_VALUE;
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return RadioManager.STATUS_DEAD_OBJECT;
+ }
+ }
+
+ @Override
+ public int getConfiguration(RadioManager.BandConfig[] config) {
+ if (config == null || config.length != 1) {
+ throw new IllegalArgumentException("The argument must be an array of length 1");
+ }
+ try {
+ config[0] = mTuner.getConfiguration();
+ return RadioManager.STATUS_OK;
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return RadioManager.STATUS_DEAD_OBJECT;
+ }
+ }
+
+ @Override
+ public int setMute(boolean mute) {
+ try {
+ mTuner.setMuted(mute);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Can't set muted", e);
+ return RadioManager.STATUS_ERROR;
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return RadioManager.STATUS_DEAD_OBJECT;
+ }
+ return RadioManager.STATUS_OK;
+ }
+
+ @Override
+ public boolean getMute() {
+ try {
+ return mTuner.isMuted();
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return true;
+ }
+ }
+
+ @Override
+ public int step(int direction, boolean skipSubChannel) {
+ try {
+ mTuner.step(direction == RadioTuner.DIRECTION_DOWN, skipSubChannel);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Can't step", e);
+ return RadioManager.STATUS_INVALID_OPERATION;
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return RadioManager.STATUS_DEAD_OBJECT;
+ }
+ return RadioManager.STATUS_OK;
+ }
+
+ @Override
+ public int scan(int direction, boolean skipSubChannel) {
+ try {
+ mTuner.scan(direction == RadioTuner.DIRECTION_DOWN, skipSubChannel);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Can't scan", e);
+ return RadioManager.STATUS_INVALID_OPERATION;
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return RadioManager.STATUS_DEAD_OBJECT;
+ }
+ return RadioManager.STATUS_OK;
+ }
+
+ @Override
+ public int tune(int channel, int subChannel) {
+ try {
+ mTuner.tune(channel, subChannel);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Can't tune", e);
+ return RadioManager.STATUS_INVALID_OPERATION;
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Can't tune", e);
+ return RadioManager.STATUS_BAD_VALUE;
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return RadioManager.STATUS_DEAD_OBJECT;
+ }
+ return RadioManager.STATUS_OK;
+ }
+
+ @Override
+ public int cancel() {
+ try {
+ mTuner.cancel();
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Can't cancel", e);
+ return RadioManager.STATUS_INVALID_OPERATION;
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return RadioManager.STATUS_DEAD_OBJECT;
+ }
+ return RadioManager.STATUS_OK;
+ }
+
+ @Override
+ public int getProgramInformation(RadioManager.ProgramInfo[] info) {
+ if (info == null || info.length != 1) {
+ throw new IllegalArgumentException("The argument must be an array of length 1");
+ }
+ try {
+ info[0] = mTuner.getProgramInformation();
+ return RadioManager.STATUS_OK;
+ } catch (RemoteException e) {
+ Log.e(TAG, "service died", e);
+ return RadioManager.STATUS_DEAD_OBJECT;
+ }
+ }
+
+ @Override
+ public boolean startBackgroundScan() {
+ try {
+ return mTuner.startBackgroundScan();
+ } catch (RemoteException e) {
+ throw new RuntimeException("service died", e);
+ }
+ }
+
+ @Override
+ public @NonNull List<RadioManager.ProgramInfo> getProgramList(@Nullable String filter) {
+ try {
+ return mTuner.getProgramList(filter);
+ } catch (RemoteException e) {
+ throw new RuntimeException("service died", e);
+ }
+ }
+
+ @Override
+ public boolean isAnalogForced() {
+ try {
+ return mTuner.isAnalogForced();
+ } catch (RemoteException e) {
+ throw new RuntimeException("service died", e);
+ }
+ }
+
+ @Override
+ public void setAnalogForced(boolean isForced) {
+ try {
+ mTuner.setAnalogForced(isForced);
+ } catch (RemoteException e) {
+ throw new RuntimeException("service died", e);
+ }
+ }
+
+ @Override
+ public boolean isAntennaConnected() {
+ try {
+ return mTuner.isAntennaConnected();
+ } catch (RemoteException e) {
+ throw new RuntimeException("service died", e);
+ }
+ }
+
+ @Override
+ public boolean hasControl() {
+ // TODO(b/36863239): forward to mTuner
+ throw new RuntimeException("Not implemented");
+ }
+}
diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java
new file mode 100644
index 0000000..ba85017
--- /dev/null
+++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+
+/**
+ * Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback.
+ */
+class TunerCallbackAdapter extends ITunerCallback.Stub {
+ @NonNull private final RadioTuner.Callback mCallback;
+ @NonNull private final Handler mHandler;
+
+ TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) {
+ mCallback = callback;
+ if (handler == null) {
+ mHandler = new Handler(Looper.getMainLooper());
+ } else {
+ mHandler = handler;
+ }
+ }
+
+ @Override
+ public void onError(int status) {
+ mHandler.post(() -> mCallback.onError(status));
+ }
+
+ @Override
+ public void onConfigurationChanged(RadioManager.BandConfig config) {
+ mHandler.post(() -> mCallback.onConfigurationChanged(config));
+ }
+
+ @Override
+ public void onProgramInfoChanged(RadioManager.ProgramInfo info) {
+ mHandler.post(() -> mCallback.onProgramInfoChanged(info));
+ }
+}
diff --git a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
index ad20ce5..160376b 100644
--- a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
+++ b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
@@ -341,7 +341,8 @@
}
}
}
- Slog.w(TAG, "No Enrollment application supports the given keyphrase/locale");
+ Slog.w(TAG, "No enrollment application supports the given keyphrase/locale: '"
+ + keyphrase + "'/" + locale);
return null;
}
diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java
index 7a0272d..9b5d0d3 100644
--- a/core/java/android/hardware/usb/UsbDeviceConnection.java
+++ b/core/java/android/hardware/usb/UsbDeviceConnection.java
@@ -349,7 +349,9 @@
@Override
protected void finalize() throws Throwable {
try {
- mCloseGuard.warnIfOpen();
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
} finally {
super.finalize();
}
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 33a92fd..0045308 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -316,10 +316,13 @@
* @return HashMap containing all connected USB devices.
*/
public HashMap<String,UsbDevice> getDeviceList() {
+ HashMap<String,UsbDevice> result = new HashMap<String,UsbDevice>();
+ if (mService == null) {
+ return result;
+ }
Bundle bundle = new Bundle();
try {
mService.getDeviceList(bundle);
- HashMap<String,UsbDevice> result = new HashMap<String,UsbDevice>();
for (String name : bundle.keySet()) {
result.put(name, (UsbDevice)bundle.get(name));
}
@@ -361,6 +364,9 @@
* @return list of USB accessories, or null if none are attached.
*/
public UsbAccessory[] getAccessoryList() {
+ if (mService == null) {
+ return null;
+ }
try {
UsbAccessory accessory = mService.getCurrentAccessory();
if (accessory == null) {
@@ -397,6 +403,9 @@
* @return true if caller has permission
*/
public boolean hasPermission(UsbDevice device) {
+ if (mService == null) {
+ return false;
+ }
try {
return mService.hasDevicePermission(device);
} catch (RemoteException e) {
@@ -414,6 +423,9 @@
* @return true if caller has permission
*/
public boolean hasPermission(UsbAccessory accessory) {
+ if (mService == null) {
+ return false;
+ }
try {
return mService.hasAccessoryPermission(accessory);
} catch (RemoteException e) {
@@ -531,6 +543,9 @@
* {@hide}
*/
public boolean isFunctionEnabled(String function) {
+ if (mService == null) {
+ return false;
+ }
try {
return mService.isFunctionEnabled(function);
} catch (RemoteException e) {
@@ -588,6 +603,9 @@
* @hide
*/
public UsbPort[] getPorts() {
+ if (mService == null) {
+ return null;
+ }
try {
return mService.getPorts();
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index fea730e..afdb202 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -65,6 +65,18 @@
public static final int MODE_DUAL = Constants.PortMode.DRP;
/**
+ * Mode bit: This USB port can support USB Type-C Audio accessory.
+ */
+ public static final int MODE_AUDIO_ACCESSORY =
+ android.hardware.usb.V1_1.Constants.PortMode_1_1.AUDIO_ACCESSORY;
+
+ /**
+ * Mode bit: This USB port can support USB Type-C debug accessory.
+ */
+ public static final int MODE_DEBUG_ACCESSORY =
+ android.hardware.usb.V1_1.Constants.PortMode_1_1.DEBUG_ACCESSORY;
+
+ /**
* Power role: This USB port does not have a power role.
*/
public static final int POWER_ROLE_NONE = Constants.PortPowerRole.NONE;
@@ -135,9 +147,9 @@
* in a bit-field.
*
* @param powerRole The desired power role: {@link UsbPort#POWER_ROLE_SOURCE}
- * or {@link UsbPort#POWER_ROLE_SINK}, or 0 if no power role.
- * @param dataRole The desired data role: {@link UsbPort#DATA_ROLE_HOST}
- * or {@link UsbPort#DATA_ROLE_DEVICE}, or 0 if no data role.
+ * or {@link UsbPort#POWER_ROLE_SINK}, or 0 if no power role.
+ * @param dataRole The desired data role: {@link UsbPort#DATA_ROLE_HOST}
+ * or {@link UsbPort#DATA_ROLE_DEVICE}, or 0 if no data role.
* @hide
*/
public static int combineRolesAsBit(int powerRole, int dataRole) {
@@ -148,18 +160,31 @@
/** @hide */
public static String modeToString(int mode) {
- switch (mode) {
- case MODE_NONE:
- return "none";
- case MODE_DFP:
- return "dfp";
- case MODE_UFP:
- return "ufp";
- case MODE_DUAL:
- return "dual";
- default:
- return Integer.toString(mode);
+ StringBuilder modeString = new StringBuilder();
+ if (mode == MODE_NONE) {
+ return "none";
}
+
+ if ((mode & MODE_DUAL) == MODE_DUAL) {
+ modeString.append("dual, ");
+ } else {
+ if ((mode & MODE_DFP) == MODE_DFP) {
+ modeString.append("dfp, ");
+ } else if ((mode & MODE_UFP) == MODE_UFP) {
+ modeString.append("ufp, ");
+ }
+ }
+ if ((mode & MODE_AUDIO_ACCESSORY) == MODE_AUDIO_ACCESSORY) {
+ modeString.append("audio_acc, ");
+ }
+ if ((mode & MODE_DEBUG_ACCESSORY) == MODE_DEBUG_ACCESSORY) {
+ modeString.append("debug_acc, ");
+ }
+
+ if (modeString.length() == 0) {
+ return Integer.toString(mode);
+ }
+ return modeString.substring(0, modeString.length() - 2);
}
/** @hide */
@@ -240,6 +265,13 @@
Preconditions.checkArgumentInRange(dataRole, DATA_ROLE_NONE, DATA_ROLE_DEVICE, "dataRole");
}
+ /** @hide */
+ public boolean isModeSupported(int mode) {
+ if ((mSupportedModes & mode) == mode) return true;
+ return false;
+ }
+
+
@Override
public String toString() {
return "UsbPort{id=" + mId + ", supportedModes=" + modeToString(mSupportedModes) + "}";
@@ -258,16 +290,16 @@
public static final Parcelable.Creator<UsbPort> CREATOR =
new Parcelable.Creator<UsbPort>() {
- @Override
- public UsbPort createFromParcel(Parcel in) {
- String id = in.readString();
- int supportedModes = in.readInt();
- return new UsbPort(id, supportedModes);
- }
+ @Override
+ public UsbPort createFromParcel(Parcel in) {
+ String id = in.readString();
+ int supportedModes = in.readInt();
+ return new UsbPort(id, supportedModes);
+ }
- @Override
- public UsbPort[] newArray(int size) {
- return new UsbPort[size];
- }
- };
+ @Override
+ public UsbPort[] newArray(int size) {
+ return new UsbPort[size];
+ }
+ };
}
diff --git a/core/java/android/hardware/usb/UsbRequest.java b/core/java/android/hardware/usb/UsbRequest.java
index 90990b7..2e8f8e1 100644
--- a/core/java/android/hardware/usb/UsbRequest.java
+++ b/core/java/android/hardware/usb/UsbRequest.java
@@ -118,7 +118,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mCloseGuard.warnIfOpen();
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
close();
} finally {
super.finalize();
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index e64cbcd..fcb99b1 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -15,8 +15,6 @@
*/
package android.net;
-import static com.android.internal.util.Preconditions.checkNotNull;
-
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -43,25 +41,27 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
import android.util.ArrayMap;
import android.util.Log;
-import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.Protocol;
-import com.android.internal.util.MessageUtils;
import libcore.net.event.NetworkEventDispatcher;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.InetAddress;
+import java.util.ArrayList;
import java.util.HashMap;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.List;
+import java.util.Map;
/**
* Class that answers queries about the state of network connectivity. It also
@@ -1465,9 +1465,7 @@
// Map from type to transports.
final int NOT_FOUND = -1;
final int transport = sLegacyTypeToTransport.get(type, NOT_FOUND);
- if (transport == NOT_FOUND) {
- throw new IllegalArgumentException("unknown legacy type: " + type);
- }
+ Preconditions.checkArgument(transport != NOT_FOUND, "unknown legacy type: " + type);
nc.addTransportType(transport);
// Map from type to capabilities.
@@ -1555,8 +1553,8 @@
}
private PacketKeepalive(Network network, PacketKeepaliveCallback callback) {
- checkNotNull(network, "network cannot be null");
- checkNotNull(callback, "callback cannot be null");
+ Preconditions.checkNotNull(network, "network cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
mNetwork = network;
mCallback = callback;
HandlerThread thread = new HandlerThread(TAG);
@@ -1815,9 +1813,7 @@
*/
public void removeDefaultNetworkActiveListener(OnNetworkActiveListener l) {
INetworkActivityListener rl = mNetworkActivityListeners.get(l);
- if (rl == null) {
- throw new IllegalArgumentException("Listener not registered: " + l);
- }
+ Preconditions.checkArgument(rl != null, "Listener was not registered.");
try {
getNetworkManagementService().unregisterNetworkActivityListener(rl);
} catch (RemoteException e) {
@@ -1845,8 +1841,8 @@
* {@hide}
*/
public ConnectivityManager(Context context, IConnectivityManager service) {
- mContext = checkNotNull(context, "missing context");
- mService = checkNotNull(service, "missing IConnectivityManager");
+ mContext = Preconditions.checkNotNull(context, "missing context");
+ mService = Preconditions.checkNotNull(service, "missing IConnectivityManager");
sInstance = this;
}
@@ -1872,8 +1868,11 @@
.getPackageNameForUid(context, uid), true /* throwException */);
}
- /** {@hide */
- public static final void enforceTetherChangePermission(Context context) {
+ /** {@hide} */
+ public static final void enforceTetherChangePermission(Context context, String callingPkg) {
+ Preconditions.checkNotNull(context, "Context cannot be null");
+ Preconditions.checkNotNull(callingPkg, "callingPkg cannot be null");
+
if (context.getResources().getStringArray(
com.android.internal.R.array.config_mobile_hotspot_provision_app).length == 2) {
// Have a provisioning app - must only let system apps (which check this app)
@@ -1882,8 +1881,10 @@
android.Manifest.permission.TETHER_PRIVILEGED, "ConnectivityService");
} else {
int uid = Binder.getCallingUid();
- Settings.checkAndNoteWriteSettingsOperation(context, uid, Settings
- .getPackageNameForUid(context, uid), true /* throwException */);
+ // If callingPkg's uid is not same as Binder.getCallingUid(),
+ // AppOpsService throws SecurityException.
+ Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPkg,
+ true /* throwException */);
}
}
@@ -2005,7 +2006,9 @@
*/
public int tether(String iface) {
try {
- return mService.tether(iface);
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "tether caller:" + pkgName);
+ return mService.tether(iface, pkgName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2031,7 +2034,9 @@
*/
public int untether(String iface) {
try {
- return mService.untether(iface);
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "untether caller:" + pkgName);
+ return mService.untether(iface, pkgName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2108,7 +2113,7 @@
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public void startTethering(int type, boolean showProvisioningUi,
final OnStartTetheringCallback callback, Handler handler) {
- checkNotNull(callback, "OnStartTetheringCallback cannot be null.");
+ Preconditions.checkNotNull(callback, "OnStartTetheringCallback cannot be null.");
ResultReceiver wrappedCallback = new ResultReceiver(handler) {
@Override
@@ -2122,7 +2127,9 @@
};
try {
- mService.startTethering(type, wrappedCallback, showProvisioningUi);
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "startTethering caller:" + pkgName);
+ mService.startTethering(type, wrappedCallback, showProvisioningUi, pkgName);
} catch (RemoteException e) {
Log.e(TAG, "Exception trying to start tethering.", e);
wrappedCallback.send(TETHER_ERROR_SERVICE_UNAVAIL, null);
@@ -2143,7 +2150,9 @@
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public void stopTethering(int type) {
try {
- mService.stopTethering(type);
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "stopTethering caller:" + pkgName);
+ mService.stopTethering(type, pkgName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2225,7 +2234,9 @@
*/
public int setUsbTethering(boolean enable) {
try {
- return mService.setUsbTethering(enable);
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "setUsbTethering caller:" + pkgName);
+ return mService.setUsbTethering(enable, pkgName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2563,8 +2574,16 @@
}
/**
- * Base class for NetworkRequest callbacks. Used for notifications about network
- * changes. Should be extended by applications wanting notifications.
+ * Base class for {@code NetworkRequest} callbacks. Used for notifications about network
+ * changes. Should be extended by applications wanting notifications.
+ *
+ * A {@code NetworkCallback} is registered by calling
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)},
+ * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)},
+ * or {@link #registerDefaultNetworkCallback(NetworkCallback). A {@code NetworkCallback} is
+ * unregistered by calling {@link #unregisterNetworkCallback(NetworkCallback)}.
+ * A {@code NetworkCallback} should be registered at most once at any time.
+ * A {@code NetworkCallback} that has been unregistered can be registered again.
*/
public static class NetworkCallback {
/**
@@ -2619,7 +2638,7 @@
/**
* Called if no network is found in the timeout time specified in
- * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)} call. This callback is not
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call. This callback is not
* called for the version of {@link #requestNetwork(NetworkRequest, NetworkCallback)}
* without timeout. When this callback is invoked the associated
* {@link NetworkRequest} will have already been removed and released, as if
@@ -2667,6 +2686,32 @@
public void onNetworkResumed(Network network) {}
private NetworkRequest networkRequest;
+
+ private boolean isRegistered() {
+ return (networkRequest != null) && (networkRequest.requestId != REQUEST_ID_UNSET);
+ }
+ }
+
+ /**
+ * Constant error codes used by ConnectivityService to communicate about failures and errors
+ * across a Binder boundary.
+ * @hide
+ */
+ public interface Errors {
+ static int TOO_MANY_REQUESTS = 1;
+ }
+
+ /** @hide */
+ public static class TooManyRequestsException extends RuntimeException {}
+
+ private static RuntimeException convertServiceException(ServiceSpecificException e) {
+ switch (e.errorCode) {
+ case Errors.TOO_MANY_REQUESTS:
+ return new TooManyRequestsException();
+ default:
+ Log.w(TAG, "Unknown service error code " + e.errorCode);
+ return new RuntimeException(e);
+ }
}
private static final int BASE = Protocol.BASE_CONNECTIVITY_MANAGER;
@@ -2684,17 +2729,12 @@
public static final int CALLBACK_CAP_CHANGED = BASE + 6;
/** @hide */
public static final int CALLBACK_IP_CHANGED = BASE + 7;
- /** @hide */
- public static final int CALLBACK_RELEASED = BASE + 8;
- // TODO: consider deleting CALLBACK_EXIT and shifting following enum codes down by 1.
- /** @hide */
- public static final int CALLBACK_EXIT = BASE + 9;
/** @hide obj = NetworkCapabilities, arg1 = seq number */
- private static final int EXPIRE_LEGACY_REQUEST = BASE + 10;
+ private static final int EXPIRE_LEGACY_REQUEST = BASE + 8;
/** @hide */
- public static final int CALLBACK_SUSPENDED = BASE + 11;
+ public static final int CALLBACK_SUSPENDED = BASE + 9;
/** @hide */
- public static final int CALLBACK_RESUMED = BASE + 12;
+ public static final int CALLBACK_RESUMED = BASE + 10;
/** @hide */
public static String getCallbackName(int whichCallback) {
@@ -2706,8 +2746,6 @@
case CALLBACK_UNAVAIL: return "CALLBACK_UNAVAIL";
case CALLBACK_CAP_CHANGED: return "CALLBACK_CAP_CHANGED";
case CALLBACK_IP_CHANGED: return "CALLBACK_IP_CHANGED";
- case CALLBACK_RELEASED: return "CALLBACK_RELEASED";
- case CALLBACK_EXIT: return "CALLBACK_EXIT";
case EXPIRE_LEGACY_REQUEST: return "EXPIRE_LEGACY_REQUEST";
case CALLBACK_SUSPENDED: return "CALLBACK_SUSPENDED";
case CALLBACK_RESUMED: return "CALLBACK_RESUMED";
@@ -2725,97 +2763,67 @@
}
CallbackHandler(Handler handler) {
- this(handler.getLooper());
+ this(Preconditions.checkNotNull(handler, "Handler cannot be null.").getLooper());
}
@Override
public void handleMessage(Message message) {
- NetworkRequest request = getObject(message, NetworkRequest.class);
- Network network = getObject(message, Network.class);
- if (DBG) {
- Log.d(TAG, whatToString(message.what) + " for network " + network);
+ if (message.what == EXPIRE_LEGACY_REQUEST) {
+ expireRequest((NetworkCapabilities) message.obj, message.arg1);
+ return;
}
+
+ final NetworkRequest request = getObject(message, NetworkRequest.class);
+ final Network network = getObject(message, Network.class);
+ final NetworkCallback callback;
+ synchronized (sCallbacks) {
+ callback = sCallbacks.get(request);
+ }
+ if (DBG) {
+ Log.d(TAG, getCallbackName(message.what) + " for network " + network);
+ }
+ if (callback == null) {
+ Log.w(TAG, "callback not found for " + getCallbackName(message.what) + " message");
+ return;
+ }
+
switch (message.what) {
case CALLBACK_PRECHECK: {
- NetworkCallback callback = getCallback(request, "PRECHECK");
- if (callback != null) {
- callback.onPreCheck(network);
- }
+ callback.onPreCheck(network);
break;
}
case CALLBACK_AVAILABLE: {
- NetworkCallback callback = getCallback(request, "AVAILABLE");
- if (callback != null) {
- callback.onAvailable(network);
- }
+ callback.onAvailable(network);
break;
}
case CALLBACK_LOSING: {
- NetworkCallback callback = getCallback(request, "LOSING");
- if (callback != null) {
- callback.onLosing(network, message.arg1);
- }
+ callback.onLosing(network, message.arg1);
break;
}
case CALLBACK_LOST: {
- NetworkCallback callback = getCallback(request, "LOST");
- if (callback != null) {
- callback.onLost(network);
- }
+ callback.onLost(network);
break;
}
case CALLBACK_UNAVAIL: {
- NetworkCallback callback = getCallback(request, "UNAVAIL");
- if (callback != null) {
- callback.onUnavailable();
- }
+ callback.onUnavailable();
break;
}
case CALLBACK_CAP_CHANGED: {
- NetworkCallback callback = getCallback(request, "CAP_CHANGED");
- if (callback != null) {
- NetworkCapabilities cap = getObject(message, NetworkCapabilities.class);
- callback.onCapabilitiesChanged(network, cap);
- }
+ NetworkCapabilities cap = getObject(message, NetworkCapabilities.class);
+ callback.onCapabilitiesChanged(network, cap);
break;
}
case CALLBACK_IP_CHANGED: {
- NetworkCallback callback = getCallback(request, "IP_CHANGED");
- if (callback != null) {
- LinkProperties lp = getObject(message, LinkProperties.class);
- callback.onLinkPropertiesChanged(network, lp);
- }
+ LinkProperties lp = getObject(message, LinkProperties.class);
+ callback.onLinkPropertiesChanged(network, lp);
break;
}
case CALLBACK_SUSPENDED: {
- NetworkCallback callback = getCallback(request, "SUSPENDED");
- if (callback != null) {
- callback.onNetworkSuspended(network);
- }
+ callback.onNetworkSuspended(network);
break;
}
case CALLBACK_RESUMED: {
- NetworkCallback callback = getCallback(request, "RESUMED");
- if (callback != null) {
- callback.onNetworkResumed(network);
- }
- break;
- }
- case CALLBACK_RELEASED: {
- final NetworkCallback callback;
- synchronized(sCallbacks) {
- callback = sCallbacks.remove(request);
- }
- if (callback == null) {
- Log.e(TAG, "callback not found for RELEASED message");
- }
- break;
- }
- case CALLBACK_EXIT: {
- break;
- }
- case EXPIRE_LEGACY_REQUEST: {
- expireRequest((NetworkCapabilities)message.obj, message.arg1);
+ callback.onNetworkResumed(network);
break;
}
}
@@ -2824,17 +2832,6 @@
private <T> T getObject(Message msg, Class<T> c) {
return (T) msg.getData().getParcelable(c.getSimpleName());
}
-
- private NetworkCallback getCallback(NetworkRequest req, String name) {
- NetworkCallback callback;
- synchronized(sCallbacks) {
- callback = sCallbacks.get(req);
- }
- if (callback == null) {
- Log.e(TAG, "callback not found for " + name + " message");
- }
- return callback;
- }
}
private CallbackHandler getDefaultHandler() {
@@ -2854,17 +2851,16 @@
private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
int timeoutMs, int action, int legacyType, CallbackHandler handler) {
- if (callback == null) {
- throw new IllegalArgumentException("null NetworkCallback");
- }
- if (need == null && action != REQUEST) {
- throw new IllegalArgumentException("null NetworkCapabilities");
- }
- // TODO: throw an exception if callback.networkRequest is not null.
- // http://b/20701525
+ checkCallbackNotNull(callback);
+ Preconditions.checkArgument(action == REQUEST || need != null, "null NetworkCapabilities");
final NetworkRequest request;
try {
synchronized(sCallbacks) {
+ if (callback.isRegistered()) {
+ // TODO: throw exception instead and enforce 1:1 mapping of callbacks
+ // and requests (http://b/20701525).
+ Log.e(TAG, "NetworkCallback was already registered");
+ }
Messenger messenger = new Messenger(handler);
Binder binder = new Binder();
if (action == LISTEN) {
@@ -2880,6 +2876,8 @@
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw convertServiceException(e);
}
return request;
}
@@ -2908,7 +2906,7 @@
* This {@link NetworkRequest} will live until released via
* {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A
* version of the method which takes a timeout is
- * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)}.
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}.
* Status of the request can be followed by listening to the various
* callbacks described in {@link NetworkCallback}. The {@link Network}
* can be used to direct traffic to the network.
@@ -2943,7 +2941,7 @@
* This {@link NetworkRequest} will live until released via
* {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A
* version of the method which takes a timeout is
- * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)}.
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}.
* Status of the request can be followed by listening to the various
* callbacks described in {@link NetworkCallback}. The {@link Network}
* can be used to direct traffic to the network.
@@ -3005,14 +3003,11 @@
*/
public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
int timeoutMs) {
- if (timeoutMs <= 0) {
- throw new IllegalArgumentException("Non-positive timeoutMs: " + timeoutMs);
- }
+ checkTimeout(timeoutMs);
int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
requestNetwork(request, networkCallback, timeoutMs, legacyType, getDefaultHandler());
}
-
/**
* Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited
* by a timeout.
@@ -3042,9 +3037,7 @@
*/
public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
Handler handler, int timeoutMs) {
- if (timeoutMs <= 0) {
- throw new IllegalArgumentException("Non-positive timeoutMs");
- }
+ checkTimeout(timeoutMs);
int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
CallbackHandler cbHandler = new CallbackHandler(handler);
requestNetwork(request, networkCallback, timeoutMs, legacyType, cbHandler);
@@ -3116,11 +3109,13 @@
* {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}.
*/
public void requestNetwork(NetworkRequest request, PendingIntent operation) {
- checkPendingIntent(operation);
+ checkPendingIntentNotNull(operation);
try {
mService.pendingRequestForNetwork(request.networkCapabilities, operation);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw convertServiceException(e);
}
}
@@ -3137,7 +3132,7 @@
* corresponding NetworkRequest you'd like to remove. Cannot be null.
*/
public void releaseNetworkRequest(PendingIntent operation) {
- checkPendingIntent(operation);
+ checkPendingIntentNotNull(operation);
try {
mService.releasePendingNetworkRequest(operation);
} catch (RemoteException e) {
@@ -3145,10 +3140,16 @@
}
}
- private void checkPendingIntent(PendingIntent intent) {
- if (intent == null) {
- throw new IllegalArgumentException("PendingIntent cannot be null.");
- }
+ private static void checkPendingIntentNotNull(PendingIntent intent) {
+ Preconditions.checkNotNull(intent, "PendingIntent cannot be null.");
+ }
+
+ private static void checkCallbackNotNull(NetworkCallback callback) {
+ Preconditions.checkNotNull(callback, "null NetworkCallback");
+ }
+
+ private static void checkTimeout(int timeoutMs) {
+ Preconditions.checkArgumentPositive(timeoutMs, "timeoutMs must be strictly positive.");
}
/**
@@ -3215,11 +3216,13 @@
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
public void registerNetworkCallback(NetworkRequest request, PendingIntent operation) {
- checkPendingIntent(operation);
+ checkPendingIntentNotNull(operation);
try {
mService.pendingListenForNetwork(request.networkCapabilities, operation);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw convertServiceException(e);
}
}
@@ -3255,8 +3258,9 @@
// capabilities, this request is guaranteed, at all times, to be
// satisfied by the same network, if any, that satisfies the default
// request, i.e., the system default network.
+ NetworkCapabilities nullCapabilities = null;
CallbackHandler cbHandler = new CallbackHandler(handler);
- sendRequestForNetwork(null, networkCallback, 0, REQUEST, TYPE_NONE, cbHandler);
+ sendRequestForNetwork(nullCapabilities, networkCallback, 0, REQUEST, TYPE_NONE, cbHandler);
}
/**
@@ -3280,25 +3284,42 @@
}
/**
- * Unregisters callbacks about and possibly releases networks originating from
+ * Unregisters a {@code NetworkCallback} and possibly releases networks originating from
* {@link #requestNetwork(NetworkRequest, NetworkCallback)} and
* {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} calls.
* If the given {@code NetworkCallback} had previously been used with
* {@code #requestNetwork}, any networks that had been connected to only to satisfy that request
* will be disconnected.
*
+ * Notifications that would have triggered that {@code NetworkCallback} will immediately stop
+ * triggering it as soon as this call returns.
+ *
* @param networkCallback The {@link NetworkCallback} used when making the request.
*/
public void unregisterNetworkCallback(NetworkCallback networkCallback) {
- if (networkCallback == null || networkCallback.networkRequest == null ||
- networkCallback.networkRequest.requestId == REQUEST_ID_UNSET) {
- throw new IllegalArgumentException("Invalid NetworkCallback");
- }
- try {
- // CallbackHandler will release callback when receiving CALLBACK_RELEASED.
- mService.releaseNetworkRequest(networkCallback.networkRequest);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ checkCallbackNotNull(networkCallback);
+ final List<NetworkRequest> reqs = new ArrayList<>();
+ // Find all requests associated to this callback and stop callback triggers immediately.
+ // Callback is reusable immediately. http://b/20701525, http://b/35921499.
+ synchronized (sCallbacks) {
+ Preconditions.checkArgument(
+ networkCallback.isRegistered(), "NetworkCallback was not registered");
+ for (Map.Entry<NetworkRequest, NetworkCallback> e : sCallbacks.entrySet()) {
+ if (e.getValue() == networkCallback) {
+ reqs.add(e.getKey());
+ }
+ }
+ // TODO: throw exception if callback was registered more than once (http://b/20701525).
+ for (NetworkRequest r : reqs) {
+ try {
+ mService.releaseNetworkRequest(r);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ // Only remove mapping if rpc was successful.
+ sCallbacks.remove(r);
+ }
+ networkCallback.networkRequest = null;
}
}
@@ -3312,6 +3333,7 @@
* Cannot be null.
*/
public void unregisterNetworkCallback(PendingIntent operation) {
+ checkPendingIntentNotNull(operation);
releaseNetworkRequest(operation);
}
@@ -3666,32 +3688,4 @@
throw e.rethrowFromSystemServer();
}
}
-
- /**
- * A holder class for debug info (mapping CALLBACK values to field names). This is stored
- * in a holder for two reasons:
- * 1) The reflection necessary to establish the map can't be run at compile-time. Thus, this
- * code will make the enclosing class not compile-time initializeable, deferring its
- * initialization to zygote startup. This leads to dirty (but shared) memory.
- * As this is debug info, use a holder that isn't initialized by default. This way the map
- * will be created on demand, while ConnectivityManager can be compile-time initialized.
- * 2) Static initialization is still preferred for its strong thread safety guarantees without
- * requiring a lock.
- */
- private static class NoPreloadHolder {
- public static final SparseArray<String> sMagicDecoderRing = MessageUtils.findMessageNames(
- new Class[]{ConnectivityManager.class}, new String[]{"CALLBACK_"});
- }
-
- static {
- // When debug is enabled, aggressively initialize the holder by touching the field (which
- // will guarantee static initialization).
- if (CallbackHandler.DBG) {
- Object dummy = NoPreloadHolder.sMagicDecoderRing;
- }
- }
-
- private static final String whatToString(int what) {
- return NoPreloadHolder.sMagicDecoderRing.get(what, Integer.toString(what));
- }
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 6b2da61..27729dc 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -69,17 +69,18 @@
boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress);
- int tether(String iface);
+ int tether(String iface, String callerPkg);
- int untether(String iface);
+ int untether(String iface, String callerPkg);
int getLastTetherError(String iface);
boolean isTetheringSupported();
- void startTethering(int type, in ResultReceiver receiver, boolean showProvisioningUi);
+ void startTethering(int type, in ResultReceiver receiver, boolean showProvisioningUi,
+ String callerPkg);
- void stopTethering(int type);
+ void stopTethering(int type, String callerPkg);
String[] getTetherableIfaces();
@@ -95,7 +96,7 @@
String[] getTetherableBluetoothRegexs();
- int setUsbTethering(boolean enable);
+ int setUsbTethering(boolean enable, String callerPkg);
void reportInetCondition(int networkType, int percentage);
diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl
index 0aa3ce6..0b1ea98 100644
--- a/core/java/android/net/IIpSecService.aidl
+++ b/core/java/android/net/IIpSecService.aidl
@@ -18,6 +18,9 @@
import android.net.Network;
import android.net.IpSecConfig;
+import android.net.IpSecUdpEncapResponse;
+import android.net.IpSecSpiResponse;
+import android.net.IpSecTransformResponse;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
@@ -27,16 +30,16 @@
*/
interface IIpSecService
{
- Bundle reserveSecurityParameterIndex(
+ IpSecSpiResponse reserveSecurityParameterIndex(
int direction, in String remoteAddress, int requestedSpi, in IBinder binder);
void releaseSecurityParameterIndex(int resourceId);
- Bundle openUdpEncapsulationSocket(int port, in IBinder binder);
+ IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, in IBinder binder);
- void closeUdpEncapsulationSocket(in ParcelFileDescriptor socket);
+ void closeUdpEncapsulationSocket(int resourceId);
- Bundle createTransportModeTransform(in IpSecConfig c, in IBinder binder);
+ IpSecTransformResponse createTransportModeTransform(in IpSecConfig c, in IBinder binder);
void deleteTransportModeTransform(int transformId);
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
index 13dc19f..8b80f2b 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -40,7 +40,7 @@
// Minimum requirements for identifying a transform
// SPI identifying the IPsec flow in packet processing
// and a remote IP address
- int spi;
+ int spiResourceId;
// Encryption Algorithm
IpSecAlgorithm encryption;
@@ -54,7 +54,7 @@
// For tunnel mode IPv4 UDP Encapsulation
// IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE
int encapType;
- int encapLocalPort;
+ int encapLocalPortResourceId;
int encapRemotePort;
// An interval, in seconds between the NattKeepalive packets
@@ -69,8 +69,8 @@
return localAddress;
}
- public int getSpi(int direction) {
- return flow[direction].spi;
+ public int getSpiResourceId(int direction) {
+ return flow[direction].spiResourceId;
}
public InetAddress getRemoteAddress() {
@@ -93,8 +93,8 @@
return encapType;
}
- public int getEncapLocalPort() {
- return encapLocalPort;
+ public int getEncapLocalResourceId() {
+ return encapLocalPortResourceId;
}
public int getEncapRemotePort() {
@@ -119,14 +119,14 @@
// TODO: Use a byte array or other better method for storing IPs that can also include scope
out.writeString((remoteAddress != null) ? remoteAddress.getHostAddress() : null);
out.writeParcelable(network, flags);
- out.writeInt(flow[IpSecTransform.DIRECTION_IN].spi);
+ out.writeInt(flow[IpSecTransform.DIRECTION_IN].spiResourceId);
out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryption, flags);
out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authentication, flags);
- out.writeInt(flow[IpSecTransform.DIRECTION_OUT].spi);
+ out.writeInt(flow[IpSecTransform.DIRECTION_OUT].spiResourceId);
out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryption, flags);
out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authentication, flags);
out.writeInt(encapType);
- out.writeInt(encapLocalPort);
+ out.writeInt(encapLocalPortResourceId);
out.writeInt(encapRemotePort);
}
@@ -151,18 +151,18 @@
localAddress = readInetAddressFromParcel(in);
remoteAddress = readInetAddressFromParcel(in);
network = (Network) in.readParcelable(Network.class.getClassLoader());
- flow[IpSecTransform.DIRECTION_IN].spi = in.readInt();
+ flow[IpSecTransform.DIRECTION_IN].spiResourceId = in.readInt();
flow[IpSecTransform.DIRECTION_IN].encryption =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
flow[IpSecTransform.DIRECTION_IN].authentication =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
- flow[IpSecTransform.DIRECTION_OUT].spi = in.readInt();
+ flow[IpSecTransform.DIRECTION_OUT].spiResourceId = in.readInt();
flow[IpSecTransform.DIRECTION_OUT].encryption =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
flow[IpSecTransform.DIRECTION_OUT].authentication =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
encapType = in.readInt();
- encapLocalPort = in.readInt();
+ encapLocalPortResourceId = in.readInt();
encapRemotePort = in.readInt();
}
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index e3cda5e..2f791e1 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -21,10 +21,10 @@
import android.annotation.SystemService;
import android.content.Context;
import android.os.Binder;
-import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.AndroidException;
+import android.util.Log;
import dalvik.system.CloseGuard;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -57,12 +57,6 @@
}
/** @hide */
- public static final String KEY_STATUS = "status";
- /** @hide */
- public static final String KEY_RESOURCE_ID = "resourceId";
- /** @hide */
- public static final String KEY_SPI = "spi";
- /** @hide */
public static final int INVALID_RESOURCE_ID = 0;
/**
@@ -127,7 +121,11 @@
*/
@Override
public void close() {
- mSpi = INVALID_SECURITY_PARAMETER_INDEX;
+ try {
+ mService.releaseSecurityParameterIndex(mResourceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
mCloseGuard.close();
}
@@ -146,7 +144,7 @@
mService = service;
mRemoteAddress = remoteAddress;
try {
- Bundle result =
+ IpSecSpiResponse result =
mService.reserveSecurityParameterIndex(
direction, remoteAddress.getHostAddress(), spi, new Binder());
@@ -154,7 +152,7 @@
throw new NullPointerException("Received null response from IpSecService");
}
- int status = result.getInt(KEY_STATUS);
+ int status = result.status;
switch (status) {
case Status.OK:
break;
@@ -167,8 +165,8 @@
throw new RuntimeException(
"Unknown status returned by IpSecService: " + status);
}
- mSpi = result.getInt(KEY_SPI);
- mResourceId = result.getInt(KEY_RESOURCE_ID);
+ mSpi = result.spi;
+ mResourceId = result.resourceId;
if (mSpi == INVALID_SECURITY_PARAMETER_INDEX) {
throw new RuntimeException("Invalid SPI returned by IpSecService: " + status);
@@ -184,6 +182,11 @@
}
mCloseGuard.open("open");
}
+
+ /** @hide */
+ int getResourceId() {
+ return mResourceId;
+ }
}
/**
@@ -200,8 +203,7 @@
* @throws SpiUnavailableException indicating that a particular SPI cannot be reserved
*/
public SecurityParameterIndex reserveSecurityParameterIndex(
- int direction, InetAddress remoteAddress)
- throws ResourceUnavailableException {
+ int direction, InetAddress remoteAddress) throws ResourceUnavailableException {
try {
return new SecurityParameterIndex(
mService,
@@ -250,7 +252,9 @@
*/
public void applyTransportModeTransform(Socket socket, IpSecTransform transform)
throws IOException {
- applyTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform);
+ try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) {
+ applyTransportModeTransform(pfd, transform);
+ }
}
/**
@@ -268,15 +272,8 @@
*/
public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
throws IOException {
- applyTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform);
- }
-
- /* Call down to activate a transform */
- private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
- try {
- mService.applyTransportModeTransform(pfd, transform.getResourceId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) {
+ applyTransportModeTransform(pfd, transform);
}
}
@@ -294,7 +291,22 @@
*/
public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
throws IOException {
- applyTransportModeTransform(new ParcelFileDescriptor(socket), transform);
+ // We dup() the FileDescriptor here because if we don't, then the ParcelFileDescriptor()
+ // constructor takes control and closes the user's FD when we exit the method
+ // This is behaviorally the same as the other versions, but the PFD constructor does not
+ // dup() automatically, whereas PFD.fromSocket() and PDF.fromDatagramSocket() do dup().
+ try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) {
+ applyTransportModeTransform(pfd, transform);
+ }
+ }
+
+ /* Call down to activate a transform */
+ private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
+ try {
+ mService.applyTransportModeTransform(pfd, transform.getResourceId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -323,7 +335,9 @@
*/
public void removeTransportModeTransform(Socket socket, IpSecTransform transform)
throws IOException {
- removeTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform);
+ try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) {
+ removeTransportModeTransform(pfd, transform);
+ }
}
/**
@@ -339,7 +353,9 @@
*/
public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
throws IOException {
- removeTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform);
+ try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) {
+ removeTransportModeTransform(pfd, transform);
+ }
}
/**
@@ -354,7 +370,9 @@
*/
public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
throws IOException {
- removeTransportModeTransform(new ParcelFileDescriptor(socket), transform);
+ try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) {
+ removeTransportModeTransform(pfd, transform);
+ }
}
/* Call down to activate a transform */
@@ -387,33 +405,48 @@
* FileDescriptor. Instead, disposing of this socket requires a call to close().
*/
public static final class UdpEncapsulationSocket implements AutoCloseable {
- private final FileDescriptor mFd;
+ private final ParcelFileDescriptor mPfd;
private final IIpSecService mService;
+ private final int mResourceId;
+ private final int mPort;
private final CloseGuard mCloseGuard = CloseGuard.get();
private UdpEncapsulationSocket(@NonNull IIpSecService service, int port)
- throws ResourceUnavailableException {
+ throws ResourceUnavailableException, IOException {
mService = service;
+ try {
+ IpSecUdpEncapResponse result =
+ mService.openUdpEncapsulationSocket(port, new Binder());
+ switch (result.status) {
+ case Status.OK:
+ break;
+ case Status.RESOURCE_UNAVAILABLE:
+ throw new ResourceUnavailableException(
+ "No more Sockets may be allocated by this requester.");
+ default:
+ throw new RuntimeException(
+ "Unknown status returned by IpSecService: " + result.status);
+ }
+ mResourceId = result.resourceId;
+ mPort = result.port;
+ mPfd = result.fileDescriptor;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
mCloseGuard.open("constructor");
- // TODO: go down to the kernel and get a socket on the specified
- mFd = new FileDescriptor();
- }
-
- private UdpEncapsulationSocket(IIpSecService service) throws ResourceUnavailableException {
- mService = service;
- mCloseGuard.open("constructor");
- // TODO: go get a random socket on a random port
- mFd = new FileDescriptor();
}
/** Access the inner UDP Encapsulation Socket */
public FileDescriptor getSocket() {
- return mFd;
+ if (mPfd == null) {
+ return null;
+ }
+ return mPfd.getFileDescriptor();
}
/** Retrieve the port number of the inner encapsulation socket */
public int getPort() {
- return 0; // TODO get the port number from the Socket;
+ return mPort;
}
@Override
@@ -428,7 +461,18 @@
* @param fd a file descriptor previously returned as a UDP Encapsulation socket.
*/
public void close() throws IOException {
- // TODO: Go close the socket
+ try {
+ mService.closeUdpEncapsulationSocket(mResourceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ try {
+ mPfd.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to close UDP Encapsulation Socket with Port= " + mPort);
+ throw e;
+ }
mCloseGuard.close();
}
@@ -437,9 +481,13 @@
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
-
close();
}
+
+ /** @hide */
+ int getResourceId() {
+ return mResourceId;
+ }
};
/**
@@ -466,7 +514,13 @@
// socket.
public UdpEncapsulationSocket openUdpEncapsulationSocket(int port)
throws IOException, ResourceUnavailableException {
- // Temporary code
+ /*
+ * Most range checking is done in the service, but this version of the constructor expects
+ * a valid port number, and zero cannot be checked after being passed to the service.
+ */
+ if (port == 0) {
+ throw new IllegalArgumentException("Specified port must be a valid port number!");
+ }
return new UdpEncapsulationSocket(mService, port);
}
@@ -490,8 +544,7 @@
// socket.
public UdpEncapsulationSocket openUdpEncapsulationSocket()
throws IOException, ResourceUnavailableException {
- // Temporary code
- return new UdpEncapsulationSocket(mService);
+ return new UdpEncapsulationSocket(mService, 0);
}
/**
diff --git a/core/java/android/net/IpSecSpiResponse.aidl b/core/java/android/net/IpSecSpiResponse.aidl
new file mode 100644
index 0000000..6484a00
--- /dev/null
+++ b/core/java/android/net/IpSecSpiResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/** @hide */
+parcelable IpSecSpiResponse;
diff --git a/core/java/android/net/IpSecSpiResponse.java b/core/java/android/net/IpSecSpiResponse.java
new file mode 100644
index 0000000..71dfa03
--- /dev/null
+++ b/core/java/android/net/IpSecSpiResponse.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to return an SPI and corresponding status from the IpSecService to an
+ * IpSecManager.SecurityParameterIndex.
+ *
+ * @hide
+ */
+public final class IpSecSpiResponse implements Parcelable {
+ private static final String TAG = "IpSecSpiResponse";
+
+ public final int resourceId;
+ public final int status;
+ public final int spi;
+ // Parcelable Methods
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(status);
+ out.writeInt(resourceId);
+ out.writeInt(spi);
+ }
+
+ public IpSecSpiResponse(int inStatus, int inResourceId, int inSpi) {
+ status = inStatus;
+ resourceId = inResourceId;
+ spi = inSpi;
+ }
+
+ public IpSecSpiResponse(int inStatus) {
+ if (inStatus == IpSecManager.Status.OK) {
+ throw new IllegalArgumentException("Valid status implies other args must be provided");
+ }
+ status = inStatus;
+ resourceId = IpSecManager.INVALID_RESOURCE_ID;
+ spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
+ }
+
+ private IpSecSpiResponse(Parcel in) {
+ status = in.readInt();
+ resourceId = in.readInt();
+ spi = in.readInt();
+ }
+
+ public static final Parcelable.Creator<IpSecSpiResponse> CREATOR =
+ new Parcelable.Creator<IpSecSpiResponse>() {
+ public IpSecSpiResponse createFromParcel(Parcel in) {
+ return new IpSecSpiResponse(in);
+ }
+
+ public IpSecSpiResponse[] newArray(int size) {
+ return new IpSecSpiResponse[size];
+ }
+ };
+}
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index 639d1f2..e65f534 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -16,15 +16,12 @@
package android.net;
import static android.net.IpSecManager.INVALID_RESOURCE_ID;
-import static android.net.IpSecManager.KEY_RESOURCE_ID;
-import static android.net.IpSecManager.KEY_STATUS;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Context;
import android.os.Binder;
-import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -78,23 +75,23 @@
public static final int ENCAP_NONE = 0;
/**
- * IpSec traffic will be encapsulated within UDP as per <a
- * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>.
- *
- * @hide
- */
- public static final int ENCAP_ESPINUDP = 1;
-
- /**
* IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad
* (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP.
*
* @hide
*/
- public static final int ENCAP_ESPINUDP_NONIKE = 2;
+ public static final int ENCAP_ESPINUDP_NON_IKE = 1;
+
+ /**
+ * IpSec traffic will be encapsulated within UDP as per <a
+ * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>.
+ *
+ * @hide
+ */
+ public static final int ENCAP_ESPINUDP = 2;
/** @hide */
- @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NONIKE})
+ @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NON_IKE})
@Retention(RetentionPolicy.SOURCE)
public @interface EncapType {}
@@ -139,10 +136,11 @@
synchronized (this) {
try {
IIpSecService svc = getIpSecService();
- Bundle result = svc.createTransportModeTransform(mConfig, new Binder());
- int status = result.getInt(KEY_STATUS);
+ IpSecTransformResponse result =
+ svc.createTransportModeTransform(mConfig, new Binder());
+ int status = result.status;
checkResultStatusAndThrow(status);
- mResourceId = result.getInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID);
+ mResourceId = result.resourceId;
/* Keepalive will silently fail if not needed by the config; but, if needed and
* it fails to start, we need to bail because a transform will not be reliable
@@ -263,7 +261,10 @@
mConfig.getNattKeepaliveInterval(),
mKeepaliveCallback,
mConfig.getLocalAddress(),
- mConfig.getEncapLocalPort(),
+ 0x1234, /* FIXME: get the real port number again,
+ which we need to retrieve from the provided
+ EncapsulationSocket, and which isn't currently
+ stashed in IpSecConfig */
mConfig.getRemoteAddress());
try {
// FIXME: this is still a horrible way to fudge the synchronous callback
@@ -360,7 +361,7 @@
@TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) {
// TODO: convert to using the resource Id of the SPI. Then build() can validate
// the owner in the IpSecService
- mConfig.flow[direction].spi = spi.getSpi();
+ mConfig.flow[direction].spiResourceId = spi.getResourceId();
return this;
}
@@ -394,7 +395,7 @@
IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
// TODO: check encap type is valid.
mConfig.encapType = ENCAP_ESPINUDP;
- mConfig.encapLocalPort = localSocket.getPort(); // TODO: plug in the encap socket
+ mConfig.encapLocalPortResourceId = localSocket.getResourceId();
mConfig.encapRemotePort = remotePort;
return this;
}
diff --git a/core/java/android/net/IpSecTransformResponse.aidl b/core/java/android/net/IpSecTransformResponse.aidl
new file mode 100644
index 0000000..546230d
--- /dev/null
+++ b/core/java/android/net/IpSecTransformResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/** @hide */
+parcelable IpSecTransformResponse;
diff --git a/core/java/android/net/IpSecTransformResponse.java b/core/java/android/net/IpSecTransformResponse.java
new file mode 100644
index 0000000..cfc1762
--- /dev/null
+++ b/core/java/android/net/IpSecTransformResponse.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to return an IpSecTransform resource Id and and corresponding status from the
+ * IpSecService to an IpSecTransform object.
+ *
+ * @hide
+ */
+public final class IpSecTransformResponse implements Parcelable {
+ private static final String TAG = "IpSecTransformResponse";
+
+ public final int resourceId;
+ public final int status;
+ // Parcelable Methods
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(status);
+ out.writeInt(resourceId);
+ }
+
+ public IpSecTransformResponse(int inStatus) {
+ if (inStatus == IpSecManager.Status.OK) {
+ throw new IllegalArgumentException("Valid status implies other args must be provided");
+ }
+ status = inStatus;
+ resourceId = IpSecManager.INVALID_RESOURCE_ID;
+ }
+
+ public IpSecTransformResponse(int inStatus, int inResourceId) {
+ status = inStatus;
+ resourceId = inResourceId;
+ }
+
+ private IpSecTransformResponse(Parcel in) {
+ status = in.readInt();
+ resourceId = in.readInt();
+ }
+
+ public static final Parcelable.Creator<IpSecTransformResponse> CREATOR =
+ new Parcelable.Creator<IpSecTransformResponse>() {
+ public IpSecTransformResponse createFromParcel(Parcel in) {
+ return new IpSecTransformResponse(in);
+ }
+
+ public IpSecTransformResponse[] newArray(int size) {
+ return new IpSecTransformResponse[size];
+ }
+ };
+}
diff --git a/core/java/android/net/IpSecUdpEncapResponse.aidl b/core/java/android/net/IpSecUdpEncapResponse.aidl
new file mode 100644
index 0000000..5e451f3
--- /dev/null
+++ b/core/java/android/net/IpSecUdpEncapResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/** @hide */
+parcelable IpSecUdpEncapResponse;
diff --git a/core/java/android/net/IpSecUdpEncapResponse.java b/core/java/android/net/IpSecUdpEncapResponse.java
new file mode 100644
index 0000000..4679267
--- /dev/null
+++ b/core/java/android/net/IpSecUdpEncapResponse.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * This class is used to return a UDP Socket and corresponding status from the IpSecService to an
+ * IpSecManager.UdpEncapsulationSocket.
+ *
+ * @hide
+ */
+public final class IpSecUdpEncapResponse implements Parcelable {
+ private static final String TAG = "IpSecUdpEncapResponse";
+
+ public final int resourceId;
+ public final int port;
+ public final int status;
+ // There is a weird asymmetry with FileDescriptor: you can write a FileDescriptor
+ // but you read a ParcelFileDescriptor. To circumvent this, when we receive a FD
+ // from the user, we immediately create a ParcelFileDescriptor DUP, which we invalidate
+ // on writeParcel() by setting the flag to do close-on-write.
+ // TODO: tests to ensure this doesn't leak
+ public final ParcelFileDescriptor fileDescriptor;
+
+ // Parcelable Methods
+
+ @Override
+ public int describeContents() {
+ return (fileDescriptor != null) ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(status);
+ out.writeInt(resourceId);
+ out.writeInt(port);
+ out.writeParcelable(fileDescriptor, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ }
+
+ public IpSecUdpEncapResponse(int inStatus) {
+ if (inStatus == IpSecManager.Status.OK) {
+ throw new IllegalArgumentException("Valid status implies other args must be provided");
+ }
+ status = inStatus;
+ resourceId = IpSecManager.INVALID_RESOURCE_ID;
+ port = -1;
+ fileDescriptor = null; // yes I know it's redundant, but readability
+ }
+
+ public IpSecUdpEncapResponse(int inStatus, int inResourceId, int inPort, FileDescriptor inFd)
+ throws IOException {
+ if (inStatus == IpSecManager.Status.OK && inFd == null) {
+ throw new IllegalArgumentException("Valid status implies FD must be non-null");
+ }
+ status = inStatus;
+ resourceId = inResourceId;
+ port = inPort;
+ fileDescriptor = (status == IpSecManager.Status.OK) ? ParcelFileDescriptor.dup(inFd) : null;
+ }
+
+ private IpSecUdpEncapResponse(Parcel in) {
+ status = in.readInt();
+ resourceId = in.readInt();
+ port = in.readInt();
+ fileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+ }
+
+ public static final Parcelable.Creator<IpSecUdpEncapResponse> CREATOR =
+ new Parcelable.Creator<IpSecUdpEncapResponse>() {
+ public IpSecUdpEncapResponse createFromParcel(Parcel in) {
+ return new IpSecUdpEncapResponse(in);
+ }
+
+ public IpSecUdpEncapResponse[] newArray(int size) {
+ return new IpSecUdpEncapResponse[size];
+ }
+ };
+}
diff --git a/core/java/android/net/NetworkBadging.java b/core/java/android/net/NetworkBadging.java
index 6de28b7..c119b63 100644
--- a/core/java/android/net/NetworkBadging.java
+++ b/core/java/android/net/NetworkBadging.java
@@ -66,15 +66,7 @@
*/
@NonNull public static Drawable getWifiIcon(
@IntRange(from=0, to=4) int signalLevel, @Badging int badging, @Nullable Theme theme) {
- Resources resources = Resources.getSystem();
- if (badging == BADGING_NONE) {
- return resources.getDrawable(getWifiSignalResource(signalLevel), theme);
- }
- Drawable[] layers = new Drawable[] {
- resources.getDrawable(getBadgedWifiSignalResource(signalLevel), theme),
- resources.getDrawable(getWifiBadgeResource(badging), theme)
- };
- return new LayerDrawable(layers);
+ return Resources.getSystem().getDrawable(getWifiSignalResource(signalLevel), theme);
}
/**
@@ -134,31 +126,4 @@
throw new IllegalArgumentException("Invalid signal level: " + signalLevel);
}
}
-
- /**
- * Returns the wifi quality badge resource id for the the given badging balue.
- *
- * <p>This badge should be displayed with the badge signal resource retrieved from
- * {@link #getBadgedWifiSignalResource(int)}.
- *
- * @param badging {@see NetworkBadging#Badging} from {@link ScoredNetwork#calculateBadge(int)}.
- * @return the @DrawableRes for the icon or {@link View#NO_ID} for
- * {@link NetworkBadging#BADGING_NONE}
- * @throws IllegalArgumentException for an invalid badging value.
- * @hide
- */
- @DrawableRes private static int getWifiBadgeResource(@Badging int badging) {
- switch (badging) {
- case BADGING_NONE:
- return View.NO_ID;
- case BADGING_SD:
- return com.android.internal.R.drawable.ic_signal_wifi_badged_sd;
- case BADGING_HD:
- return com.android.internal.R.drawable.ic_signal_wifi_badged_hd;
- case BADGING_4K:
- return com.android.internal.R.drawable.ic_signal_wifi_badged_4k;
- default:
- throw new IllegalArgumentException("No resource found for badge: " + badging);
- }
- }
}
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index bf7207c..2dd7f75 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -418,10 +418,16 @@
*/
public static final int TRANSPORT_WIFI_AWARE = 5;
+ /**
+ * Indicates this network uses a LoWPAN transport.
+ * @hide
+ */
+ public static final int TRANSPORT_LOWPAN = 6;
+
/** @hide */
public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
/** @hide */
- public static final int MAX_TRANSPORT = TRANSPORT_WIFI_AWARE;
+ public static final int MAX_TRANSPORT = TRANSPORT_LOWPAN;
private static final String[] TRANSPORT_NAMES = {
"CELLULAR",
@@ -429,7 +435,8 @@
"BLUETOOTH",
"ETHERNET",
"VPN",
- "WIFI_AWARE"
+ "WIFI_AWARE",
+ "LOWPAN"
};
/**
diff --git a/core/java/android/net/NetworkSpecifier.java b/core/java/android/net/NetworkSpecifier.java
index 87a2b05..9ce2a5b 100644
--- a/core/java/android/net/NetworkSpecifier.java
+++ b/core/java/android/net/NetworkSpecifier.java
@@ -33,4 +33,20 @@
* @hide
*/
public abstract boolean satisfiedBy(NetworkSpecifier other);
+
+ /**
+ * Optional method which can be overriden by concrete implementations of NetworkSpecifier to
+ * check a self-reported UID. A concrete implementation may contain a UID which would be self-
+ * reported by the caller (since NetworkSpecifier implementations should be non-mutable). This
+ * function is called by ConnectivityService and is passed the actual UID of the caller -
+ * allowing the verification of the self-reported UID. In cases of mismatch the implementation
+ * should throw a SecurityException.
+ *
+ * @param requestorUid The UID of the requestor as obtained from its binder.
+ *
+ * @hide
+ */
+ public void assertValidFromUid(int requestorUid) {
+ // empty
+ }
}
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index caf7982..0d2fcd0 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -18,8 +18,8 @@
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
-import static android.net.ConnectivityManager.TYPE_PROXY;
import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_PROXY;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
import static android.net.ConnectivityManager.TYPE_WIMAX;
@@ -34,8 +34,8 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.BackupUtils;
+import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import java.io.ByteArrayOutputStream;
@@ -52,14 +52,18 @@
* @hide
*/
public class NetworkTemplate implements Parcelable {
+ private static final String TAG = "NetworkTemplate";
+
/**
* Current Version of the Backup Serializer.
*/
private static final int BACKUP_VERSION = 1;
public static final int MATCH_MOBILE_ALL = 1;
+ /** @deprecated don't use this any more */
@Deprecated
public static final int MATCH_MOBILE_3G_LOWER = 2;
+ /** @deprecated don't use this any more */
@Deprecated
public static final int MATCH_MOBILE_4G = 3;
public static final int MATCH_WIFI = 4;
@@ -69,9 +73,26 @@
public static final int MATCH_BLUETOOTH = 8;
public static final int MATCH_PROXY = 9;
+ private static boolean isKnownMatchRule(final int rule) {
+ switch (rule) {
+ case MATCH_MOBILE_ALL:
+ case MATCH_MOBILE_3G_LOWER:
+ case MATCH_MOBILE_4G:
+ case MATCH_WIFI:
+ case MATCH_ETHERNET:
+ case MATCH_MOBILE_WILDCARD:
+ case MATCH_WIFI_WILDCARD:
+ case MATCH_BLUETOOTH:
+ case MATCH_PROXY:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
private static boolean sForceAllNetworkTypes = false;
- @VisibleForTesting
public static void forceAllNetworkTypes() {
sForceAllNetworkTypes = true;
}
@@ -180,6 +201,11 @@
mSubscriberId = subscriberId;
mMatchSubscriberIds = matchSubscriberIds;
mNetworkId = networkId;
+
+ if (!isKnownMatchRule(matchRule)) {
+ Log.e(TAG, "Unknown network template rule " + matchRule
+ + " will not match any identity.");
+ }
}
private NetworkTemplate(Parcel in) {
@@ -294,7 +320,9 @@
case MATCH_PROXY:
return matchesProxy(ident);
default:
- throw new IllegalArgumentException("unknown network template");
+ // We have no idea what kind of network template we are, so we
+ // just claim not to match anything.
+ return false;
}
}
@@ -428,7 +456,7 @@
case MATCH_PROXY:
return "PROXY";
default:
- return "UNKNOWN";
+ return "UNKNOWN(" + matchRule + ")";
}
}
@@ -496,6 +524,11 @@
String subscriberId = BackupUtils.readString(in);
String networkId = BackupUtils.readString(in);
+ if (!isKnownMatchRule(matchRule)) {
+ throw new BackupUtils.BadVersionException(
+ "Restored network template contains unknown match rule " + matchRule);
+ }
+
return new NetworkTemplate(matchRule, subscriberId, networkId);
}
}
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index b56437e..0b1569c 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -20,6 +20,7 @@
import android.util.Log;
import com.android.internal.os.RoSystemProperties;
+import com.android.org.conscrypt.Conscrypt;
import com.android.org.conscrypt.OpenSSLContextImpl;
import com.android.org.conscrypt.OpenSSLSocketImpl;
import com.android.org.conscrypt.SSLClientSessionCache;
@@ -212,7 +213,7 @@
private SSLSocketFactory makeSocketFactory(
KeyManager[] keyManagers, TrustManager[] trustManagers) {
try {
- OpenSSLContextImpl sslContext = OpenSSLContextImpl.getPreferred();
+ OpenSSLContextImpl sslContext = (OpenSSLContextImpl) Conscrypt.newPreferredSSLContextSpi();
sslContext.engineInit(keyManagers, trustManagers, null);
sslContext.engineGetClientSessionContext().setPersistentCache(mSessionCache);
return sslContext.engineGetSocketFactory();
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index 2d480a1..ace3748 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -32,6 +32,7 @@
import java.util.concurrent.CountDownLatch;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
@@ -268,6 +269,14 @@
}
/**
+ * @hide
+ */
+ @VisibleForTesting
+ public void disconnect() {
+ mAsyncChannel.disconnect();
+ }
+
+ /**
* Failures are passed with {@link RegistrationListener#onRegistrationFailed},
* {@link RegistrationListener#onUnregistrationFailed},
* {@link DiscoveryListener#onStartDiscoveryFailed},
@@ -325,7 +334,8 @@
public void onServiceResolved(NsdServiceInfo serviceInfo);
}
- private class ServiceHandler extends Handler {
+ @VisibleForTesting
+ class ServiceHandler extends Handler {
ServiceHandler(Looper looper) {
super(looper);
}
@@ -369,6 +379,8 @@
((DiscoveryListener) listener).onServiceLost((NsdServiceInfo) message.obj);
break;
case STOP_DISCOVERY_FAILED:
+ // TODO: failure to stop discovery should be internal and retried internally, as
+ // the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED
removeListener(message.arg2);
((DiscoveryListener) listener).onStopDiscoveryFailed(getNsdServiceInfoType(ns),
message.arg1);
@@ -390,6 +402,8 @@
((RegistrationListener) listener).onUnregistrationFailed(ns, message.arg1);
break;
case UNREGISTER_SERVICE_SUCCEEDED:
+ // TODO: do not unregister listener until service is unregistered, or provide
+ // alternative way for unregistering ?
removeListener(message.arg2);
((RegistrationListener) listener).onServiceUnregistered(ns);
break;
diff --git a/core/java/android/net/nsd/NsdServiceInfo.java b/core/java/android/net/nsd/NsdServiceInfo.java
index 7b845be..bccaf60 100644
--- a/core/java/android/net/nsd/NsdServiceInfo.java
+++ b/core/java/android/net/nsd/NsdServiceInfo.java
@@ -30,7 +30,6 @@
import java.util.Collections;
import java.util.Map;
-
/**
* A class representing service information for network service discovery
* {@see NsdManager}
@@ -43,7 +42,7 @@
private String mServiceType;
- private final ArrayMap<String, byte[]> mTxtRecord = new ArrayMap<String, byte[]>();
+ private final ArrayMap<String, byte[]> mTxtRecord = new ArrayMap<>();
private InetAddress mHost;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index ecc4dec..9bea403 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -201,7 +201,7 @@
* New in version 22:
* - BLE scan result background count, BLE unoptimized scan time
*/
- static final String CHECKIN_VERSION = "22";
+ static final String CHECKIN_VERSION = "23";
/**
* Old version, we hit 9 and ran out of room, need to remove.
@@ -1219,6 +1219,7 @@
// Platform-level low power state stats
public String statPlatformIdleState;
+ public String statSubsystemPowerState;
public HistoryStepDetails() {
clear();
@@ -1250,6 +1251,7 @@
out.writeInt(statSoftIrqTime);
out.writeInt(statIdlTime);
out.writeString(statPlatformIdleState);
+ out.writeString(statSubsystemPowerState);
}
public void readFromParcel(Parcel in) {
@@ -1271,6 +1273,7 @@
statSoftIrqTime = in.readInt();
statIdlTime = in.readInt();
statPlatformIdleState = in.readString();
+ statSubsystemPowerState = in.readString();
}
}
@@ -3051,6 +3054,7 @@
boolean wifiOnly) {
final long rawUptime = SystemClock.uptimeMillis() * 1000;
final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
+ final long rawRealtimeMs = (rawRealtime + 500) / 1000;
final long batteryUptime = getBatteryUptime(rawUptime);
final long whichBatteryUptime = computeBatteryUptime(rawUptime, which);
final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which);
@@ -3437,7 +3441,6 @@
final int count = bleTimer.getCountLocked(which);
final Timer bleTimerBg = u.getBluetoothScanBackgroundTimer();
final int countBg = bleTimerBg != null ? bleTimerBg.getCountLocked(which) : 0;
- final long rawRealtimeMs = (rawRealtime + 500) / 1000;
// 'actualTime' are unpooled and always since reset (regardless of 'which')
final long actualTime = bleTimer.getTotalDurationMsLocked(rawRealtimeMs);
final long actualTimeBg = bleTimerBg != null ?
@@ -3486,11 +3489,11 @@
if (u.getAggregatedPartialWakelockTimer() != null) {
final Timer timer = u.getAggregatedPartialWakelockTimer();
- // Convert from microseconds to milliseconds with rounding
- final long totTimeMs = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+ // Times are since reset (regardless of 'which')
+ final long totTimeMs = timer.getTotalDurationMsLocked(rawRealtimeMs);
final Timer bgTimer = timer.getSubTimer();
final long bgTimeMs = bgTimer != null ?
- (bgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000 : 0;
+ bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
dumpLine(pw, uid, category, AGGREGATED_WAKELOCK_DATA, totTimeMs, bgTimeMs);
}
@@ -3527,7 +3530,7 @@
final int count = timer.getCountLocked(which);
final Timer bgTimer = timer.getSubTimer();
final long bgTime = bgTimer != null ?
- (bgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000 : -1;
+ bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : -1;
final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : -1;
if (totalTime != 0) {
dumpLine(pw, uid, category, SYNC_DATA, "\"" + syncs.keyAt(isy) + "\"",
@@ -3543,7 +3546,7 @@
final int count = timer.getCountLocked(which);
final Timer bgTimer = timer.getSubTimer();
final long bgTime = bgTimer != null ?
- (bgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000 : -1;
+ bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : -1;
final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : -1;
if (totalTime != 0) {
dumpLine(pw, uid, category, JOB_DATA, "\"" + jobs.keyAt(ij) + "\"",
@@ -3574,7 +3577,6 @@
final int count = timer.getCountLocked(which);
final Timer bgTimer = se.getSensorBackgroundTime();
final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : 0;
- final long rawRealtimeMs = (rawRealtime + 500) / 1000;
// 'actualTime' are unpooled and always since reset (regardless of 'which')
final long actualTime = timer.getTotalDurationMsLocked(rawRealtimeMs);
final long bgActualTime = bgTimer != null ?
@@ -3715,6 +3717,7 @@
int reqUid, boolean wifiOnly) {
final long rawUptime = SystemClock.uptimeMillis() * 1000;
final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
+ final long rawRealtimeMs = (rawRealtime + 500) / 1000;
final long batteryUptime = getBatteryUptime(rawUptime);
final long whichBatteryUptime = computeBatteryUptime(rawUptime, which);
@@ -4655,7 +4658,6 @@
final int count = bleTimer.getCountLocked(which);
final Timer bleTimerBg = u.getBluetoothScanBackgroundTimer();
final int countBg = bleTimerBg != null ? bleTimerBg.getCountLocked(which) : 0;
- final long rawRealtimeMs = (rawRealtime + 500) / 1000;
// 'actualTime' are unpooled and always since reset (regardless of 'which')
final long actualTimeMs = bleTimer.getTotalDurationMsLocked(rawRealtimeMs);
final long actualTimeMsBg = bleTimerBg != null ?
@@ -4823,10 +4825,10 @@
final Timer aggTimer = u.getAggregatedPartialWakelockTimer();
// Convert from microseconds to milliseconds with rounding
actualTotalPartialWakelock =
- (aggTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+ aggTimer.getTotalDurationMsLocked(rawRealtimeMs);
final Timer bgAggTimer = aggTimer.getSubTimer();
actualBgPartialWakelock = bgAggTimer != null ?
- (bgAggTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000 : 0;
+ bgAggTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
}
if (actualTotalPartialWakelock != 0 || actualBgPartialWakelock != 0 ||
@@ -4894,7 +4896,7 @@
final int count = timer.getCountLocked(which);
final Timer bgTimer = timer.getSubTimer();
final long bgTime = bgTimer != null ?
- (bgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000 : -1;
+ bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : -1;
final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : -1;
sb.setLength(0);
sb.append(prefix);
@@ -4928,7 +4930,7 @@
final int count = timer.getCountLocked(which);
final Timer bgTimer = timer.getSubTimer();
final long bgTime = bgTimer != null ?
- (bgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000 : -1;
+ bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : -1;
final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : -1;
sb.setLength(0);
sb.append(prefix);
@@ -4987,7 +4989,6 @@
final int count = timer.getCountLocked(which);
final Timer bgTimer = se.getSensorBackgroundTime();
final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : 0;
- final long rawRealtimeMs = (rawRealtime + 500) / 1000;
// 'actualTime' are unpooled and always since reset (regardless of 'which')
final long actualTime = timer.getTotalDurationMsLocked(rawRealtimeMs);
final long bgActualTime = bgTimer != null ?
@@ -5543,6 +5544,10 @@
pw.print(", PlatformIdleStat ");
pw.print(rec.stepDetails.statPlatformIdleState);
pw.println();
+
+ pw.print(", SubsystemPowerState ");
+ pw.print(rec.stepDetails.statSubsystemPowerState);
+ pw.println();
} else {
pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
pw.print(HISTORY_DATA); pw.print(",0,Dcpu=");
@@ -5580,6 +5585,11 @@
pw.print(rec.stepDetails.statPlatformIdleState);
}
pw.println();
+
+ if (rec.stepDetails.statSubsystemPowerState != null) {
+ pw.print(rec.stepDetails.statSubsystemPowerState);
+ }
+ pw.println();
}
}
oldState = rec.states;
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 9b5ff29..ec01364 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -260,6 +260,19 @@
}
/**
+ * Return the size of {@link #mParcelledData} in bytes if available, otherwise {@code 0}.
+ *
+ * @hide
+ */
+ public int getSize() {
+ if (mParcelledData != null) {
+ return mParcelledData.dataSize();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
* Reports whether the bundle contains any parcelled file descriptors.
*/
public boolean hasFileDescriptors() {
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index e05bd89..fa854b0 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -16,13 +16,20 @@
package android.os;
-import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.TypedProperties;
-
import android.app.AppGlobals;
import android.content.Context;
import android.util.Log;
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.TypedProperties;
+
+import dalvik.bytecode.OpcodeInfo;
+import dalvik.system.VMDebug;
+
+import org.apache.harmony.dalvik.ddmc.Chunk;
+import org.apache.harmony.dalvik.ddmc.ChunkHandler;
+import org.apache.harmony.dalvik.ddmc.DdmServer;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
@@ -31,21 +38,16 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
-import org.apache.harmony.dalvik.ddmc.Chunk;
-import org.apache.harmony.dalvik.ddmc.ChunkHandler;
-import org.apache.harmony.dalvik.ddmc.DdmServer;
-import dalvik.bytecode.OpcodeInfo;
-import dalvik.system.VMDebug;
/**
@@ -2219,13 +2221,26 @@
}
/**
- * Append the stack traces of a given native process to a specified file.
+ * Append the Java stack traces of a given native process to a specified file.
+ *
* @param pid pid to dump.
* @param file path of file to append dump to.
* @param timeoutSecs time to wait in seconds, or 0 to wait forever.
* @hide
*/
- public static native void dumpNativeBacktraceToFileTimeout(int pid, String file, int timeoutSecs);
+ public static native boolean dumpJavaBacktraceToFileTimeout(int pid, String file,
+ int timeoutSecs);
+
+ /**
+ * Append the native stack traces of a given process to a specified file.
+ *
+ * @param pid pid to dump.
+ * @param file path of file to append dump to.
+ * @param timeoutSecs time to wait in seconds, or 0 to wait forever.
+ * @hide
+ */
+ public static native boolean dumpNativeBacktraceToFileTimeout(int pid, String file,
+ int timeoutSecs);
/**
* Get description of unreachable native memory.
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index f9eaba9..07c6055 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -35,23 +35,12 @@
private static final String TAG = "GraphicsEnvironment";
private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0";
- public static void setupGraphicsEnvironment(Context context) {
- chooseDriver(context);
-
- // Now that we've figured out which driver to use for this process, load and initialize it.
- // This can take multiple frame periods, and it would otherwise happen as part of the first
- // frame, increasing first-frame latency. Starting it here, as a low-priority background
- // thread, means that it's usually done long before we start drawing the first frame,
- // without significantly disrupting other activity launch work.
- Thread eglInitThread = new Thread(
- () -> {
- EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
- },
- "EGL Init");
- eglInitThread.start();
- }
-
- private static void chooseDriver(Context context) {
+ /**
+ * Choose whether the current process should use the builtin or an updated driver.
+ *
+ * @hide
+ */
+ public static void chooseDriver(Context context) {
String driverPackageName = SystemProperties.get(PROPERTY_GFX_DRIVER);
if (driverPackageName == null || driverPackageName.isEmpty()) {
return;
@@ -101,6 +90,27 @@
setDriverPath(paths);
}
+ /**
+ * Start a background thread to initialize EGL.
+ *
+ * Initializing EGL involves loading and initializing the graphics driver. Some drivers take
+ * several 10s of milliseconds to do this, so doing it on-demand when an app tries to render
+ * its first frame adds directly to user-visible app launch latency. By starting it earlier
+ * on a separate thread, it can usually be finished well before the UI is ready to be drawn.
+ *
+ * Should only be called after chooseDriver().
+ *
+ * @hide
+ */
+ public static void earlyInitEGL() {
+ Thread eglInitThread = new Thread(
+ () -> {
+ EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+ },
+ "EGL Init");
+ eglInitThread.start();
+ }
+
private static String chooseAbi(ApplicationInfo ai) {
String isa = VMRuntime.getCurrentInstructionSet();
if (ai.primaryCpuAbi != null &&
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index dbe2f6d..c34de15 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -315,8 +315,6 @@
void setFirewallEnabled(boolean enabled);
boolean isFirewallEnabled();
void setFirewallInterfaceRule(String iface, boolean allow);
- void setFirewallEgressSourceRule(String addr, boolean allow);
- void setFirewallEgressDestRule(String addr, int port, boolean allow);
void setFirewallUidRule(int chain, int uid, int rule);
void setFirewallUidRules(int chain, in int[] uids, in int[] rules);
void setFirewallChainEnabled(int chain, boolean enable);
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 0a5e9a8..68a81ca 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -295,6 +295,7 @@
private static native long nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);
private static native byte[] nativeCreateByteArray(long nativePtr);
+ private static native boolean nativeReadByteArray(long nativePtr, byte[] dest, int destLen);
private static native byte[] nativeReadBlob(long nativePtr);
@FastNative
private static native int nativeReadInt(long nativePtr);
@@ -2211,11 +2212,8 @@
* given byte array.
*/
public final void readByteArray(byte[] val) {
- // TODO: make this a native method to avoid the extra copy.
- byte[] ba = createByteArray();
- if (ba.length == val.length) {
- System.arraycopy(ba, 0, val, 0, ba.length);
- } else {
+ boolean valid = nativeReadByteArray(mNativePtr, val, (val != null) ? val.length : 0);
+ if (!valid) {
throw new RuntimeException("bad array lengths");
}
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 7a709ed..3074942 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -386,6 +386,12 @@
**/
public static final int THREAD_GROUP_TOP_APP = 5;
+ /**
+ * Thread group for RT app.
+ * @hide
+ **/
+ public static final int THREAD_GROUP_RT_APP = 6;
+
public static final int SIGNAL_QUIT = 3;
public static final int SIGNAL_KILL = 9;
public static final int SIGNAL_USR1 = 10;
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index ba339a3..db9f28b 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -839,9 +839,11 @@
int timeTotal = -1;
int uncryptTime = -1;
int sourceVersion = -1;
- int temperature_start = -1;
- int temperature_end = -1;
- int temperature_max = -1;
+ int temperatureStart = -1;
+ int temperatureEnd = -1;
+ int temperatureMax = -1;
+ int errorCode = -1;
+ int causeCode = -1;
while ((line = in.readLine()) != null) {
// Here is an example of lines in last_install:
@@ -888,11 +890,15 @@
bytesStashedInMiB = (bytesStashedInMiB == -1) ? scaled :
bytesStashedInMiB + scaled;
} else if (line.startsWith("temperature_start")) {
- temperature_start = scaled;
+ temperatureStart = scaled;
} else if (line.startsWith("temperature_end")) {
- temperature_end = scaled;
+ temperatureEnd = scaled;
} else if (line.startsWith("temperature_max")) {
- temperature_max = scaled;
+ temperatureMax = scaled;
+ } else if (line.startsWith("error")) {
+ errorCode = scaled;
+ } else if (line.startsWith("cause")) {
+ causeCode = scaled;
}
}
@@ -912,14 +918,20 @@
if (bytesStashedInMiB != -1) {
MetricsLogger.histogram(context, "ota_stashed_in_MiBs", bytesStashedInMiB);
}
- if (temperature_start != -1) {
- MetricsLogger.histogram(context, "ota_temperature_start", temperature_start);
+ if (temperatureStart != -1) {
+ MetricsLogger.histogram(context, "ota_temperature_start", temperatureStart);
}
- if (temperature_end != -1) {
- MetricsLogger.histogram(context, "ota_temperature_end", temperature_end);
+ if (temperatureEnd != -1) {
+ MetricsLogger.histogram(context, "ota_temperature_end", temperatureEnd);
}
- if (temperature_max != -1) {
- MetricsLogger.histogram(context, "ota_temperature_max", temperature_max);
+ if (temperatureMax != -1) {
+ MetricsLogger.histogram(context, "ota_temperature_max", temperatureMax);
+ }
+ if (errorCode != -1) {
+ MetricsLogger.histogram(context, "ota_non_ab_error_code", errorCode);
+ }
+ if (causeCode != -1) {
+ MetricsLogger.histogram(context, "ota_non_ab_cause_code", causeCode);
}
} catch (IOException e) {
diff --git a/core/java/android/os/StatFs.java b/core/java/android/os/StatFs.java
index d9e516c..4e0b238 100644
--- a/core/java/android/os/StatFs.java
+++ b/core/java/android/os/StatFs.java
@@ -34,11 +34,16 @@
* class.
*
* @param path path in the desired file system to stat.
+ *
+ * @throws IllegalArgumentException if the file system access fails
*/
public StatFs(String path) {
mStat = doStat(path);
}
+ /**
+ * @throws IllegalArgumentException if the file system access fails
+ */
private static StructStatVfs doStat(String path) {
try {
return Os.statvfs(path);
@@ -51,6 +56,8 @@
* Perform a restat of the file system referenced by this object. This is
* the same as re-constructing the object with the same file system path,
* and the new stat values are available upon return.
+ *
+ * @throws IllegalArgumentException if the file system access fails
*/
public void restat(String path) {
mStat = doStat(path);
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 6c01b36..fa96dd3 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -16,6 +16,8 @@
package android.os;
+import com.android.internal.os.Zygote;
+
import dalvik.annotation.optimization.FastNative;
/**
@@ -94,6 +96,8 @@
// Must be volatile to avoid word tearing.
private static volatile long sEnabledTags = TRACE_TAG_NOT_READY;
+ private static int sZygoteDebugFlags = 0;
+
private static native long nativeGetEnabledTags();
private static native void nativeSetAppTracingAllowed(boolean allowed);
private static native void nativeSetTracingEnabled(boolean allowed);
@@ -118,9 +122,10 @@
// The system provides ordering through a priority level. Callbacks made through
// SystemProperties.addChangeCallback currently have a negative priority, while
// our native code is using a priority of zero.
- SystemProperties.addChangeCallback(new Runnable() {
- @Override public void run() {
- cacheEnabledTags();
+ SystemProperties.addChangeCallback(() -> {
+ cacheEnabledTags();
+ if ((sZygoteDebugFlags & Zygote.DEBUG_JAVA_DEBUGGABLE) != 0) {
+ traceCounter(TRACE_TAG_ALWAYS, "java_debuggable", 1);
}
});
}
@@ -201,8 +206,9 @@
*
* @hide
*/
- public static void setTracingEnabled(boolean enabled) {
+ public static void setTracingEnabled(boolean enabled, int debugFlags) {
nativeSetTracingEnabled(enabled);
+ sZygoteDebugFlags = debugFlags;
// Setting whether tracing is enabled may change the tags, so we update the cached tags
// here.
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a7fc2e7..3337def 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -901,11 +901,8 @@
* @return the user name
*/
public String getUserName() {
- try {
- return mService.getUserInfo(getUserHandle()).name;
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
+ UserInfo user = getUserInfo(getUserHandle());
+ return user == null ? "" : user.name;
}
/**
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 6aa601a..fe9e8c6 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -16,7 +16,7 @@
package android.os;
-import android.hardware.vibrator.V1_0.Constants.Effect;
+import android.hardware.vibrator.V1_1.Constants.Effect_1_1;
import java.util.Arrays;
@@ -41,7 +41,7 @@
* @see #get(int)
* @hide
*/
- public static final int EFFECT_CLICK = Effect.CLICK;
+ public static final int EFFECT_CLICK = Effect_1_1.CLICK;
/**
* A double click effect.
@@ -49,7 +49,14 @@
* @see #get(int)
* @hide
*/
- public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
+ public static final int EFFECT_DOUBLE_CLICK = Effect_1_1.DOUBLE_CLICK;
+
+ /**
+ * A tick effect.
+ * @see #get(int)
+ * @hide
+ */
+ public static final int EFFECT_TICK = Effect_1_1.TICK;
/** @hide to prevent subclassing from outside of the framework */
public VibrationEffect() { }
@@ -382,9 +389,14 @@
@Override
public void validate() {
- if (mEffectId != EFFECT_CLICK) {
- throw new IllegalArgumentException(
- "Unknown prebaked effect type (value=" + mEffectId + ")");
+ switch (mEffectId) {
+ case EFFECT_CLICK:
+ case EFFECT_DOUBLE_CLICK:
+ case EFFECT_TICK:
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown prebaked effect type (value=" + mEffectId + ")");
}
}
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 8208438..0d81bd9 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -19,6 +19,7 @@
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.Zygote;
import com.android.internal.util.Preconditions;
@@ -529,4 +530,27 @@
return (state.inputStream.readInt() == 0);
}
}
+
+ /**
+ * Try connecting to the Zygote over and over again until we hit a time-out.
+ * @param socketName The name of the socket to connect to.
+ */
+ public static void waitForConnectionToZygote(String socketName) {
+ for (int n = 20; n >= 0; n--) {
+ try {
+ final ZygoteState zs = ZygoteState.connect(socketName);
+ zs.close();
+ return;
+ } catch (IOException ioe) {
+ Log.w(LOG_TAG,
+ "Got error connecting to zygote, retrying. msg= " + ioe.getMessage());
+ }
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ie) {
+ }
+ }
+ Slog.wtf(LOG_TAG, "Failed to connect to Zygote through socket " + socketName);
+ }
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index a6cdb03..5046735 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1403,23 +1403,7 @@
/** {@hide} */
public static File maybeTranslateEmulatedPathToInternal(File path) {
- final IStorageManager storageManager = IStorageManager.Stub.asInterface(
- ServiceManager.getService("mount"));
- try {
- final VolumeInfo[] vols = storageManager.getVolumes(0);
- for (VolumeInfo vol : vols) {
- if ((vol.getType() == VolumeInfo.TYPE_EMULATED
- || vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.isMountedReadable()) {
- final File internalPath = FileUtils.rewriteAfterRename(vol.getPath(),
- vol.getInternalPath(), path);
- if (internalPath != null && internalPath.exists()) {
- return internalPath;
- }
- }
- }
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ // Disabled now that FUSE has been replaced by sdcardfs
return path;
}
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index e8ff2e2..52dccb4 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.app.Activity;
import android.app.Application.ActivityLifecycleCallbacks;
import android.content.ComponentName;
@@ -141,6 +142,7 @@
* @see #getPrintServices
* @hide
*/
+ @TestApi
public static final int ALL_SERVICES = ENABLED_SERVICES | DISABLED_SERVICES;
/**
@@ -627,6 +629,7 @@
*
* @hide
*/
+ @TestApi
@SystemApi
public @NonNull List<PrintServiceInfo> getPrintServices(int selectionFlags) {
Preconditions.checkFlagsArgument(selectionFlags, ALL_SERVICES);
diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java
index 57f1229..5ef9319 100644
--- a/core/java/android/printservice/PrintServiceInfo.java
+++ b/core/java/android/printservice/PrintServiceInfo.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -48,6 +49,7 @@
*
* @hide
*/
+@TestApi
@SystemApi
public final class PrintServiceInfo implements Parcelable {
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 5c4fe96..a8acb97 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -862,10 +862,34 @@
}
try {
+ // When cleaning up the call log, try to delete older call long entries on a per
+ // PhoneAccount basis first. There can be multiple ConnectionServices causing
+ // the addition of entries in the call log. With the introduction of Self-Managed
+ // ConnectionServices, we want to ensure that a misbehaving self-managed CS cannot
+ // spam the call log with its own entries, causing entries from Telephony to be
+ // removed.
final Uri result = resolver.insert(uri, values);
- resolver.delete(uri, "_id IN " +
- "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
- + " LIMIT -1 OFFSET 500)", null);
+ if (values.containsKey(PHONE_ACCOUNT_ID)
+ && !TextUtils.isEmpty(values.getAsString(PHONE_ACCOUNT_ID))
+ && values.containsKey(PHONE_ACCOUNT_COMPONENT_NAME)
+ && !TextUtils.isEmpty(values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME))) {
+ // Only purge entries for the same phone account.
+ resolver.delete(uri, "_id IN " +
+ "(SELECT _id FROM calls"
+ + " WHERE " + PHONE_ACCOUNT_COMPONENT_NAME + " = ?"
+ + " AND " + PHONE_ACCOUNT_ID + " = ?"
+ + " ORDER BY " + DEFAULT_SORT_ORDER
+ + " LIMIT -1 OFFSET 500)", new String[] {
+ values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME),
+ values.getAsString(PHONE_ACCOUNT_ID)
+ });
+ } else {
+ // No valid phone account specified, so default to the old behavior.
+ resolver.delete(uri, "_id IN " +
+ "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
+ + " LIMIT -1 OFFSET 500)", null);
+ }
+
return result;
} catch (IllegalArgumentException e) {
Log.w(LOG_TAG, "Failed to insert calllog", e);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 6a17ed1..784ed7a 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1779,6 +1779,12 @@
return mContentProvider;
}
}
+
+ public void clearProviderForTest() {
+ synchronized (mLock) {
+ mContentProvider = null;
+ }
+ }
}
// Thread-safe.
@@ -1994,6 +2000,16 @@
if (c != null) c.close();
}
}
+
+ public void clearGenerationTrackerForTest() {
+ synchronized (NameValueCache.this) {
+ if (mGenerationTracker != null) {
+ mGenerationTracker.destroy();
+ }
+ mValues.clear();
+ mGenerationTracker = null;
+ }
+ }
}
/**
@@ -2182,6 +2198,12 @@
outKeySet.addAll(MOVED_TO_GLOBAL);
}
+ /** @hide */
+ public static void clearProviderForTest() {
+ sProviderHolder.clearProviderForTest();
+ sNameValueCache.clearGenerationTrackerForTest();
+ }
+
/**
* Look up a name in the database.
* @param resolver to access the database with
@@ -4595,6 +4617,12 @@
outKeySet.addAll(MOVED_TO_GLOBAL);
}
+ /** @hide */
+ public static void clearProviderForTest() {
+ sProviderHolder.clearProviderForTest();
+ sNameValueCache.clearGenerationTrackerForTest();
+ }
+
/**
* Look up a name in the database.
* @param resolver to access the database with
@@ -5850,6 +5878,7 @@
*
* @hide
*/
+ @TestApi
public static final String DISABLED_PRINT_SERVICES =
"disabled_print_services";
@@ -6745,6 +6774,21 @@
"camera_double_twist_to_flip_enabled";
/**
+ * Whether or not the smart camera lift trigger that launches the camera when the user moves
+ * the phone into a position for taking photos should be enabled.
+ *
+ * @hide
+ */
+ public static final String CAMERA_LIFT_TRIGGER_ENABLED = "camera_lift_trigger_enabled";
+
+ /**
+ * The default enable state of the camera lift trigger.
+ *
+ * @hide
+ */
+ public static final int CAMERA_LIFT_TRIGGER_ENABLED_DEFAULT = 1;
+
+ /**
* Whether the assist gesture should be enabled.
*
* @hide
@@ -6759,6 +6803,13 @@
public static final String ASSIST_GESTURE_SENSITIVITY = "assist_gesture_sensitivity";
/**
+ * Whether Assist Gesture Deferred Setup has been completed
+ *
+ * @hide
+ */
+ public static final String ASSIST_GESTURE_SETUP_COMPLETE = "assist_gesture_setup_complete";
+
+ /**
* Control whether Night display is currently activated.
* @hide
*/
@@ -7033,8 +7084,6 @@
NIGHT_DISPLAY_CUSTOM_END_TIME,
NIGHT_DISPLAY_COLOR_TEMPERATURE,
NIGHT_DISPLAY_AUTO_MODE,
- NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
- NIGHT_DISPLAY_ACTIVATED,
SYNC_PARENT_SOUNDS,
CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED,
CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED,
@@ -7630,6 +7679,31 @@
public static final String FORCE_ALLOW_ON_EXTERNAL = "force_allow_on_external";
/**
+ * The default SM-DP+ configured for this device.
+ *
+ * <p>An SM-DP+ is used by an LPA (see {@link android.service.euicc.EuiccService}) to
+ * download profiles. If this value is set, the LPA will query this server for any profiles
+ * available to this device. If any are available, they may be downloaded during device
+ * provisioning or in settings without needing the user to enter an activation code.
+ *
+ * @see android.service.euicc.EuiccService
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+ public static final String DEFAULT_SM_DP_PLUS = "default_sm_dp_plus";
+
+ /**
+ * Whether any profile has ever been downloaded onto a eUICC on the device.
+ *
+ * <p>Used to hide eUICC UI from users who have never made use of it and would only be
+ * confused by seeing references to it in settings.
+ * (0 = false, 1 = true)
+ * @hide
+ */
+ public static final String EUICC_PROVISIONED = "euicc_provisioned";
+
+ /**
* Whether any activity can be resized. When this is true, any
* activity, regardless of manifest values, can be resized for multi-window.
* (0 = false, 1 = true)
@@ -8116,6 +8190,15 @@
*/
public static final String TETHER_DUN_APN = "tether_dun_apn";
+ /**
+ * Used to disable trying to talk to any available tethering offload HAL.
+ *
+ * Integer values are interpreted as boolean, and the absence of an explicit setting
+ * is interpreted as |false|.
+ * @hide
+ */
+ public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled";
+
/**
* List of carrier apps which are whitelisted to prompt the user for install when
* a sim card with matching uicc carrier privilege rules is inserted.
@@ -9120,6 +9203,23 @@
public static final String BATTERY_SAVER_CONSTANTS = "battery_saver_constants";
/**
+ * Battery anomaly detection specific settings
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * "anomaly_detection_enabled=true,wakelock_threshold=2000"
+ *
+ * The following keys are supported:
+ *
+ * <pre>
+ * anomaly_detection_enabled (boolean)
+ * wakelock_enabled (boolean)
+ * wakelock_threshold (long)
+ * </pre>
+ * @hide
+ */
+ public static final String ANOMALY_DETECTION_CONSTANTS = "anomaly_detection_constants";
+
+ /**
* App standby (app idle) specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
*
@@ -9958,6 +10058,14 @@
"location_settings_link_to_permissions_enabled";
/**
+ * Flag to enable use of RefactoredBackupManagerService.
+ *
+ * @hide
+ */
+ public static final String BACKUP_REFACTORED_SERVICE_DISABLED =
+ "backup_refactored_service_disabled";
+
+ /**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
*
@@ -10017,6 +10125,12 @@
outKeySet.addAll(MOVED_TO_SECURE);
}
+ /** @hide */
+ public static void clearProviderForTest() {
+ sProviderHolder.clearProviderForTest();
+ sNameValueCache.clearGenerationTrackerForTest();
+ }
+
/**
* Look up a name in the database.
* @param resolver to access the database with
diff --git a/core/java/android/provider/TimeZoneRulesDataContract.java b/core/java/android/provider/TimeZoneRulesDataContract.java
new file mode 100644
index 0000000..19e914b
--- /dev/null
+++ b/core/java/android/provider/TimeZoneRulesDataContract.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider;
+
+import android.net.Uri;
+
+/**
+ * A set of constants for implementing a time zone data content provider, which is used by the time
+ * zone updater application.
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public final class TimeZoneRulesDataContract {
+
+ private TimeZoneRulesDataContract() {}
+
+ /**
+ * The authority that <em>must</em> be used for the time zone data content provider.
+ * To be accepted by the time zone updater application it <em>must</em> be exposed by the
+ * package specified in the config_timeZoneRulesDataPackage config value.
+ */
+ public static final String AUTHORITY = "com.android.timezone";
+
+ /** A content:// style uri to the authority for the time zone data content provider */
+ private static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+
+ /**
+ * The content:// style URI for determining what type of update is available.
+ *
+ * <p>The URI can be queried using
+ * {@link android.content.ContentProvider#query(Uri, String[], String, String[], String)};
+ * the result will be a cursor with a single row. If the {@link #COLUMN_OPERATION}
+ * column is {@link #OPERATION_INSTALL} then see {@link #DATA_URI} for how to obtain the
+ * binary data.
+ */
+ public static final Uri OPERATION_URI = Uri.withAppendedPath(AUTHORITY_URI, "operation");
+
+ /**
+ * The {@code String} column of the {@link #OPERATION_URI} that provides an int specifying the
+ * type of operation to perform. See {@link #OPERATION_NO_OP}, {@link #OPERATION_UNINSTALL} and
+ * {@link #OPERATION_INSTALL}.
+ */
+ public static final String COLUMN_OPERATION = "operation";
+
+ /**
+ * An operation type used when the time zone rules on device should be left as they are.
+ * This is not expected to be used in normal operation but a safe result in the event of an
+ * error that cannot be recovered from.
+ */
+ public static final String OPERATION_NO_OP = "NOOP";
+
+ /**
+ * An operation type used when the current time zone rules on device should be uninstalled,
+ * returning to the values held in the system partition.
+ */
+ public static final String OPERATION_UNINSTALL = "UNINSTALL";
+
+ /**
+ * An operation type used when the current time zone rules on device should be replaced by
+ * a new set obtained via the {@link android.content.ContentProvider#openFile(Uri, String)}
+ * method.
+ */
+ public static final String OPERATION_INSTALL = "INSTALL";
+
+ /**
+ * The {@code nullable int} column of the {@link #OPERATION_URI} that describes the major
+ * version of the distro to be installed.
+ * Only non-null if {@link #COLUMN_OPERATION} contains {@link #OPERATION_INSTALL}.
+ */
+ public static final String COLUMN_DISTRO_MAJOR_VERSION = "distro_major_version";
+
+ /**
+ * The {@code nullable int} column of the {@link #OPERATION_URI} that describes the minor
+ * version of the distro to be installed.
+ * Only non-null if {@link #COLUMN_OPERATION} contains {@link #OPERATION_INSTALL}.
+ */
+ public static final String COLUMN_DISTRO_MINOR_VERSION = "distro_minor_version";
+
+ /**
+ * The {@code nullable String} column of the {@link #OPERATION_URI} that describes the IANA
+ * rules version of the distro to be installed.
+ * Only non-null if {@link #COLUMN_OPERATION} contains {@link #OPERATION_INSTALL}.
+ */
+ public static final String COLUMN_RULES_VERSION = "rules_version";
+
+ /**
+ * The {@code nullable int} column of the {@link #OPERATION_URI} that describes the revision
+ * number of the distro to be installed.
+ * Only non-null if {@link #COLUMN_OPERATION} contains {@link #OPERATION_INSTALL}.
+ */
+ public static final String COLUMN_REVISION = "revision";
+
+ /**
+ * The content:// style URI for obtaining time zone bundle data.
+ *
+ * <p>Use {@link android.content.ContentProvider#openFile(Uri, String)} with "r" mode.
+ */
+ public static final Uri DATA_URI = Uri.withAppendedPath(AUTHORITY_URI, "data");
+}
diff --git a/core/java/android/service/autofill/FillContext.java b/core/java/android/service/autofill/FillContext.java
index 251d346..6956c8a 100644
--- a/core/java/android/service/autofill/FillContext.java
+++ b/core/java/android/service/autofill/FillContext.java
@@ -114,7 +114,7 @@
*
* @hide
*/
- @NonNull public ViewNode[] findViewNodesByAutofillIds(@NonNull AutofillId[] ids) {
+ @NonNull public ViewNode[] findViewNodesByAutofillIds(@NonNull AutofillId... ids) {
final LinkedList<ViewNode> nodesToProcess = new LinkedList<>();
final ViewNode[] foundNodes = new AssistStructure.ViewNode[ids.length];
diff --git a/core/java/android/service/euicc/DeleteResult.aidl b/core/java/android/service/euicc/DeleteResult.aidl
new file mode 100644
index 0000000..3da8b49
--- /dev/null
+++ b/core/java/android/service/euicc/DeleteResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+parcelable DeleteResult;
diff --git a/core/java/android/service/euicc/DeleteResult.java b/core/java/android/service/euicc/DeleteResult.java
new file mode 100644
index 0000000..8be9ac9
--- /dev/null
+++ b/core/java/android/service/euicc/DeleteResult.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.euicc;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Result of a {@link EuiccService#onDeleteSubscription} operation.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+public final class DeleteResult implements Parcelable {
+
+ public static final Creator<DeleteResult> CREATOR = new Creator<DeleteResult>() {
+ @Override
+ public DeleteResult createFromParcel(Parcel in) {
+ return new DeleteResult(in);
+ }
+
+ @Override
+ public DeleteResult[] newArray(int size) {
+ return new DeleteResult[size];
+ }
+ };
+
+ /** @hide */
+ @IntDef({
+ RESULT_OK,
+ RESULT_GENERIC_ERROR,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultCode {}
+
+ public static final int RESULT_OK = 0;
+ public static final int RESULT_GENERIC_ERROR = 1;
+
+ /** Result of the operation - one of the RESULT_* constants. */
+ public final @ResultCode int result;
+
+ /** Implementation-defined detailed error code in case of a failure not covered here. */
+ public final int detailedCode;
+
+ private DeleteResult(int result, int detailedCode) {
+ this.result = result;
+ this.detailedCode = detailedCode;
+ }
+
+ private DeleteResult(Parcel in) {
+ this.result = in.readInt();
+ this.detailedCode = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(result);
+ dest.writeInt(detailedCode);
+ }
+
+ /** Return a result indicating that the delete was successful. */
+ public static DeleteResult success() {
+ return new DeleteResult(RESULT_OK, 0);
+ }
+
+ /**
+ * Return a result indicating that an error occurred for which no other more specific error
+ * code has been defined.
+ *
+ * @param detailedCode an implemenation-defined detailed error code for debugging purposes.
+ */
+ public static DeleteResult genericError(int detailedCode) {
+ return new DeleteResult(RESULT_GENERIC_ERROR, detailedCode);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/service/euicc/DownloadResult.aidl b/core/java/android/service/euicc/DownloadResult.aidl
new file mode 100644
index 0000000..66ec999
--- /dev/null
+++ b/core/java/android/service/euicc/DownloadResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+parcelable DownloadResult;
diff --git a/core/java/android/service/euicc/DownloadResult.java b/core/java/android/service/euicc/DownloadResult.java
new file mode 100644
index 0000000..ad75bff
--- /dev/null
+++ b/core/java/android/service/euicc/DownloadResult.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.euicc;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Result of a {@link EuiccService#onDownloadSubscription} operation.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+public final class DownloadResult implements Parcelable {
+
+ public static final Creator<DownloadResult> CREATOR = new Creator<DownloadResult>() {
+ @Override
+ public DownloadResult createFromParcel(Parcel in) {
+ return new DownloadResult(in);
+ }
+
+ @Override
+ public DownloadResult[] newArray(int size) {
+ return new DownloadResult[size];
+ }
+ };
+
+ /** @hide */
+ @IntDef({
+ RESULT_OK,
+ RESULT_GENERIC_ERROR,
+ RESULT_MUST_DEACTIVATE_SIM,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultCode {}
+
+ public static final int RESULT_OK = 0;
+ public static final int RESULT_GENERIC_ERROR = 1;
+ public static final int RESULT_MUST_DEACTIVATE_SIM = 2;
+
+ /** Result of the operation - one of the RESULT_* constants. */
+ public final @ResultCode int result;
+
+ /** Implementation-defined detailed error code in case of a failure not covered here. */
+ public final int detailedCode;
+
+ private DownloadResult(int result, int detailedCode) {
+ this.result = result;
+ this.detailedCode = detailedCode;
+ }
+
+ private DownloadResult(Parcel in) {
+ this.result = in.readInt();
+ this.detailedCode = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(result);
+ dest.writeInt(detailedCode);
+ }
+
+ /** Return a result indicating that the download was successful. */
+ public static DownloadResult success() {
+ return new DownloadResult(RESULT_OK, 0);
+ }
+
+ /**
+ * Return a result indicating that an active SIM must be deactivated to perform the operation.
+ */
+ public static DownloadResult mustDeactivateSim() {
+ return new DownloadResult(RESULT_MUST_DEACTIVATE_SIM, 0);
+ }
+
+ /**
+ * Return a result indicating that an error occurred for which no other more specific error
+ * code has been defined.
+ *
+ * @param detailedCode an implemenation-defined detailed error code for debugging purposes.
+ */
+ public static DownloadResult genericError(int detailedCode) {
+ return new DownloadResult(RESULT_GENERIC_ERROR, detailedCode);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/service/euicc/EraseResult.aidl b/core/java/android/service/euicc/EraseResult.aidl
new file mode 100644
index 0000000..e28a097
--- /dev/null
+++ b/core/java/android/service/euicc/EraseResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+parcelable EraseResult;
diff --git a/core/java/android/service/euicc/EraseResult.java b/core/java/android/service/euicc/EraseResult.java
new file mode 100644
index 0000000..1cce5a9
--- /dev/null
+++ b/core/java/android/service/euicc/EraseResult.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.euicc;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Result of a {@link EuiccService#onEraseSubscriptions} operation.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+public final class EraseResult implements Parcelable {
+
+ public static final Creator<EraseResult> CREATOR = new Creator<EraseResult>() {
+ @Override
+ public EraseResult createFromParcel(Parcel in) {
+ return new EraseResult(in);
+ }
+
+ @Override
+ public EraseResult[] newArray(int size) {
+ return new EraseResult[size];
+ }
+ };
+
+ /** @hide */
+ @IntDef({
+ RESULT_OK,
+ RESULT_GENERIC_ERROR,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultCode {}
+
+ public static final int RESULT_OK = 0;
+ public static final int RESULT_GENERIC_ERROR = 1;
+
+ /** Result of the operation - one of the RESULT_* constants. */
+ public final @ResultCode int result;
+
+ /** Implementation-defined detailed error code in case of a failure not covered here. */
+ public final int detailedCode;
+
+ private EraseResult(int result, int detailedCode) {
+ this.result = result;
+ this.detailedCode = detailedCode;
+ }
+
+ private EraseResult(Parcel in) {
+ this.result = in.readInt();
+ this.detailedCode = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(result);
+ dest.writeInt(detailedCode);
+ }
+
+ /** Return a result indicating that the erase was successful. */
+ public static EraseResult success() {
+ return new EraseResult(RESULT_OK, 0);
+ }
+
+ /**
+ * Return a result indicating that an error occurred for which no other more specific error
+ * code has been defined.
+ *
+ * @param detailedCode an implemenation-defined detailed error code for debugging purposes.
+ */
+ public static EraseResult genericError(int detailedCode) {
+ return new EraseResult(RESULT_GENERIC_ERROR, detailedCode);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/service/euicc/EuiccProfileInfo.java b/core/java/android/service/euicc/EuiccProfileInfo.java
new file mode 100644
index 0000000..ba6c9a2
--- /dev/null
+++ b/core/java/android/service/euicc/EuiccProfileInfo.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.euicc;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.UiccAccessRule;
+import android.text.TextUtils;
+
+/**
+ * Information about an embedded profile (subscription) on an eUICC.
+ *
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+public final class EuiccProfileInfo implements Parcelable {
+
+ /** The iccid of the subscription. */
+ public final String iccid;
+
+ /**
+ * Optional access rules defining which apps can manage this subscription. If unset, only the
+ * platform can manage it.
+ */
+ public final @Nullable UiccAccessRule[] accessRules;
+
+ /** An optional nickname for the subscription. */
+ public final @Nullable String nickname;
+
+ public static final Creator<EuiccProfileInfo> CREATOR = new Creator<EuiccProfileInfo>() {
+ @Override
+ public EuiccProfileInfo createFromParcel(Parcel in) {
+ return new EuiccProfileInfo(in);
+ }
+
+ @Override
+ public EuiccProfileInfo[] newArray(int size) {
+ return new EuiccProfileInfo[size];
+ }
+ };
+
+ public EuiccProfileInfo(String iccid, @Nullable UiccAccessRule[] accessRules,
+ @Nullable String nickname) {
+ if (!TextUtils.isDigitsOnly(iccid)) {
+ throw new IllegalArgumentException("iccid contains invalid characters: " + iccid);
+ }
+ this.iccid = iccid;
+ this.accessRules = accessRules;
+ this.nickname = nickname;
+ }
+
+ private EuiccProfileInfo(Parcel in) {
+ iccid = in.readString();
+ accessRules = in.createTypedArray(UiccAccessRule.CREATOR);
+ nickname = in.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(iccid);
+ dest.writeTypedArray(accessRules, flags);
+ dest.writeString(nickname);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
new file mode 100644
index 0000000..3734904
--- /dev/null
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.euicc;
+
+import android.annotation.CallSuper;
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telephony.euicc.DownloadableSubscription;
+import android.telephony.euicc.EuiccInfo;
+import android.util.ArraySet;
+
+/**
+ * Service interface linking the system with an eUICC local profile assistant (LPA) application.
+ *
+ * <p>An LPA consists of two separate components (which may both be implemented in the same APK):
+ * the LPA backend, and the LPA UI or LUI.
+ *
+ * <p>To implement the LPA backend, you must extend this class and declare this service in your
+ * manifest file. The service must require the
+ * {@link android.Manifest.permission#BIND_EUICC_SERVICE} permission and include an intent filter
+ * with the {@link #EUICC_SERVICE_INTERFACE} action. The priority of the intent filter must be set
+ * to a non-zero value in case multiple implementations are present on the device. For example:
+ *
+ * <pre>{@code
+ * <service android:name=".MyEuiccService"
+ * android:permission="android.permission.BIND_EUICC_SERVICE">
+ * <intent-filter android:priority="100">
+ * <action android:name="android.service.euicc.EuiccService" />
+ * </intent-filter>
+ * </service>
+ * }</pre>
+ *
+ * <p>To implement the LUI, you must provide an activity for the following actions:
+ *
+ * <ul>
+ * <li>{@link #ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS}
+ * <li>{@link #ACTION_PROVISION_EMBEDDED_SUBSCRIPTION}
+ * </ul>
+ *
+ * <p>As with the service, each activity must require the
+ * {@link android.Manifest.permission#BIND_EUICC_SERVICE} permission. Each should have an intent
+ * filter with the appropriate action, the {@link #CATEGORY_EUICC_UI} category, and a non-zero
+ * priority.
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ * @hide
+ */
+public abstract class EuiccService extends Service {
+ /** Action which must be included in this service's intent filter. */
+ public static final String EUICC_SERVICE_INTERFACE = "android.service.euicc.EuiccService";
+
+ /** Category which must be defined to all UI actions, for efficient lookup. */
+ public static final String CATEGORY_EUICC_UI = "android.service.euicc.category.EUICC_UI";
+
+ // LUI actions. These are passthroughs of the corresponding EuiccManager actions.
+
+ /** @see android.telephony.euicc.EuiccManager#ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS */
+ public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS =
+ "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
+ /** @see android.telephony.euicc.EuiccManager#ACTION_PROVISION_EMBEDDED_SUBSCRIPTION */
+ public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION =
+ "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
+
+ // LUI resolution actions. These are called by the platform to resolve errors in situations that
+ // require user interaction.
+ // TODO(b/33075886): Define extras for any input parameters to these dialogs once they are
+ // more scoped out.
+ /** Alert the user that this action will result in an active SIM being deactivated. */
+ public static final String ACTION_RESOLVE_DEACTIVATE_SIM =
+ "android.service.euicc.action.RESOLVE_DEACTIVATE_SIM";
+ /**
+ * Alert the user about a download/switch being done for an app that doesn't currently have
+ * carrier privileges.
+ */
+ public static final String ACTION_RESOLVE_NO_PRIVILEGES =
+ "android.service.euicc.action.RESOLVE_NO_PRIVILEGES";
+ /**
+ * List of all valid resolution actions for validation purposes.
+ * @hide
+ */
+ public static final ArraySet<String> RESOLUTION_ACTIONS;
+ static {
+ RESOLUTION_ACTIONS = new ArraySet<>();
+ RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM);
+ RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_NO_PRIVILEGES);
+ }
+
+ /** Boolean extra for resolution actions indicating whether the user granted consent. */
+ public static final String RESOLUTION_EXTRA_CONSENT = "consent";
+
+ private final IEuiccService.Stub mStubWrapper;
+
+ public EuiccService() {
+ mStubWrapper = new IEuiccServiceWrapper();
+ }
+
+ /**
+ * If overriding this method, call through to the super method for any unknown actions.
+ * {@inheritDoc}
+ */
+ @Override
+ @CallSuper
+ public IBinder onBind(Intent intent) {
+ return mStubWrapper;
+ }
+
+ /**
+ * Return the EID of the eUICC.
+ *
+ * @param slotId ID of the SIM slot being queried. This is currently not populated but is here
+ * to future-proof the APIs.
+ * @return the EID.
+ * @see android.telephony.euicc.EuiccManager#getEid
+ */
+ // TODO(b/36260308): Update doc when we have multi-SIM support.
+ public abstract String onGetEid(int slotId);
+
+ /**
+ * Populate {@link DownloadableSubscription} metadata for the given downloadable subscription.
+ *
+ * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
+ * but is here to future-proof the APIs.
+ * @param subscription A subscription whose metadata needs to be populated.
+ * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the
+ * eUICC, perform this action automatically. Otherwise,
+ * {@link GetDownloadableSubscriptionMetadataResult#mustDeactivateSim()} should be returned
+ * to allow the user to consent to this operation first.
+ * @return The result of the operation.
+ * @see android.telephony.euicc.EuiccManager#getDownloadableSubscriptionMetadata
+ */
+ public abstract GetDownloadableSubscriptionMetadataResult onGetDownloadableSubscriptionMetadata(
+ int slotId, DownloadableSubscription subscription, boolean forceDeactivateSim);
+
+ /**
+ * Return metadata for subscriptions which are available for download for this device.
+ *
+ * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
+ * but is here to future-proof the APIs.
+ * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the
+ * eUICC, perform this action automatically. Otherwise,
+ * {@link GetDefaultDownloadableSubscriptionListResult#mustDeactivateSim()} should be
+ * returned to allow the user to consent to this operation first.
+ * @return The result of the list operation.
+ * @see android.telephony.euicc.EuiccManager#getDefaultDownloadableSubscriptionList
+ */
+ public abstract GetDefaultDownloadableSubscriptionListResult
+ onGetDefaultDownloadableSubscriptionList(int slotId, boolean forceDeactivateSim);
+
+ /**
+ * Download the given subscription.
+ *
+ * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
+ * but is here to future-proof the APIs.
+ * @param subscription The subscription to download.
+ * @param switchAfterDownload If true, the subscription should be enabled upon successful
+ * download.
+ * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the
+ * eUICC, perform this action automatically. Otherwise,
+ * {@link DownloadResult#mustDeactivateSim()} should be returned to allow the user to
+ * consent to this operation first.
+ * @return the result of the download operation.
+ * @see android.telephony.euicc.EuiccManager#downloadSubscription
+ */
+ public abstract DownloadResult onDownloadSubscription(int slotId,
+ DownloadableSubscription subscription, boolean switchAfterDownload,
+ boolean forceDeactivateSim);
+
+ /**
+ * Return a list of all @link EuiccProfileInfo}s.
+ *
+ * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
+ * but is here to future-proof the APIs.
+ * @return The result of the operation.
+ * @see android.telephony.SubscriptionManager#getAvailableSubscriptionInfoList
+ * @see android.telephony.SubscriptionManager#getAccessibleSubscriptionInfoList
+ */
+ public abstract GetEuiccProfileInfoListResult onGetEuiccProfileInfoList(int slotId);
+
+ /**
+ * Return info about the eUICC chip/device.
+ *
+ * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
+ * but is here to future-proof the APIs.
+ * @return the {@link EuiccInfo} for the eUICC chip/device.
+ * @see android.telephony.euicc.EuiccManager#getEuiccInfo
+ */
+ public abstract EuiccInfo onGetEuiccInfo(int slotId);
+
+ /**
+ * Delete the given subscription.
+ *
+ * <p>If the subscription is currently active, it should be deactivated first (equivalent to a
+ * physical SIM being ejected).
+ *
+ * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
+ * but is here to future-proof the APIs.
+ * @param iccid the ICCID of the subscription to delete.
+ * @return the result of the delete operation.
+ * @see android.telephony.euicc.EuiccManager#deleteSubscription
+ */
+ public abstract DeleteResult onDeleteSubscription(int slotId, String iccid);
+
+ /**
+ * Switch to the given subscription.
+ *
+ * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
+ * but is here to future-proof the APIs.
+ * @param iccid the ICCID of the subscription to enable. May be null, in which case the current
+ * profile should be deactivated and no profile should be activated to replace it - this is
+ * equivalent to a physical SIM being ejected.
+ * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the
+ * eUICC, perform this action automatically. Otherwise,
+ * {@link SwitchResult#mustDeactivateSim()} should be returned to allow the user to consent
+ * to this operation first.
+ * @return the result of the switch operation.
+ * @see android.telephony.euicc.EuiccManager#switchToSubscription
+ */
+ public abstract SwitchResult onSwitchToSubscription(int slotId, @Nullable String iccid,
+ boolean forceDeactivateSim);
+
+ /**
+ * Update the nickname of the given subscription.
+ *
+ * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
+ * but is here to future-proof the APIs.
+ * @param iccid the ICCID of the subscription to update.
+ * @param nickname the new nickname to apply.
+ * @return the result of the update operation.
+ * @see android.telephony.euicc.EuiccManager#updateSubscriptionNickname
+ */
+ public abstract UpdateNicknameResult onUpdateSubscriptionNickname(int slotId, String iccid,
+ String nickname);
+
+ /**
+ * Erase all of the subscriptions on the device.
+ *
+ * <p>This is intended to be used for device resets. As such, the reset should be performed even
+ * if an active SIM must be deactivated in order to access the eUICC.
+ *
+ * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
+ * but is here to future-proof the APIs.
+ * @return the result of the erase operation.
+ * @see android.telephony.euicc.EuiccManager#eraseSubscriptions
+ */
+ public abstract EraseResult onEraseSubscriptions(int slotId);
+
+ /**
+ * Wrapper around IEuiccService that forwards calls to implementations of {@link EuiccService}.
+ */
+ private class IEuiccServiceWrapper extends IEuiccService.Stub {
+ @Override
+ public void downloadSubscription(int slotId, DownloadableSubscription subscription,
+ boolean switchAfterDownload, boolean forceDeactivateSim,
+ IDownloadSubscriptionCallback callback) {
+ DownloadResult result = EuiccService.this.onDownloadSubscription(
+ slotId, subscription, switchAfterDownload, forceDeactivateSim);
+ try {
+ callback.onComplete(result);
+ } catch (RemoteException e) {
+ // Can't communicate with the phone process; ignore.
+ }
+ }
+
+ @Override
+ public void getEid(int slotId, IGetEidCallback callback) {
+ String eid = EuiccService.this.onGetEid(slotId);
+ try {
+ callback.onSuccess(eid);
+ } catch (RemoteException e) {
+ // Can't communicate with the phone process; ignore.
+ }
+ }
+
+ @Override
+ public void getDownloadableSubscriptionMetadata(int slotId,
+ DownloadableSubscription subscription,
+ boolean forceDeactivateSim,
+ IGetDownloadableSubscriptionMetadataCallback callback) {
+ GetDownloadableSubscriptionMetadataResult result =
+ EuiccService.this.onGetDownloadableSubscriptionMetadata(
+ slotId, subscription, forceDeactivateSim);
+ try {
+ callback.onComplete(result);
+ } catch (RemoteException e) {
+ // Can't communicate with the phone process; ignore.
+ }
+ }
+
+ @Override
+ public void getDefaultDownloadableSubscriptionList(int slotId, boolean forceDeactivateSim,
+ IGetDefaultDownloadableSubscriptionListCallback callback) {
+ GetDefaultDownloadableSubscriptionListResult result =
+ EuiccService.this.onGetDefaultDownloadableSubscriptionList(
+ slotId, forceDeactivateSim);
+ try {
+ callback.onComplete(result);
+ } catch (RemoteException e) {
+ // Can't communicate with the phone process; ignore.
+ }
+ }
+
+ @Override
+ public void getEuiccProfileInfoList(int slotId, IGetEuiccProfileInfoListCallback callback) {
+ GetEuiccProfileInfoListResult result =
+ EuiccService.this.onGetEuiccProfileInfoList(slotId);
+ try {
+ callback.onComplete(result);
+ } catch (RemoteException e) {
+ // Can't communicate with the phone process; ignore.
+ }
+ }
+
+ @Override
+ public void getEuiccInfo(int slotId, IGetEuiccInfoCallback callback) {
+ EuiccInfo euiccInfo = EuiccService.this.onGetEuiccInfo(slotId);
+ try {
+ callback.onSuccess(euiccInfo);
+ } catch (RemoteException e) {
+ // Can't communicate with the phone process; ignore.
+ }
+ }
+
+ @Override
+ public void deleteSubscription(int slotId, String iccid,
+ IDeleteSubscriptionCallback callback) {
+ DeleteResult result = EuiccService.this.onDeleteSubscription(slotId, iccid);
+ try {
+ callback.onComplete(result);
+ } catch (RemoteException e) {
+ // Can't communicate with the phone process; ignore.
+ }
+ }
+
+ @Override
+ public void switchToSubscription(int slotId, String iccid, boolean forceDeactivateSim,
+ ISwitchToSubscriptionCallback callback) {
+ SwitchResult result =
+ EuiccService.this.onSwitchToSubscription(slotId, iccid, forceDeactivateSim);
+ try {
+ callback.onComplete(result);
+ } catch (RemoteException e) {
+ // Can't communicate with the phone process; ignore.
+ }
+ }
+
+ @Override
+ public void updateSubscriptionNickname(int slotId, String iccid, String nickname,
+ IUpdateSubscriptionNicknameCallback callback) {
+ UpdateNicknameResult result =
+ EuiccService.this.onUpdateSubscriptionNickname(slotId, iccid, nickname);
+ try {
+ callback.onComplete(result);
+ } catch (RemoteException e) {
+ // Can't communicate with the phone process; ignore.
+ }
+ }
+
+ @Override
+ public void eraseSubscriptions(int slotId, IEraseSubscriptionsCallback callback) {
+ EraseResult result = EuiccService.this.onEraseSubscriptions(slotId);
+ try {
+ callback.onComplete(result);
+ } catch (RemoteException e) {
+ // Can't communicate with the phone process; ignore.
+ }
+ }
+ }
+}
diff --git a/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.aidl b/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.aidl
new file mode 100644
index 0000000..c2636a1
--- /dev/null
+++ b/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+parcelable GetDefaultDownloadableSubscriptionListResult;
diff --git a/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java b/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java
new file mode 100644
index 0000000..95569b2
--- /dev/null
+++ b/core/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.euicc;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.euicc.DownloadableSubscription;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Result of a {@link EuiccService#onGetDefaultDownloadableSubscriptionList} operation.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+public final class GetDefaultDownloadableSubscriptionListResult implements Parcelable {
+
+ public static final Creator<GetDefaultDownloadableSubscriptionListResult> CREATOR =
+ new Creator<GetDefaultDownloadableSubscriptionListResult>() {
+ @Override
+ public GetDefaultDownloadableSubscriptionListResult createFromParcel(Parcel in) {
+ return new GetDefaultDownloadableSubscriptionListResult(in);
+ }
+
+ @Override
+ public GetDefaultDownloadableSubscriptionListResult[] newArray(int size) {
+ return new GetDefaultDownloadableSubscriptionListResult[size];
+ }
+ };
+
+ /** @hide */
+ @IntDef({
+ RESULT_OK,
+ RESULT_GENERIC_ERROR,
+ RESULT_MUST_DEACTIVATE_SIM,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultCode {}
+
+ public static final int RESULT_OK = 0;
+ public static final int RESULT_MUST_DEACTIVATE_SIM = 1;
+ public static final int RESULT_GENERIC_ERROR = 2;
+
+ /** Result of the operation - one of the RESULT_* constants. */
+ public final @ResultCode int result;
+
+ /**
+ * The available {@link DownloadableSubscription}s (with filled-in metadata).
+ *
+ * <p>Only non-null if {@link #result} is {@link #RESULT_OK}.
+ */
+ @Nullable
+ public final DownloadableSubscription[] subscriptions;
+
+ /** Implementation-defined detailed error code in case of a failure not covered here. */
+ public final int detailedCode;
+
+ private GetDefaultDownloadableSubscriptionListResult(int result,
+ @Nullable DownloadableSubscription[] subscriptions, int detailedCode) {
+ this.result = result;
+ this.subscriptions = subscriptions;
+ this.detailedCode = detailedCode;
+ }
+
+ private GetDefaultDownloadableSubscriptionListResult(Parcel in) {
+ this.result = in.readInt();
+ this.subscriptions = in.createTypedArray(DownloadableSubscription.CREATOR);
+ this.detailedCode = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(result);
+ dest.writeTypedArray(subscriptions, flags);
+ dest.writeInt(detailedCode);
+ }
+
+ /** Return a result indicating that the list operation was successful. */
+ public static GetDefaultDownloadableSubscriptionListResult success(
+ DownloadableSubscription[] subscriptions) {
+ return new GetDefaultDownloadableSubscriptionListResult(RESULT_OK, subscriptions,
+ 0 /* detailedCode */);
+ }
+
+ /**
+ * Return a result indicating that an active SIM must be deactivated to perform the operation.
+ */
+ public static GetDefaultDownloadableSubscriptionListResult mustDeactivateSim() {
+ return new GetDefaultDownloadableSubscriptionListResult(RESULT_MUST_DEACTIVATE_SIM,
+ null /* subscription */, 0 /* detailedCode */);
+ }
+
+ /**
+ * Return a result indicating that an error occurred for which no other more specific error
+ * code has been defined.
+ *
+ * @param detailedCode an implementation-defined detailed error code for debugging purposes.
+ */
+ public static GetDefaultDownloadableSubscriptionListResult genericError(int detailedCode) {
+ return new GetDefaultDownloadableSubscriptionListResult(RESULT_GENERIC_ERROR,
+ null /* subscription */, detailedCode);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.aidl b/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.aidl
new file mode 100644
index 0000000..791ad9b
--- /dev/null
+++ b/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+parcelable GetDownloadableSubscriptionMetadataResult;
diff --git a/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java b/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java
new file mode 100644
index 0000000..99e7614
--- /dev/null
+++ b/core/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.euicc;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.euicc.DownloadableSubscription;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Result of a {@link EuiccService#onGetDownloadableSubscriptionMetadata} operation.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+public final class GetDownloadableSubscriptionMetadataResult implements Parcelable {
+
+ public static final Creator<GetDownloadableSubscriptionMetadataResult> CREATOR =
+ new Creator<GetDownloadableSubscriptionMetadataResult>() {
+ @Override
+ public GetDownloadableSubscriptionMetadataResult createFromParcel(Parcel in) {
+ return new GetDownloadableSubscriptionMetadataResult(in);
+ }
+
+ @Override
+ public GetDownloadableSubscriptionMetadataResult[] newArray(int size) {
+ return new GetDownloadableSubscriptionMetadataResult[size];
+ }
+ };
+
+ /** @hide */
+ @IntDef({
+ RESULT_OK,
+ RESULT_GENERIC_ERROR,
+ RESULT_MUST_DEACTIVATE_SIM,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultCode {}
+
+ public static final int RESULT_OK = 0;
+ public static final int RESULT_MUST_DEACTIVATE_SIM = 1;
+ public static final int RESULT_GENERIC_ERROR = 2;
+
+ /** Result of the operation - one of the RESULT_* constants. */
+ public final @ResultCode int result;
+
+ /**
+ * The {@link DownloadableSubscription} with filled-in metadata.
+ *
+ * <p>Only non-null if {@link #result} is {@link #RESULT_OK}.
+ */
+ @Nullable
+ public final DownloadableSubscription subscription;
+
+ /** Implementation-defined detailed error code in case of a failure not covered here. */
+ public final int detailedCode;
+
+ private GetDownloadableSubscriptionMetadataResult(int result,
+ @Nullable DownloadableSubscription subscription, int detailedCode) {
+ this.result = result;
+ this.subscription = subscription;
+ this.detailedCode = detailedCode;
+ }
+
+ private GetDownloadableSubscriptionMetadataResult(Parcel in) {
+ this.result = in.readInt();
+ this.subscription = in.readTypedObject(DownloadableSubscription.CREATOR);
+ this.detailedCode = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(result);
+ dest.writeTypedObject(this.subscription, flags);
+ dest.writeInt(detailedCode);
+ }
+
+ /** Return a result indicating that the lookup was successful. */
+ public static GetDownloadableSubscriptionMetadataResult success(
+ DownloadableSubscription subscription) {
+ return new GetDownloadableSubscriptionMetadataResult(RESULT_OK, subscription,
+ 0 /* detailedCode */);
+ }
+
+ /**
+ * Return a result indicating that an active SIM must be deactivated to perform the operation.
+ */
+ public static GetDownloadableSubscriptionMetadataResult mustDeactivateSim() {
+ return new GetDownloadableSubscriptionMetadataResult(RESULT_MUST_DEACTIVATE_SIM,
+ null /* subscription */, 0 /* detailedCode */);
+ }
+
+ /**
+ * Return a result indicating that an error occurred for which no other more specific error
+ * code has been defined.
+ *
+ * @param detailedCode an implementation-defined detailed error code for debugging purposes.
+ */
+ public static GetDownloadableSubscriptionMetadataResult genericError(int detailedCode) {
+ return new GetDownloadableSubscriptionMetadataResult(RESULT_GENERIC_ERROR,
+ null /* subscription */, detailedCode);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/service/euicc/GetEuiccProfileInfoListResult.aidl b/core/java/android/service/euicc/GetEuiccProfileInfoListResult.aidl
new file mode 100644
index 0000000..6003b79
--- /dev/null
+++ b/core/java/android/service/euicc/GetEuiccProfileInfoListResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+parcelable GetEuiccProfileInfoListResult;
diff --git a/core/java/android/service/euicc/GetEuiccProfileInfoListResult.java b/core/java/android/service/euicc/GetEuiccProfileInfoListResult.java
new file mode 100644
index 0000000..5ac10fe
--- /dev/null
+++ b/core/java/android/service/euicc/GetEuiccProfileInfoListResult.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.euicc;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Result of a {@link EuiccService#onGetEuiccProfileInfoList} operation.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+public final class GetEuiccProfileInfoListResult implements Parcelable {
+
+ public static final Creator<GetEuiccProfileInfoListResult> CREATOR =
+ new Creator<GetEuiccProfileInfoListResult>() {
+ @Override
+ public GetEuiccProfileInfoListResult createFromParcel(Parcel in) {
+ return new GetEuiccProfileInfoListResult(in);
+ }
+
+ @Override
+ public GetEuiccProfileInfoListResult[] newArray(int size) {
+ return new GetEuiccProfileInfoListResult[size];
+ }
+ };
+
+ /** @hide */
+ @IntDef({
+ RESULT_OK,
+ RESULT_GENERIC_ERROR,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultCode {}
+
+ public static final int RESULT_OK = 0;
+ public static final int RESULT_GENERIC_ERROR = 1;
+
+ /** Result of the operation - one of the RESULT_* constants. */
+ public final @ResultCode int result;
+
+ /** Implementation-defined detailed error code in case of a failure not covered here. */
+ public final int detailedCode;
+
+ /** The profile list (only upon success). */
+ @Nullable
+ public final EuiccProfileInfo[] profiles;
+
+ /** Whether the eUICC is removable. */
+ public final boolean isRemovable;
+
+ private GetEuiccProfileInfoListResult(int result, int detailedCode, EuiccProfileInfo[] profiles,
+ boolean isRemovable) {
+ this.result = result;
+ this.detailedCode = detailedCode;
+ this.profiles = profiles;
+ this.isRemovable = isRemovable;
+ }
+
+ private GetEuiccProfileInfoListResult(Parcel in) {
+ this.result = in.readInt();
+ this.detailedCode = in.readInt();
+ this.profiles = in.createTypedArray(EuiccProfileInfo.CREATOR);
+ this.isRemovable = in.readBoolean();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(result);
+ dest.writeInt(detailedCode);
+ dest.writeTypedArray(profiles, flags);
+ dest.writeBoolean(isRemovable);
+ }
+
+ /**
+ * Return a result indicating that the listing was successful.
+ *
+ * @param profiles the list of profiles
+ * @param isRemovable whether the eUICC in this slot is removable. If true, the profiles
+ * returned here will only be considered accessible as long as this eUICC is present.
+ * Otherwise, they will remain accessible until the next time a response with isRemovable
+ * set to false is returned.
+ */
+ public static GetEuiccProfileInfoListResult success(
+ EuiccProfileInfo[] profiles, boolean isRemovable) {
+ return new GetEuiccProfileInfoListResult(
+ RESULT_OK, 0 /* detailedCode */, profiles, isRemovable);
+ }
+
+ /**
+ * Return a result indicating that an error occurred for which no other more specific error
+ * code has been defined.
+ *
+ * @param detailedCode an implementation-defined detailed error code for debugging purposes.
+ * @param isRemovable whether the eUICC in this slot is removable. If true, only removable
+ * profiles will be made inaccessible. Otherwise, all embedded profiles will be
+ * considered inaccessible.
+ */
+ public static GetEuiccProfileInfoListResult genericError(
+ int detailedCode, boolean isRemovable) {
+ return new GetEuiccProfileInfoListResult(
+ RESULT_GENERIC_ERROR, detailedCode, null /* profiles */, isRemovable);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl b/core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl
new file mode 100644
index 0000000..224cbd3
--- /dev/null
+++ b/core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+import android.service.euicc.DeleteResult;
+
+/** @hide */
+oneway interface IDeleteSubscriptionCallback {
+ void onComplete(in DeleteResult result);
+}
\ No newline at end of file
diff --git a/core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl b/core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl
new file mode 100644
index 0000000..0677cbe
--- /dev/null
+++ b/core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+import android.service.euicc.DownloadResult;
+
+/** @hide */
+oneway interface IDownloadSubscriptionCallback {
+ void onComplete(in DownloadResult result);
+}
\ No newline at end of file
diff --git a/core/java/android/service/euicc/IEraseSubscriptionsCallback.aidl b/core/java/android/service/euicc/IEraseSubscriptionsCallback.aidl
new file mode 100644
index 0000000..aa70e76
--- /dev/null
+++ b/core/java/android/service/euicc/IEraseSubscriptionsCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+import android.service.euicc.EraseResult;
+
+/** @hide */
+oneway interface IEraseSubscriptionsCallback {
+ void onComplete(in EraseResult result);
+}
\ No newline at end of file
diff --git a/core/java/android/service/euicc/IEuiccService.aidl b/core/java/android/service/euicc/IEuiccService.aidl
new file mode 100644
index 0000000..18ea915
--- /dev/null
+++ b/core/java/android/service/euicc/IEuiccService.aidl
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+import android.service.euicc.IDeleteSubscriptionCallback;
+import android.service.euicc.IDownloadSubscriptionCallback;
+import android.service.euicc.IEraseSubscriptionsCallback;
+import android.service.euicc.IGetDefaultDownloadableSubscriptionListCallback;
+import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback;
+import android.service.euicc.IGetEidCallback;
+import android.service.euicc.IGetEuiccInfoCallback;
+import android.service.euicc.IGetEuiccProfileInfoListCallback;
+import android.service.euicc.ISwitchToSubscriptionCallback;
+import android.service.euicc.IUpdateSubscriptionNicknameCallback;
+import android.telephony.euicc.DownloadableSubscription;
+
+/** @hide */
+oneway interface IEuiccService {
+ void downloadSubscription(int slotId, in DownloadableSubscription subscription,
+ boolean switchAfterDownload, boolean forceDeactivateSim,
+ in IDownloadSubscriptionCallback callback);
+ void getDownloadableSubscriptionMetadata(int slotId, in DownloadableSubscription subscription,
+ boolean forceDeactivateSim, in IGetDownloadableSubscriptionMetadataCallback callback);
+ void getEid(int slotId, in IGetEidCallback callback);
+ void getEuiccProfileInfoList(int slotId, in IGetEuiccProfileInfoListCallback callback);
+ void getDefaultDownloadableSubscriptionList(int slotId, boolean forceDeactivateSim,
+ in IGetDefaultDownloadableSubscriptionListCallback callback);
+ void getEuiccInfo(int slotId, in IGetEuiccInfoCallback callback);
+ void deleteSubscription(int slotId, String iccid, in IDeleteSubscriptionCallback callback);
+ void switchToSubscription(int slotId, String iccid, boolean forceDeactivateSim,
+ in ISwitchToSubscriptionCallback callback);
+ void updateSubscriptionNickname(int slotId, String iccid, String nickname,
+ in IUpdateSubscriptionNicknameCallback callback);
+ void eraseSubscriptions(int slotId, in IEraseSubscriptionsCallback callback);
+}
\ No newline at end of file
diff --git a/core/java/android/service/euicc/IGetDefaultDownloadableSubscriptionListCallback.aidl b/core/java/android/service/euicc/IGetDefaultDownloadableSubscriptionListCallback.aidl
new file mode 100644
index 0000000..0c5a0c6
--- /dev/null
+++ b/core/java/android/service/euicc/IGetDefaultDownloadableSubscriptionListCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+import android.service.euicc.GetDefaultDownloadableSubscriptionListResult;
+
+/** @hide */
+oneway interface IGetDefaultDownloadableSubscriptionListCallback {
+ void onComplete(in GetDefaultDownloadableSubscriptionListResult result);
+}
\ No newline at end of file
diff --git a/core/java/android/service/euicc/IGetDownloadableSubscriptionMetadataCallback.aidl b/core/java/android/service/euicc/IGetDownloadableSubscriptionMetadataCallback.aidl
new file mode 100644
index 0000000..3353061
--- /dev/null
+++ b/core/java/android/service/euicc/IGetDownloadableSubscriptionMetadataCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+import android.service.euicc.GetDownloadableSubscriptionMetadataResult;
+
+/** @hide */
+oneway interface IGetDownloadableSubscriptionMetadataCallback {
+ void onComplete(in GetDownloadableSubscriptionMetadataResult result);
+}
\ No newline at end of file
diff --git a/core/java/android/service/euicc/IGetEidCallback.aidl b/core/java/android/service/euicc/IGetEidCallback.aidl
new file mode 100644
index 0000000..35ee9c2
--- /dev/null
+++ b/core/java/android/service/euicc/IGetEidCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+/** @hide */
+oneway interface IGetEidCallback {
+ void onSuccess(String eid);
+}
\ No newline at end of file
diff --git a/core/java/android/service/euicc/IGetEuiccInfoCallback.aidl b/core/java/android/service/euicc/IGetEuiccInfoCallback.aidl
new file mode 100644
index 0000000..6d28148
--- /dev/null
+++ b/core/java/android/service/euicc/IGetEuiccInfoCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+import android.telephony.euicc.EuiccInfo;
+
+/** @hide */
+oneway interface IGetEuiccInfoCallback {
+ void onSuccess(in EuiccInfo euiccInfo);
+}
\ No newline at end of file
diff --git a/core/java/android/service/euicc/IGetEuiccProfileInfoListCallback.aidl b/core/java/android/service/euicc/IGetEuiccProfileInfoListCallback.aidl
new file mode 100644
index 0000000..761ec1f
--- /dev/null
+++ b/core/java/android/service/euicc/IGetEuiccProfileInfoListCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+import android.service.euicc.GetEuiccProfileInfoListResult;
+
+/** @hide */
+oneway interface IGetEuiccProfileInfoListCallback {
+ void onComplete(in GetEuiccProfileInfoListResult result);
+}
\ No newline at end of file
diff --git a/core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl b/core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl
new file mode 100644
index 0000000..970adcd
--- /dev/null
+++ b/core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+import android.service.euicc.SwitchResult;
+
+/** @hide */
+oneway interface ISwitchToSubscriptionCallback {
+ void onComplete(in SwitchResult result);
+}
\ No newline at end of file
diff --git a/core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl b/core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl
new file mode 100644
index 0000000..439759d
--- /dev/null
+++ b/core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+import android.service.euicc.UpdateNicknameResult;
+
+/** @hide */
+oneway interface IUpdateSubscriptionNicknameCallback {
+ void onComplete(in UpdateNicknameResult result);
+}
\ No newline at end of file
diff --git a/core/java/android/service/euicc/SwitchResult.aidl b/core/java/android/service/euicc/SwitchResult.aidl
new file mode 100644
index 0000000..eb706a5
--- /dev/null
+++ b/core/java/android/service/euicc/SwitchResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+parcelable SwitchResult;
diff --git a/core/java/android/service/euicc/SwitchResult.java b/core/java/android/service/euicc/SwitchResult.java
new file mode 100644
index 0000000..f5dc4d3
--- /dev/null
+++ b/core/java/android/service/euicc/SwitchResult.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.euicc;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Result of a {@link EuiccService#onSwitchToSubscription} operation.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+public final class SwitchResult implements Parcelable {
+
+ public static final Creator<SwitchResult> CREATOR = new Creator<SwitchResult>() {
+ @Override
+ public SwitchResult createFromParcel(Parcel in) {
+ return new SwitchResult(in);
+ }
+
+ @Override
+ public SwitchResult[] newArray(int size) {
+ return new SwitchResult[size];
+ }
+ };
+
+ /** @hide */
+ @IntDef({
+ RESULT_OK,
+ RESULT_GENERIC_ERROR,
+ RESULT_MUST_DEACTIVATE_SIM,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultCode {}
+
+ public static final int RESULT_OK = 0;
+ public static final int RESULT_GENERIC_ERROR = 1;
+ public static final int RESULT_MUST_DEACTIVATE_SIM = 2;
+
+ /** Result of the operation - one of the RESULT_* constants. */
+ public final @ResultCode int result;
+
+ /** Implementation-defined detailed error code in case of a failure not covered here. */
+ public final int detailedCode;
+
+ private SwitchResult(int result, int detailedCode) {
+ this.result = result;
+ this.detailedCode = detailedCode;
+ }
+
+ private SwitchResult(Parcel in) {
+ this.result = in.readInt();
+ this.detailedCode = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(result);
+ dest.writeInt(detailedCode);
+ }
+
+ /** Return a result indicating that the switch was successful. */
+ public static SwitchResult success() {
+ return new SwitchResult(RESULT_OK, 0);
+ }
+
+ /**
+ * Return a result indicating that an active SIM must be deactivated to perform the operation.
+ */
+ public static SwitchResult mustDeactivateSim() {
+ return new SwitchResult(RESULT_MUST_DEACTIVATE_SIM, 0);
+ }
+
+ /**
+ * Return a result indicating that an error occurred for which no other more specific error
+ * code has been defined.
+ *
+ * @param detailedCode an implemenation-defined detailed error code for debugging purposes.
+ */
+ public static SwitchResult genericError(int detailedCode) {
+ return new SwitchResult(RESULT_GENERIC_ERROR, detailedCode);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/service/euicc/UpdateNicknameResult.aidl b/core/java/android/service/euicc/UpdateNicknameResult.aidl
new file mode 100644
index 0000000..08b8491
--- /dev/null
+++ b/core/java/android/service/euicc/UpdateNicknameResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+parcelable UpdateNicknameResult;
diff --git a/core/java/android/service/euicc/UpdateNicknameResult.java b/core/java/android/service/euicc/UpdateNicknameResult.java
new file mode 100644
index 0000000..d871fc8
--- /dev/null
+++ b/core/java/android/service/euicc/UpdateNicknameResult.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.euicc;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Result of a {@link EuiccService#onUpdateSubscriptionNickname} operation.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+public final class UpdateNicknameResult implements Parcelable {
+
+ public static final Creator<UpdateNicknameResult> CREATOR =
+ new Creator<UpdateNicknameResult>() {
+ @Override
+ public UpdateNicknameResult createFromParcel(Parcel in) {
+ return new UpdateNicknameResult(in);
+ }
+
+ @Override
+ public UpdateNicknameResult[] newArray(int size) {
+ return new UpdateNicknameResult[size];
+ }
+ };
+
+ /** @hide */
+ @IntDef({
+ RESULT_OK,
+ RESULT_GENERIC_ERROR,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultCode {}
+
+ public static final int RESULT_OK = 0;
+ public static final int RESULT_GENERIC_ERROR = 1;
+
+ /** Result of the operation - one of the RESULT_* constants. */
+ public final @ResultCode int result;
+
+ /** Implementation-defined detailed error code in case of a failure not covered here. */
+ public final int detailedCode;
+
+ private UpdateNicknameResult(int result, int detailedCode) {
+ this.result = result;
+ this.detailedCode = detailedCode;
+ }
+
+ private UpdateNicknameResult(Parcel in) {
+ this.result = in.readInt();
+ this.detailedCode = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(result);
+ dest.writeInt(detailedCode);
+ }
+
+ /** Return a result indicating that the update was successful. */
+ public static UpdateNicknameResult success() {
+ return new UpdateNicknameResult(RESULT_OK, 0);
+ }
+
+ /**
+ * Return a result indicating that an error occurred for which no other more specific error
+ * code has been defined.
+ *
+ * @param detailedCode an implemenation-defined detailed error code for debugging purposes.
+ */
+ public static UpdateNicknameResult genericError(int detailedCode) {
+ return new UpdateNicknameResult(RESULT_GENERIC_ERROR, detailedCode);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/service/gatekeeper/IGateKeeperService.aidl b/core/java/android/service/gatekeeper/IGateKeeperService.aidl
index 6db2110..abc6466 100644
--- a/core/java/android/service/gatekeeper/IGateKeeperService.aidl
+++ b/core/java/android/service/gatekeeper/IGateKeeperService.aidl
@@ -78,4 +78,10 @@
* @param uid the Android user id.
*/
void clearSecureUserId(int uid);
+
+ /**
+ * Notifies gatekeeper that device setup has been completed and any potentially still existing
+ * state from before a factory reset can be cleaned up (if it has not been already).
+ */
+ void reportDeviceSetupComplete();
}
diff --git a/core/java/android/service/oemlock/IOemLockService.aidl b/core/java/android/service/oemlock/IOemLockService.aidl
index 2c606f9..682e7ee 100644
--- a/core/java/android/service/oemlock/IOemLockService.aidl
+++ b/core/java/android/service/oemlock/IOemLockService.aidl
@@ -27,4 +27,8 @@
void setOemUnlockAllowedByUser(boolean allowed);
boolean isOemUnlockAllowedByUser();
+
+ boolean canUserAllowOemUnlock();
+ boolean isOemUnlockAllowed();
+ boolean isDeviceOemUnlocked();
}
diff --git a/core/java/android/service/oemlock/OemLockManager.java b/core/java/android/service/oemlock/OemLockManager.java
index e2ade87..3a56d9f 100644
--- a/core/java/android/service/oemlock/OemLockManager.java
+++ b/core/java/android/service/oemlock/OemLockManager.java
@@ -88,7 +88,8 @@
*
* All actors involved must agree for OEM unlock to be possible.
*
- * @param unlocked Whether the device should be made OEM unlocked.
+ * @param allowed Whether the device should be allowed to be unlocked.
+ * @throws SecurityException if the user is not allowed to unlock the device.
*
* @see #isOemUnlockAllowedByUser()
*/
@@ -115,4 +116,48 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Returns whether all parties other than the user allow OEM unlock meaning the user can
+ * directly control whether or not the device can be OEM unlocked.
+ *
+ * If this is true, {@link #isOemUnlockAllowedByUser} is the same as {@link #isOemUnlockAllowed}
+ *
+ * @return Whether the user can directly control whether the device can be OEM unlocked.
+ *
+ * @hide
+ */
+ public boolean canUserAllowOemUnlock() {
+ try {
+ return mService.canUserAllowOemUnlock();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return Whether the bootloader is able to OEM unlock the device.
+ *
+ * @hide
+ */
+ public boolean isOemUnlockAllowed() {
+ try {
+ return mService.isOemUnlockAllowed();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return Whether the device has been OEM unlocked by the bootloader.
+ *
+ * @hide
+ */
+ public boolean isDeviceOemUnlocked() {
+ try {
+ return mService.isDeviceOemUnlocked();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
index 626b408..31352f1 100644
--- a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
+++ b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
@@ -36,5 +36,6 @@
void setOemUnlockEnabled(boolean enabled);
boolean getOemUnlockEnabled();
int getFlashLockState();
+ boolean hasFrpCredentialHandle();
}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 479c9e2..fbc6fc6 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -262,7 +262,8 @@
* @param keyphrase The keyphrase that's being used, for example "Hello Android".
* @param locale The locale for which the enrollment needs to be performed.
* @param callback The callback to notify of detection events.
- * @return An always-on hotword detector for the given keyphrase and locale.
+ * @return An always-on hotword detector for the given keyphrase and locale. Is null if the
+ * keyphrase and locale is not supported.
*/
public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
String keyphrase, Locale locale, AlwaysOnHotwordDetector.Callback callback) {
@@ -272,8 +273,10 @@
synchronized (mLock) {
// Allow only one concurrent recognition via the APIs.
safelyShutdownHotwordDetector();
- mHotwordDetector = new AlwaysOnHotwordDetector(keyphrase, locale, callback,
- mKeyphraseEnrollmentInfo, mInterface, mSystemService);
+ if (isKeyphraseAndLocaleSupportedForHotword(keyphrase, locale)) {
+ mHotwordDetector = new AlwaysOnHotwordDetector(keyphrase, locale, callback,
+ mKeyphraseEnrollmentInfo, mInterface, mSystemService);
+ }
}
return mHotwordDetector;
}
@@ -287,6 +290,20 @@
return mKeyphraseEnrollmentInfo;
}
+ /**
+ * Checks if a given keyphrase and locale are supported to create an
+ * {@link AlwaysOnHotwordDetector}.
+ *
+ * @return true if the keyphrase and locale combination is supported, false otherwise.
+ * @hide
+ */
+ public final boolean isKeyphraseAndLocaleSupportedForHotword(String keyphrase, Locale locale) {
+ if (mKeyphraseEnrollmentInfo == null) {
+ return false;
+ }
+ return mKeyphraseEnrollmentInfo.getKeyphraseMetadata(keyphrase, locale) != null;
+ }
+
private void safelyShutdownHotwordDetector() {
try {
synchronized (mLock) {
diff --git a/core/java/android/service/wallpaper/IWallpaperConnection.aidl b/core/java/android/service/wallpaper/IWallpaperConnection.aidl
index f9c5aaa..3c3ef0c 100644
--- a/core/java/android/service/wallpaper/IWallpaperConnection.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperConnection.aidl
@@ -18,6 +18,7 @@
import android.os.ParcelFileDescriptor;
import android.service.wallpaper.IWallpaperEngine;
+import android.app.WallpaperColors;
/**
* @hide
@@ -26,4 +27,5 @@
void attachEngine(IWallpaperEngine engine);
void engineShown(IWallpaperEngine engine);
ParcelFileDescriptor setWallpaper(String name);
+ void onWallpaperColorsChanged(in WallpaperColors colors);
}
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index de527e9..eff52e6 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -30,5 +30,5 @@
void dispatchPointer(in MotionEvent event);
void dispatchWallpaperCommand(String action, int x, int y,
int z, in Bundle extras);
- void destroy();
+ void destroy();
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 6bbb0ff..539278f 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -16,6 +16,8 @@
package android.service.wallpaper;
+import android.annotation.Nullable;
+import android.app.WallpaperColors;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.util.MergedConfiguration;
@@ -542,7 +544,31 @@
*/
public void onSurfaceDestroyed(SurfaceHolder holder) {
}
-
+
+ /**
+ * Notifies the engine that wallpaper colors changed significantly.
+ * This will trigger a {@link #onComputeWallpaperColors()} call.
+ */
+ public void invalidateColors() {
+ try {
+ mConnection.onWallpaperColorsChanged(onComputeWallpaperColors());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Can't invalidate wallpaper colors because " +
+ "wallpaper connection was lost", e);
+ }
+ }
+
+ /**
+ * Notifies the system about what colors the wallpaper is using.
+ * You might return null if no color information is available at the moment. In that case
+ * you might want to call {@link #invalidateColors()} in a near future.
+ *
+ * @return List of wallpaper colors and their weights.
+ */
+ public @Nullable WallpaperColors onComputeWallpaperColors() {
+ return null;
+ }
+
protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
out.print(prefix); out.print("mInitializing="); out.print(mInitializing);
out.print(" mDestroyed="); out.println(mDestroyed);
@@ -1179,6 +1205,7 @@
mEngine = engine;
mActiveEngines.add(engine);
engine.attach(this);
+ engine.invalidateColors();
return;
}
case DO_DETACH: {
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 6208c53..c7a5fce 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -491,8 +491,6 @@
* An index is associated to each block (which will be used by display lists),
* this class simply invalidates the index of blocks overlapping a modification.
*
- * This method is package private and not private so that it can be tested.
- *
* @param startLine the first line of the range of modified lines
* @param endLine the last line of the range, possibly equal to startLine, lower
* than getLineCount()
@@ -613,16 +611,20 @@
}
/**
- * This package private method is used for test purposes only
+ * This method is used for test purposes only.
* @hide
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public void setBlocksDataForTest(int[] blockEndLines, int[] blockIndices, int numberOfBlocks) {
+ public void setBlocksDataForTest(int[] blockEndLines, int[] blockIndices, int numberOfBlocks,
+ int totalLines) {
mBlockEndLines = new int[blockEndLines.length];
mBlockIndices = new int[blockIndices.length];
System.arraycopy(blockEndLines, 0, mBlockEndLines, 0, blockEndLines.length);
System.arraycopy(blockIndices, 0, mBlockIndices, 0, blockIndices.length);
mNumberOfBlocks = numberOfBlocks;
+ while (mInts.size() < totalLines) {
+ mInts.insertAt(mInts.size(), new int[COLUMNS_NORMAL]);
+ }
}
/**
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 756e9a0..e4ed62a 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -16,6 +16,8 @@
package android.text;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
@@ -28,6 +30,8 @@
import com.android.internal.util.ArrayUtils;
+import java.util.ArrayList;
+
/**
* Represents a line of styled text, for measuring in visual order and
* for rendering.
@@ -58,7 +62,9 @@
// Additional width of whitespace for justification. This value is per whitespace, thus
// the line width will increase by mAddedWidth x (number of stretchable whitespaces).
private float mAddedWidth;
+
private final TextPaint mWorkPaint = new TextPaint();
+ private final TextPaint mActivePaint = new TextPaint();
private final SpanSet<MetricAffectingSpan> mMetricAffectingSpanSpanSet =
new SpanSet<MetricAffectingSpan>(MetricAffectingSpan.class);
private final SpanSet<CharacterStyle> mCharacterStyleSpanSet =
@@ -66,6 +72,9 @@
private final SpanSet<ReplacementSpan> mReplacementSpanSpanSet =
new SpanSet<ReplacementSpan>(ReplacementSpan.class);
+ private final UnderlineInfo mUnderlineInfo = new UnderlineInfo();
+ private final ArrayList<UnderlineInfo> mUnderlines = new ArrayList();
+
private static final TextLine[] sCached = new TextLine[3];
/**
@@ -695,6 +704,36 @@
fmi.leading = Math.max(fmi.leading, previousLeading);
}
+ private static void drawUnderline(TextPaint wp, Canvas c, int color, float thickness,
+ float xleft, float xright, float baseline) {
+ final float underlineTop = baseline + wp.baselineShift + wp.getUnderlinePosition();
+
+ final int previousColor = wp.getColor();
+ final Paint.Style previousStyle = wp.getStyle();
+ final boolean previousAntiAlias = wp.isAntiAlias();
+
+ wp.setStyle(Paint.Style.FILL);
+ wp.setAntiAlias(true);
+
+ wp.setColor(color);
+ c.drawRect(xleft, underlineTop, xright, underlineTop + thickness, wp);
+
+ wp.setStyle(previousStyle);
+ wp.setColor(previousColor);
+ wp.setAntiAlias(previousAntiAlias);
+ }
+
+ private float getRunAdvance(TextPaint wp, int start, int end, int contextStart, int contextEnd,
+ boolean runIsRtl, int offset) {
+ if (mCharsValid) {
+ return wp.getRunAdvance(mChars, start, end, contextStart, contextEnd, runIsRtl, offset);
+ } else {
+ final int delta = mStart;
+ return wp.getRunAdvance(mText, delta + start, delta + end,
+ delta + contextStart, delta + contextEnd, runIsRtl, delta + offset);
+ }
+ }
+
/**
* Utility function for measuring and rendering text. The text must
* not include a tab.
@@ -711,13 +750,15 @@
* @param fmi receives metrics information, can be null
* @param needWidth true if the width of the run is needed
* @param offset the offset for the purpose of measuring
+ * @param underlines the list of locations and paremeters for drawing underlines
* @return the signed width of the run based on the run direction; only
* valid if needWidth is true
*/
private float handleText(TextPaint wp, int start, int end,
int contextStart, int contextEnd, boolean runIsRtl,
Canvas c, float x, int top, int y, int bottom,
- FontMetricsInt fmi, boolean needWidth, int offset) {
+ FontMetricsInt fmi, boolean needWidth, int offset,
+ @Nullable ArrayList<UnderlineInfo> underlines) {
wp.setWordSpacing(mAddedWidth);
// Get metrics first (even for empty strings or "0" width runs)
@@ -725,28 +766,26 @@
expandMetricsFromPaint(fmi, wp);
}
- int runLen = end - start;
// No need to do anything if the run width is "0"
- if (runLen == 0) {
+ if (end == start) {
return 0f;
}
- float ret = 0;
+ float totalWidth = 0;
- if (needWidth || (c != null && (wp.bgColor != 0 || wp.underlineColor != 0 || runIsRtl))) {
- if (mCharsValid) {
- ret = wp.getRunAdvance(mChars, start, end, contextStart, contextEnd,
- runIsRtl, offset);
- } else {
- int delta = mStart;
- ret = wp.getRunAdvance(mText, delta + start, delta + end,
- delta + contextStart, delta + contextEnd, runIsRtl, delta + offset);
- }
+ final int numUnderlines = underlines == null ? 0 : underlines.size();
+ if (needWidth || (c != null && (wp.bgColor != 0 || numUnderlines != 0 || runIsRtl))) {
+ totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset);
}
if (c != null) {
+ final float leftX, rightX;
if (runIsRtl) {
- x -= ret;
+ leftX = x - totalWidth;
+ rightX = x;
+ } else {
+ leftX = x;
+ rightX = x + totalWidth;
}
if (wp.bgColor != 0) {
@@ -755,36 +794,50 @@
wp.setColor(wp.bgColor);
wp.setStyle(Paint.Style.FILL);
- c.drawRect(x, top, x + ret, bottom, wp);
+ c.drawRect(leftX, top, rightX, bottom, wp);
wp.setStyle(previousStyle);
wp.setColor(previousColor);
}
- if (wp.underlineColor != 0) {
- // kStdUnderline_Offset = 1/9, defined in SkTextFormatParams.h
- float underlineTop = y + wp.baselineShift + (1.0f / 9.0f) * wp.getTextSize();
+ if (numUnderlines != 0) {
+ for (int i = 0; i < numUnderlines; i++) {
+ final UnderlineInfo info = underlines.get(i);
- int previousColor = wp.getColor();
- Paint.Style previousStyle = wp.getStyle();
- boolean previousAntiAlias = wp.isAntiAlias();
+ final int underlineStart = Math.max(info.start, start);
+ final int underlineEnd = Math.min(info.end, offset);
+ float underlineStartAdvance = getRunAdvance(
+ wp, start, end, contextStart, contextEnd, runIsRtl, underlineStart);
+ float underlineEndAdvance = getRunAdvance(
+ wp, start, end, contextStart, contextEnd, runIsRtl, underlineEnd);
+ final float underlineXLeft, underlineXRight;
+ if (runIsRtl) {
+ underlineXLeft = rightX - underlineEndAdvance;
+ underlineXRight = rightX - underlineStartAdvance;
+ } else {
+ underlineXLeft = leftX + underlineStartAdvance;
+ underlineXRight = leftX + underlineEndAdvance;
+ }
- wp.setStyle(Paint.Style.FILL);
- wp.setAntiAlias(true);
-
- wp.setColor(wp.underlineColor);
- c.drawRect(x, underlineTop, x + ret, underlineTop + wp.underlineThickness, wp);
-
- wp.setStyle(previousStyle);
- wp.setColor(previousColor);
- wp.setAntiAlias(previousAntiAlias);
+ // Theoretically, there could be cases where both Paint's and TextPaint's
+ // setUnderLineText() are called. For backward compatibility, we need to draw
+ // both underlines, the one with custom color first.
+ if (info.underlineColor != 0) {
+ drawUnderline(wp, c, info.underlineColor, info.underlineThickness,
+ underlineXLeft, underlineXRight, y);
+ }
+ if (info.isUnderlineText) {
+ drawUnderline(wp, c, wp.getColor(), ((Paint) wp).getUnderlineThickness(),
+ underlineXLeft, underlineXRight, y);
+ }
+ }
}
drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
- x, y + wp.baselineShift);
+ leftX, y + wp.baselineShift);
}
- return runIsRtl ? -ret : ret;
+ return runIsRtl ? -totalWidth : totalWidth;
}
/**
@@ -864,6 +917,37 @@
return result;
}
+ private static final class UnderlineInfo {
+ public boolean isUnderlineText;
+ public int underlineColor;
+ public float underlineThickness;
+ public int start = -1;
+ public int end = -1;
+
+ public boolean hasUnderline() {
+ return isUnderlineText || underlineColor != 0;
+ }
+
+ // Copies the info, but not the start and end range.
+ public UnderlineInfo copyInfo() {
+ final UnderlineInfo copy = new UnderlineInfo();
+ copy.isUnderlineText = isUnderlineText;
+ copy.underlineColor = underlineColor;
+ copy.underlineThickness = underlineThickness;
+ return copy;
+ }
+ }
+
+ private void extractUnderlineInfo(@NonNull TextPaint paint, @NonNull UnderlineInfo info) {
+ info.isUnderlineText = paint.isUnderlineText();
+ if (info.isUnderlineText) {
+ paint.setUnderlineText(false);
+ }
+ info.underlineColor = paint.underlineColor;
+ info.underlineThickness = paint.underlineThickness;
+ paint.setUnderlineText(0, 0.0f);
+ }
+
/**
* Utility function for handling a unidirectional run. The run must not
* contain tabs but can contain styles.
@@ -894,7 +978,7 @@
// Case of an empty line, make sure we update fmi according to mPaint
if (start == measureLimit) {
- TextPaint wp = mWorkPaint;
+ final TextPaint wp = mWorkPaint;
wp.set(mPaint);
if (fmi != null) {
expandMetricsFromPaint(fmi, wp);
@@ -903,11 +987,11 @@
}
if (mSpanned == null) {
- TextPaint wp = mWorkPaint;
+ final TextPaint wp = mWorkPaint;
wp.set(mPaint);
wp.setHyphenEdit(adjustHyphenEdit(start, limit, wp.getHyphenEdit()));
return handleText(wp, start, limit, start, limit, runIsRtl, c, x, top,
- y, bottom, fmi, needWidth, measureLimit);
+ y, bottom, fmi, needWidth, measureLimit, null);
}
mMetricAffectingSpanSpanSet.init(mSpanned, mStart + start, mStart + limit);
@@ -920,7 +1004,7 @@
// for the run bounds.
final float originalX = x;
for (int i = start, inext; i < measureLimit; i = inext) {
- TextPaint wp = mWorkPaint;
+ final TextPaint wp = mWorkPaint;
wp.set(mPaint);
inext = mMetricAffectingSpanSpanSet.getNextTransition(mStart + i, mStart + limit) -
@@ -934,7 +1018,7 @@
// empty by construction. This special case in getSpans() explains the >= & <= tests
if ((mMetricAffectingSpanSpanSet.spanStarts[j] >= mStart + mlimit) ||
(mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + i)) continue;
- MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j];
+ final MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j];
if (span instanceof ReplacementSpan) {
replacement = (ReplacementSpan)span;
} else {
@@ -950,26 +1034,68 @@
continue;
}
+ final TextPaint activePaint = mActivePaint;
+ activePaint.set(mPaint);
+ int activeStart = i;
+ int activeEnd = mlimit;
+ final UnderlineInfo underlineInfo = mUnderlineInfo;
+ mUnderlines.clear();
for (int j = i, jnext; j < mlimit; j = jnext) {
jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + inext) -
mStart;
- int offset = Math.min(jnext, mlimit);
+ final int offset = Math.min(jnext, mlimit);
wp.set(mPaint);
for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
// Intentionally using >= and <= as explained above
if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + offset) ||
(mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue;
- CharacterStyle span = mCharacterStyleSpanSet.spans[k];
+ final CharacterStyle span = mCharacterStyleSpanSet.spans[k];
span.updateDrawState(wp);
}
- wp.setHyphenEdit(adjustHyphenEdit(j, jnext, wp.getHyphenEdit()));
+ extractUnderlineInfo(wp, underlineInfo);
- x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
- top, y, bottom, fmi, needWidth || jnext < measureLimit, offset);
+ if (j == i) {
+ // First chunk of text. We can't handle it yet, since we may need to merge it
+ // with the next chunk. So we just save the TextPaint for future comparisons
+ // and use.
+ activePaint.set(wp);
+ } else if (!wp.hasEqualAttributes(activePaint)) {
+ // The style of the present chunk of text is substantially different from the
+ // style of the previous chunk. We need to handle the active piece of text
+ // and restart with the present chunk.
+ activePaint.setHyphenEdit(adjustHyphenEdit(
+ activeStart, activeEnd, mPaint.getHyphenEdit()));
+ x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x,
+ top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
+ Math.min(activeEnd, mlimit), mUnderlines);
+
+ activeStart = j;
+ activePaint.set(wp);
+ mUnderlines.clear();
+ } else {
+ // The present TextPaint is substantially equal to the last TextPaint except
+ // perhaps for underlines. We just need to expand the active piece of text to
+ // include the present chunk, which we always do anyway. We don't need to save
+ // wp to activePaint, since they are already equal.
+ }
+
+ activeEnd = jnext;
+ if (underlineInfo.hasUnderline()) {
+ final UnderlineInfo copy = underlineInfo.copyInfo();
+ copy.start = j;
+ copy.end = jnext;
+ mUnderlines.add(copy);
+ }
}
+ // Handle the final piece of text.
+ activePaint.setHyphenEdit(adjustHyphenEdit(
+ activeStart, activeEnd, mPaint.getHyphenEdit()));
+ x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x,
+ top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
+ Math.min(activeEnd, mlimit), mUnderlines);
}
return x - originalX;
diff --git a/core/java/android/text/TextPaint.java b/core/java/android/text/TextPaint.java
index 4f8cff0..5234fa9 100644
--- a/core/java/android/text/TextPaint.java
+++ b/core/java/android/text/TextPaint.java
@@ -17,6 +17,7 @@
package android.text;
import android.annotation.ColorInt;
+import android.annotation.NonNull;
import android.graphics.Paint;
/**
@@ -40,7 +41,7 @@
@ColorInt
public int underlineColor = 0;
/**
- * Defined as a multiplier of the default underline thickness. Use 1.0f for default thickness.
+ * Thickness of the underline, in pixels.
* @hide
*/
public float underlineThickness;
@@ -74,6 +75,24 @@
}
/**
+ * Returns true if all attributes, including the attributes inherited from Paint, are equal.
+ *
+ * The caller is expected to have checked the trivial cases, like the pointers being equal,
+ * the objects having different classes, or the parameter being null.
+ * @hide
+ */
+ public boolean hasEqualAttributes(@NonNull TextPaint other) {
+ return bgColor == other.bgColor
+ && baselineShift == other.baselineShift
+ && linkColor == other.linkColor
+ && drawableState == other.drawableState
+ && density == other.density
+ && underlineColor == other.underlineColor
+ && underlineThickness == other.underlineThickness
+ && super.hasEqualAttributes((Paint) other);
+ }
+
+ /**
* Defines a custom underline for this Paint.
* @param color underline solid color
* @param thickness underline thickness
@@ -83,4 +102,16 @@
underlineColor = color;
underlineThickness = thickness;
}
+
+ /**
+ * @hide
+ */
+ @Override
+ public float getUnderlineThickness() {
+ if (underlineColor != 0) { // Return custom thickness only if underline color is set.
+ return underlineThickness;
+ } else {
+ return super.getUnderlineThickness();
+ }
+ }
}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 585f882..3baadd4 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -1943,6 +1943,16 @@
return charSequence;
}
+ /**
+ * Prepends {@code start} and appends {@code end} to a given {@link StringBuilder}
+ *
+ * @hide
+ */
+ public static void wrap(StringBuilder builder, String start, String end) {
+ builder.insert(0, start);
+ builder.append(end);
+ }
+
private static Object sLock = new Object();
private static char[] sTemp = null;
diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java
index 9326203..3617aa7 100644
--- a/core/java/android/util/IntArray.java
+++ b/core/java/android/util/IntArray.java
@@ -17,7 +17,7 @@
package android.util;
import com.android.internal.util.ArrayUtils;
-
+import com.android.internal.util.Preconditions;
import java.util.Arrays;
import libcore.util.EmptyArray;
@@ -32,6 +32,11 @@
private int[] mValues;
private int mSize;
+ private IntArray(int[] array, int size) {
+ mValues = array;
+ mSize = Preconditions.checkArgumentInRange(size, 0, array.length, "size");
+ }
+
/**
* Creates an empty IntArray with the default initial capacity.
*/
@@ -52,6 +57,35 @@
}
/**
+ * Creates an IntArray wrapping the given primitive int array.
+ */
+ public static IntArray wrap(int[] array) {
+ return new IntArray(array, array.length);
+ }
+
+ /**
+ * Creates an IntArray from the given primitive int array, copying it.
+ */
+ public static IntArray fromArray(int[] array, int size) {
+ return wrap(Arrays.copyOf(array, size));
+ }
+
+ /**
+ * Changes the size of this IntArray. If this IntArray is shrinked, the backing array capacity
+ * is unchanged. If the new size is larger than backing array capacity, a new backing array is
+ * created from the current content of this IntArray padded with 0s.
+ */
+ public void resize(int newSize) {
+ Preconditions.checkArgumentNonnegative(newSize);
+ if (newSize <= mValues.length) {
+ Arrays.fill(mValues, newSize, mValues.length, 0);
+ } else {
+ ensureCapacity(newSize - mSize);
+ }
+ mSize = newSize;
+ }
+
+ /**
* Appends the specified value to the end of this array.
*/
public void add(int value) {
@@ -59,23 +93,23 @@
}
/**
- * Inserts a value at the specified position in this array.
+ * Inserts a value at the specified position in this array. If the specified index is equal to
+ * the length of the array, the value is added at the end.
*
* @throws IndexOutOfBoundsException when index < 0 || index > size()
*/
public void add(int index, int value) {
- if (index < 0 || index > mSize) {
- throw new IndexOutOfBoundsException();
- }
-
ensureCapacity(1);
+ int rightSegment = mSize - index;
+ mSize++;
+ checkBounds(index);
- if (mSize - index != 0) {
- System.arraycopy(mValues, index, mValues, index + 1, mSize - index);
+ if (rightSegment != 0) {
+ // Move by 1 all values from the right of 'index'
+ System.arraycopy(mValues, index, mValues, index + 1, rightSegment);
}
mValues[index] = value;
- mSize++;
}
/**
@@ -141,13 +175,19 @@
* Returns the value at the specified position in this array.
*/
public int get(int index) {
- if (index >= mSize) {
- throw new ArrayIndexOutOfBoundsException(mSize, index);
- }
+ checkBounds(index);
return mValues[index];
}
/**
+ * Sets the value at the specified position in this array.
+ */
+ public void set(int index, int value) {
+ checkBounds(index);
+ mValues[index] = value;
+ }
+
+ /**
* Returns the index of the first occurrence of the specified value in this
* array, or -1 if this array does not contain the value.
*/
@@ -165,9 +205,7 @@
* Removes the value at the specified index from this array.
*/
public void remove(int index) {
- if (index >= mSize) {
- throw new ArrayIndexOutOfBoundsException(mSize, index);
- }
+ checkBounds(index);
System.arraycopy(mValues, index + 1, mValues, index, mSize - index - 1);
mSize--;
}
@@ -185,4 +223,10 @@
public int[] toArray() {
return Arrays.copyOf(mValues, mSize);
}
+
+ private void checkBounds(int index) {
+ if (index < 0 || mSize <= index) {
+ throw new ArrayIndexOutOfBoundsException(mSize, index);
+ }
+ }
}
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index d857bf7..951aa8d 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -89,8 +89,9 @@
/**
* Exception class used to capture a stack trace in {@link #wtf}.
+ * @hide
*/
- private static class TerribleFailure extends Exception {
+ public static class TerribleFailure extends Exception {
TerribleFailure(String msg, Throwable cause) { super(msg, cause); }
}
diff --git a/core/java/android/util/LongArray.java b/core/java/android/util/LongArray.java
index 54a6882..9b0489c 100644
--- a/core/java/android/util/LongArray.java
+++ b/core/java/android/util/LongArray.java
@@ -17,6 +17,8 @@
package android.util;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+import java.util.Arrays;
import libcore.util.EmptyArray;
/**
@@ -30,6 +32,11 @@
private long[] mValues;
private int mSize;
+ private LongArray(long[] array, int size) {
+ mValues = array;
+ mSize = Preconditions.checkArgumentInRange(size, 0, array.length, "size");
+ }
+
/**
* Creates an empty LongArray with the default initial capacity.
*/
@@ -50,6 +57,35 @@
}
/**
+ * Creates an LongArray wrapping the given primitive long array.
+ */
+ public static LongArray wrap(long[] array) {
+ return new LongArray(array, array.length);
+ }
+
+ /**
+ * Creates an LongArray from the given primitive long array, copying it.
+ */
+ public static LongArray fromArray(long[] array, int size) {
+ return wrap(Arrays.copyOf(array, size));
+ }
+
+ /**
+ * Changes the size of this LongArray. If this LongArray is shrinked, the backing array capacity
+ * is unchanged. If the new size is larger than backing array capacity, a new backing array is
+ * created from the current content of this LongArray padded with 0s.
+ */
+ public void resize(int newSize) {
+ Preconditions.checkArgumentNonnegative(newSize);
+ if (newSize <= mValues.length) {
+ Arrays.fill(mValues, newSize, mValues.length, 0);
+ } else {
+ ensureCapacity(newSize - mSize);
+ }
+ mSize = newSize;
+ }
+
+ /**
* Appends the specified value to the end of this array.
*/
public void add(long value) {
@@ -57,23 +93,23 @@
}
/**
- * Inserts a value at the specified position in this array.
+ * Inserts a value at the specified position in this array. If the specified index is equal to
+ * the length of the array, the value is added at the end.
*
* @throws IndexOutOfBoundsException when index < 0 || index > size()
*/
public void add(int index, long value) {
- if (index < 0 || index > mSize) {
- throw new IndexOutOfBoundsException();
- }
-
ensureCapacity(1);
+ int rightSegment = mSize - index;
+ mSize++;
+ checkBounds(index);
- if (mSize - index != 0) {
- System.arraycopy(mValues, index, mValues, index + 1, mSize - index);
+ if (rightSegment != 0) {
+ // Move by 1 all values from the right of 'index'
+ System.arraycopy(mValues, index, mValues, index + 1, rightSegment);
}
mValues[index] = value;
- mSize++;
}
/**
@@ -126,13 +162,19 @@
* Returns the value at the specified position in this array.
*/
public long get(int index) {
- if (index >= mSize) {
- throw new ArrayIndexOutOfBoundsException(mSize, index);
- }
+ checkBounds(index);
return mValues[index];
}
/**
+ * Sets the value at the specified position in this array.
+ */
+ public void set(int index, long value) {
+ checkBounds(index);
+ mValues[index] = value;
+ }
+
+ /**
* Returns the index of the first occurrence of the specified value in this
* array, or -1 if this array does not contain the value.
*/
@@ -150,9 +192,7 @@
* Removes the value at the specified index from this array.
*/
public void remove(int index) {
- if (index >= mSize) {
- throw new ArrayIndexOutOfBoundsException(mSize, index);
- }
+ checkBounds(index);
System.arraycopy(mValues, index + 1, mValues, index, mSize - index - 1);
mSize--;
}
@@ -163,4 +203,17 @@
public int size() {
return mSize;
}
+
+ /**
+ * Returns a new array with the contents of this LongArray.
+ */
+ public long[] toArray() {
+ return Arrays.copyOf(mValues, mSize);
+ }
+
+ private void checkBounds(int index) {
+ if (index < 0 || mSize <= index) {
+ throw new ArrayIndexOutOfBoundsException(mSize, index);
+ }
+ }
}
diff --git a/core/java/android/util/MathUtils.java b/core/java/android/util/MathUtils.java
index 5c718f1..e865b6e 100644
--- a/core/java/android/util/MathUtils.java
+++ b/core/java/android/util/MathUtils.java
@@ -16,8 +16,6 @@
package android.util;
-import java.util.Random;
-
/**
* A class that contains utility methods related to numbers.
*
@@ -181,7 +179,7 @@
}
public static float map(float minStart, float minStop, float maxStart, float maxStop, float value) {
- return maxStart + (maxStart - maxStop) * ((value - minStart) / (minStop - minStart));
+ return maxStart + (maxStop - maxStart) * ((value - minStart) / (minStop - minStart));
}
/**
diff --git a/core/java/android/util/MemoryIntArray.java b/core/java/android/util/MemoryIntArray.java
index 589edbc..bf33519 100644
--- a/core/java/android/util/MemoryIntArray.java
+++ b/core/java/android/util/MemoryIntArray.java
@@ -158,7 +158,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mCloseGuard.warnIfOpen();
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
IoUtils.closeQuietly(this);
} finally {
super.finalize();
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 0a294ab..2b03ed6 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -16,24 +16,17 @@
package android.util;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
import android.os.SystemClock;
-import com.android.internal.util.XmlUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
+import java.util.Collections;
import java.util.Date;
-import java.util.TimeZone;
-
+import java.util.List;
+import libcore.util.TimeZoneFinder;
import libcore.util.ZoneInfoDB;
/**
@@ -44,14 +37,9 @@
private static final boolean DBG = false;
private static final String TAG = "TimeUtils";
- /** Cached results of getTineZones */
- private static final Object sLastLockObj = new Object();
- private static ArrayList<TimeZone> sLastZones = null;
- private static String sLastCountry = null;
-
/** Cached results of getTimeZonesWithUniqueOffsets */
private static final Object sLastUniqueLockObj = new Object();
- private static ArrayList<TimeZone> sLastUniqueZoneOffsets = null;
+ private static List<String> sLastUniqueZoneOffsets = null;
private static String sLastUniqueCountry = null;
/** {@hide} */
@@ -62,50 +50,39 @@
* and DST value at the specified moment in the specified country.
* Returns null if no suitable zone could be found.
*/
- public static TimeZone getTimeZone(int offset, boolean dst, long when, String country) {
- TimeZone best = null;
- final Date d = new Date(when);
+ public static java.util.TimeZone getTimeZone(
+ int offset, boolean dst, long when, String country) {
- TimeZone current = TimeZone.getDefault();
- String currentName = current.getID();
- int currentOffset = current.getOffset(when);
- boolean currentDst = current.inDaylightTime(d);
-
- for (TimeZone tz : getTimeZones(country)) {
- // If the current time zone is from the right country
- // and meets the other known properties, keep it
- // instead of changing to another one.
-
- if (tz.getID().equals(currentName)) {
- if (currentOffset == offset && currentDst == dst) {
- return current;
- }
- }
-
- // Otherwise, take the first zone from the right
- // country that has the correct current offset and DST.
- // (Keep iterating instead of returning in case we
- // haven't encountered the current time zone yet.)
-
- if (best == null) {
- if (tz.getOffset(when) == offset &&
- tz.inDaylightTime(d) == dst) {
- best = tz;
- }
- }
- }
-
- return best;
+ android.icu.util.TimeZone icuTimeZone = getIcuTimeZone(offset, dst, when, country);
+ // We must expose a java.util.TimeZone here for API compatibility because this is a public
+ // API method.
+ return icuTimeZone != null ? java.util.TimeZone.getTimeZone(icuTimeZone.getID()) : null;
}
/**
- * Return list of unique time zones for the country. Do not modify
+ * Tries to return a frozen ICU time zone that would have had the specified offset
+ * and DST value at the specified moment in the specified country.
+ * Returns null if no suitable zone could be found.
+ */
+ private static android.icu.util.TimeZone getIcuTimeZone(
+ int offset, boolean dst, long when, String country) {
+ if (country == null) {
+ return null;
+ }
+
+ android.icu.util.TimeZone bias = android.icu.util.TimeZone.getDefault();
+ return TimeZoneFinder.getInstance()
+ .lookupTimeZoneByCountryAndOffset(country, offset, dst, when, bias);
+ }
+
+ /**
+ * Returns an immutable list of unique time zone IDs for the country.
*
* @param country to find
- * @return list of unique time zones, maybe empty but never null. Do not modify.
+ * @return unmodifiable list of unique time zones, maybe empty but never null.
* @hide
*/
- public static ArrayList<TimeZone> getTimeZonesWithUniqueOffsets(String country) {
+ public static List<String> getTimeZoneIdsWithUniqueOffsets(String country) {
synchronized(sLastUniqueLockObj) {
if ((country != null) && country.equals(sLastUniqueCountry)) {
if (DBG) {
@@ -116,9 +93,9 @@
}
}
- Collection<TimeZone> zones = getTimeZones(country);
- ArrayList<TimeZone> uniqueTimeZones = new ArrayList<TimeZone>();
- for (TimeZone zone : zones) {
+ Collection<android.icu.util.TimeZone> zones = getIcuTimeZones(country);
+ ArrayList<android.icu.util.TimeZone> uniqueTimeZones = new ArrayList<>();
+ for (android.icu.util.TimeZone zone : zones) {
// See if we already have this offset,
// Using slow but space efficient and these are small.
boolean found = false;
@@ -128,7 +105,7 @@
break;
}
}
- if (found == false) {
+ if (!found) {
if (DBG) {
Log.d(TAG, "getTimeZonesWithUniqueOffsets: add unique offset=" +
zone.getRawOffset() + " zone.getID=" + zone.getID());
@@ -139,81 +116,43 @@
synchronized(sLastUniqueLockObj) {
// Cache the last result
- sLastUniqueZoneOffsets = uniqueTimeZones;
+ sLastUniqueZoneOffsets = extractZoneIds(uniqueTimeZones);
sLastUniqueCountry = country;
return sLastUniqueZoneOffsets;
}
}
+ private static List<String> extractZoneIds(List<android.icu.util.TimeZone> timeZones) {
+ List<String> ids = new ArrayList<>(timeZones.size());
+ for (android.icu.util.TimeZone timeZone : timeZones) {
+ ids.add(timeZone.getID());
+ }
+ return Collections.unmodifiableList(ids);
+ }
+
/**
- * Returns the time zones for the country, which is the code
- * attribute of the timezone element in time_zones_by_country.xml. Do not modify.
+ * Returns an immutable list of frozen ICU time zones for the country.
*
- * @param country is a two character country code.
- * @return TimeZone list, maybe empty but never null. Do not modify.
+ * @param countryIso is a two character country code.
+ * @return TimeZone list, maybe empty but never null.
* @hide
*/
- public static ArrayList<TimeZone> getTimeZones(String country) {
- synchronized (sLastLockObj) {
- if ((country != null) && country.equals(sLastCountry)) {
- if (DBG) Log.d(TAG, "getTimeZones(" + country + "): return cached version");
- return sLastZones;
+ private static List<android.icu.util.TimeZone> getIcuTimeZones(String countryIso) {
+ if (countryIso == null) {
+ if (DBG) Log.d(TAG, "getIcuTimeZones(null): return empty list");
+ return Collections.emptyList();
+ }
+ List<android.icu.util.TimeZone> timeZones =
+ TimeZoneFinder.getInstance().lookupTimeZonesByCountry(countryIso);
+ if (timeZones == null) {
+ if (DBG) {
+ Log.d(TAG, "getIcuTimeZones(" + countryIso
+ + "): returned null, converting to empty list");
}
+ return Collections.emptyList();
}
-
- ArrayList<TimeZone> tzs = new ArrayList<TimeZone>();
-
- if (country == null) {
- if (DBG) Log.d(TAG, "getTimeZones(null): return empty list");
- return tzs;
- }
-
- Resources r = Resources.getSystem();
- XmlResourceParser parser = r.getXml(com.android.internal.R.xml.time_zones_by_country);
-
- try {
- XmlUtils.beginDocument(parser, "timezones");
-
- while (true) {
- XmlUtils.nextElement(parser);
-
- String element = parser.getName();
- if (element == null || !(element.equals("timezone"))) {
- break;
- }
-
- String code = parser.getAttributeValue(null, "code");
-
- if (country.equals(code)) {
- if (parser.next() == XmlPullParser.TEXT) {
- String zoneIdString = parser.getText();
- TimeZone tz = TimeZone.getTimeZone(zoneIdString);
- if (tz.getID().startsWith("GMT") == false) {
- // tz.getID doesn't start not "GMT" so its valid
- tzs.add(tz);
- if (DBG) {
- Log.d(TAG, "getTimeZone('" + country + "'): found tz.getID=="
- + ((tz != null) ? tz.getID() : "<no tz>"));
- }
- }
- }
- }
- }
- } catch (XmlPullParserException e) {
- Log.e(TAG, "Got xml parser exception getTimeZone('" + country + "'): e=", e);
- } catch (IOException e) {
- Log.e(TAG, "Got IO exception getTimeZone('" + country + "'): e=", e);
- } finally {
- parser.close();
- }
-
- synchronized(sLastLockObj) {
- // Cache the last result;
- sLastZones = tzs;
- sLastCountry = country;
- return sLastZones;
- }
+ return timeZones;
}
/**
diff --git a/core/java/android/util/proto/ProtoOutputStream.java b/core/java/android/util/proto/ProtoOutputStream.java
index 480abc1..9afa56d 100644
--- a/core/java/android/util/proto/ProtoOutputStream.java
+++ b/core/java/android/util/proto/ProtoOutputStream.java
@@ -24,7 +24,6 @@
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
-import java.util.List;
/**
* Class to write to a protobuf stream.
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 1b74c13..45fa561 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -17,6 +17,7 @@
package android.view;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN;
+import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY;
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
import android.graphics.Point;
@@ -34,13 +35,17 @@
import android.text.style.AccessibilityClickableSpan;
import android.text.style.ClickableSpan;
import android.util.LongSparseArray;
+import android.util.Slog;
import android.view.View.AttachInfo;
import android.view.accessibility.AccessibilityInteractionClient;
+import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.accessibility.AccessibilityRequestPreparer;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.SomeArgs;
import java.util.ArrayList;
@@ -61,11 +66,24 @@
*/
final class AccessibilityInteractionController {
+ private static final String LOG_TAG = "AccessibilityInteractionController";
+
+ // Debugging flag
private static final boolean ENFORCE_NODE_TREE_CONSISTENT = false;
+ // Constants for readability
+ private static final boolean IGNORE_REQUEST_PREPARERS = true;
+ private static final boolean CONSIDER_REQUEST_PREPARERS = false;
+
+ // If an app holds off accessibility for longer than this, the hold-off is canceled to prevent
+ // accessibility from hanging
+ private static final long REQUEST_PREPARER_TIMEOUT_MS = 500;
+
private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
new ArrayList<AccessibilityNodeInfo>();
+ private final Object mLock = new Object();
+
private final Handler mHandler;
private final ViewRootImpl mViewRootImpl;
@@ -76,6 +94,8 @@
private final int mMyProcessId;
+ private final AccessibilityManager mA11yManager;
+
private final ArrayList<View> mTempArrayList = new ArrayList<View>();
private final Point mTempPoint = new Point();
@@ -85,6 +105,13 @@
private AddNodeInfosForViewId mAddNodeInfosForViewId;
+ @GuardedBy("mLock")
+ private int mNumActiveRequestPreparers;
+ @GuardedBy("mLock")
+ private List<MessageHolder> mMessagesWaitingForRequestPreparer;
+ @GuardedBy("mLock")
+ private int mActiveRequestPreparerId;
+
public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
Looper looper = viewRootImpl.mHandler.getLooper();
mMyLooperThreadId = looper.getThread().getId();
@@ -92,18 +119,23 @@
mHandler = new PrivateHandler(looper);
mViewRootImpl = viewRootImpl;
mPrefetcher = new AccessibilityNodePrefetcher();
+ mA11yManager = mViewRootImpl.mContext.getSystemService(AccessibilityManager.class);
}
- private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid) {
- // If the interrogation is performed by the same thread as the main UI
- // thread in this process, set the message as a static reference so
- // after this call completes the same thread but in the interrogating
- // client can handle the message to generate the result.
- if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
- AccessibilityInteractionClient.getInstanceForThread(
- interrogatingTid).setSameThreadMessage(message);
- } else {
- mHandler.sendMessage(message);
+ private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid,
+ boolean ignoreRequestPreparers) {
+ if (ignoreRequestPreparers
+ || !holdOffMessageIfNeeded(message, interrogatingPid, interrogatingTid)) {
+ // If the interrogation is performed by the same thread as the main UI
+ // thread in this process, set the message as a static reference so
+ // after this call completes the same thread but in the interrogating
+ // client can handle the message to generate the result.
+ if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
+ AccessibilityInteractionClient.getInstanceForThread(
+ interrogatingTid).setSameThreadMessage(message);
+ } else {
+ mHandler.sendMessage(message);
+ }
}
}
@@ -121,11 +153,11 @@
long accessibilityNodeId, Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
long interrogatingTid, MagnificationSpec spec, Bundle arguments) {
- Message message = mHandler.obtainMessage();
+ final Message message = mHandler.obtainMessage();
message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID;
message.arg1 = flags;
- SomeArgs args = SomeArgs.obtain();
+ final SomeArgs args = SomeArgs.obtain();
args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
args.argi3 = interactionId;
@@ -135,7 +167,141 @@
args.arg4 = arguments;
message.obj = args;
- scheduleMessage(message, interrogatingPid, interrogatingTid);
+ scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
+ }
+
+ /**
+ * Check if this message needs to be held off while the app prepares to meet either this
+ * request, or a request ahead of it.
+ *
+ * @param originalMessage The message to be processed
+ * @param callingPid The calling process id
+ * @param callingTid The calling thread id
+ *
+ * @return {@code true} if the message is held off and will be processed later, {@code false} if
+ * the message should be posted.
+ */
+ private boolean holdOffMessageIfNeeded(
+ Message originalMessage, int callingPid, long callingTid) {
+ synchronized (mLock) {
+ // If a request is already pending, queue this request for when it's finished
+ if (mNumActiveRequestPreparers != 0) {
+ queueMessageToHandleOncePrepared(originalMessage, callingPid, callingTid);
+ return true;
+ }
+
+ // Currently the only message that can hold things off is findByA11yId with extra data.
+ if (originalMessage.what
+ != PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID) {
+ return false;
+ }
+ SomeArgs originalMessageArgs = (SomeArgs) originalMessage.obj;
+ Bundle requestArguments = (Bundle) originalMessageArgs.arg4;
+ if (requestArguments == null) {
+ return false;
+ }
+
+ // If nothing it registered for this view, nothing to do
+ int accessibilityViewId = originalMessageArgs.argi1;
+ final List<AccessibilityRequestPreparer> preparers =
+ mA11yManager.getRequestPreparersForAccessibilityId(accessibilityViewId);
+ if (preparers == null) {
+ return false;
+ }
+
+ // If the bundle doesn't request the extra data, nothing to do
+ final String extraDataKey = requestArguments.getString(EXTRA_DATA_REQUESTED_KEY);
+ if (extraDataKey == null) {
+ return false;
+ }
+
+ // Send the request to the AccessibilityRequestPreparers on the UI thread
+ mNumActiveRequestPreparers = preparers.size();
+ for (int i = 0; i < preparers.size(); i++) {
+ final Message requestPreparerMessage = mHandler.obtainMessage(
+ PrivateHandler.MSG_PREPARE_FOR_EXTRA_DATA_REQUEST);
+ final SomeArgs requestPreparerArgs = SomeArgs.obtain();
+ // virtualDescendentId
+ requestPreparerArgs.argi1 =
+ (originalMessageArgs.argi2 == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)
+ ? AccessibilityNodeProvider.HOST_VIEW_ID : originalMessageArgs.argi2;
+ requestPreparerArgs.arg1 = preparers.get(i);
+ requestPreparerArgs.arg2 = extraDataKey;
+ requestPreparerArgs.arg3 = requestArguments;
+ Message preparationFinishedMessage = mHandler.obtainMessage(
+ PrivateHandler.MSG_APP_PREPARATION_FINISHED);
+ preparationFinishedMessage.arg1 = ++mActiveRequestPreparerId;
+ requestPreparerArgs.arg4 = preparationFinishedMessage;
+
+ requestPreparerMessage.obj = requestPreparerArgs;
+ scheduleMessage(requestPreparerMessage, callingPid, callingTid,
+ IGNORE_REQUEST_PREPARERS);
+ mHandler.obtainMessage(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT);
+ mHandler.sendEmptyMessageDelayed(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT,
+ REQUEST_PREPARER_TIMEOUT_MS);
+ }
+
+ // Set the initial request aside
+ queueMessageToHandleOncePrepared(originalMessage, callingPid, callingTid);
+ return true;
+ }
+ }
+
+ private void prepareForExtraDataRequestUiThread(Message message) {
+ SomeArgs args = (SomeArgs) message.obj;
+ final int virtualDescendantId = args.argi1;
+ final AccessibilityRequestPreparer preparer = (AccessibilityRequestPreparer) args.arg1;
+ final String extraDataKey = (String) args.arg2;
+ final Bundle requestArguments = (Bundle) args.arg3;
+ final Message preparationFinishedMessage = (Message) args.arg4;
+
+ preparer.onPrepareExtraData(virtualDescendantId, extraDataKey,
+ requestArguments, preparationFinishedMessage);
+ }
+
+ private void queueMessageToHandleOncePrepared(Message message, int interrogatingPid,
+ long interrogatingTid) {
+ if (mMessagesWaitingForRequestPreparer == null) {
+ mMessagesWaitingForRequestPreparer = new ArrayList<>(1);
+ }
+ MessageHolder messageHolder =
+ new MessageHolder(message, interrogatingPid, interrogatingTid);
+ mMessagesWaitingForRequestPreparer.add(messageHolder);
+ }
+
+ private void requestPreparerDoneUiThread(Message message) {
+ synchronized (mLock) {
+ if (message.arg1 != mActiveRequestPreparerId) {
+ Slog.e(LOG_TAG, "Surprising AccessibilityRequestPreparer callback (likely late)");
+ return;
+ }
+ mNumActiveRequestPreparers--;
+ if (mNumActiveRequestPreparers <= 0) {
+ mHandler.removeMessages(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT);
+ scheduleAllMessagesWaitingForRequestPreparerLocked();
+ }
+ }
+ }
+
+ private void requestPreparerTimeoutUiThread() {
+ synchronized (mLock) {
+ Slog.e(LOG_TAG, "AccessibilityRequestPreparer timed out");
+ scheduleAllMessagesWaitingForRequestPreparerLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void scheduleAllMessagesWaitingForRequestPreparerLocked() {
+ int numMessages = mMessagesWaitingForRequestPreparer.size();
+ for (int i = 0; i < numMessages; i++) {
+ MessageHolder request = mMessagesWaitingForRequestPreparer.get(i);
+ scheduleMessage(request.mMessage, request.mInterrogatingPid,
+ request.mInterrogatingTid,
+ (i == 0) /* the app is ready for the first request */);
+ }
+ mMessagesWaitingForRequestPreparer.clear();
+ mNumActiveRequestPreparers = 0; // Just to be safe - should be unnecessary
+ mActiveRequestPreparerId = -1;
}
private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
@@ -193,7 +359,7 @@
args.arg4 = interactiveRegion;
message.obj = args;
- scheduleMessage(message, interrogatingPid, interrogatingTid);
+ scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
}
private void findAccessibilityNodeInfosByViewIdUiThread(Message message) {
@@ -259,7 +425,7 @@
args.arg4 = interactiveRegion;
message.obj = args;
- scheduleMessage(message, interrogatingPid, interrogatingTid);
+ scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
}
private void findAccessibilityNodeInfosByTextUiThread(Message message) {
@@ -347,7 +513,7 @@
message.obj = args;
- scheduleMessage(message, interrogatingPid, interrogatingTid);
+ scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
}
private void findFocusUiThread(Message message) {
@@ -442,7 +608,7 @@
message.obj = args;
- scheduleMessage(message, interrogatingPid, interrogatingTid);
+ scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
}
private void focusSearchUiThread(Message message) {
@@ -501,7 +667,7 @@
message.obj = args;
- scheduleMessage(message, interrogatingPid, interrogatingTid);
+ scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
}
private void performAccessibilityActionUiThread(Message message) {
@@ -787,7 +953,7 @@
AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
// Determine if we'll be populating extra data
final String extraDataRequested = (arguments == null) ? null
- : arguments.getString(AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY);
+ : arguments.getString(EXTRA_DATA_REQUESTED_KEY);
if (provider == null) {
AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
if (root != null) {
@@ -1115,6 +1281,9 @@
private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4;
private static final int MSG_FIND_FOCUS = 5;
private static final int MSG_FOCUS_SEARCH = 6;
+ private static final int MSG_PREPARE_FOR_EXTRA_DATA_REQUEST = 7;
+ private static final int MSG_APP_PREPARATION_FINISHED = 8;
+ private static final int MSG_APP_PREPARATION_TIMEOUT = 9;
public PrivateHandler(Looper looper) {
super(looper);
@@ -1136,6 +1305,12 @@
return "MSG_FIND_FOCUS";
case MSG_FOCUS_SEARCH:
return "MSG_FOCUS_SEARCH";
+ case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST:
+ return "MSG_PREPARE_FOR_EXTRA_DATA_REQUEST";
+ case MSG_APP_PREPARATION_FINISHED:
+ return "MSG_APP_PREPARATION_FINISHED";
+ case MSG_APP_PREPARATION_TIMEOUT:
+ return "MSG_APP_PREPARATION_TIMEOUT";
default:
throw new IllegalArgumentException("Unknown message type: " + type);
}
@@ -1163,6 +1338,15 @@
case MSG_FOCUS_SEARCH: {
focusSearchUiThread(message);
} break;
+ case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST: {
+ prepareForExtraDataRequestUiThread(message);
+ } break;
+ case MSG_APP_PREPARATION_FINISHED: {
+ requestPreparerDoneUiThread(message);
+ } break;
+ case MSG_APP_PREPARATION_TIMEOUT: {
+ requestPreparerTimeoutUiThread();
+ } break;
default:
throw new IllegalArgumentException("Unknown message type: " + type);
}
@@ -1191,4 +1375,16 @@
return false;
}
}
+
+ private static final class MessageHolder {
+ final Message mMessage;
+ final int mInterrogatingPid;
+ final long mInterrogatingTid;
+
+ MessageHolder(Message message, int interrogatingPid, long interrogatingTid) {
+ mMessage = message;
+ mInterrogatingPid = interrogatingPid;
+ mInterrogatingTid = interrogatingTid;
+ }
+ }
}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index aac5baa..597be68 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -16,6 +16,9 @@
package android.view;
+import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP;
+import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER;
+
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
import android.os.Looper;
@@ -102,10 +105,23 @@
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
- return new Choreographer(looper);
+ return new Choreographer(looper, VSYNC_SOURCE_APP);
}
};
+ // Thread local storage for the SF choreographer.
+ private static final ThreadLocal<Choreographer> sSfThreadInstance =
+ new ThreadLocal<Choreographer>() {
+ @Override
+ protected Choreographer initialValue() {
+ Looper looper = Looper.myLooper();
+ if (looper == null) {
+ throw new IllegalStateException("The current thread must have a looper!");
+ }
+ return new Choreographer(looper, VSYNC_SOURCE_SURFACE_FLINGER);
+ }
+ };
+
// Enable/disable vsync for animations and drawing.
private static final boolean USE_VSYNC = SystemProperties.getBoolean(
"debug.choreographer.vsync", true);
@@ -202,10 +218,12 @@
private static final int CALLBACK_LAST = CALLBACK_COMMIT;
- private Choreographer(Looper looper) {
+ private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
mHandler = new FrameHandler(looper);
- mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
+ mDisplayEventReceiver = USE_VSYNC
+ ? new FrameDisplayEventReceiver(looper, vsyncSource)
+ : null;
mLastFrameTimeNanos = Long.MIN_VALUE;
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
@@ -233,6 +251,13 @@
return sThreadInstance.get();
}
+ /**
+ * @hide
+ */
+ public static Choreographer getSfInstance() {
+ return sSfThreadInstance.get();
+ }
+
/** Destroys the calling thread's choreographer
* @hide
*/
@@ -816,8 +841,8 @@
private long mTimestampNanos;
private int mFrame;
- public FrameDisplayEventReceiver(Looper looper) {
- super(looper);
+ public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
+ super(looper, vsyncSource);
}
@Override
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 67cdfc5..caadc36 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -35,6 +35,23 @@
* @hide
*/
public abstract class DisplayEventReceiver {
+
+ /**
+ * When retrieving vsync events, this specifies that the vsync event should happen at the normal
+ * vsync-app tick.
+ * <p>
+ * Needs to be kept in sync with frameworks/native/include/gui/ISurfaceComposer.h
+ */
+ public static final int VSYNC_SOURCE_APP = 0;
+
+ /**
+ * When retrieving vsync events, this specifies that the vsync event should happen whenever
+ * Surface Flinger is processing a frame.
+ * <p>
+ * Needs to be kept in sync with frameworks/native/include/gui/ISurfaceComposer.h
+ */
+ public static final int VSYNC_SOURCE_SURFACE_FLINGER = 1;
+
private static final String TAG = "DisplayEventReceiver";
private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -46,7 +63,7 @@
private MessageQueue mMessageQueue;
private static native long nativeInit(WeakReference<DisplayEventReceiver> receiver,
- MessageQueue messageQueue);
+ MessageQueue messageQueue, int vsyncSource);
private static native void nativeDispose(long receiverPtr);
@FastNative
private static native void nativeScheduleVsync(long receiverPtr);
@@ -55,14 +72,16 @@
* Creates a display event receiver.
*
* @param looper The looper to use when invoking callbacks.
+ * @param vsyncSource The source of the vsync tick. Must be on of the VSYNC_SOURCE_* values.
*/
- public DisplayEventReceiver(Looper looper) {
+ public DisplayEventReceiver(Looper looper, int vsyncSource) {
if (looper == null) {
throw new IllegalArgumentException("looper must not be null");
}
mMessageQueue = looper.getQueue();
- mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue);
+ mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
+ vsyncSource);
mCloseGuard.open("dispose");
}
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 2f87d2e..31cece4 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -29,12 +29,12 @@
* in an action being performed.
*/
public static final int LONG_PRESS = 0;
-
+
/**
* The user has pressed on a virtual on-screen key.
*/
public static final int VIRTUAL_KEY = 1;
-
+
/**
* The user has pressed a soft keyboard key.
*/
@@ -57,24 +57,29 @@
public static final int CONTEXT_CLICK = 6;
/**
+ * The user has released a virtual or software keyboard key.
+ */
+ public static final int VIRTUAL_KEY_RELEASE = 7;
+
+ /**
* This is a private constant. Feel free to renumber as desired.
* @hide
*/
public static final int SAFE_MODE_DISABLED = 10000;
-
+
/**
* This is a private constant. Feel free to renumber as desired.
* @hide
*/
public static final int SAFE_MODE_ENABLED = 10001;
-
+
/**
* Flag for {@link View#performHapticFeedback(int, int)
* View.performHapticFeedback(int, int)}: Ignore the setting in the
* view for whether to perform haptic feedback, do it always.
*/
public static final int FLAG_IGNORE_VIEW_SETTING = 0x0001;
-
+
/**
* Flag for {@link View#performHapticFeedback(int, int)
* View.performHapticFeedback(int, int)}: Ignore the global setting
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 58a6a5e..2b73c14 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -29,6 +29,7 @@
import android.graphics.GraphicBuffer;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.Region;
import android.os.Bundle;
import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
@@ -377,4 +378,9 @@
* associated with that InputConsumer.
*/
boolean destroyInputConsumer(String name);
+
+ /**
+ * Return the touch region for the current IME window, or an empty region if there is none.
+ */
+ Region getCurrentImeTouchRegion();
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 286e790..51d6514 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -96,7 +96,7 @@
int flags, out Rect outFrame, out Rect outOverscanInsets,
out Rect outContentInsets, out Rect outVisibleInsets, out Rect outStableInsets,
out Rect outOutsets, out Rect outBackdropFrame,
- inout MergedConfiguration mergedConfiguration, out Surface outSurface);
+ out MergedConfiguration outMergedConfiguration, out Surface outSurface);
/*
* Notify the window manager that an application is relaunching and
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index ea2434e..8405d9e 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -16,6 +16,8 @@
package android.view;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
import android.content.Context;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
@@ -768,6 +770,36 @@
}
/**
+ * Returns true if input device is enabled.
+ * @return Whether the input device is enabled.
+ */
+ public boolean isEnabled() {
+ return InputManager.getInstance().isInputDeviceEnabled(mId);
+ }
+
+ /**
+ * Enables the input device.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.DISABLE_INPUT_DEVICE)
+ @TestApi
+ public void enable() {
+ InputManager.getInstance().enableInputDevice(mId);
+ }
+
+ /**
+ * Disables the input device.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.DISABLE_INPUT_DEVICE)
+ @TestApi
+ public void disable() {
+ InputManager.getInstance().disableInputDevice(mId);
+ }
+
+ /**
* Reports whether the device has a built-in microphone.
* @return Whether the device has a built-in microphone.
*/
diff --git a/core/java/android/view/KeyboardShortcutGroup.java b/core/java/android/view/KeyboardShortcutGroup.java
index 78f0b30..52e9832 100644
--- a/core/java/android/view/KeyboardShortcutGroup.java
+++ b/core/java/android/view/KeyboardShortcutGroup.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -58,6 +59,7 @@
* @param isSystemGroup Set this to {@code true} if this is s system group.
* @hide
*/
+ @TestApi
public KeyboardShortcutGroup(@Nullable CharSequence label,
@NonNull List<KeyboardShortcutInfo> items, boolean isSystemGroup) {
mLabel = label;
@@ -70,6 +72,7 @@
* @param isSystemGroup Set this to {@code true} if this is s system group.
* @hide
*/
+ @TestApi
public KeyboardShortcutGroup(@Nullable CharSequence label, boolean isSystemGroup) {
this(label, Collections.<KeyboardShortcutInfo>emptyList(), isSystemGroup);
}
@@ -96,6 +99,7 @@
}
/** @hide **/
+ @TestApi
public boolean isSystemGroup() {
return mSystemGroup;
}
@@ -123,11 +127,11 @@
public static final Creator<KeyboardShortcutGroup> CREATOR =
new Creator<KeyboardShortcutGroup>() {
- public KeyboardShortcutGroup createFromParcel(Parcel source) {
- return new KeyboardShortcutGroup(source);
- }
- public KeyboardShortcutGroup[] newArray(int size) {
- return new KeyboardShortcutGroup[size];
- }
- };
+ public KeyboardShortcutGroup createFromParcel(Parcel source) {
+ return new KeyboardShortcutGroup(source);
+ }
+ public KeyboardShortcutGroup[] newArray(int size) {
+ return new KeyboardShortcutGroup[size];
+ }
+ };
}
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 20f7ace..568c40a 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Rect;
@@ -27,6 +28,7 @@
import android.widget.ImageView;
import android.widget.RemoteViews;
+import com.android.internal.R;
import com.android.internal.widget.CachingIconView;
import java.util.ArrayList;
@@ -52,9 +54,12 @@
private int mIconColor;
private int mOriginalNotificationColor;
private boolean mExpanded;
+ private boolean mShowExpandButtonAtEnd;
private boolean mShowWorkBadgeAtEnd;
private Drawable mBackground;
private int mHeaderBackgroundHeight;
+ private boolean mEntireHeaderClickable;
+ private boolean mAcceptAllTouches;
ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@Override
@@ -65,7 +70,6 @@
}
}
};
- private boolean mAcceptAllTouches;
public NotificationHeaderView(Context context) {
this(context, null);
@@ -81,12 +85,12 @@
public NotificationHeaderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mChildMinWidth = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_header_shrink_min_width);
- mContentEndMargin = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_margin_end);
- mHeaderBackgroundHeight = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_header_background_height);
+ Resources res = getResources();
+ mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width);
+ mContentEndMargin = res.getDimensionPixelSize(R.dimen.notification_content_margin_end);
+ mHeaderBackgroundHeight = res.getDimensionPixelSize(
+ R.dimen.notification_header_background_height);
+ mEntireHeaderClickable = res.getBoolean(R.bool.config_notificationHeaderClickableForExpand);
}
@Override
@@ -147,8 +151,9 @@
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left = getPaddingStart();
+ int end = getMeasuredWidth();
int childCount = getChildCount();
- int ownHeight = getHeight() - getPaddingTop() - getPaddingBottom();
+ int ownHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE) {
@@ -162,13 +167,17 @@
int bottom = top + childHeight;
int layoutLeft = left;
int layoutRight = right;
+ if (child == mExpandButton && mShowExpandButtonAtEnd) {
+ layoutRight = end - mContentEndMargin;
+ end = layoutLeft = layoutRight - child.getMeasuredWidth();
+ }
if (child == mProfileBadge) {
int paddingEnd = getPaddingEnd();
if (mShowWorkBadgeAtEnd) {
paddingEnd = mContentEndMargin;
}
- layoutRight = getWidth() - paddingEnd;
- layoutLeft = layoutRight - child.getMeasuredWidth();
+ layoutRight = end - paddingEnd;
+ end = layoutLeft = layoutRight - child.getMeasuredWidth();
}
if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
int ltrLeft = layoutLeft;
@@ -265,13 +274,11 @@
int drawableId;
int contentDescriptionId;
if (mExpanded) {
- drawableId = com.android.internal.R.drawable.ic_collapse_notification;
- contentDescriptionId
- = com.android.internal.R.string.expand_button_content_description_expanded;
+ drawableId = R.drawable.ic_collapse_notification;
+ contentDescriptionId = R.string.expand_button_content_description_expanded;
} else {
- drawableId = com.android.internal.R.drawable.ic_expand_notification;
- contentDescriptionId
- = com.android.internal.R.string.expand_button_content_description_collapsed;
+ drawableId = R.drawable.ic_expand_notification;
+ contentDescriptionId = R.string.expand_button_content_description_collapsed;
}
mExpandButton.setImageDrawable(getContext().getDrawable(drawableId));
mExpandButton.setColorFilter(mOriginalNotificationColor);
@@ -285,6 +292,18 @@
}
}
+ /**
+ * Sets whether or not the expand button appears at the end of the NotificationHeaderView. If
+ * both this and {@link #setShowWorkBadgeAtEnd(boolean)} have been set to true, then the
+ * expand button will appear closer to the end than the work badge.
+ */
+ public void setShowExpandButtonAtEnd(boolean showExpandButtonAtEnd) {
+ if (showExpandButtonAtEnd != mShowExpandButtonAtEnd) {
+ setClipToPadding(!showExpandButtonAtEnd);
+ mShowExpandButtonAtEnd = showExpandButtonAtEnd;
+ }
+ }
+
public View getWorkProfileIcon() {
return mProfileBadge;
}
@@ -306,8 +325,8 @@
public void bindTouchRects() {
mTouchRects.clear();
- addRectAroundViewView(mIcon);
- addRectAroundViewView(mExpandButton);
+ addRectAroundView(mIcon);
+ addRectAroundView(mExpandButton);
addWidthRect();
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
@@ -321,7 +340,7 @@
mTouchRects.add(r);
}
- private void addRectAroundViewView(View view) {
+ private void addRectAroundView(View view) {
final Rect r = getRectAroundView(view);
mTouchRects.add(r);
}
@@ -412,8 +431,13 @@
return mTouchListener.isInside(x, y);
}
+ /**
+ * Sets whether or not all touches to this header view will register as a click. Note that
+ * if the config value for {@code config_notificationHeaderClickableForExpand} is {@code true},
+ * then calling this method with {@code false} will not override that configuration.
+ */
@RemotableViewMethod
public void setAcceptAllTouches(boolean acceptAllTouches) {
- mAcceptAllTouches = acceptAllTouches;
+ mAcceptAllTouches = mEntireHeaderClickable || acceptAllTouches;
}
}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 0ad591b..8bb3fa9 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -129,17 +129,11 @@
public static final int SCALING_MODE_NO_SCALE_CROP = 3;
/** @hide */
- @IntDef({ROTATION_UNDEFINED, ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
+ @IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
@Retention(RetentionPolicy.SOURCE)
public @interface Rotation {}
/**
- * Rotation constant: undefined
- * @hide
- */
- public static final int ROTATION_UNDEFINED = -1;
-
- /**
* Rotation constant: 0 degree rotation (natural orientation)
*/
public static final int ROTATION_0 = 0;
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index a97babd..ef78559 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -454,7 +454,7 @@
}
private void updateOpaqueFlag() {
- if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
+ if (!PixelFormat.formatHasAlpha(mRequestedFormat)) {
mSurfaceFlags |= SurfaceControl.OPAQUE;
} else {
mSurfaceFlags &= ~SurfaceControl.OPAQUE;
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 7cec957..de4d03e 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -29,6 +29,7 @@
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.Trace;
import android.util.Log;
import android.view.Surface.OutOfResourcesException;
@@ -188,6 +189,11 @@
public static final String DEBUG_SHOW_NON_RECTANGULAR_CLIP_PROPERTY =
"debug.hwui.show_non_rect_clip";
+ static {
+ // Try to check OpenGL support early if possible.
+ isAvailable();
+ }
+
/**
* A process can set this flag to false to prevent the use of threaded
* rendering.
@@ -227,8 +233,7 @@
sTrimForeground = true;
}
- private static native boolean nSupportsOpenGL();
- private static boolean sSupportsOpenGL = nSupportsOpenGL();
+ private static Boolean sSupportsOpenGL;
/**
* Indicates whether threaded rendering is available under any form for
@@ -238,7 +243,24 @@
* false otherwise
*/
public static boolean isAvailable() {
- return sSupportsOpenGL;
+ if (sSupportsOpenGL != null) {
+ return sSupportsOpenGL.booleanValue();
+ }
+ if (SystemProperties.getInt("ro.kernel.qemu", 0) == 0) {
+ // Device is not an emulator.
+ sSupportsOpenGL = true;
+ return true;
+ }
+ int qemu_gles = SystemProperties.getInt("qemu.gles", -1);
+ if (qemu_gles == -1) {
+ // In this case, the value of the qemu.gles property is not ready
+ // because the SurfaceFlinger service may not start at this point.
+ return false;
+ }
+ // In the emulator this property will be set > 0 when OpenGL ES 2.0 is
+ // enabled, 0 otherwise. On old emulator versions it will be undefined.
+ sSupportsOpenGL = qemu_gles > 0;
+ return sSupportsOpenGL.booleanValue();
}
/**
@@ -336,7 +358,6 @@
private long mNativeProxy;
private boolean mInitialized = false;
private RenderNode mRootNode;
- private Choreographer mChoreographer;
private boolean mRootNodeNeedsUpdate;
private boolean mEnabled;
@@ -405,8 +426,6 @@
/**
* Indicates whether threaded rendering is currently requested but not
* necessarily enabled yet.
- *
- * @return True to request threaded rendering, false otherwise.
*/
void setRequested(boolean requested) {
mRequested = requested;
@@ -582,6 +601,13 @@
}
/**
+ * Enable/disable wide gamut rendering on this renderer.
+ */
+ void setWideGamut(boolean wideGamut) {
+ nSetWideGamut(mNativeProxy, wideGamut);
+ }
+
+ /**
* Gets the current width of the surface. This is the width that the surface
* was last set to in a call to {@link #setup(int, int, View.AttachInfo, Rect)}.
*
@@ -918,11 +944,11 @@
if (mInitialized) return;
mInitialized = true;
mAppContext = context.getApplicationContext();
- initSched(context, renderProxy);
+ initSched(renderProxy);
initGraphicsStats();
}
- private void initSched(Context context, long renderProxy) {
+ private void initSched(long renderProxy) {
try {
int tid = nGetRenderThreadTid(renderProxy);
ActivityManager.getService().setRenderThread(tid);
@@ -992,6 +1018,7 @@
private static native void nSetLightCenter(long nativeProxy,
float lightX, float lightY, float lightZ);
private static native void nSetOpaque(long nativeProxy, boolean opaque);
+ private static native void nSetWideGamut(long nativeProxy, boolean wideGamut);
private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
private static native void nDestroy(long nativeProxy, long rootRenderNode);
private static native void nRegisterAnimatingRenderNode(long rootRenderNode, long animatingNode);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 19ae253..83f1ed9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1139,6 +1139,10 @@
/**
* Autofill type for views that cannot be autofilled.
+ *
+ * <p>Typically used when the view is read-only; for example, a text label.
+ *
+ * @see #getAutofillType()
*/
public static final int AUTOFILL_TYPE_NONE = 0;
@@ -1148,6 +1152,8 @@
* <p>{@link AutofillValue} instances for autofilling a {@link View} can be obtained through
* {@link AutofillValue#forText(CharSequence)}, and the value passed to autofill a
* {@link View} can be fetched through {@link AutofillValue#getTextValue()}.
+ *
+ * @see #getAutofillType()
*/
public static final int AUTOFILL_TYPE_TEXT = 1;
@@ -1157,6 +1163,8 @@
* <p>{@link AutofillValue} instances for autofilling a {@link View} can be obtained through
* {@link AutofillValue#forToggle(boolean)}, and the value passed to autofill a
* {@link View} can be fetched through {@link AutofillValue#getToggleValue()}.
+ *
+ * @see #getAutofillType()
*/
public static final int AUTOFILL_TYPE_TOGGLE = 2;
@@ -1170,6 +1178,8 @@
*
* <p>The available options in the selection list are typically provided by
* {@link android.app.assist.AssistStructure.ViewNode#getAutofillOptions()}.
+ *
+ * @see #getAutofillType()
*/
public static final int AUTOFILL_TYPE_LIST = 3;
@@ -1182,6 +1192,8 @@
* <p>{@link AutofillValue} instances for autofilling a {@link View} can be obtained through
* {@link AutofillValue#forDate(long)}, and the values passed to
* autofill a {@link View} can be fetched through {@link AutofillValue#getDateValue()}.
+ *
+ * @see #getAutofillType()
*/
public static final int AUTOFILL_TYPE_DATE = 4;
@@ -1198,26 +1210,41 @@
/**
* Automatically determine whether a view is important for autofill.
+ *
+ * @see #isImportantForAutofill()
+ * @see #setImportantForAutofill(int)
*/
public static final int IMPORTANT_FOR_AUTOFILL_AUTO = 0x0;
/**
* The view is important for autofill, and its children (if any) will be traversed.
+ *
+ * @see #isImportantForAutofill()
+ * @see #setImportantForAutofill(int)
*/
public static final int IMPORTANT_FOR_AUTOFILL_YES = 0x1;
/**
* The view is not important for autofill, but its children (if any) will be traversed.
+ *
+ * @see #isImportantForAutofill()
+ * @see #setImportantForAutofill(int)
*/
public static final int IMPORTANT_FOR_AUTOFILL_NO = 0x2;
/**
* The view is important for autofill, but its children (if any) will not be traversed.
+ *
+ * @see #isImportantForAutofill()
+ * @see #setImportantForAutofill(int)
*/
public static final int IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS = 0x4;
/**
* The view is not important for autofill, and its children (if any) will not be traversed.
+ *
+ * @see #isImportantForAutofill()
+ * @see #setImportantForAutofill(int)
*/
public static final int IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS = 0x8;
@@ -2073,8 +2100,6 @@
private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
- SendViewStateChangedAccessibilityEvent mSendViewStateChangedAccessibilityEvent;
-
/**
* The view's tag.
* {@hide}
@@ -7366,23 +7391,70 @@
}
/**
- * Called when assist structure is being retrieved from a view as part of an autofill request.
+ * Populates a {@link ViewStructure} to fullfil an autofill request.
*
- * <p>This method already provides most of what's needed for autofill, but should be overridden
- * when:
+ * <p>The structure should contain at least the following properties:
* <ul>
- * <li>The view contents does not include PII (Personally Identifiable Information), so it
- * can call {@link ViewStructure#setDataIsSensitive(boolean)} passing {@code false}.
- * <li>It must set fields such {@link ViewStructure#setText(CharSequence)},
- * {@link ViewStructure#setAutofillOptions(CharSequence[])},
- * or {@link ViewStructure#setWebDomain(String)}.
- * <li> The {@code left} and {@code top} values set in
- * {@link ViewStructure#setDimens(int, int, int, int, int, int)} need to be relative to the next
- * {@link ViewGroup#isImportantForAutofill() included} parent in the structure.
+ * <li>Autofill id ({@link ViewStructure#setAutofillId(AutofillId, int)}).
+ * <li>Autofill type ({@link ViewStructure#setAutofillType(int)}).
+ * <li>Autofill value ({@link ViewStructure#setAutofillValue(AutofillValue)}).
+ * <li>Whether the data is sensitive ({@link ViewStructure#setDataIsSensitive(boolean)}).
* </ul>
*
- * @param structure Fill in with structured view data. The default implementation
- * fills in all data that can be inferred from the view itself.
+ * <p>It's also recommended to set the following properties - the more properties the structure
+ * has, the higher the changes of an {@link android.service.autofill.AutofillService} properly
+ * using the structure:
+ *
+ * <ul>
+ * <li>Autofill hints ({@link ViewStructure#setAutofillHints(String[])}).
+ * <li>Autofill options ({@link ViewStructure#setAutofillOptions(CharSequence[])}) when the
+ * view can only be filled with predefined values (typically used when the autofill type
+ * is {@link #AUTOFILL_TYPE_LIST}).
+ * <li>Resource id ({@link ViewStructure#setId(int, String, String, String)}).
+ * <li>Class name ({@link ViewStructure#setClassName(String)}).
+ * <li>Content description ({@link ViewStructure#setContentDescription(CharSequence)}).
+ * <li>Visual properties such as visibility ({@link ViewStructure#setVisibility(int)}),
+ * dimensions ({@link ViewStructure#setDimens(int, int, int, int, int, int)}), and
+ * opacity ({@link ViewStructure#setOpaque(boolean)}).
+ * <li>For views representing text fields, text properties such as the text itself
+ * ({@link ViewStructure#setText(CharSequence)}), text hints
+ * ({@link ViewStructure#setHint(CharSequence)}, input type
+ * ({@link ViewStructure#setInputType(int)}),
+ * <li>For views representing HTML nodes, its web domain
+ * ({@link ViewStructure#setWebDomain(String)}) and HTML properties
+ * (({@link ViewStructure#setHtmlInfo(android.view.ViewStructure.HtmlInfo)}).
+ * </ul>
+ *
+ * <p>The default implementation of this method already sets most of these properties based on
+ * related {@link View} methods (for example, the autofill id is set using
+ * {@link #getAutofillId()}, the autofill type set using {@link #getAutofillType()}, etc.),
+ * and views in the standard Android widgets library also override it to set their
+ * relevant properties (for example, {@link android.widget.TextView} already sets the text
+ * properties), so it's recommended to only override this method
+ * (and call {@code super.onProvideAutofillStructure()}) when:
+ *
+ * <ul>
+ * <li>The view contents does not include PII (Personally Identifiable Information), so it
+ * can call {@link ViewStructure#setDataIsSensitive(boolean)} passing {@code false}.
+ * <li>The view can only be autofilled with predefined options, so it can call
+ * {@link ViewStructure#setAutofillOptions(CharSequence[])}.
+ * </ul>
+ *
+ * <p><b>NOTE:</b> the {@code left} and {@code top} values set in
+ * {@link ViewStructure#setDimens(int, int, int, int, int, int)} must be relative to the next
+ * {@link ViewGroup#isImportantForAutofill()} predecessor view included in the structure.
+ *
+ * <p>Views support the Autofill Framework mainly by:
+ * <ul>
+ * <li>Providing the metadata defining what the view means and how it can be autofilled.
+ * <li>Notifying the Android System when the view value changed by calling
+ * {@link AutofillManager#notifyValueChanged(View)}.
+ * <li>Implementing the methods that autofill the view.
+ * </ul>
+ * <p>This method is responsible for the former; {@link #autofill(AutofillValue)} is responsible
+ * for the latter.
+ *
+ * @param structure fill in with structured view data for autofill purposes.
* @param flags optional flags.
*
* @see #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
@@ -7510,32 +7582,56 @@
}
/**
- * Called when assist structure is being retrieved from a view as part of an autofill request
- * to generate additional virtual structure under this view.
+ * Populates a {@link ViewStructure} containing virtual children to fullfil an autofill
+ * request.
+ *
+ * <p>This method should be used when the view manages a virtual structure under this view. For
+ * example, a view that draws input fields using {@link #draw(Canvas)}.
*
* <p>When implementing this method, subclasses must follow the rules below:
*
- * <ol>
- * <li>Also implement {@link #autofill(SparseArray)} to autofill the virtual
- * children.
- * <li>Call
- * {@link android.view.autofill.AutofillManager#notifyViewEntered} and
- * {@link android.view.autofill.AutofillManager#notifyViewExited(View, int)}
- * when the focus inside the view changed.
- * <li>Call {@link android.view.autofill.AutofillManager#notifyValueChanged(View, int,
- * AutofillValue)} when the value of a child changed.
- * <li>Call {@link AutofillManager#commit()} when the autofill context
- * of the view structure changed and you want the current autofill interaction if such
- * to be commited.
- * <li>Call {@link AutofillManager#cancel()} when the autofill context
- * of the view structure changed and you want the current autofill interaction if such
- * to be cancelled.
- * <li> The {@code left} and {@code top} values set in
- * {@link ViewStructure#setDimens(int, int, int, int, int, int)} need to be relative to the next
- * {@link ViewGroup#isImportantForAutofill() included} parent in the structure.
- * </ol>
+ * <ul>
+ * <li>Add virtual children by calling the {@link ViewStructure#newChild(int)} or
+ * {@link ViewStructure#asyncNewChild(int)} methods, where the {@code id} is an unique id
+ * identifying the children in the virtual structure.
+ * <li>The children hierarchy can have multiple levels if necessary, but ideally it should
+ * exclude intermediate levels that are irrelevant for autofill; that would improve the
+ * autofill performance.
+ * <li>Also implement {@link #autofill(SparseArray)} to autofill the virtual
+ * children.
+ * <li>Set the autofill properties of the child structure as defined by
+ * {@link #onProvideAutofillStructure(ViewStructure, int)}, using
+ * {@link ViewStructure#setAutofillId(AutofillId, int)} to set its autofill id.
+ * <li>Call {@link android.view.autofill.AutofillManager#notifyViewEntered(View, int, Rect)}
+ * and/or {@link android.view.autofill.AutofillManager#notifyViewExited(View, int)}
+ * when the focused virtual child changed.
+ * <li>Call
+ * {@link android.view.autofill.AutofillManager#notifyValueChanged(View, int, AutofillValue)}
+ * when the value of a virtual child changed.
+ * <li>Call {@link AutofillManager#commit()} when the autofill context of the view structure
+ * changed and the current context should be committed (for example, when the user tapped
+ * a {@code SUBMIT} button in an HTML page).
+ * <li>Call {@link AutofillManager#cancel()} when the autofill context of the view structure
+ * changed and the current context should be canceled (for example, when the user tapped
+ * a {@code CANCEL} button in an HTML page).
+ * <li>Provide ways for users to manually request autofill by calling
+ * {@link AutofillManager#requestAutofill(View, int, Rect)}.
+ * <li>The {@code left} and {@code top} values set in
+ * {@link ViewStructure#setDimens(int, int, int, int, int, int)} must be relative to the
+ * next {@link ViewGroup#isImportantForAutofill()} predecessor view included in the
+ * structure.
+ * </ul>
*
- * @param structure Fill in with structured view data.
+ * <p>Views with virtual children support the Autofill Framework mainly by:
+ * <ul>
+ * <li>Providing the metadata defining what the virtual children mean and how they can be
+ * autofilled.
+ * <li>Implementing the methods that autofill the virtual children.
+ * </ul>
+ * <p>This method is responsible for the former; {@link #autofill(SparseArray)} is responsible
+ * for the latter.
+ *
+ * @param structure fill in with virtual children data for autofill purposes.
* @param flags optional flags.
*
* @see #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
@@ -7546,15 +7642,21 @@
/**
* Automatically fills the content of this view with the {@code value}.
*
- * <p>By default does nothing, but views should override it (and {@link #getAutofillType()},
- * {@link #getAutofillValue()}, and {@link #onProvideAutofillStructure(ViewStructure, int)}
- * to support the Autofill Framework.
+ * <p>Views support the Autofill Framework mainly by:
+ * <ul>
+ * <li>Providing the metadata defining what the view means and how it can be autofilled.
+ * <li>Implementing the methods that autofill the view.
+ * </ul>
+ * <p>{@link #onProvideAutofillStructure(ViewStructure, int)} is responsible for the former,
+ * this method is responsible for latter.
*
- * <p>Typically, it is implemented by:
- *
+ * <p>This method does nothing by default, but when overridden it typically:
* <ol>
- * <li>Calling the proper getter method on {@link AutofillValue} to fetch the actual value.
- * <li>Passing the actual value to the equivalent setter in the view.
+ * <li>Checks if the provided value matches the expected type (which is defined by
+ * {@link #getAutofillType()}).
+ * <li>Checks if the view is editable - if it isn't, it should return right away.
+ * <li>Call the proper getter method on {@link AutofillValue} to fetch the actual value.
+ * <li>Pass the actual value to the equivalent setter in the view.
* </ol>
*
* <p>For example, a text-field view could implement the method this way:
@@ -7573,7 +7675,7 @@
* </pre>
*
* <p>If the value is updated asynchronously the next call to
- * {@link AutofillManager#notifyValueChanged(View)} must happen <u>after</u> the value was
+ * {@link AutofillManager#notifyValueChanged(View)} must happen <b>after</b> the value was
* changed to the autofilled value. If not, the view will not be considered autofilled.
*
* @param value value to be autofilled.
@@ -7582,11 +7684,19 @@
}
/**
- * Automatically fills the content of a virtual views.
+ * Automatically fills the content of the virtual children within this view.
*
- * <p>See {@link #autofill(AutofillValue)} and
- * {@link #onProvideAutofillVirtualStructure(ViewStructure, int)} for more info.
- * <p>To indicate that a virtual view was autofilled
+ * <p>Views with virtual children support the Autofill Framework mainly by:
+ * <ul>
+ * <li>Providing the metadata defining what the virtual children mean and how they can be
+ * autofilled.
+ * <li>Implementing the methods that autofill the virtual children.
+ * </ul>
+ * <p>{@link #onProvideAutofillVirtualStructure(ViewStructure, int)} is responsible for the
+ * former, this method is responsible for the latter - see {@link #autofill(AutofillValue)} and
+ * {@link #onProvideAutofillVirtualStructure(ViewStructure, int)} for more info about autofill.
+ *
+ * <p><b>NOTE:</b> to indicate that a virtual view was autofilled,
* <code>?android:attr/autofilledHighlight</code> should be drawn over it until the data
* changes.
*
@@ -7598,9 +7708,9 @@
}
/**
- * Gets the unique identifier of this view on the screen for Autofill purposes.
+ * Gets the unique identifier of this view in the screen, for autofill purposes.
*
- * @return The View's Autofill id.
+ * @return The View's autofill id.
*/
public final AutofillId getAutofillId() {
if (mAutofillId == null) {
@@ -7612,11 +7722,18 @@
}
/**
- * Describes the autofill type that should be used on calls to
- * {@link #autofill(AutofillValue)} and {@link #autofill(SparseArray)}.
+ * Describes the autofill type of this view, so an
+ * {@link android.service.autofill.AutofillService} can create the proper {@link AutofillValue}
+ * when autofilling the view.
*
- * <p>By default returns {@link #AUTOFILL_TYPE_NONE}, but views should override it (and
- * {@link #autofill(AutofillValue)} to support the Autofill Framework.
+ * <p>By default returns {@link #AUTOFILL_TYPE_NONE}, but views should override it to properly
+ * support the Autofill Framework.
+ *
+ * @return either {@link #AUTOFILL_TYPE_NONE}, {@link #AUTOFILL_TYPE_TEXT},
+ * {@link #AUTOFILL_TYPE_LIST}, {@link #AUTOFILL_TYPE_DATE}, or {@link #AUTOFILL_TYPE_TOGGLE}.
+ *
+ * @see #onProvideAutofillStructure(ViewStructure, int)
+ * @see #autofill(AutofillValue)
*/
public @AutofillType int getAutofillType() {
return AUTOFILL_TYPE_NONE;
@@ -7648,9 +7765,11 @@
/**
* Gets the {@link View}'s current autofill value.
*
- * <p>By default returns {@code null}, but views should override it (and
- * {@link #autofill(AutofillValue)}, and {@link #getAutofillType()} to support the Autofill
- * Framework.
+ * <p>By default returns {@code null}, but views should override it to properly support the
+ * Autofill Framework.
+ *
+ * @see #onProvideAutofillStructure(ViewStructure, int)
+ * @see #autofill(AutofillValue)
*/
@Nullable
public AutofillValue getAutofillValue() {
@@ -7658,9 +7777,10 @@
}
/**
- * Gets the mode for determining whether this View is important for autofill.
+ * Gets the mode for determining whether this view is important for autofill.
*
- * <p>See {@link #setImportantForAutofill(int)} for more info about this mode.
+ * <p>See {@link #setImportantForAutofill(int)} and {@link #isImportantForAutofill()} for more
+ * info about this mode.
*
* @return {@link #IMPORTANT_FOR_AUTOFILL_AUTO} by default, or value passed to
* {@link #setImportantForAutofill(int)}.
@@ -7681,20 +7801,29 @@
}
/**
- * Sets the mode for determining whether this View is important for autofill.
- *
- * <p>This property controls how this view is presented to the autofill components
- * which help users to fill credentials, addresses, etc. For example, views
- * that contain labels and input fields are useful for autofill components to
- * determine the user context and provide values for the inputs. Note that the
- * user can always override this by manually triggering autotill which would
- * expose the view to the autofill provider.
+ * Sets the mode for determining whether this view is considered important for autofill.
*
* <p>The platform determines the importance for autofill automatically but you
- * can use this method to customize the behavior. See the autofill modes below
- * for more details.
+ * can use this method to customize the behavior. For example:
*
- * <p>See {@link #setImportantForAutofill(int)} for more info about this mode.
+ * <ol>
+ * <li>When the view contents is irrelevant for autofill (for example, a text field used in a
+ * "Captcha" challenge), it should be {@link #IMPORTANT_FOR_AUTOFILL_NO}.
+ * <li>When both the view and its children are irrelevant for autofill (for example, the root
+ * view of an activity containing a spreadhseet editor), it should be
+ * {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS}.
+ * <li>When the view content is relevant for autofill but its children aren't (for example,
+ * a credit card expiration date represented by a custom view that overrides the proper
+ * autofill methods and has 2 children representing the month and year), it should
+ * be {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS}.
+ * </ol>
+ *
+ * <p><b>NOTE:</strong> setting the mode as does {@link #IMPORTANT_FOR_AUTOFILL_NO} or
+ * {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS} does not guarantee the view (and its
+ * children) will be always be considered not important; for example, when the user explicitly
+ * makes an autofill request, all views are considered important. See
+ * {@link #isImportantForAutofill()} for more details about how the View's importance for
+ * autofill is used.
*
* @param mode {@link #IMPORTANT_FOR_AUTOFILL_AUTO}, {@link #IMPORTANT_FOR_AUTOFILL_YES},
* {@link #IMPORTANT_FOR_AUTOFILL_NO}, {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS},
@@ -7710,40 +7839,51 @@
/**
* Hints the Android System whether the {@link android.app.assist.AssistStructure.ViewNode}
- * associated with this View should be included in a {@link ViewStructure} used for
- * autofill purposes.
+ * associated with this view is considered important for autofill purposes.
*
* <p>Generally speaking, a view is important for autofill if:
* <ol>
- * <li>The view can-be autofilled by an {@link android.service.autofill.AutofillService}.
- * <li>The view contents can help an {@link android.service.autofill.AutofillService} to
- * autofill other views.
+ * <li>The view can be autofilled by an {@link android.service.autofill.AutofillService}.
+ * <li>The view contents can help an {@link android.service.autofill.AutofillService}
+ * determine how other views can be autofilled.
* <ol>
*
* <p>For example, view containers should typically return {@code false} for performance reasons
- * (since the important info is provided by their children), but if the container is actually
- * whose children are part of a compound view, it should return {@code true} (and then override
- * {@link #dispatchProvideAutofillStructure(ViewStructure, int)} to simply call
- * {@link #onProvideAutofillStructure(ViewStructure, int)} so its children are not included in
- * the structure). On the other hand, views representing labels or editable fields should
- * typically return {@code true}, but in some cases they could return {@code false} (for
- * example, if they're part of a "Captcha" mechanism).
+ * (since the important info is provided by their children), but if its properties have relevant
+ * information (for example, a resource id called {@code credentials}, it should return
+ * {@code true}. On the other hand, views representing labels or editable fields should
+ * typically return {@code true}, but in some cases they could return {@code false}
+ * (for example, if they're part of a "Captcha" mechanism).
*
- * <p>By default, this method returns {@code true} if {@link #getImportantForAutofill()} returns
- * {@link #IMPORTANT_FOR_AUTOFILL_YES}, {@code false } if it returns
- * {@link #IMPORTANT_FOR_AUTOFILL_NO}, and use some heuristics to define the importance when it
- * returns {@link #IMPORTANT_FOR_AUTOFILL_AUTO}. Hence, it should rarely be overridden - Views
- * should use {@link #setImportantForAutofill(int)} instead.
+ * <p>The value returned by this method depends on the value returned by
+ * {@link #getImportantForAutofill()}:
*
- * <p><strong>Note:</strong> returning {@code false} does not guarantee the view will be
- * excluded from the structure; for example, if the user explicitly requested autofill, the
- * View might be always included.
+ * <ol>
+ * <li>if it returns {@link #IMPORTANT_FOR_AUTOFILL_YES} or
+ * {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS}, then it returns {@code true}
+ * <li>if it returns {@link #IMPORTANT_FOR_AUTOFILL_NO} or
+ * {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS}, then it returns {@code false}
+ * <li>if it returns {@link #IMPORTANT_FOR_AUTOFILL_AUTO}, then it uses some simple heuristics
+ * that can return {@code true} in some cases (like a container with a resource id),
+ * but {@code false} in most.
+ * <li>otherwise, it returns {@code false}.
+ * </ol>
*
- * <p>This decision applies just for the view, not its children - if the view children are not
- * important for autofill, the view should override
- * {@link #dispatchProvideAutofillStructure(ViewStructure, int)} to simply call
- * {@link #onProvideAutofillStructure(ViewStructure, int)} (instead of calling
- * {@link #dispatchProvideAutofillStructure(ViewStructure, int)} for each child).
+ * <p>When a view is considered important for autofill:
+ * <ul>
+ * <li>The view might automatically trigger an autofill request when focused on.
+ * <li>The contents of the view are included in the {@link ViewStructure} used in an autofill
+ * request.
+ * </ul>
+ *
+ * <p>On the other hand, when a view is considered not important for autofill:
+ * <ul>
+ * <li>The view never automatically triggers autofill requests, but it can trigger a manual
+ * request through {@link AutofillManager#requestAutofill(View)}.
+ * <li>The contents of the view are not included in the {@link ViewStructure} used in an
+ * autofill request, unless the request has the
+ * {@link #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} flag.
+ * </ul>
*
* @return whether the view is considered important for autofill.
*
@@ -7753,6 +7893,7 @@
* @see #IMPORTANT_FOR_AUTOFILL_NO
* @see #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS
* @see #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS
+ * @see AutofillManager#requestAutofill(View)
*/
public final boolean isImportantForAutofill() {
// Check parent mode to ensure we're not hidden.
@@ -7877,29 +8018,38 @@
}
/**
- * Dispatch creation of {@link ViewStructure} down the hierarchy.
+ * Dispatches creation of a {@link ViewStructure}s for autofill purposes down the hierarchy,
+ * when an Assist structure is being created as part of an autofill request.
*
* <p>The default implementation does the following:
- *
* <ul>
* <li>Sets the {@link AutofillId} in the structure.
* <li>Calls {@link #onProvideAutofillStructure(ViewStructure, int)}.
* <li>Calls {@link #onProvideAutofillVirtualStructure(ViewStructure, int)}.
* </ul>
*
- * <p>When overridden, it must either call
- * {@code super.dispatchProvideAutofillStructure(structure, flags)} or explicitly
- * set the {@link AutofillId} in the structure (for example, by calling
- * {@code structure.setAutofillId(getAutofillId())}).
+ * <p>Typically, this method should only be overridden by subclasses that provide a view
+ * hierarchy (such as {@link ViewGroup}) - other classes should override
+ * {@link #onProvideAutofillStructure(ViewStructure, int)} or
+ * {@link #onProvideAutofillVirtualStructure(ViewStructure, int)} instead.
*
- * <p>When providing your implementation you need to decide how to handle
- * the {@link #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} flag which instructs you
- * to report all views to the structure regardless if {@link #isImportantForAutofill()}
- * returns true. We encourage you respect the importance property for a better
- * user experience in your app. If the flag is not set then you should filter out
- * not important views to optimize autofill performance in your app.
+ * <p>When overridden, it must:
*
- * @param structure Fill in with structured view data.
+ * <ul>
+ * <li>Either call
+ * {@code super.dispatchProvideAutofillStructure(structure, flags)} or explicitly
+ * set the {@link AutofillId} in the structure (for example, by calling
+ * {@code structure.setAutofillId(getAutofillId())}).
+ * <li>Decide how to handle the {@link #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} flag - when
+ * set, all views in the structure should be considered important for autofill,
+ * regardless of what {@link #isImportantForAutofill()} returns. We encourage you to
+ * respect this flag to provide a better user experience - this flag is typically used
+ * when an user explicitly requested autofill. If the flag is not set,
+ * then only views marked as important for autofill should be included in the
+ * structure - skipping non-important views optimizes the overall autofill performance.
+ * </ul>
+ *
+ * @param structure fill in with structured view data for autofill purposes.
* @param flags optional flags.
*
* @see #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
@@ -7913,6 +8063,10 @@
boolean forAutofill, @AutofillFlags int flags) {
if (forAutofill) {
structure.setAutofillId(getAutofillId());
+ if (!isLaidOut()) {
+ Log.w(VIEW_LOG_TAG, "dispatchProvideAutofillStructure(): not laid out, ignoring");
+ return;
+ }
onProvideAutofillStructure(structure, flags);
onProvideAutofillVirtualStructure(structure, flags);
} else if (!isAssistBlocked()) {
@@ -10979,11 +11133,21 @@
if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
return;
}
- if (mSendViewStateChangedAccessibilityEvent == null) {
- mSendViewStateChangedAccessibilityEvent =
- new SendViewStateChangedAccessibilityEvent();
+ // If this is a live region, we should send a subtree change event
+ // from this view immediately. Otherwise, we can let it propagate up.
+ if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) {
+ final AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ event.setContentChangeTypes(changeType);
+ sendAccessibilityEventUnchecked(event);
+ } else if (mParent != null) {
+ try {
+ mParent.notifySubtreeAccessibilityStateChanged(this, this, changeType);
+ } catch (AbstractMethodError e) {
+ Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
+ " does not fully implement ViewParent", e);
+ }
}
- mSendViewStateChangedAccessibilityEvent.runOrPost(changeType);
}
/**
@@ -25697,8 +25861,8 @@
* </p>
* <p>
* The default implementation behaves as
- * {@link View#addExtraDataToAccessibilityNodeInfo(AccessibilityNodeInfo, int) for
- * the case where no accessibility delegate is set.
+ * {@link View#addExtraDataToAccessibilityNodeInfo(AccessibilityNodeInfo, String, Bundle)
+ * for the case where no accessibility delegate is set.
* </p>
*
* @param host The View hosting the delegate. Never {@code null}.
@@ -25806,79 +25970,6 @@
}
}
- private class SendViewStateChangedAccessibilityEvent implements Runnable {
- private int mChangeTypes = 0;
- private boolean mPosted;
- private boolean mPostedWithDelay;
- private long mLastEventTimeMillis;
-
- @Override
- public void run() {
- mPosted = false;
- mPostedWithDelay = false;
- mLastEventTimeMillis = SystemClock.uptimeMillis();
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- final AccessibilityEvent event = AccessibilityEvent.obtain();
- event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- event.setContentChangeTypes(mChangeTypes);
- sendAccessibilityEventUnchecked(event);
- }
- mChangeTypes = 0;
- }
-
- public void runOrPost(int changeType) {
- mChangeTypes |= changeType;
-
- // If this is a live region or the child of a live region, collect
- // all events from this frame and send them on the next frame.
- if (inLiveRegion()) {
- // If we're already posted with a delay, remove that.
- if (mPostedWithDelay) {
- removeCallbacks(this);
- mPostedWithDelay = false;
- }
- // Only post if we're not already posted.
- if (!mPosted) {
- post(this);
- mPosted = true;
- }
- return;
- }
-
- if (mPosted) {
- return;
- }
-
- final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastEventTimeMillis;
- final long minEventIntevalMillis =
- ViewConfiguration.getSendRecurringAccessibilityEventsInterval();
- if (timeSinceLastMillis >= minEventIntevalMillis) {
- removeCallbacks(this);
- run();
- } else {
- postDelayed(this, minEventIntevalMillis - timeSinceLastMillis);
- mPostedWithDelay = true;
- }
- }
- }
-
- private boolean inLiveRegion() {
- if (getAccessibilityLiveRegion() != View.ACCESSIBILITY_LIVE_REGION_NONE) {
- return true;
- }
-
- ViewParent parent = getParent();
- while (parent instanceof View) {
- if (((View) parent).getAccessibilityLiveRegion()
- != View.ACCESSIBILITY_LIVE_REGION_NONE) {
- return true;
- }
- parent = parent.getParent();
- }
-
- return false;
- }
-
/**
* Dump all private flags in readable format, useful for documentation and
* sanity checking.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 8637593..2e817eb 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3544,7 +3544,8 @@
// If this is a live region, we should send a subtree change event
// from this view. Otherwise, we can let it propagate up.
if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) {
- notifyViewAccessibilityStateChangedIfNeeded(changeType);
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
} else if (mParent != null) {
try {
mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType);
@@ -4936,9 +4937,6 @@
needGlobalAttributesUpdate(true);
}
ai.mKeepScreenOn = lastKeepOn;
- if (!childHasFocus && child.hasFocusable()) {
- focusableViewAvailable(child);
- }
}
if (child.isLayoutDirectionInherited()) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 19e0c95..4a231ef 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -35,6 +35,7 @@
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
@@ -961,8 +962,12 @@
final boolean hasSurfaceInsets = insets.left != 0 || insets.right != 0
|| insets.top != 0 || insets.bottom != 0;
final boolean translucent = attrs.format != PixelFormat.OPAQUE || hasSurfaceInsets;
+ final boolean wideGamut =
+ attrs.getColorMode() == ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT;
+
mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
attrs.getTitle().toString());
+ mAttachInfo.mThreadedRenderer.setWideGamut(wideGamut);
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mHardwareAccelerated =
mAttachInfo.mHardwareAccelerationRequested = true;
@@ -1845,12 +1850,8 @@
final boolean isViewVisible = viewVisibility == View.VISIBLE;
final boolean windowRelayoutWasForced = mForceNextWindowRelayout;
- final int contextConfigSeq = mContext.getResources().getConfiguration().seq;
- final int lastConfigSeq = mLastReportedMergedConfiguration.getMergedConfiguration().seq;
- final boolean staleConfig = lastConfigSeq != 0 && contextConfigSeq != lastConfigSeq;
-
- if (mFirst || windowShouldResize || insetsChanged || staleConfig || viewVisibilityChanged
- || params != null || mForceNextWindowRelayout) {
+ if (mFirst || windowShouldResize || insetsChanged ||
+ viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
mForceNextWindowRelayout = false;
if (isViewVisible) {
@@ -6087,13 +6088,7 @@
if (params != null) {
if (DBG) Log.d(mTag, "WindowLayout in layoutWindow:" + params);
}
-
- if (mPendingMergedConfiguration.getMergedConfiguration().seq == 0) {
- mPendingMergedConfiguration.setTo(mLastReportedMergedConfiguration);
- }
-
- int initialConfigSeq = mPendingMergedConfiguration.getMergedConfiguration().seq;
-
+ mPendingMergedConfiguration.getMergedConfiguration().seq = 0;
//Log.d(mTag, ">>>>>> CALLING relayout");
if (params != null && mOrigWindowType != params.type) {
// For compatibility with old apps, don't crash here.
@@ -6112,10 +6107,6 @@
mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame,
mPendingMergedConfiguration, mSurface);
- if (initialConfigSeq == mPendingMergedConfiguration.getMergedConfiguration().seq) {
- mPendingMergedConfiguration.getMergedConfiguration().seq = 0;
- }
-
mPendingAlwaysConsumeNavBar =
(relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR) != 0;
@@ -7081,6 +7072,14 @@
if (mView == null || mStopped || mPausedForTransition) {
return false;
}
+
+ // Immediately flush pending content changed event (if any) to preserve event order
+ if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+ && mSendWindowContentChangedAccessibilityEvent != null
+ && mSendWindowContentChangedAccessibilityEvent.mSource != null) {
+ mSendWindowContentChangedAccessibilityEvent.removeCallbacksAndRun();
+ }
+
// Intercept accessibility focus events fired by virtual nodes to keep
// track of accessibility focus position in such nodes.
final int eventType = event.getEventType();
@@ -7890,19 +7889,21 @@
@Override
public void run() {
+ // mSource may be changed in calls below.
+ View source = mSource;
+ mSource = null;
// The accessibility may be turned off while we were waiting so check again.
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
mLastEventTimeMillis = SystemClock.uptimeMillis();
AccessibilityEvent event = AccessibilityEvent.obtain();
event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
event.setContentChangeTypes(mChangeTypes);
- mSource.sendAccessibilityEventUnchecked(event);
+ source.sendAccessibilityEventUnchecked(event);
} else {
mLastEventTimeMillis = 0;
}
// In any case reset to initial state.
- mSource.resetSubtreeAccessibilityStateChanged();
- mSource = null;
+ source.resetSubtreeAccessibilityStateChanged();
mChangeTypes = 0;
}
@@ -7921,11 +7922,15 @@
final long minEventIntevalMillis =
ViewConfiguration.getSendRecurringAccessibilityEventsInterval();
if (timeSinceLastMillis >= minEventIntevalMillis) {
- mSource.removeCallbacks(this);
- run();
+ removeCallbacksAndRun();
} else {
mSource.postDelayed(this, minEventIntevalMillis - timeSinceLastMillis);
}
}
+
+ public void removeCallbacksAndRun() {
+ mSource.removeCallbacks(this);
+ run();
+ }
}
}
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index ddfe697..0ecd20d 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -269,6 +269,9 @@
* Create a new child {@link ViewStructure} in this view, putting into the list of
* children at <var>index</var>.
*
+ * <p><b>NOTE: </b>you must pre-allocate space for the child first, by calling either
+ * {@link #addChildCount(int)} or {@link #setChildCount(int)}.
+ *
* @return Returns an fresh {@link ViewStructure} ready to be filled in.
*/
public abstract ViewStructure newChild(int index);
@@ -279,6 +282,10 @@
* to build its content (and children etc). Once done, some thread must call
* {@link #asyncCommit} to tell the containing {@link ViewStructure} that the async
* population is done.
+ *
+ * <p><b>NOTE: </b>you must pre-allocate space for the child first, by calling either
+ * {@link #addChildCount(int)} or {@link #setChildCount(int)}.
+ *
* @return Returns an fresh {@link ViewStructure} ready to be filled in.
*/
public abstract ViewStructure asyncNewChild(int index);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 4060b9a..dfbf962 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2087,6 +2087,7 @@
out.writeInt(needsMenuKey);
out.writeInt(accessibilityIdOfAnchor);
TextUtils.writeToParcel(accessibilityTitle, out, parcelableFlags);
+ out.writeInt(mColorMode);
out.writeLong(hideTimeoutMilliseconds);
}
@@ -2141,6 +2142,7 @@
needsMenuKey = in.readInt();
accessibilityIdOfAnchor = in.readInt();
accessibilityTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mColorMode = in.readInt();
hideTimeoutMilliseconds = in.readLong();
}
@@ -2186,6 +2188,8 @@
@TestApi
public static final int ACCESSIBILITY_TITLE_CHANGED = 1 << 25;
/** {@hide} */
+ public static final int COLOR_MODE_CHANGED = 1 << 26;
+ /** {@hide} */
public static final int EVERYTHING_CHANGED = 0xffffffff;
// internal buffer to backup/restore parameters under compatibility mode.
@@ -2363,6 +2367,11 @@
changes |= ACCESSIBILITY_TITLE_CHANGED;
}
+ if (mColorMode != o.mColorMode) {
+ mColorMode = o.mColorMode;
+ changes |= COLOR_MODE_CHANGED;
+ }
+
// This can't change, it's only set at window creation time.
hideTimeoutMilliseconds = o.hideTimeoutMilliseconds;
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index eecfdca..9dd0fb0 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -21,6 +21,8 @@
import android.text.TextUtils;
import android.util.Pools.SynchronizedPool;
+import com.android.internal.util.BitUtils;
+
import java.util.ArrayList;
import java.util.List;
@@ -851,6 +853,22 @@
return mContentChangeTypes;
}
+ private static String contentChangeTypesToString(int types) {
+ return BitUtils.flagsToString(types, AccessibilityEvent::singleContentChangeTypeToString);
+ }
+
+ private static String singleContentChangeTypeToString(int type) {
+ switch (type) {
+ case CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION: {
+ return "CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION";
+ }
+ case CONTENT_CHANGE_TYPE_SUBTREE: return "CONTENT_CHANGE_TYPE_SUBTREE";
+ case CONTENT_CHANGE_TYPE_TEXT: return "CONTENT_CHANGE_TYPE_TEXT";
+ case CONTENT_CHANGE_TYPE_UNDEFINED: return "CONTENT_CHANGE_TYPE_UNDEFINED";
+ default: return Integer.toHexString(type);
+ }
+ }
+
/**
* Sets the bit mask of node tree changes signaled by an
* {@link #TYPE_WINDOW_CONTENT_CHANGED} event.
@@ -1109,7 +1127,7 @@
record.mParcelableData = parcel.readParcelable(null);
parcel.readList(record.mText, null);
record.mSourceWindowId = parcel.readInt();
- record.mSourceNode = parcel.readParcelable(null);
+ record.mSourceNodeId = parcel.readLong();
record.mSealed = (parcel.readInt() == 1);
}
@@ -1161,10 +1179,7 @@
parcel.writeParcelable(record.mParcelableData, flags);
parcel.writeList(record.mText);
parcel.writeInt(record.mSourceWindowId);
- // create copy of the node here because the node would be recycled just after it is written
- // to parcel
- parcel.writeParcelable(record.mSourceNode != null ?
- AccessibilityNodeInfo.obtain(record.mSourceNode) : null, flags);
+ parcel.writeLong(record.mSourceNodeId);
parcel.writeInt(record.mSealed ? 1 : 0);
}
@@ -1186,11 +1201,10 @@
builder.append(super.toString());
if (DEBUG) {
builder.append("\n");
- builder.append("; ContentChangeTypes: ").append(mContentChangeTypes);
+ builder.append("; ContentChangeTypes: ").append(
+ contentChangeTypesToString(mContentChangeTypes));
builder.append("; sourceWindowId: ").append(mSourceWindowId);
- if (mSourceNode != null) {
- builder.append("; mSourceNodeId: ").append(mSourceNode.getSourceNodeId());
- }
+ builder.append("; mSourceNodeId: ").append(mSourceNodeId);
for (int i = 0; i < getRecordCount(); i++) {
final AccessibilityRecord record = getRecord(i);
builder.append(" Record ");
@@ -1239,183 +1253,13 @@
while (eventType != 0) {
final int eventTypeFlag = 1 << Integer.numberOfTrailingZeros(eventType);
eventType &= ~eventTypeFlag;
- switch (eventTypeFlag) {
- case TYPE_VIEW_CLICKED: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_VIEW_CLICKED");
- eventTypeCount++;
- } break;
- case TYPE_VIEW_LONG_CLICKED: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_VIEW_LONG_CLICKED");
- eventTypeCount++;
- } break;
- case TYPE_VIEW_SELECTED: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_VIEW_SELECTED");
- eventTypeCount++;
- } break;
- case TYPE_VIEW_FOCUSED: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_VIEW_FOCUSED");
- eventTypeCount++;
- } break;
- case TYPE_VIEW_TEXT_CHANGED: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_VIEW_TEXT_CHANGED");
- eventTypeCount++;
- } break;
- case TYPE_WINDOW_STATE_CHANGED: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_WINDOW_STATE_CHANGED");
- eventTypeCount++;
- } break;
- case TYPE_VIEW_HOVER_ENTER: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_VIEW_HOVER_ENTER");
- eventTypeCount++;
- } break;
- case TYPE_VIEW_HOVER_EXIT: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_VIEW_HOVER_EXIT");
- eventTypeCount++;
- } break;
- case TYPE_NOTIFICATION_STATE_CHANGED: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_NOTIFICATION_STATE_CHANGED");
- eventTypeCount++;
- } break;
- case TYPE_TOUCH_EXPLORATION_GESTURE_START: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_TOUCH_EXPLORATION_GESTURE_START");
- eventTypeCount++;
- } break;
- case TYPE_TOUCH_EXPLORATION_GESTURE_END: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_TOUCH_EXPLORATION_GESTURE_END");
- eventTypeCount++;
- } break;
- case TYPE_WINDOW_CONTENT_CHANGED: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_WINDOW_CONTENT_CHANGED");
- eventTypeCount++;
- } break;
- case TYPE_VIEW_TEXT_SELECTION_CHANGED: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_VIEW_TEXT_SELECTION_CHANGED");
- eventTypeCount++;
- } break;
- case TYPE_VIEW_SCROLLED: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_VIEW_SCROLLED");
- eventTypeCount++;
- } break;
- case TYPE_ANNOUNCEMENT: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_ANNOUNCEMENT");
- eventTypeCount++;
- } break;
- case TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_VIEW_ACCESSIBILITY_FOCUSED");
- eventTypeCount++;
- } break;
- case TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED");
- eventTypeCount++;
- } break;
- case TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY");
- eventTypeCount++;
- } break;
- case TYPE_GESTURE_DETECTION_START: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_GESTURE_DETECTION_START");
- eventTypeCount++;
- } break;
- case TYPE_GESTURE_DETECTION_END: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_GESTURE_DETECTION_END");
- eventTypeCount++;
- } break;
- case TYPE_TOUCH_INTERACTION_START: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_TOUCH_INTERACTION_START");
- eventTypeCount++;
- } break;
- case TYPE_TOUCH_INTERACTION_END: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_TOUCH_INTERACTION_END");
- eventTypeCount++;
- } break;
- case TYPE_WINDOWS_CHANGED: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_WINDOWS_CHANGED");
- eventTypeCount++;
- } break;
- case TYPE_VIEW_CONTEXT_CLICKED: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_VIEW_CONTEXT_CLICKED");
- eventTypeCount++;
- } break;
- case TYPE_ASSIST_READING_CONTEXT: {
- if (eventTypeCount > 0) {
- builder.append(", ");
- }
- builder.append("TYPE_ASSIST_READING_CONTEXT");
- eventTypeCount++;
- } break;
+
+ if (eventTypeCount > 0) {
+ builder.append(", ");
}
+ builder.append(singleEventTypeToString(eventTypeFlag));
+
+ eventTypeCount++;
}
if (eventTypeCount > 1) {
builder.insert(0, '[');
@@ -1424,6 +1268,43 @@
return builder.toString();
}
+ private static String singleEventTypeToString(int eventType) {
+ switch (eventType) {
+ case TYPE_VIEW_CLICKED: return "TYPE_VIEW_CLICKED";
+ case TYPE_VIEW_LONG_CLICKED: return "TYPE_VIEW_LONG_CLICKED";
+ case TYPE_VIEW_SELECTED: return "TYPE_VIEW_SELECTED";
+ case TYPE_VIEW_FOCUSED: return "TYPE_VIEW_FOCUSED";
+ case TYPE_VIEW_TEXT_CHANGED: return "TYPE_VIEW_TEXT_CHANGED";
+ case TYPE_WINDOW_STATE_CHANGED: return "TYPE_WINDOW_STATE_CHANGED";
+ case TYPE_VIEW_HOVER_ENTER: return "TYPE_VIEW_HOVER_ENTER";
+ case TYPE_VIEW_HOVER_EXIT: return "TYPE_VIEW_HOVER_EXIT";
+ case TYPE_NOTIFICATION_STATE_CHANGED: return "TYPE_NOTIFICATION_STATE_CHANGED";
+ case TYPE_TOUCH_EXPLORATION_GESTURE_START: {
+ return "TYPE_TOUCH_EXPLORATION_GESTURE_START";
+ }
+ case TYPE_TOUCH_EXPLORATION_GESTURE_END: return "TYPE_TOUCH_EXPLORATION_GESTURE_END";
+ case TYPE_WINDOW_CONTENT_CHANGED: return "TYPE_WINDOW_CONTENT_CHANGED";
+ case TYPE_VIEW_TEXT_SELECTION_CHANGED: return "TYPE_VIEW_TEXT_SELECTION_CHANGED";
+ case TYPE_VIEW_SCROLLED: return "TYPE_VIEW_SCROLLED";
+ case TYPE_ANNOUNCEMENT: return "TYPE_ANNOUNCEMENT";
+ case TYPE_VIEW_ACCESSIBILITY_FOCUSED: return "TYPE_VIEW_ACCESSIBILITY_FOCUSED";
+ case TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
+ return "TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED";
+ }
+ case TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: {
+ return "TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY";
+ }
+ case TYPE_GESTURE_DETECTION_START: return "TYPE_GESTURE_DETECTION_START";
+ case TYPE_GESTURE_DETECTION_END: return "TYPE_GESTURE_DETECTION_END";
+ case TYPE_TOUCH_INTERACTION_START: return "TYPE_TOUCH_INTERACTION_START";
+ case TYPE_TOUCH_INTERACTION_END: return "TYPE_TOUCH_INTERACTION_END";
+ case TYPE_WINDOWS_CHANGED: return "TYPE_WINDOWS_CHANGED";
+ case TYPE_VIEW_CONTEXT_CLICKED: return "TYPE_VIEW_CONTEXT_CLICKED";
+ case TYPE_ASSIST_READING_CONTEXT: return "TYPE_ASSIST_READING_CONTEXT";
+ default: return Integer.toHexString(eventType);
+ }
+ }
+
/**
* @see Parcelable.Creator
*/
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 41a8b8a..8fc586e 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -42,6 +42,7 @@
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.SparseArray;
import android.view.IWindow;
import android.view.View;
@@ -145,6 +146,11 @@
mServicesStateChangeListeners = new ArrayMap<>();
/**
+ * Map from a view's accessibility id to the list of request preparers set for that view
+ */
+ private SparseArray<List<AccessibilityRequestPreparer>> mRequestPreparerLists;
+
+ /**
* Listener for the system accessibility state. To listen for changes to the
* accessibility state on the device, implement this interface and register
* it with the system by calling {@link #addAccessibilityStateChangeListener}.
@@ -697,6 +703,54 @@
}
/**
+ * Registers a {@link AccessibilityRequestPreparer}.
+ */
+ public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
+ if (mRequestPreparerLists == null) {
+ mRequestPreparerLists = new SparseArray<>(1);
+ }
+ int id = preparer.getView().getAccessibilityViewId();
+ List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(id);
+ if (requestPreparerList == null) {
+ requestPreparerList = new ArrayList<>(1);
+ mRequestPreparerLists.put(id, requestPreparerList);
+ }
+ requestPreparerList.add(preparer);
+ }
+
+ /**
+ * Unregisters a {@link AccessibilityRequestPreparer}.
+ */
+ public void removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
+ if (mRequestPreparerLists == null) {
+ return;
+ }
+ int viewId = preparer.getView().getAccessibilityViewId();
+ List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(viewId);
+ if (requestPreparerList != null) {
+ requestPreparerList.remove(preparer);
+ if (requestPreparerList.isEmpty()) {
+ mRequestPreparerLists.remove(viewId);
+ }
+ }
+ }
+
+ /**
+ * Get the preparers that are registered for an accessibility ID
+ *
+ * @param id The ID of interest
+ * @return The list of preparers, or {@code null} if there are none.
+ *
+ * @hide
+ */
+ public List<AccessibilityRequestPreparer> getRequestPreparersForAccessibilityId(int id) {
+ if (mRequestPreparerLists == null) {
+ return null;
+ }
+ return mRequestPreparerLists.get(id);
+ }
+
+ /**
* Registers a {@link HighTextContrastChangeListener} for changes in
* the global high text contrast state of the system.
*
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 5148d92..82a6de7 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -16,6 +16,9 @@
package android.view.accessibility;
+import static com.android.internal.util.BitUtils.bitAt;
+import static com.android.internal.util.BitUtils.isBitSet;
+
import static java.util.Collections.EMPTY_LIST;
import android.accessibilityservice.AccessibilityService;
@@ -41,10 +44,12 @@
import android.view.View;
import com.android.internal.R;
+import com.android.internal.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -85,7 +90,6 @@
/** @hide */
public static final int UNDEFINED_SELECTION_INDEX = -1;
- /* Special IDs for node source IDs */
/** @hide */
public static final int UNDEFINED_ITEM_ID = Integer.MAX_VALUE;
@@ -636,7 +640,6 @@
* Bits that provide the id of a virtual descendant of a view.
*/
private static final long VIRTUAL_DESCENDANT_ID_MASK = 0xffffffff00000000L;
-
/**
* Bit shift of {@link #VIRTUAL_DESCENDANT_ID_MASK} to get to the id for a
* virtual descendant of a view. Such a descendant does not exist in the view
@@ -692,6 +695,8 @@
private static final SynchronizedPool<AccessibilityNodeInfo> sPool =
new SynchronizedPool<>(MAX_POOL_SIZE);
+ private static final AccessibilityNodeInfo DEFAULT = new AccessibilityNodeInfo();
+
private boolean mSealed;
// Data.
@@ -1068,11 +1073,7 @@
* Gets the actions that can be performed on the node.
*/
public List<AccessibilityAction> getActionList() {
- if (mActions == null) {
- return Collections.emptyList();
- }
-
- return mActions;
+ return CollectionUtils.emptyIfNull(mActions);
}
/**
@@ -3050,127 +3051,227 @@
*/
@Override
public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeInt(isSealed() ? 1 : 0);
- parcel.writeLong(mSourceNodeId);
- parcel.writeInt(mWindowId);
- parcel.writeLong(mParentNodeId);
- parcel.writeLong(mLabelForId);
- parcel.writeLong(mLabeledById);
- parcel.writeLong(mTraversalBefore);
- parcel.writeLong(mTraversalAfter);
-
- parcel.writeInt(mConnectionId);
-
- final LongArray childIds = mChildNodeIds;
- if (childIds == null) {
- parcel.writeInt(0);
- } else {
- final int childIdsSize = childIds.size();
- parcel.writeInt(childIdsSize);
- for (int i = 0; i < childIdsSize; i++) {
- parcel.writeLong(childIds.get(i));
- }
+ // Write bit set of indices of fields with values differing from default
+ long nonDefaultFields = 0;
+ int fieldIndex = 0; // index of the current field
+ if (isSealed() != DEFAULT.isSealed()) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mSourceNodeId != DEFAULT.mSourceNodeId) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mWindowId != DEFAULT.mWindowId) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mParentNodeId != DEFAULT.mParentNodeId) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mLabelForId != DEFAULT.mLabelForId) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mLabeledById != DEFAULT.mLabeledById) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mTraversalBefore != DEFAULT.mTraversalBefore) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mTraversalAfter != DEFAULT.mTraversalAfter) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mConnectionId != DEFAULT.mConnectionId) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mChildNodeIds, DEFAULT.mChildNodeIds)) {
+ nonDefaultFields |= bitAt(fieldIndex);
}
+ fieldIndex++;
+ if (!Objects.equals(mBoundsInParent, DEFAULT.mBoundsInParent)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mBoundsInScreen, DEFAULT.mBoundsInScreen)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mActions, DEFAULT.mActions)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mMaxTextLength != DEFAULT.mMaxTextLength) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mMovementGranularities != DEFAULT.mMovementGranularities) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mBooleanProperties != DEFAULT.mBooleanProperties) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mPackageName, DEFAULT.mPackageName)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mClassName, DEFAULT.mClassName)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mText, DEFAULT.mText)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mHintText, DEFAULT.mHintText)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mError, DEFAULT.mError)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mContentDescription, DEFAULT.mContentDescription)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mViewIdResourceName, DEFAULT.mViewIdResourceName)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mTextSelectionStart != DEFAULT.mTextSelectionStart) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mTextSelectionEnd != DEFAULT.mTextSelectionEnd) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mInputType != DEFAULT.mInputType) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mLiveRegion != DEFAULT.mLiveRegion) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (mDrawingOrderInParent != DEFAULT.mDrawingOrderInParent) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mExtraDataKeys, DEFAULT.mExtraDataKeys)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mExtras, DEFAULT.mExtras)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mRangeInfo, DEFAULT.mRangeInfo)) nonDefaultFields |= bitAt(fieldIndex);
+ fieldIndex++;
+ if (!Objects.equals(mCollectionInfo, DEFAULT.mCollectionInfo)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mCollectionItemInfo, DEFAULT.mCollectionItemInfo)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ int totalFields = fieldIndex;
+ parcel.writeLong(nonDefaultFields);
- parcel.writeInt(mBoundsInParent.top);
- parcel.writeInt(mBoundsInParent.bottom);
- parcel.writeInt(mBoundsInParent.left);
- parcel.writeInt(mBoundsInParent.right);
+ fieldIndex = 0;
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(isSealed() ? 1 : 0);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mSourceNodeId);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mWindowId);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mParentNodeId);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabelForId);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabeledById);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalBefore);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalAfter);
- parcel.writeInt(mBoundsInScreen.top);
- parcel.writeInt(mBoundsInScreen.bottom);
- parcel.writeInt(mBoundsInScreen.left);
- parcel.writeInt(mBoundsInScreen.right);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mConnectionId);
- if (mActions != null && !mActions.isEmpty()) {
- final int actionCount = mActions.size();
-
- int nonLegacyActionCount = 0;
- int defaultLegacyStandardActions = 0;
- for (int i = 0; i < actionCount; i++) {
- AccessibilityAction action = mActions.get(i);
- if (isDefaultLegacyStandardAction(action)) {
- defaultLegacyStandardActions |= action.getId();
- } else {
- nonLegacyActionCount++;
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ final LongArray childIds = mChildNodeIds;
+ if (childIds == null) {
+ parcel.writeInt(0);
+ } else {
+ final int childIdsSize = childIds.size();
+ parcel.writeInt(childIdsSize);
+ for (int i = 0; i < childIdsSize; i++) {
+ parcel.writeLong(childIds.get(i));
}
}
- parcel.writeInt(defaultLegacyStandardActions);
- parcel.writeInt(nonLegacyActionCount);
+ }
- for (int i = 0; i < actionCount; i++) {
- AccessibilityAction action = mActions.get(i);
- if (!isDefaultLegacyStandardAction(action)) {
- parcel.writeInt(action.getId());
- parcel.writeCharSequence(action.getLabel());
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeInt(mBoundsInParent.top);
+ parcel.writeInt(mBoundsInParent.bottom);
+ parcel.writeInt(mBoundsInParent.left);
+ parcel.writeInt(mBoundsInParent.right);
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeInt(mBoundsInScreen.top);
+ parcel.writeInt(mBoundsInScreen.bottom);
+ parcel.writeInt(mBoundsInScreen.left);
+ parcel.writeInt(mBoundsInScreen.right);
+ }
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ if (mActions != null && !mActions.isEmpty()) {
+ final int actionCount = mActions.size();
+
+ int nonLegacyActionCount = 0;
+ int defaultLegacyStandardActions = 0;
+ for (int i = 0; i < actionCount; i++) {
+ AccessibilityAction action = mActions.get(i);
+ if (isDefaultLegacyStandardAction(action)) {
+ defaultLegacyStandardActions |= action.getId();
+ } else {
+ nonLegacyActionCount++;
+ }
}
+ parcel.writeInt(defaultLegacyStandardActions);
+ parcel.writeInt(nonLegacyActionCount);
+
+ for (int i = 0; i < actionCount; i++) {
+ AccessibilityAction action = mActions.get(i);
+ if (!isDefaultLegacyStandardAction(action)) {
+ parcel.writeInt(action.getId());
+ parcel.writeCharSequence(action.getLabel());
+ }
+ }
+ } else {
+ parcel.writeInt(0);
+ parcel.writeInt(0);
}
- } else {
- parcel.writeInt(0);
- parcel.writeInt(0);
}
- parcel.writeInt(mMaxTextLength);
- parcel.writeInt(mMovementGranularities);
- parcel.writeInt(mBooleanProperties);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mMaxTextLength);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mMovementGranularities);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mBooleanProperties);
- parcel.writeCharSequence(mPackageName);
- parcel.writeCharSequence(mClassName);
- parcel.writeCharSequence(mText);
- parcel.writeCharSequence(mHintText);
- parcel.writeCharSequence(mError);
- parcel.writeCharSequence(mContentDescription);
- parcel.writeString(mViewIdResourceName);
-
- parcel.writeInt(mTextSelectionStart);
- parcel.writeInt(mTextSelectionEnd);
- parcel.writeInt(mInputType);
- parcel.writeInt(mLiveRegion);
- parcel.writeInt(mDrawingOrderInParent);
- if (mExtraDataKeys != null) {
- parcel.writeInt(1);
- parcel.writeStringList(mExtraDataKeys);
- } else {
- parcel.writeInt(0);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPackageName);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mClassName);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mText);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mHintText);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mError);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeCharSequence(mContentDescription);
}
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeString(mViewIdResourceName);
- if (mExtras != null) {
- parcel.writeInt(1);
- parcel.writeBundle(mExtras);
- } else {
- parcel.writeInt(0);
- }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionStart);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionEnd);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mInputType);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mLiveRegion);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mDrawingOrderInParent);
- if (mRangeInfo != null) {
- parcel.writeInt(1);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeStringList(mExtraDataKeys);
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeBundle(mExtras);
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
parcel.writeInt(mRangeInfo.getType());
parcel.writeFloat(mRangeInfo.getMin());
parcel.writeFloat(mRangeInfo.getMax());
parcel.writeFloat(mRangeInfo.getCurrent());
- } else {
- parcel.writeInt(0);
}
- if (mCollectionInfo != null) {
- parcel.writeInt(1);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
parcel.writeInt(mCollectionInfo.getRowCount());
parcel.writeInt(mCollectionInfo.getColumnCount());
parcel.writeInt(mCollectionInfo.isHierarchical() ? 1 : 0);
parcel.writeInt(mCollectionInfo.getSelectionMode());
- } else {
- parcel.writeInt(0);
}
- if (mCollectionItemInfo != null) {
- parcel.writeInt(1);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
parcel.writeInt(mCollectionItemInfo.getRowIndex());
parcel.writeInt(mCollectionItemInfo.getRowSpan());
parcel.writeInt(mCollectionItemInfo.getColumnIndex());
parcel.writeInt(mCollectionItemInfo.getColumnSpan());
parcel.writeInt(mCollectionItemInfo.isHeading() ? 1 : 0);
parcel.writeInt(mCollectionItemInfo.isSelected() ? 1 : 0);
- } else {
- parcel.writeInt(0);
+ }
+
+ if (DEBUG) {
+ fieldIndex--;
+ if (totalFields != fieldIndex) {
+ throw new IllegalStateException("Number of fields mismatch: " + totalFields
+ + " vs " + fieldIndex);
+ }
}
// Since instances of this class are fetched via synchronous i.e. blocking
@@ -3203,12 +3304,12 @@
mContentDescription = other.mContentDescription;
mViewIdResourceName = other.mViewIdResourceName;
+ if (mActions != null) mActions.clear();
final ArrayList<AccessibilityAction> otherActions = other.mActions;
if (otherActions != null && otherActions.size() > 0) {
if (mActions == null) {
mActions = new ArrayList(otherActions);
} else {
- mActions.clear();
mActions.addAll(other.mActions);
}
}
@@ -3217,12 +3318,13 @@
mMaxTextLength = other.mMaxTextLength;
mMovementGranularities = other.mMovementGranularities;
+
+ if (mChildNodeIds != null) mChildNodeIds.clear();
final LongArray otherChildNodeIds = other.mChildNodeIds;
if (otherChildNodeIds != null && otherChildNodeIds.size() > 0) {
if (mChildNodeIds == null) {
mChildNodeIds = otherChildNodeIds.clone();
} else {
- mChildNodeIds.clear();
mChildNodeIds.addAll(otherChildNodeIds);
}
}
@@ -3232,17 +3334,18 @@
mInputType = other.mInputType;
mLiveRegion = other.mLiveRegion;
mDrawingOrderInParent = other.mDrawingOrderInParent;
+
mExtraDataKeys = other.mExtraDataKeys;
- if (other.mExtras != null) {
- mExtras = new Bundle(other.mExtras);
- } else {
- mExtras = null;
- }
+ mExtras = other.mExtras != null ? new Bundle(other.mExtras) : null;
+
+ if (mRangeInfo != null) mRangeInfo.recycle();
mRangeInfo = (other.mRangeInfo != null)
? RangeInfo.obtain(other.mRangeInfo) : null;
+ if (mCollectionInfo != null) mCollectionInfo.recycle();
mCollectionInfo = (other.mCollectionInfo != null)
? CollectionInfo.obtain(other.mCollectionInfo) : null;
+ if (mCollectionItemInfo != null) mCollectionItemInfo.recycle();
mCollectionItemInfo = (other.mCollectionItemInfo != null)
? CollectionItemInfo.obtain(other.mCollectionItemInfo) : null;
}
@@ -3253,103 +3356,117 @@
* @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}.
*/
private void initFromParcel(Parcel parcel) {
- final boolean sealed = (parcel.readInt() == 1);
- mSourceNodeId = parcel.readLong();
- mWindowId = parcel.readInt();
- mParentNodeId = parcel.readLong();
- mLabelForId = parcel.readLong();
- mLabeledById = parcel.readLong();
- mTraversalBefore = parcel.readLong();
- mTraversalAfter = parcel.readLong();
+ // Bit mask of non-default-valued field indices
+ long nonDefaultFields = parcel.readLong();
+ int fieldIndex = 0;
+ final boolean sealed = isBitSet(nonDefaultFields, fieldIndex++)
+ ? (parcel.readInt() == 1)
+ : DEFAULT.mSealed;
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mSourceNodeId = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mWindowId = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mParentNodeId = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mLabelForId = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mLabeledById = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalBefore = parcel.readLong();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalAfter = parcel.readLong();
- mConnectionId = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mConnectionId = parcel.readInt();
- final int childrenSize = parcel.readInt();
- if (childrenSize <= 0) {
- mChildNodeIds = null;
- } else {
- mChildNodeIds = new LongArray(childrenSize);
- for (int i = 0; i < childrenSize; i++) {
- final long childId = parcel.readLong();
- mChildNodeIds.add(childId);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ final int childrenSize = parcel.readInt();
+ if (childrenSize <= 0) {
+ mChildNodeIds = null;
+ } else {
+ mChildNodeIds = new LongArray(childrenSize);
+ for (int i = 0; i < childrenSize; i++) {
+ final long childId = parcel.readLong();
+ mChildNodeIds.add(childId);
+ }
}
}
- mBoundsInParent.top = parcel.readInt();
- mBoundsInParent.bottom = parcel.readInt();
- mBoundsInParent.left = parcel.readInt();
- mBoundsInParent.right = parcel.readInt();
-
- mBoundsInScreen.top = parcel.readInt();
- mBoundsInScreen.bottom = parcel.readInt();
- mBoundsInScreen.left = parcel.readInt();
- mBoundsInScreen.right = parcel.readInt();
-
- final int legacyStandardActions = parcel.readInt();
- addLegacyStandardActions(legacyStandardActions);
- final int nonLegacyActionCount = parcel.readInt();
- for (int i = 0; i < nonLegacyActionCount; i++) {
- final AccessibilityAction action = new AccessibilityAction(
- parcel.readInt(), parcel.readCharSequence());
- addActionUnchecked(action);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mBoundsInParent.top = parcel.readInt();
+ mBoundsInParent.bottom = parcel.readInt();
+ mBoundsInParent.left = parcel.readInt();
+ mBoundsInParent.right = parcel.readInt();
}
- mMaxTextLength = parcel.readInt();
- mMovementGranularities = parcel.readInt();
- mBooleanProperties = parcel.readInt();
-
- mPackageName = parcel.readCharSequence();
- mClassName = parcel.readCharSequence();
- mText = parcel.readCharSequence();
- mHintText = parcel.readCharSequence();
- mError = parcel.readCharSequence();
- mContentDescription = parcel.readCharSequence();
- mViewIdResourceName = parcel.readString();
-
- mTextSelectionStart = parcel.readInt();
- mTextSelectionEnd = parcel.readInt();
-
- mInputType = parcel.readInt();
- mLiveRegion = parcel.readInt();
- mDrawingOrderInParent = parcel.readInt();
-
- if (parcel.readInt() == 1) {
- mExtraDataKeys = parcel.createStringArrayList();
- } else {
- mExtraDataKeys = null;
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mBoundsInScreen.top = parcel.readInt();
+ mBoundsInScreen.bottom = parcel.readInt();
+ mBoundsInScreen.left = parcel.readInt();
+ mBoundsInScreen.right = parcel.readInt();
}
- if (parcel.readInt() == 1) {
- mExtras = parcel.readBundle();
- } else {
- mExtras = null;
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ final int legacyStandardActions = parcel.readInt();
+ addLegacyStandardActions(legacyStandardActions);
+ final int nonLegacyActionCount = parcel.readInt();
+ for (int i = 0; i < nonLegacyActionCount; i++) {
+ final AccessibilityAction action = new AccessibilityAction(
+ parcel.readInt(), parcel.readCharSequence());
+ addActionUnchecked(action);
+ }
}
- if (parcel.readInt() == 1) {
- mRangeInfo = RangeInfo.obtain(
- parcel.readInt(),
- parcel.readFloat(),
- parcel.readFloat(),
- parcel.readFloat());
- }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mMaxTextLength = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mMovementGranularities = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mBooleanProperties = parcel.readInt();
- if (parcel.readInt() == 1) {
- mCollectionInfo = CollectionInfo.obtain(
- parcel.readInt(),
- parcel.readInt(),
- parcel.readInt() == 1,
- parcel.readInt());
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mPackageName = parcel.readCharSequence();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mClassName = parcel.readCharSequence();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mText = parcel.readCharSequence();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mHintText = parcel.readCharSequence();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mError = parcel.readCharSequence();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mContentDescription = parcel.readCharSequence();
}
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mViewIdResourceName = parcel.readString();
- if (parcel.readInt() == 1) {
- mCollectionItemInfo = CollectionItemInfo.obtain(
- parcel.readInt(),
- parcel.readInt(),
- parcel.readInt(),
- parcel.readInt(),
- parcel.readInt() == 1,
- parcel.readInt() == 1);
- }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mTextSelectionStart = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mTextSelectionEnd = parcel.readInt();
+
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mInputType = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mLiveRegion = parcel.readInt();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mDrawingOrderInParent = parcel.readInt();
+
+ mExtraDataKeys = isBitSet(nonDefaultFields, fieldIndex++)
+ ? parcel.createStringArrayList()
+ : null;
+
+ mExtras = isBitSet(nonDefaultFields, fieldIndex++)
+ ? parcel.readBundle()
+ : null;
+
+ if (mRangeInfo != null) mRangeInfo.recycle();
+ mRangeInfo = isBitSet(nonDefaultFields, fieldIndex++)
+ ? RangeInfo.obtain(
+ parcel.readInt(),
+ parcel.readFloat(),
+ parcel.readFloat(),
+ parcel.readFloat())
+ : null;
+
+ if (mCollectionInfo != null) mCollectionInfo.recycle();
+ mCollectionInfo = isBitSet(nonDefaultFields, fieldIndex++)
+ ? CollectionInfo.obtain(
+ parcel.readInt(),
+ parcel.readInt(),
+ parcel.readInt() == 1,
+ parcel.readInt())
+ : null;
+
+ if (mCollectionItemInfo != null) mCollectionItemInfo.recycle();
+ mCollectionItemInfo = isBitSet(nonDefaultFields, fieldIndex++)
+ ? CollectionItemInfo.obtain(
+ parcel.readInt(),
+ parcel.readInt(),
+ parcel.readInt(),
+ parcel.readInt(),
+ parcel.readInt() == 1,
+ parcel.readInt() == 1)
+ : null;
mSealed = sealed;
}
@@ -3358,50 +3475,7 @@
* Clears the state of this instance.
*/
private void clear() {
- mSealed = false;
- mSourceNodeId = UNDEFINED_NODE_ID;
- mParentNodeId = UNDEFINED_NODE_ID;
- mLabelForId = UNDEFINED_NODE_ID;
- mLabeledById = UNDEFINED_NODE_ID;
- mTraversalBefore = UNDEFINED_NODE_ID;
- mTraversalAfter = UNDEFINED_NODE_ID;
- mWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
- mConnectionId = UNDEFINED_CONNECTION_ID;
- mMaxTextLength = -1;
- mMovementGranularities = 0;
- if (mChildNodeIds != null) {
- mChildNodeIds.clear();
- }
- mBoundsInParent.set(0, 0, 0, 0);
- mBoundsInScreen.set(0, 0, 0, 0);
- mBooleanProperties = 0;
- mDrawingOrderInParent = 0;
- mExtraDataKeys = null;
- mPackageName = null;
- mClassName = null;
- mText = null;
- mHintText = null;
- mError = null;
- mContentDescription = null;
- mViewIdResourceName = null;
- removeAllActions();
- mTextSelectionStart = UNDEFINED_SELECTION_INDEX;
- mTextSelectionEnd = UNDEFINED_SELECTION_INDEX;
- mInputType = InputType.TYPE_NULL;
- mLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE;
- mExtras = null;
- if (mRangeInfo != null) {
- mRangeInfo.recycle();
- mRangeInfo = null;
- }
- if (mCollectionInfo != null) {
- mCollectionInfo.recycle();
- mCollectionInfo = null;
- }
- if (mCollectionItemInfo != null) {
- mCollectionItemInfo.recycle();
- mCollectionItemInfo = null;
- }
+ init(DEFAULT);
}
private static boolean isDefaultLegacyStandardAction(AccessibilityAction action) {
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index 3f1fece..401cac8 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -16,6 +16,7 @@
package android.view.accessibility;
+import android.annotation.Nullable;
import android.os.Parcelable;
import android.view.View;
@@ -90,7 +91,7 @@
int mAddedCount= UNDEFINED;
int mRemovedCount = UNDEFINED;
- AccessibilityNodeInfo mSourceNode;
+ long mSourceNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
int mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
CharSequence mClassName;
@@ -116,7 +117,7 @@
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setSource(View source) {
- setSource(source, UNDEFINED);
+ setSource(source, AccessibilityNodeProvider.HOST_VIEW_ID);
}
/**
@@ -133,46 +134,28 @@
* @param root The root of the virtual subtree.
* @param virtualDescendantId The id of the virtual descendant.
*/
- public void setSource(View root, int virtualDescendantId) {
+ public void setSource(@Nullable View root, int virtualDescendantId) {
enforceNotSealed();
boolean important = true;
+ int rootViewId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
- clearSourceNode();
if (root != null) {
- if (virtualDescendantId == View.NO_ID
- || virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID
- || virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
- important = root.isImportantForAccessibility();
- mSourceNode = root.createAccessibilityNodeInfo();
- } else {
- AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
- if (provider != null) {
- mSourceNode = provider.createAccessibilityNodeInfo(virtualDescendantId);
- }
- }
-
+ important = root.isImportantForAccessibility();
+ rootViewId = root.getAccessibilityViewId();
mSourceWindowId = root.getAccessibilityWindowId();
}
setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, important);
+ mSourceNodeId = AccessibilityNodeInfo.makeNodeId(rootViewId, virtualDescendantId);
}
/**
- * Set the source directly to an AccessibilityNodeInfo rather than indirectly via a View
+ * Set the source node ID directly
*
- * @param info The source
- *
+ * @param sourceNodeId The source node Id
* @hide
*/
- public void setSource(AccessibilityNodeInfo info) {
- enforceNotSealed();
- clearSourceNode();
- mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
- if (info != null) {
- mSourceNode = AccessibilityNodeInfo.obtain(info);
- setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY,
- mSourceNode.isImportantForAccessibility());
- mSourceWindowId = info.getWindowId();
- }
+ public void setSourceNodeId(long sourceNodeId) {
+ mSourceNodeId = sourceNodeId;
}
/**
@@ -186,11 +169,15 @@
*/
public AccessibilityNodeInfo getSource() {
enforceSealed();
- if (mSourceNode != null) {
- return AccessibilityNodeInfo.obtain(mSourceNode);
+ if ((mConnectionId == UNDEFINED)
+ || (mSourceWindowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
+ || (AccessibilityNodeInfo.getAccessibilityViewId(mSourceNodeId)
+ == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)) {
+ return null;
}
-
- return null;
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mSourceWindowId,
+ mSourceNodeId, false, GET_SOURCE_PREFETCH_FLAGS, null);
}
/**
@@ -647,7 +634,7 @@
* @hide
*/
public long getSourceNodeId() {
- return mSourceNode != null ? mSourceNode.getSourceNodeId() : UNDEFINED;
+ return mSourceNodeId;
}
/**
@@ -661,9 +648,6 @@
public void setConnectionId(int connectionId) {
enforceNotSealed();
mConnectionId = connectionId;
- if (mSourceNode != null) {
- mSourceNode.setConnectionId(mConnectionId);
- }
}
/**
@@ -675,9 +659,6 @@
*/
public void setSealed(boolean sealed) {
mSealed = sealed;
- if (mSourceNode != null) {
- mSourceNode.setSealed(sealed);
- }
}
/**
@@ -816,9 +797,7 @@
mParcelableData = record.mParcelableData;
mText.addAll(record.mText);
mSourceWindowId = record.mSourceWindowId;
- if (record.mSourceNode != null) {
- mSourceNode = AccessibilityNodeInfo.obtain(record.mSourceNode);
- }
+ mSourceNodeId = record.mSourceNodeId;
mConnectionId = record.mConnectionId;
}
@@ -843,19 +822,11 @@
mBeforeText = null;
mParcelableData = null;
mText.clear();
- clearSourceNode();
- mSourceWindowId = UNDEFINED;
+ mSourceNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+ mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
mConnectionId = UNDEFINED;
}
- private void clearSourceNode() {
- if (mSourceNode != null) {
- mSourceNode.recycle();
- mSourceNode = null;
- }
-
- }
-
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
diff --git a/core/java/android/view/accessibility/AccessibilityRequestPreparer.java b/core/java/android/view/accessibility/AccessibilityRequestPreparer.java
new file mode 100644
index 0000000..889feb9
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityRequestPreparer.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Message;
+import android.view.View;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+
+/**
+ * Object responsible to ensuring that a {@link View} is prepared to meet a synchronous request for
+ * accessibility data.
+ * <p>
+ * Because accessibility requests arrive to {@link View}s synchronously on the UI thread, a View
+ * that requires information from other processes can struggle to meet those requests. Registering
+ * an instance of this class with {@link AccessibilityManager} allows a View to be notified when
+ * a request is about to be made, and to asynchronously inform the accessibility system when it is
+ * ready to meet the request.
+ * <p>
+ * <strong>Note:</strong> This class should only be needed in exceptional situations where a
+ * {@link View} cannot otherwise synchronously meet the request for accessibility data.
+ */
+public abstract class AccessibilityRequestPreparer {
+ public static final int REQUEST_TYPE_EXTRA_DATA = 0x00000001;
+
+ /** @hide */
+ @IntDef(flag = true,
+ value = {
+ REQUEST_TYPE_EXTRA_DATA
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RequestTypes {}
+
+ private final WeakReference<View> mViewRef;
+ private final int mRequestTypes;
+
+ /**
+ * @param view The view whose requests need preparation. It must be attached to a
+ * window. This object will retain a weak reference to this view, and will unregister itself
+ * from AccessibilityManager if the view is detached from a window. It will not re-register
+ * itself.
+ * @param requestTypes The types of requests that require preparation. Different types may
+ * be ORed together.
+ *
+ * @throws IllegalStateException if the view is not attached to a window.
+ */
+ public AccessibilityRequestPreparer(View view, @RequestTypes int requestTypes) {
+ if (!view.isAttachedToWindow()) {
+ throw new IllegalStateException("View must be attached to a window");
+ }
+ mViewRef = new WeakReference<>(view);
+ mRequestTypes = requestTypes;
+ view.addOnAttachStateChangeListener(new ViewAttachStateListener());
+ }
+
+ /**
+ * Callback to allow preparation for filling extra data. Only called back if
+ * REQUEST_TYPE_EXTRA_DATA is requested.
+ *
+ * @param virtualViewId The ID of a virtual child node, if the {@link View} for this preparer
+ * supports virtual descendents, or {@link AccessibilityNodeProvider#HOST_VIEW_ID}
+ * if the request is for the view itself.
+ * @param extraDataKey The extra data key for the request
+ * @param args The arguments for the request
+ * @param preparationFinishedMessage A message that must be sent to its target when preparations
+ * are complete.
+ *
+ * @see View#addExtraDataToAccessibilityNodeInfo(AccessibilityNodeInfo, String, Bundle)
+ * @see AccessibilityDelegate#addExtraDataToAccessibilityNodeInfo(View, AccessibilityNodeInfo,
+ * String, Bundle)
+ * @see AccessibilityNodeProvider#addExtraDataToAccessibilityNodeInfo(
+ * int, AccessibilityNodeInfo, String, Bundle)
+ */
+ public abstract void onPrepareExtraData(int virtualViewId, String extraDataKey,
+ Bundle args, Message preparationFinishedMessage);
+
+ /**
+ * Get the view this object was created with.
+ *
+ * @return The view this object was created with, or {@code null} if the weak reference held
+ * to the view is no longer valid.
+ */
+ public @Nullable View getView() {
+ return mViewRef.get();
+ }
+
+ private class ViewAttachStateListener implements View.OnAttachStateChangeListener {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ Context context = v.getContext();
+ if (context != null) {
+ context.getSystemService(AccessibilityManager.class)
+ .removeAccessibilityRequestPreparer(AccessibilityRequestPreparer.this);
+ }
+ v.removeOnAttachStateChangeListener(this);
+ }
+ }
+}
diff --git a/core/java/android/view/autofill/AutofillManagerInternal.java b/core/java/android/view/autofill/AutofillManagerInternal.java
new file mode 100644
index 0000000..fc5d306
--- /dev/null
+++ b/core/java/android/view/autofill/AutofillManagerInternal.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.autofill;
+
+/**
+ * Autofill Manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class AutofillManagerInternal {
+
+ /**
+ * Notifies the manager that the back key was pressed.
+ */
+ public abstract void onBackKeyPressed();
+}
diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java
index 2e2056c..779eefb 100644
--- a/core/java/android/view/textservice/SpellCheckerSession.java
+++ b/core/java/android/view/textservice/SpellCheckerSession.java
@@ -29,6 +29,8 @@
import com.android.internal.textservice.ITextServicesManager;
import com.android.internal.textservice.ITextServicesSessionListener;
+import dalvik.system.CloseGuard;
+
import java.util.LinkedList;
import java.util.Queue;
@@ -98,7 +100,7 @@
private final SpellCheckerSessionListener mSpellCheckerSessionListener;
private final SpellCheckerSessionListenerImpl mSpellCheckerSessionListenerImpl;
- private boolean mIsUsed;
+ private final CloseGuard mGuard = CloseGuard.get();
/** Handler that will execute the main tasks */
private final Handler mHandler = new Handler() {
@@ -128,8 +130,9 @@
mSpellCheckerSessionListenerImpl = new SpellCheckerSessionListenerImpl(mHandler);
mInternalListener = new InternalListener(mSpellCheckerSessionListenerImpl);
mTextServicesManager = tsm;
- mIsUsed = true;
mSpellCheckerSessionListener = listener;
+
+ mGuard.open("finishSession");
}
/**
@@ -160,7 +163,7 @@
* checker.
*/
public void close() {
- mIsUsed = false;
+ mGuard.close();
try {
mSpellCheckerSessionListenerImpl.close();
mTextServicesManager.finishSpellCheckerService(mSpellCheckerSessionListenerImpl);
@@ -542,11 +545,14 @@
@Override
protected void finalize() throws Throwable {
- super.finalize();
- if (mIsUsed) {
- Log.e(TAG, "SpellCheckerSession was not finished properly." +
- "You should call finishSession() when you finished to use a spell checker.");
- close();
+ try {
+ // Note that mGuard will be null if the constructor threw.
+ if (mGuard != null) {
+ mGuard.warnIfOpen();
+ close();
+ }
+ } finally {
+ super.finalize();
}
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index dda5df6..ec2b302 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -31,15 +31,15 @@
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.net.http.SslCertificate;
import android.net.Uri;
+import android.net.http.SslCertificate;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.StrictMode;
import android.os.RemoteException;
+import android.os.StrictMode;
import android.print.PrintDocumentAdapter;
import android.security.KeyChain;
import android.util.AttributeSet;
@@ -49,10 +49,10 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewStructure;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewHierarchyEncoder;
+import android.view.ViewStructure;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -1621,6 +1621,23 @@
}
/**
+ * Starts Safe Browsing initialization. This should only be called once.
+ * @param context is the activity context the WebView will be used in.
+ * @param callback will be called with the value true if initialization is
+ * successful. The callback will be run on the UI thread.
+ */
+ public static void initSafeBrowsing(Context context, ValueCallback<Boolean> callback) {
+ getFactory().getStatics().initSafeBrowsing(context, callback);
+ }
+
+ /**
+ * Shuts down Safe Browsing. This should only be called once.
+ */
+ public static void shutdownSafeBrowsing() {
+ getFactory().getStatics().shutdownSafeBrowsing();
+ }
+
+ /**
* Gets the WebBackForwardList for this WebView. This contains the
* back/forward list for use in querying each item in the history stack.
* This is a copy of the private WebBackForwardList so it contains only a
@@ -1939,12 +1956,12 @@
* messages to a certain target origin. See
* <a href="https://html.spec.whatwg.org/multipage/comms.html#posting-messages">
* HTML5 spec</a> for how target origin can be used.
+ * <p>
+ * A target origin can be set as a wildcard ("*"). However this is not recommended.
+ * See the page above for security issues.
*
* @param message the WebMessage
- * @param targetOrigin the target origin. This is the origin of the page
- * that is intended to receive the message. For best security
- * practices, the user should not specify a wildcard (*) when
- * specifying the origin.
+ * @param targetOrigin the target origin.
*/
public void postWebMessage(WebMessage message, Uri targetOrigin) {
checkThread();
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 71db6b1..1b6b392 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -107,6 +107,7 @@
// error for namespace lookup
public static final int LIBLOAD_FAILED_TO_FIND_NAMESPACE = 10;
+
private static String getWebViewPreparationErrorReason(int error) {
switch (error) {
case LIBLOAD_FAILED_WAITING_FOR_RELRO:
@@ -119,10 +120,7 @@
return "Unknown";
}
- /**
- * @hide
- */
- public static class MissingWebViewPackageException extends AndroidRuntimeException {
+ private static class MissingWebViewPackageException extends Exception {
public MissingWebViewPackageException(String message) { super(message); }
public MissingWebViewPackageException(Exception e) { super(e); }
}
@@ -184,11 +182,16 @@
return LIBLOAD_WRONG_PACKAGE_NAME;
}
- int loadNativeRet = loadNativeLibrary(clazzLoader, packageInfo);
- // If we failed waiting for relro we want to return that fact even if we successfully load
- // the relro file.
- if (loadNativeRet == LIBLOAD_SUCCESS) return response.status;
- return loadNativeRet;
+ try {
+ int loadNativeRet = loadNativeLibrary(clazzLoader, packageInfo);
+ // If we failed waiting for relro we want to return that fact even if we successfully
+ // load the relro file.
+ if (loadNativeRet == LIBLOAD_SUCCESS) return response.status;
+ return loadNativeRet;
+ } catch (MissingWebViewPackageException e) {
+ Log.e(LOGTAG, "Couldn't load native library: " + e);
+ return LIBLOAD_FAILED_TO_LOAD_LIBRARY;
+ }
}
static WebViewFactoryProvider getProvider() {
@@ -259,7 +262,8 @@
}
// Throws MissingWebViewPackageException on failure
- private static void verifyPackageInfo(PackageInfo chosen, PackageInfo toUse) {
+ private static void verifyPackageInfo(PackageInfo chosen, PackageInfo toUse)
+ throws MissingWebViewPackageException {
if (!chosen.packageName.equals(toUse.packageName)) {
throw new MissingWebViewPackageException("Failed to verify WebView provider, "
+ "packageName mismatch, expected: "
@@ -285,7 +289,8 @@
* required values from the donor package. If the ApplicationInfo is for a full WebView,
* leave it alone. Throws MissingWebViewPackageException if the donor is missing.
*/
- private static void fixupStubApplicationInfo(ApplicationInfo ai, PackageManager pm) {
+ private static void fixupStubApplicationInfo(ApplicationInfo ai, PackageManager pm)
+ throws MissingWebViewPackageException {
String donorPackageName = null;
if (ai.metaData != null) {
donorPackageName = ai.metaData.getString("com.android.webview.WebViewDonorPackage");
@@ -318,7 +323,7 @@
}
}
- private static Context getWebViewContextAndSetProvider() {
+ private static Context getWebViewContextAndSetProvider() throws MissingWebViewPackageException {
Application initialApplication = AppGlobals.getInitialApplication();
try {
WebViewProviderResponse response = null;
@@ -549,10 +554,10 @@
return prepareWebViewInSystemServer(nativeLibs);
}
- // throws MissingWebViewPackageException
private static String getLoadFromApkPath(String apkPath,
String[] abiList,
- String nativeLibFileName) {
+ String nativeLibFileName)
+ throws MissingWebViewPackageException {
// Search the APK for a native library conforming to a listed ABI.
try (ZipFile z = new ZipFile(apkPath)) {
for (String abi : abiList) {
@@ -569,8 +574,8 @@
return "";
}
- // throws MissingWebViewPackageException
- private static String[] getWebViewNativeLibraryPaths(PackageInfo packageInfo) {
+ private static String[] getWebViewNativeLibraryPaths(PackageInfo packageInfo)
+ throws MissingWebViewPackageException {
ApplicationInfo ai = packageInfo.applicationInfo;
final String NATIVE_LIB_FILE_NAME = getWebViewLibrary(ai);
@@ -695,7 +700,8 @@
}
// Assumes that we have waited for relro creation
- private static int loadNativeLibrary(ClassLoader clazzLoader, PackageInfo packageInfo) {
+ private static int loadNativeLibrary(ClassLoader clazzLoader, PackageInfo packageInfo)
+ throws MissingWebViewPackageException {
if (!sAddressSpaceReserved) {
Log.e(LOGTAG, "can't load with relro file; address space not reserved");
return LIBLOAD_ADDRESS_SPACE_NOT_RESERVED;
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 8359a10..5cf48b5 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -74,6 +74,18 @@
* {@link android.webkit.WebChromeClient.FileChooserParams#parseResult(int, Intent)}
*/
Uri[] parseFileChooserResult(int resultCode, Intent intent);
+
+ /**
+ * Implement the API method
+ * {@link android.webkit.WebView#initSafeBrowsing(Context , ValueCallback<Boolean>)}
+ */
+ void initSafeBrowsing(Context context, ValueCallback<Boolean> callback);
+
+ /**
+ * Implement the API method
+ * {@link android.webkit.WebView#shutdownSafeBrowsing()}
+ */
+ void shutdownSafeBrowsing();
}
Statics getStatics();
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index b519ec9..0204dff 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -218,7 +218,7 @@
final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
TextUtils.join(File.pathSeparator, zipPaths);
- waitForZygote();
+ ZygoteProcess.waitForConnectionToZygote(WEBVIEW_ZYGOTE_SOCKET);
Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
sZygote.preloadPackageForAbi(zip, librarySearchPath, sPackageCacheKey,
@@ -228,25 +228,4 @@
sZygote = null;
}
}
-
- /**
- * Wait until a connection to the Zygote can be established.
- */
- private static void waitForZygote() {
- while (true) {
- try {
- final ZygoteProcess.ZygoteState zs =
- ZygoteProcess.ZygoteState.connect(WEBVIEW_ZYGOTE_SOCKET);
- zs.close();
- break;
- } catch (IOException ioe) {
- Log.w(LOGTAG, "Got error connecting to zygote, retrying. msg= " + ioe.getMessage());
- }
-
- try {
- Thread.sleep(1000);
- } catch (InterruptedException ie) {
- }
- }
- }
}
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
index cfd1445..1e8207a 100644
--- a/core/java/android/widget/DayPickerView.java
+++ b/core/java/android/widget/DayPickerView.java
@@ -16,8 +16,6 @@
package android.widget;
-import static android.os.Build.VERSION_CODES.O;
-
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -293,22 +291,10 @@
* @param timeInMillis the target day in milliseconds
* @param animate whether to smooth scroll to the new position
* @param setSelected whether to set the specified day as selected
- *
- * @throws IllegalArgumentException if the build version is greater than
- * {@link android.os.Build.VERSION_CODES#N_MR1} and the provided timeInMillis is before
- * the range start or after the range end.
*/
private void setDate(long timeInMillis, boolean animate, boolean setSelected) {
getTempCalendarForTime(timeInMillis);
- final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
- if (targetSdkVersion >= O) {
- if (mTempCalendar.before(mMinDate) || mTempCalendar.after(mMaxDate)) {
- throw new IllegalArgumentException("timeInMillis must be between the values of "
- + "getMinDate() and getMaxDate()");
- }
- }
-
if (setSelected) {
mSelectedDay.setTimeInMillis(timeInMillis);
}
@@ -367,6 +353,13 @@
public void onRangeChanged() {
mAdapter.setRange(mMinDate, mMaxDate);
+ // Clamp the selected day to the new min/max.
+ if (mSelectedDay.before(mMinDate)) {
+ mSelectedDay.setTimeInMillis(mMinDate.getTimeInMillis());
+ } else if (mSelectedDay.after(mMaxDate)) {
+ mSelectedDay.setTimeInMillis(mMaxDate.getTimeInMillis());
+ }
+
// Changing the min/max date changes the selection position since we
// don't really have stable IDs. Jumps immediately to the new position.
setDate(mSelectedDay.getTimeInMillis(), false, false);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 1fef7cb..c19ee56 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -3842,11 +3842,12 @@
}
if (mTextView.canRequestAutofill()) {
- final int mode = mTextView.getText().length() <= 0
- ? MenuItem.SHOW_AS_ACTION_IF_ROOM : MenuItem.SHOW_AS_ACTION_NEVER;
- menu.add(Menu.NONE, TextView.ID_AUTOFILL, MENU_ITEM_ORDER_AUTOFILL,
- com.android.internal.R.string.autofill)
- .setShowAsAction(mode);
+ final String selected = mTextView.getSelectedText();
+ if (selected == null || selected.isEmpty()) {
+ menu.add(Menu.NONE, TextView.ID_AUTOFILL, MENU_ITEM_ORDER_AUTOFILL,
+ com.android.internal.R.string.autofill)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ }
}
if (mTextView.canPasteAsPlainText()) {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index e534233..af45985 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -99,6 +99,26 @@
*/
private static final int MAX_NESTED_VIEWS = 10;
+ // The unique identifiers for each custom {@link Action}.
+ private static final int SET_ON_CLICK_PENDING_INTENT_TAG = 1;
+ private static final int REFLECTION_ACTION_TAG = 2;
+ private static final int SET_DRAWABLE_PARAMETERS_TAG = 3;
+ private static final int VIEW_GROUP_ACTION_ADD_TAG = 4;
+ private static final int SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG = 5;
+ private static final int SET_EMPTY_VIEW_ACTION_TAG = 6;
+ private static final int VIEW_GROUP_ACTION_REMOVE_TAG = 7;
+ private static final int SET_PENDING_INTENT_TEMPLATE_TAG = 8;
+ private static final int SET_ON_CLICK_FILL_IN_INTENT_TAG = 9;
+ private static final int SET_REMOTE_VIEW_ADAPTER_INTENT_TAG = 10;
+ private static final int TEXT_VIEW_DRAWABLE_ACTION_TAG = 11;
+ private static final int BITMAP_REFLECTION_ACTION_TAG = 12;
+ private static final int TEXT_VIEW_SIZE_ACTION_TAG = 13;
+ private static final int VIEW_PADDING_ACTION_TAG = 14;
+ private static final int SET_REMOTE_VIEW_ADAPTER_LIST_TAG = 15;
+ private static final int TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG = 17;
+ private static final int SET_REMOTE_INPUTS_ACTION_TAG = 18;
+ private static final int LAYOUT_PARAM_ACTION_TAG = 19;
+
/**
* Application that hosts the remote views.
*
@@ -441,8 +461,6 @@
int viewId;
int emptyViewId;
- public final static int TAG = 6;
-
SetEmptyView(int viewId, int emptyViewId) {
this.viewId = viewId;
this.emptyViewId = emptyViewId;
@@ -454,7 +472,7 @@
}
public void writeToParcel(Parcel out, int flags) {
- out.writeInt(TAG);
+ out.writeInt(SET_EMPTY_VIEW_ACTION_TAG);
out.writeInt(this.viewId);
out.writeInt(this.emptyViewId);
}
@@ -489,7 +507,7 @@
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
+ dest.writeInt(SET_ON_CLICK_FILL_IN_INTENT_TAG);
dest.writeInt(viewId);
fillInIntent.writeToParcel(dest, 0 /* no flags */);
}
@@ -555,8 +573,6 @@
}
Intent fillInIntent;
-
- public final static int TAG = 9;
}
private class SetPendingIntentTemplate extends Action {
@@ -571,7 +587,7 @@
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
+ dest.writeInt(SET_PENDING_INTENT_TEMPLATE_TAG);
dest.writeInt(viewId);
pendingIntentTemplate.writeToParcel(dest, 0 /* no flags */);
}
@@ -632,8 +648,6 @@
}
PendingIntent pendingIntentTemplate;
-
- public final static int TAG = 8;
}
private class SetRemoteViewsAdapterList extends Action {
@@ -656,7 +670,7 @@
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
+ dest.writeInt(SET_REMOTE_VIEW_ADAPTER_LIST_TAG);
dest.writeInt(viewId);
dest.writeInt(viewTypeCount);
@@ -715,7 +729,6 @@
int viewTypeCount;
ArrayList<RemoteViews> list;
- public final static int TAG = 15;
}
private class SetRemoteViewsAdapterIntent extends Action {
@@ -730,7 +743,7 @@
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
+ dest.writeInt(SET_REMOTE_VIEW_ADAPTER_INTENT_TAG);
dest.writeInt(viewId);
intent.writeToParcel(dest, flags);
}
@@ -782,8 +795,6 @@
Intent intent;
boolean isAsync = false;
-
- public final static int TAG = 10;
}
/**
@@ -807,7 +818,7 @@
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
+ dest.writeInt(SET_ON_CLICK_PENDING_INTENT_TAG);
dest.writeInt(viewId);
// We use a flag to indicate whether the parcel contains a valid object.
@@ -860,8 +871,6 @@
}
PendingIntent pendingIntent;
-
- public final static int TAG = 1;
}
private static Rect getSourceBounds(View v) {
@@ -997,7 +1006,7 @@
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
+ dest.writeInt(SET_DRAWABLE_PARAMETERS_TAG);
dest.writeInt(viewId);
dest.writeInt(targetBackground ? 1 : 0);
dest.writeInt(alpha);
@@ -1048,15 +1057,11 @@
int colorFilter;
PorterDuff.Mode filterMode;
int level;
-
- public final static int TAG = 3;
}
private final class ReflectionActionWithoutParams extends Action {
final String methodName;
- public final static int TAG = 5;
-
ReflectionActionWithoutParams(int viewId, String methodName) {
this.viewId = viewId;
this.methodName = methodName;
@@ -1068,7 +1073,7 @@
}
public void writeToParcel(Parcel out, int flags) {
- out.writeInt(TAG);
+ out.writeInt(SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG);
out.writeInt(this.viewId);
out.writeString(this.methodName);
}
@@ -1192,7 +1197,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
+ dest.writeInt(BITMAP_REFLECTION_ACTION_TAG);
dest.writeInt(viewId);
dest.writeString(methodName);
dest.writeInt(bitmapId);
@@ -1214,16 +1219,12 @@
public String getActionName() {
return "BitmapReflectionAction";
}
-
- public final static int TAG = 12;
}
/**
* Base class for the reflection actions.
*/
private final class ReflectionAction extends Action {
- static final int TAG = 2;
-
static final int BOOLEAN = 1;
static final int BYTE = 2;
static final int SHORT = 3;
@@ -1330,7 +1331,7 @@
}
public void writeToParcel(Parcel out, int flags) {
- out.writeInt(TAG);
+ out.writeInt(REFLECTION_ACTION_TAG);
out.writeInt(this.viewId);
out.writeString(this.methodName);
out.writeInt(this.type);
@@ -1555,137 +1556,220 @@
}
/**
- * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
- * given {@link RemoteViews}, or calling {@link ViewGroup#removeAllViews()}
- * when null. This allows users to build "nested" {@link RemoteViews}.
+ * ViewGroup methods that are related to adding Views.
*/
- private class ViewGroupAction extends Action {
- public ViewGroupAction(int viewId, RemoteViews nestedViews) {
+ private class ViewGroupActionAdd extends Action {
+ private RemoteViews mNestedViews;
+ private int mIndex;
+
+ ViewGroupActionAdd(int viewId, RemoteViews nestedViews) {
+ this(viewId, nestedViews, -1 /* index */);
+ }
+
+ ViewGroupActionAdd(int viewId, RemoteViews nestedViews, int index) {
this.viewId = viewId;
- this.nestedViews = nestedViews;
+ mNestedViews = nestedViews;
+ mIndex = index;
if (nestedViews != null) {
configureRemoteViewsAsChild(nestedViews);
}
}
- ViewGroupAction(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth) {
+ ViewGroupActionAdd(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info,
+ int depth) {
viewId = parcel.readInt();
- boolean nestedViewsNull = parcel.readInt() == 0;
- if (!nestedViewsNull) {
- nestedViews = new RemoteViews(parcel, bitmapCache, info, depth);
- } else {
- nestedViews = null;
- }
+ mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth);
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
+ dest.writeInt(VIEW_GROUP_ACTION_ADD_TAG);
dest.writeInt(viewId);
- if (nestedViews != null) {
- dest.writeInt(1);
- nestedViews.writeToParcel(dest, flags);
- } else {
- // signifies null
- dest.writeInt(0);
- }
+ mNestedViews.writeToParcel(dest, flags);
}
@Override
public boolean hasSameAppInfo(ApplicationInfo parentInfo) {
- return nestedViews != null
- && nestedViews.mApplication.packageName.equals(parentInfo.packageName)
- && nestedViews.mApplication.uid == parentInfo.uid;
+ return mNestedViews.mApplication.packageName.equals(parentInfo.packageName)
+ && mNestedViews.mApplication.uid == parentInfo.uid;
}
@Override
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
final Context context = root.getContext();
final ViewGroup target = root.findViewById(viewId);
- if (target == null) return;
- if (nestedViews != null) {
- // Inflate nested views and add as children
- target.addView(nestedViews.apply(context, target, handler));
- } else {
- // Clear all children when nested views omitted
- target.removeAllViews();
+
+ if (target == null) {
+ return;
}
+
+ // Inflate nested views and add as children
+ target.addView(mNestedViews.apply(context, target, handler), mIndex);
}
@Override
public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
// In the async implementation, update the view tree so that subsequent calls to
- // findViewById return the currect view.
+ // findViewById return the current view.
root.createTree();
ViewTree target = root.findViewTreeById(viewId);
if ((target == null) || !(target.mRoot instanceof ViewGroup)) {
return ACTION_NOOP;
}
final ViewGroup targetVg = (ViewGroup) target.mRoot;
- if (nestedViews == null) {
- // Clear all children when nested views omitted
- target.mChildren = null;
- return new RuntimeAction() {
- @Override
- public void apply(View root, ViewGroup rootParent, OnClickHandler handler)
- throws ActionException {
- targetVg.removeAllViews();
- }
- };
- } else {
- // Inflate nested views and perform all the async tasks for the child remoteView.
- final Context context = root.mRoot.getContext();
- final AsyncApplyTask task = nestedViews.getAsyncApplyTask(
- context, targetVg, null, handler);
- final ViewTree tree = task.doInBackground();
- if (tree == null) {
- throw new ActionException(task.mError);
- }
- // Update the global view tree, so that next call to findViewTreeById
- // goes through the subtree as well.
- target.addChild(tree);
+ // Inflate nested views and perform all the async tasks for the child remoteView.
+ final Context context = root.mRoot.getContext();
+ final AsyncApplyTask task = mNestedViews.getAsyncApplyTask(
+ context, targetVg, null, handler);
+ final ViewTree tree = task.doInBackground();
- return new RuntimeAction() {
-
- @Override
- public void apply(View root, ViewGroup rootParent, OnClickHandler handler) throws ActionException {
- task.onPostExecute(tree);
- targetVg.addView(task.mResult);
- }
- };
+ if (tree == null) {
+ throw new ActionException(task.mError);
}
+
+ // Update the global view tree, so that next call to findViewTreeById
+ // goes through the subtree as well.
+ target.addChild(tree, mIndex);
+
+ return new RuntimeAction() {
+ @Override
+ public void apply(View root, ViewGroup rootParent, OnClickHandler handler)
+ throws ActionException {
+ task.onPostExecute(tree);
+ targetVg.addView(task.mResult, mIndex);
+ }
+ };
}
@Override
public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
- if (nestedViews != null) {
- counter.increment(nestedViews.estimateMemoryUsage());
- }
+ counter.increment(mNestedViews.estimateMemoryUsage());
}
@Override
public void setBitmapCache(BitmapCache bitmapCache) {
- if (nestedViews != null) {
- nestedViews.setBitmapCache(bitmapCache);
- }
+ mNestedViews.setBitmapCache(bitmapCache);
}
- public String getActionName() {
- return "ViewGroupAction" + (nestedViews == null ? "Remove" : "Add");
- }
-
+ @Override
public int mergeBehavior() {
return MERGE_APPEND;
}
@Override
public boolean prefersAsyncApply() {
- return nestedViews != null && nestedViews.prefersAsyncApply();
+ return mNestedViews.prefersAsyncApply();
}
- RemoteViews nestedViews;
- public final static int TAG = 4;
+ @Override
+ public String getActionName() {
+ return "ViewGroupActionAdd";
+ }
+ }
+
+ /**
+ * ViewGroup methods related to removing child views.
+ */
+ private class ViewGroupActionRemove extends Action {
+ /**
+ * Id that indicates that all child views of the affected ViewGroup should be removed.
+ *
+ * <p>Using -2 because the default id is -1. This avoids accidentally matching that.
+ */
+ private static final int REMOVE_ALL_VIEWS_ID = -2;
+
+ private int mViewIdToKeep;
+
+ ViewGroupActionRemove(int viewId) {
+ this(viewId, REMOVE_ALL_VIEWS_ID);
+ }
+
+ ViewGroupActionRemove(int viewId, int viewIdToKeep) {
+ this.viewId = viewId;
+ mViewIdToKeep = viewIdToKeep;
+ }
+
+ ViewGroupActionRemove(Parcel parcel) {
+ viewId = parcel.readInt();
+ mViewIdToKeep = parcel.readInt();
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(VIEW_GROUP_ACTION_REMOVE_TAG);
+ dest.writeInt(viewId);
+ dest.writeInt(mViewIdToKeep);
+ }
+
+ @Override
+ public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
+ final ViewGroup target = root.findViewById(viewId);
+
+ if (target == null) {
+ return;
+ }
+
+ if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) {
+ target.removeAllViews();
+ return;
+ }
+
+ removeAllViewsExceptIdToKeep(target);
+ }
+
+ @Override
+ public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
+ // In the async implementation, update the view tree so that subsequent calls to
+ // findViewById return the current view.
+ root.createTree();
+ ViewTree target = root.findViewTreeById(viewId);
+
+ if ((target == null) || !(target.mRoot instanceof ViewGroup)) {
+ return ACTION_NOOP;
+ }
+
+ final ViewGroup targetVg = (ViewGroup) target.mRoot;
+
+ // Clear all children when nested views omitted
+ target.mChildren = null;
+ return new RuntimeAction() {
+ @Override
+ public void apply(View root, ViewGroup rootParent, OnClickHandler handler)
+ throws ActionException {
+ if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) {
+ targetVg.removeAllViews();
+ return;
+ }
+
+ removeAllViewsExceptIdToKeep(targetVg);
+ }
+ };
+ }
+
+ /**
+ * Iterates through the children in the given ViewGroup and removes all the views that
+ * do not have an id of {@link #mViewIdToKeep}.
+ */
+ private void removeAllViewsExceptIdToKeep(ViewGroup viewGroup) {
+ // Otherwise, remove all the views that do not match the id to keep.
+ int index = viewGroup.getChildCount() - 1;
+ while (index >= 0) {
+ if (viewGroup.getChildAt(index).getId() != mViewIdToKeep) {
+ viewGroup.removeViewAt(index);
+ }
+ index--;
+ }
+ }
+
+ @Override
+ public String getActionName() {
+ return "ViewGroupActionRemove";
+ }
+
+ @Override
+ public int mergeBehavior() {
+ return MERGE_APPEND;
+ }
}
/**
@@ -1740,7 +1824,7 @@
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
+ dest.writeInt(TEXT_VIEW_DRAWABLE_ACTION_TAG);
dest.writeInt(viewId);
dest.writeInt(isRelative ? 1 : 0);
dest.writeInt(useIcons ? 1 : 0);
@@ -1850,8 +1934,6 @@
boolean drawablesLoaded = false;
Drawable id1, id2, id3, id4;
-
- public final static int TAG = 11;
}
/**
@@ -1871,7 +1953,7 @@
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
+ dest.writeInt(TEXT_VIEW_SIZE_ACTION_TAG);
dest.writeInt(viewId);
dest.writeInt(units);
dest.writeFloat(size);
@@ -1890,8 +1972,6 @@
int units;
float size;
-
- public final static int TAG = 13;
}
/**
@@ -1915,7 +1995,7 @@
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
+ dest.writeInt(VIEW_PADDING_ACTION_TAG);
dest.writeInt(viewId);
dest.writeInt(left);
dest.writeInt(top);
@@ -1935,8 +2015,6 @@
}
int left, top, right, bottom;
-
- public final static int TAG = 14;
}
/**
@@ -1968,7 +2046,7 @@
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
+ dest.writeInt(LAYOUT_PARAM_ACTION_TAG);
dest.writeInt(viewId);
dest.writeInt(property);
dest.writeInt(value);
@@ -2021,8 +2099,6 @@
int property;
int value;
-
- public final static int TAG = 19;
}
/**
@@ -2057,7 +2133,7 @@
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
+ dest.writeInt(TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG);
dest.writeInt(viewId);
dest.writeInt(isRelative ? 1 : 0);
dest.writeInt(index);
@@ -2090,8 +2166,6 @@
final int index;
final int color;
final PorterDuff.Mode mode;
-
- public final static int TAG = 17;
}
/**
@@ -2110,14 +2184,14 @@
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
+ dest.writeInt(SET_REMOTE_INPUTS_ACTION_TAG);
dest.writeInt(viewId);
dest.writeTypedArray(remoteInputs, flags);
}
@Override
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
- final TextView target = (TextView) root.findViewById(viewId);
+ final View target = root.findViewById(viewId);
if (target == null) return;
target.setTagInternal(R.id.remote_input_tag, remoteInputs);
@@ -2128,7 +2202,6 @@
}
final Parcelable[] remoteInputs;
- public final static int TAG = 18;
}
/**
@@ -2271,56 +2344,59 @@
for (int i=0; i<count; i++) {
int tag = parcel.readInt();
switch (tag) {
- case SetOnClickPendingIntent.TAG:
+ case SET_ON_CLICK_PENDING_INTENT_TAG:
mActions.add(new SetOnClickPendingIntent(parcel));
break;
- case SetDrawableParameters.TAG:
+ case SET_DRAWABLE_PARAMETERS_TAG:
mActions.add(new SetDrawableParameters(parcel));
break;
- case ReflectionAction.TAG:
+ case REFLECTION_ACTION_TAG:
mActions.add(new ReflectionAction(parcel));
break;
- case ViewGroupAction.TAG:
- mActions.add(new ViewGroupAction(parcel, mBitmapCache, mApplication,
+ case VIEW_GROUP_ACTION_ADD_TAG:
+ mActions.add(new ViewGroupActionAdd(parcel, mBitmapCache, mApplication,
depth));
break;
- case ReflectionActionWithoutParams.TAG:
+ case VIEW_GROUP_ACTION_REMOVE_TAG:
+ mActions.add(new ViewGroupActionRemove(parcel));
+ break;
+ case SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG:
mActions.add(new ReflectionActionWithoutParams(parcel));
break;
- case SetEmptyView.TAG:
+ case SET_EMPTY_VIEW_ACTION_TAG:
mActions.add(new SetEmptyView(parcel));
break;
- case SetPendingIntentTemplate.TAG:
+ case SET_PENDING_INTENT_TEMPLATE_TAG:
mActions.add(new SetPendingIntentTemplate(parcel));
break;
- case SetOnClickFillInIntent.TAG:
+ case SET_ON_CLICK_FILL_IN_INTENT_TAG:
mActions.add(new SetOnClickFillInIntent(parcel));
break;
- case SetRemoteViewsAdapterIntent.TAG:
+ case SET_REMOTE_VIEW_ADAPTER_INTENT_TAG:
mActions.add(new SetRemoteViewsAdapterIntent(parcel));
break;
- case TextViewDrawableAction.TAG:
+ case TEXT_VIEW_DRAWABLE_ACTION_TAG:
mActions.add(new TextViewDrawableAction(parcel));
break;
- case TextViewSizeAction.TAG:
+ case TEXT_VIEW_SIZE_ACTION_TAG:
mActions.add(new TextViewSizeAction(parcel));
break;
- case ViewPaddingAction.TAG:
+ case VIEW_PADDING_ACTION_TAG:
mActions.add(new ViewPaddingAction(parcel));
break;
- case BitmapReflectionAction.TAG:
+ case BITMAP_REFLECTION_ACTION_TAG:
mActions.add(new BitmapReflectionAction(parcel));
break;
- case SetRemoteViewsAdapterList.TAG:
+ case SET_REMOTE_VIEW_ADAPTER_LIST_TAG:
mActions.add(new SetRemoteViewsAdapterList(parcel));
break;
- case TextViewDrawableColorFilterAction.TAG:
+ case TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG:
mActions.add(new TextViewDrawableColorFilterAction(parcel));
break;
- case SetRemoteInputsAction.TAG:
+ case SET_REMOTE_INPUTS_ACTION_TAG:
mActions.add(new SetRemoteInputsAction(parcel));
break;
- case LayoutParamAction.TAG:
+ case LAYOUT_PARAM_ACTION_TAG:
mActions.add(new LayoutParamAction(parcel));
break;
default:
@@ -2342,7 +2418,7 @@
}
- public RemoteViews clone() {
+ public synchronized RemoteViews clone() {
Preconditions.checkState(mIsRoot, "RemoteView has been attached to another RemoteView. "
+ "May only clone the root of a RemoteView hierarchy.");
@@ -2469,7 +2545,23 @@
* @param nestedView {@link RemoteViews} that describes the child.
*/
public void addView(int viewId, RemoteViews nestedView) {
- addAction(new ViewGroupAction(viewId, nestedView));
+ addAction(nestedView == null
+ ? new ViewGroupActionRemove(viewId)
+ : new ViewGroupActionAdd(viewId, nestedView));
+ }
+
+ /**
+ * Equivalent to calling {@link ViewGroup#addView(View, int)} after inflating the
+ * given {@link RemoteViews}.
+ *
+ * @param viewId The id of the parent {@link ViewGroup} to add the child into.
+ * @param nestedView {@link RemoveViews} of the child to add.
+ * @param index The position at which to add the child.
+ *
+ * @hide
+ */
+ public void addView(int viewId, RemoteViews nestedView, int index) {
+ addAction(new ViewGroupActionAdd(viewId, nestedView, index));
}
/**
@@ -2479,7 +2571,20 @@
* children from.
*/
public void removeAllViews(int viewId) {
- addAction(new ViewGroupAction(viewId, null));
+ addAction(new ViewGroupActionRemove(viewId));
+ }
+
+ /**
+ * Removes all views in the {@link ViewGroup} specified by the {@code viewId} except for any
+ * child that has the {@code viewIdToKeep} as its id.
+ *
+ * @param viewId The id of the parent {@link ViewGroup} to remove children from.
+ * @param viewIdToKeep The id of a child that should not be removed.
+ *
+ * @hide
+ */
+ public void removeAllViewsExceptId(int viewId, int viewIdToKeep) {
+ addAction(new ViewGroupActionRemove(viewId, viewIdToKeep));
}
/**
@@ -3652,8 +3757,8 @@
* and can be searched.
*/
private static class ViewTree {
+ private static final int INSERT_AT_END_INDEX = -1;
private View mRoot;
-
private ArrayList<ViewTree> mChildren;
private ViewTree(View root) {
@@ -3706,11 +3811,26 @@
}
public void addChild(ViewTree child) {
+ addChild(child, INSERT_AT_END_INDEX);
+ }
+
+ /**
+ * Adds the given {@link ViewTree} as a child at the given index.
+ *
+ * @param index The position at which to add the child or -1 to add last.
+ */
+ public void addChild(ViewTree child, int index) {
if (mChildren == null) {
mChildren = new ArrayList<>();
}
child.createTree();
- mChildren.add(child);
+
+ if (index == INSERT_AT_END_INDEX) {
+ mChildren.add(child);
+ return;
+ }
+
+ mChildren.add(index, child);
}
private void addViewChild(View v) {
diff --git a/core/java/com/android/internal/alsa/AlsaCardsParser.java b/core/java/com/android/internal/alsa/AlsaCardsParser.java
index 17e8c9c..5b92a17 100644
--- a/core/java/com/android/internal/alsa/AlsaCardsParser.java
+++ b/core/java/com/android/internal/alsa/AlsaCardsParser.java
@@ -49,7 +49,7 @@
public AlsaCardRecord() {}
- public boolean parse(String line, int lineIndex) {
+ private boolean parse(String line, int lineIndex) {
int tokenIndex = 0;
int delimIndex = 0;
@@ -258,7 +258,7 @@
//
// Logging
//
- public void Log(String heading) {
+ private void Log(String heading) {
if (DEBUG) {
Slog.i(TAG, heading);
for (AlsaCardRecord cardRec : mCardRecords) {
@@ -267,7 +267,7 @@
}
}
- static public void LogDevices(String caption, ArrayList<AlsaCardRecord> deviceList) {
+ static private void LogDevices(String caption, ArrayList<AlsaCardRecord> deviceList) {
Slog.d(TAG, caption + " ----------------");
int listIndex = 0;
for (AlsaCardRecord device : deviceList) {
diff --git a/core/java/com/android/internal/alsa/AlsaDevicesParser.java b/core/java/com/android/internal/alsa/AlsaDevicesParser.java
index 5203723..7cdd897 100644
--- a/core/java/com/android/internal/alsa/AlsaDevicesParser.java
+++ b/core/java/com/android/internal/alsa/AlsaDevicesParser.java
@@ -155,6 +155,7 @@
switch (mDeviceType) {
case kDeviceType_Unknown:
+ default:
sb.append(" N/A");
break;
case kDeviceType_Audio:
@@ -170,6 +171,7 @@
switch (mDeviceDir) {
case kDeviceDir_Unknown:
+ default:
sb.append(" N/A");
break;
case kDeviceDir_Capture:
@@ -282,7 +284,7 @@
//
// Loging
//
- public void Log(String heading) {
+ private void Log(String heading) {
if (DEBUG) {
Slog.i(TAG, heading);
for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 0b27c60..398d087 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -16,16 +16,14 @@
package com.android.internal.app;
-import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
-
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.admin.DevicePolicyManager;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.RemoteException;
@@ -34,8 +32,12 @@
import android.util.Slog;
import android.widget.Toast;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.List;
+import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
+
/**
* This is used in conjunction with
* {@link DevicePolicyManager#addCrossProfileIntentFilter} to enable intents to
@@ -51,15 +53,17 @@
public static String FORWARD_INTENT_TO_MANAGED_PROFILE
= "com.android.internal.app.ForwardIntentToManagedProfile";
+ private Injector mInjector;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Intent intentReceived = getIntent();
+ mInjector = createInjector();
+ Intent intentReceived = getIntent();
String className = intentReceived.getComponent().getClassName();
final int targetUserId;
final int userMessageId;
-
if (className.equals(FORWARD_INTENT_TO_PARENT)) {
userMessageId = com.android.internal.R.string.forward_intent_to_owner;
targetUserId = getProfileParent();
@@ -76,17 +80,12 @@
finish();
return;
}
- Intent newIntent = new Intent(intentReceived);
- newIntent.setComponent(null);
- // Apps should not be allowed to target a specific package in the target user.
- newIntent.setPackage(null);
- newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
- |Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
- int callingUserId = getUserId();
- if (canForward(newIntent, targetUserId)) {
+ final int callingUserId = getUserId();
+ final Intent newIntent = canForward(intentReceived, targetUserId);
+ if (newIntent != null) {
if (Intent.ACTION_CHOOSER.equals(newIntent.getAction())) {
- Intent innerIntent = (Intent) newIntent.getParcelableExtra(Intent.EXTRA_INTENT);
+ Intent innerIntent = newIntent.getParcelableExtra(Intent.EXTRA_INTENT);
// At this point, innerIntent is not null. Otherwise, canForward would have returned
// false.
innerIntent.prepareToLeaveUser(callingUserId);
@@ -94,15 +93,18 @@
newIntent.prepareToLeaveUser(callingUserId);
}
- final android.content.pm.ResolveInfo ri = getPackageManager().resolveActivityAsUser(
- newIntent, MATCH_DEFAULT_ONLY, targetUserId);
+ final android.content.pm.ResolveInfo ri =
+ mInjector.getPackageManager().resolveActivityAsUser(
+ newIntent,
+ MATCH_DEFAULT_ONLY,
+ targetUserId);
// Don't show the disclosure if next activity is ResolverActivity or ChooserActivity
// as those will already have shown work / personal as neccesary etc.
final boolean shouldShowDisclosure = ri == null || ri.activityInfo == null ||
!"android".equals(ri.activityInfo.packageName) ||
!(ResolverActivity.class.getName().equals(ri.activityInfo.name)
- || ChooserActivity.class.getName().equals(ri.activityInfo.name));
+ || ChooserActivity.class.getName().equals(ri.activityInfo.name));
try {
startActivityAsCaller(newIntent, null, false, targetUserId);
@@ -126,44 +128,56 @@
Toast.makeText(this, getString(userMessageId), Toast.LENGTH_LONG).show();
}
} else {
- Slog.wtf(TAG, "the intent: " + newIntent + " cannot be forwarded from user "
+ Slog.wtf(TAG, "the intent: " + intentReceived + " cannot be forwarded from user "
+ callingUserId + " to user " + targetUserId);
}
finish();
}
- boolean canForward(Intent intent, int targetUserId) {
- IPackageManager ipm = AppGlobals.getPackageManager();
- if (Intent.ACTION_CHOOSER.equals(intent.getAction())) {
+ /**
+ * Check whether the intent can be forwarded to target user. Return the intent used for
+ * forwarding if it can be forwarded, {@code null} otherwise.
+ */
+ Intent canForward(Intent incomingIntent, int targetUserId) {
+ Intent forwardIntent = new Intent(incomingIntent);
+ forwardIntent.addFlags(
+ Intent.FLAG_ACTIVITY_FORWARD_RESULT | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
+ sanitizeIntent(forwardIntent);
+
+ Intent intentToCheck = forwardIntent;
+ if (Intent.ACTION_CHOOSER.equals(forwardIntent.getAction())) {
// The EXTRA_INITIAL_INTENTS may not be allowed to be forwarded.
- if (intent.hasExtra(Intent.EXTRA_INITIAL_INTENTS)) {
+ if (forwardIntent.hasExtra(Intent.EXTRA_INITIAL_INTENTS)) {
Slog.wtf(TAG, "An chooser intent with extra initial intents cannot be forwarded to"
+ " a different user");
- return false;
+ return null;
}
- if (intent.hasExtra(Intent.EXTRA_REPLACEMENT_EXTRAS)) {
+ if (forwardIntent.hasExtra(Intent.EXTRA_REPLACEMENT_EXTRAS)) {
Slog.wtf(TAG, "A chooser intent with replacement extras cannot be forwarded to a"
+ " different user");
- return false;
+ return null;
}
- intent = (Intent) intent.getParcelableExtra(Intent.EXTRA_INTENT);
- if (intent == null) {
+ intentToCheck = forwardIntent.getParcelableExtra(Intent.EXTRA_INTENT);
+ if (intentToCheck == null) {
Slog.wtf(TAG, "Cannot forward a chooser intent with no extra "
+ Intent.EXTRA_INTENT);
- return false;
+ return null;
}
}
- String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
- if (intent.getSelector() != null) {
- intent = intent.getSelector();
+ if (forwardIntent.getSelector() != null) {
+ intentToCheck = forwardIntent.getSelector();
}
+ String resolvedType = intentToCheck.resolveTypeIfNeeded(getContentResolver());
+ sanitizeIntent(intentToCheck);
try {
- return ipm.canForwardTo(intent, resolvedType, getUserId(),
- targetUserId);
+ if (mInjector.getIPackageManager().
+ canForwardTo(intentToCheck, resolvedType, getUserId(), targetUserId)) {
+ return forwardIntent;
+ }
} catch (RemoteException e) {
Slog.e(TAG, "PackageManagerService is dead?");
- return false;
}
+ return null;
}
/**
@@ -174,8 +188,7 @@
* on the device.
*/
private int getManagedProfile() {
- UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
- List<UserInfo> relatedUsers = userManager.getProfiles(UserHandle.myUserId());
+ List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId());
for (UserInfo userInfo : relatedUsers) {
if (userInfo.isManagedProfile()) return userInfo.id;
}
@@ -189,8 +202,7 @@
* no parent.
*/
private int getProfileParent() {
- UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
- UserInfo parent = userManager.getProfileParent(UserHandle.myUserId());
+ UserInfo parent = mInjector.getUserManager().getProfileParent(UserHandle.myUserId());
if (parent == null) {
Slog.wtf(TAG, FORWARD_INTENT_TO_PARENT
+ " has been called, but there is no parent");
@@ -198,4 +210,44 @@
}
return parent.id;
}
+
+ /**
+ * Sanitize the intent in place.
+ */
+ private void sanitizeIntent(Intent intent) {
+ // Apps should not be allowed to target a specific package/ component in the target user.
+ intent.setPackage(null);
+ intent.setComponent(null);
+ }
+
+ @VisibleForTesting
+ protected Injector createInjector() {
+ return new InjectorImpl();
+ }
+
+ private class InjectorImpl implements Injector {
+
+ @Override
+ public IPackageManager getIPackageManager() {
+ return AppGlobals.getPackageManager();
+ }
+
+ @Override
+ public UserManager getUserManager() {
+ return getSystemService(UserManager.class);
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return IntentForwarderActivity.this.getPackageManager();
+ }
+ }
+
+ public interface Injector {
+ IPackageManager getIPackageManager();
+
+ UserManager getUserManager();
+
+ PackageManager getPackageManager();
+ }
}
diff --git a/core/java/com/android/internal/car/ICarServiceHelper.aidl b/core/java/com/android/internal/car/ICarServiceHelper.aidl
new file mode 100644
index 0000000..9ee330b
--- /dev/null
+++ b/core/java/com/android/internal/car/ICarServiceHelper.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.car;
+
+/**
+ * Helper API for car service. Only for itneraction between system server and car service.
+ * @hide
+ */
+interface ICarServiceHelper {
+}
diff --git a/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java b/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java
index 2d0ad66..56d60a1 100644
--- a/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java
+++ b/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java
@@ -376,7 +376,9 @@
for (int i = mLowerIndex, count = 0; i <= mUpperIndex; i++) {
count += hist[colors[i]];
if (count >= midPoint) {
- return i;
+ // we never want to split on the upperIndex, as this will result in the same
+ // box
+ return Math.min(mUpperIndex - 1, i);
}
}
diff --git a/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java b/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java
index ddf07f4..00fa711a 100644
--- a/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java
+++ b/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.os.Build;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.text.TextUtils;
@@ -75,13 +76,13 @@
}
public boolean alwaysOnEnabled(int user) {
- return boolSettingDefaultOff(Settings.Secure.DOZE_ALWAYS_ON, user)
+ return boolSettingDefaultOn(Settings.Secure.DOZE_ALWAYS_ON, user)
&& alwaysOnAvailable();
}
public boolean alwaysOnAvailable() {
- // Does not work properly yet.
- return false;
+ return (alwaysOnDisplayDebuggingEnabled() || alwaysOnDisplayAvailable())
+ && ambientDisplayAvailable();
}
public String ambientDisplayComponent() {
@@ -92,6 +93,15 @@
return !TextUtils.isEmpty(ambientDisplayComponent());
}
+ private boolean alwaysOnDisplayAvailable() {
+ return mContext.getResources().getBoolean(R.bool.config_dozeAlwaysOnDisplayAvailable);
+ }
+
+ private boolean alwaysOnDisplayDebuggingEnabled() {
+ return SystemProperties.getBoolean("debug.doze.aod", false) && Build.IS_DEBUGGABLE;
+ }
+
+
private boolean boolSettingDefaultOn(String name, int user) {
return boolSetting(name, user, 1);
}
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index 2fb3532..3d3e148 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -192,46 +192,43 @@
NetworkStats lastStats) throws IOException {
final NetworkStats stats =
readNetworkStatsDetailInternal(limitUid, limitIfaces, limitTag, lastStats);
- NetworkStats.Entry entry = null; // for recycling
-
+ final ArrayMap<String, String> stackedIfaces;
synchronized (sStackedIfaces) {
- // For 464xlat traffic, xt_qtaguid sees every IPv4 packet twice, once as a native IPv4
- // packet on the stacked interface, and once as translated to an IPv6 packet on the
- // base interface. For correct stats accounting on the base interface, every 464xlat
- // 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).
- final int size = sStackedIfaces.size();
- for (int i = 0; i < size; i++) {
- final String stackedIface = sStackedIfaces.keyAt(i);
- final String baseIface = sStackedIfaces.valueAt(i);
- if (!stackedIface.startsWith(CLATD_INTERFACE_PREFIX)) {
- continue;
- }
-
- NetworkStats.Entry adjust =
- new NetworkStats.Entry(baseIface, 0, 0, 0, 0L, 0L, 0L, 0L, 0L);
- for (int j = 0; j < stats.size(); j++) {
- entry = stats.getValues(j, entry);
- if (Objects.equals(entry.iface, stackedIface)) {
- adjust.rxBytes -= (entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA);
- adjust.txBytes -= (entry.txBytes + entry.txPackets * IPV4V6_HEADER_DELTA);
- adjust.rxPackets -= entry.rxPackets;
- adjust.txPackets -= entry.txPackets;
- }
- }
- stats.combineValues(adjust);
- }
+ stackedIfaces = new ArrayMap<>(sStackedIfaces);
}
+ // Total 464xlat traffic to subtract from uid 0 on all base interfaces.
+ final NetworkStats adjustments = new NetworkStats(0, stackedIfaces.size());
- // For 464xlat traffic, xt_qtaguid only counts the bytes of the inner 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).
+ NetworkStats.Entry entry = null; // For recycling
+
+ // For 464xlat traffic, xt_qtaguid sees every IPv4 packet twice, once as a native IPv4
+ // packet on the stacked interface, and once as translated to an IPv6 packet on the
+ // base interface. For correct stats accounting on the base interface, every 464xlat
+ // 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).
for (int i = 0; i < stats.size(); i++) {
entry = stats.getValues(i, entry);
if (entry.iface == null || !entry.iface.startsWith(CLATD_INTERFACE_PREFIX)) {
continue;
}
+ final String baseIface = stackedIfaces.get(entry.iface);
+ if (baseIface == null) {
+ continue;
+ }
+
+ NetworkStats.Entry adjust =
+ new NetworkStats.Entry(baseIface, 0, 0, 0, 0L, 0L, 0L, 0L, 0L);
+ // Subtract any 464lat traffic seen for the root UID on the current base interface.
+ adjust.rxBytes -= (entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA);
+ 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
+ // 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;
entry.txBytes = entry.txPackets * IPV4V6_HEADER_DELTA;
entry.rxPackets = 0;
@@ -239,6 +236,8 @@
stats.combineValues(entry);
}
+ stats.combineAllValues(adjustments);
+
return stats;
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 46942bf..2363f68 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -171,6 +171,7 @@
public interface PlatformIdleStateCallback {
public String getPlatformLowPowerStats();
+ public String getSubsystemLowPowerStats();
}
private final PlatformIdleStateCallback mPlatformIdleStateCallback;
@@ -2878,6 +2879,12 @@
mPlatformIdleStateCallback.getPlatformLowPowerStats();
if (DEBUG) Slog.i(TAG, "WRITE PlatformIdleState:" +
mCurHistoryStepDetails.statPlatformIdleState);
+
+ mCurHistoryStepDetails.statSubsystemPowerState =
+ mPlatformIdleStateCallback.getSubsystemLowPowerStats();
+ if (DEBUG) Slog.i(TAG, "WRITE SubsystemPowerState:" +
+ mCurHistoryStepDetails.statSubsystemPowerState);
+
}
computeHistoryStepDetails(mCurHistoryStepDetails, mLastHistoryStepDetails);
if (includeStepDetails != 0) {
@@ -3518,6 +3525,7 @@
public void removeIsolatedUidLocked(int isolatedUid) {
mIsolatedUids.delete(isolatedUid);
mKernelUidCpuTimeReader.removeUid(isolatedUid);
+ mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
}
public int mapUid(int uid) {
@@ -10351,6 +10359,9 @@
public void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs) {
uid = mapUid(uid);
if (Process.isIsolated(uid)) {
+ mKernelUidCpuFreqTimeReader.removeUid(uid);
+ Slog.d(TAG, "Got freq readings for an isolated uid with"
+ + " no mapping to owning uid: " + uid);
return;
}
final Uid u = getUidStatsLocked(uid);
@@ -11019,6 +11030,7 @@
*/
public void removeUidStatsLocked(int uid) {
mKernelUidCpuTimeReader.removeUid(uid);
+ mKernelUidCpuFreqTimeReader.removeUid(uid);
mUidStats.remove(uid);
}
diff --git a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
index 9fbc4a8..ff521c2 100644
--- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
@@ -72,6 +72,10 @@
}
}
+ public void removeUid(int uid) {
+ mLastUidCpuFreqTimeMs.delete(uid);
+ }
+
@VisibleForTesting
public void readDelta(BufferedReader reader, @Nullable Callback callback) throws IOException {
String line = reader.readLine();
diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
index 1d26df0..89a4e17 100644
--- a/core/java/com/android/internal/os/RoSystemProperties.java
+++ b/core/java/com/android/internal/os/RoSystemProperties.java
@@ -33,6 +33,8 @@
// ------ ro.config.* -------- //
public static final boolean CONFIG_LOW_RAM =
SystemProperties.getBoolean("ro.config.low_ram", false);
+ public static final boolean CONFIG_SMALL_BATTERY =
+ SystemProperties.getBoolean("ro.config.small_battery", false);
// ------ ro.fw.* ------------ //
public static final boolean FW_SYSTEM_USER_SPLIT =
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index e065843..5eca496 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -107,7 +107,7 @@
fdsToIgnore, instructionSet, appDataDir);
// Enable tracing as soon as possible for the child process.
if (pid == 0) {
- Trace.setTracingEnabled(true);
+ Trace.setTracingEnabled(true, debugFlags);
// Note that this event ends at the end of handleChildProc,
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
@@ -152,7 +152,7 @@
uid, gid, gids, debugFlags, rlimits, permittedCapabilities, effectiveCapabilities);
// Enable tracing as soon as we enter the system_server.
if (pid == 0) {
- Trace.setTracingEnabled(true);
+ Trace.setTracingEnabled(true, debugFlags);
}
VM_HOOKS.postForkCommon();
return pid;
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 142effb..9e61a99 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -745,7 +745,7 @@
bootTimingsTraceLog.traceEnd(); // ZygoteInit
// Disable tracing so that forked processes do not inherit stale tracing tags from
// Zygote.
- Trace.setTracingEnabled(false);
+ Trace.setTracingEnabled(false, 0);
// Zygote process unmounts root storage spaces.
Zygote.nativeUnmountStorageOnInit();
@@ -786,21 +786,7 @@
private static void waitForSecondaryZygote(String socketName) {
String otherZygoteName = Process.ZYGOTE_SOCKET.equals(socketName) ?
Process.SECONDARY_ZYGOTE_SOCKET : Process.ZYGOTE_SOCKET;
- while (true) {
- try {
- final ZygoteProcess.ZygoteState zs =
- ZygoteProcess.ZygoteState.connect(otherZygoteName);
- zs.close();
- break;
- } catch (IOException ioe) {
- Log.w(TAG, "Got error connecting to zygote, retrying. msg= " + ioe.getMessage());
- }
-
- try {
- Thread.sleep(1000);
- } catch (InterruptedException ie) {
- }
- }
+ ZygoteProcess.waitForConnectionToZygote(otherZygoteName);
}
static boolean isPreloadComplete() {
diff --git a/core/java/com/android/internal/util/BitUtils.java b/core/java/com/android/internal/util/BitUtils.java
index e349f3d..28f12eb 100644
--- a/core/java/com/android/internal/util/BitUtils.java
+++ b/core/java/com/android/internal/util/BitUtils.java
@@ -18,13 +18,21 @@
package com.android.internal.util;
import android.annotation.Nullable;
+import android.text.TextUtils;
import libcore.util.Objects;
+import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.UUID;
+import java.util.function.IntFunction;
-public class BitUtils {
+/**
+ * A utility class for handling unsigned integers and unsigned arithmetics, as well as syntactic
+ * sugar methods for ByteBuffer. Useful for networking and packet manipulations.
+ * {@hide}
+ */
+public final class BitUtils {
private BitUtils() {}
public static boolean maskedEquals(long a, long b, long mask) {
@@ -76,4 +84,68 @@
}
return packed;
}
+
+ public static int uint8(byte b) {
+ return b & 0xff;
+ }
+
+ public static int uint16(short s) {
+ return s & 0xffff;
+ }
+
+ public static long uint32(int i) {
+ return i & 0xffffffffL;
+ }
+
+ public static int bytesToBEInt(byte[] bytes) {
+ return (uint8(bytes[0]) << 24)
+ + (uint8(bytes[1]) << 16)
+ + (uint8(bytes[2]) << 8)
+ + (uint8(bytes[3]));
+ }
+
+ public static int bytesToLEInt(byte[] bytes) {
+ return Integer.reverseBytes(bytesToBEInt(bytes));
+ }
+
+ public static int getUint8(ByteBuffer buffer, int position) {
+ return uint8(buffer.get(position));
+ }
+
+ public static int getUint16(ByteBuffer buffer, int position) {
+ return uint16(buffer.getShort(position));
+ }
+
+ public static long getUint32(ByteBuffer buffer, int position) {
+ return uint32(buffer.getInt(position));
+ }
+
+ public static void put(ByteBuffer buffer, int position, byte[] bytes) {
+ final int original = buffer.position();
+ buffer.position(position);
+ buffer.put(bytes);
+ buffer.position(original);
+ }
+
+ public static boolean isBitSet(long flags, int bitIndex) {
+ return (flags & bitAt(bitIndex)) != 0;
+ }
+
+ public static long bitAt(int bitIndex) {
+ return 1L << bitIndex;
+ }
+
+ public static String flagsToString(int flags, IntFunction<String> getFlagName) {
+ StringBuilder builder = new StringBuilder();
+ int count = 0;
+ while (flags != 0) {
+ final int flag = 1 << Integer.numberOfTrailingZeros(flags);
+ flags &= ~flag;
+ if (count > 0) builder.append(", ");
+ builder.append(getFlagName.apply(flag));
+ count++;
+ }
+ TextUtils.wrap(builder, "[", "]");
+ return builder.toString();
+ }
}
diff --git a/core/java/com/android/internal/util/Predicates.java b/core/java/com/android/internal/util/Predicates.java
deleted file mode 100644
index c006564..0000000
--- a/core/java/com/android/internal/util/Predicates.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.util;
-
-import java.util.Arrays;
-
-/**
- * Predicates contains static methods for creating the standard set of
- * {@code Predicate} objects.
- */
-public class Predicates {
-
- private Predicates() {
- }
-
- /**
- * Returns a Predicate that evaluates to true iff each of its components
- * evaluates to true. The components are evaluated in order, and evaluation
- * will be "short-circuited" as soon as the answer is determined.
- */
- public static <T> Predicate<T> and(Predicate<? super T>... components) {
- return Predicates.<T>and(Arrays.asList(components));
- }
-
- /**
- * Returns a Predicate that evaluates to true iff each of its components
- * evaluates to true. The components are evaluated in order, and evaluation
- * will be "short-circuited" as soon as the answer is determined. Does not
- * defensively copy the iterable passed in, so future changes to it will alter
- * the behavior of this Predicate. If components is empty, the returned
- * Predicate will always evaluate to true.
- */
- public static <T> Predicate<T> and(Iterable<? extends Predicate<? super T>> components) {
- return new AndPredicate(components);
- }
-
- /**
- * Returns a Predicate that evaluates to true iff any one of its components
- * evaluates to true. The components are evaluated in order, and evaluation
- * will be "short-circuited" as soon as the answer is determined.
- */
- public static <T> Predicate<T> or(Predicate<? super T>... components) {
- return Predicates.<T>or(Arrays.asList(components));
- }
-
- /**
- * Returns a Predicate that evaluates to true iff any one of its components
- * evaluates to true. The components are evaluated in order, and evaluation
- * will be "short-circuited" as soon as the answer is determined. Does not
- * defensively copy the iterable passed in, so future changes to it will alter
- * the behavior of this Predicate. If components is empty, the returned
- * Predicate will always evaluate to false.
- */
- public static <T> Predicate<T> or(Iterable<? extends Predicate<? super T>> components) {
- return new OrPredicate(components);
- }
-
- /**
- * Returns a Predicate that evaluates to true iff the given Predicate
- * evaluates to false.
- */
- public static <T> Predicate<T> not(Predicate<? super T> predicate) {
- return new NotPredicate<T>(predicate);
- }
-
- private static class AndPredicate<T> implements Predicate<T> {
- private final Iterable<? extends Predicate<? super T>> components;
-
- private AndPredicate(Iterable<? extends Predicate<? super T>> components) {
- this.components = components;
- }
-
- public boolean apply(T t) {
- for (Predicate<? super T> predicate : components) {
- if (!predicate.apply(t)) {
- return false;
- }
- }
- return true;
- }
- }
-
- private static class OrPredicate<T> implements Predicate<T> {
- private final Iterable<? extends Predicate<? super T>> components;
-
- private OrPredicate(Iterable<? extends Predicate<? super T>> components) {
- this.components = components;
- }
-
- public boolean apply(T t) {
- for (Predicate<? super T> predicate : components) {
- if (predicate.apply(t)) {
- return true;
- }
- }
- return false;
- }
- }
-
- private static class NotPredicate<T> implements Predicate<T> {
- private final Predicate<? super T> predicate;
-
- private NotPredicate(Predicate<? super T> predicate) {
- this.predicate = predicate;
- }
-
- public boolean apply(T t) {
- return !predicate.apply(t);
- }
- }
-}
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index d67cef3..8d9630f 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -2070,8 +2070,6 @@
* @param args
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- // Cannot just invoke pw.println(this.toString()) because if the
- // resulting string is to long it won't be displayed.
pw.println(getName() + ":");
pw.println(" total records=" + getLogRecCount());
for (int i = 0; i < getLogRecSize(); i++) {
@@ -2083,12 +2081,15 @@
@Override
public String toString() {
- StringWriter sr = new StringWriter();
- PrintWriter pr = new PrintWriter(sr);
- dump(null, pr, null);
- pr.flush();
- pr.close();
- return sr.toString();
+ String name = "(null)";
+ String state = "(null)";
+ try {
+ name = mName.toString();
+ state = mSmHandler.getCurrentState().getName().toString();
+ } catch (NullPointerException npe) {
+ // Will use default(s) initialized above.
+ }
+ return "name=" + name + " state=" + state;
}
/**
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index b8c062e..ee16ab6 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -28,7 +28,7 @@
boolean getBoolean(in String key, in boolean defaultValue, in int userId);
long getLong(in String key, in long defaultValue, in int userId);
String getString(in String key, in String defaultValue, in int userId);
- void setLockCredential(in String credential, int type, in String savedCredential, int userId);
+ void setLockCredential(in String credential, int type, in String savedCredential, int requestedQuality, int userId);
void resetKeyStore(int userId);
VerifyCredentialResponse checkCredential(in String credential, int type, int userId,
in ICheckCredentialProgressCallback progressCallback);
@@ -49,6 +49,7 @@
long addEscrowToken(in byte[] token, int userId);
boolean removeEscrowToken(long handle, int userId);
boolean isEscrowTokenActive(long handle, int userId);
- boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, in byte[] token, int userId);
+ boolean setLockCredentialWithToken(String credential, int type, long tokenHandle,
+ in byte[] token, int requestedQuality, int userId);
void unlockUserWithToken(long tokenHandle, in byte[] token, int userId);
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 0aba9c2..e3b9178 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -27,6 +27,7 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -34,6 +35,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.IStorageManager;
@@ -65,6 +67,8 @@
private static final String TAG = "LockPatternUtils";
private static final boolean DEBUG = false;
+ private static final boolean FRP_CREDENTIAL_ENABLED =
+ Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.frpcredential.enable", false);
/**
* The key to identify when the lock pattern enabled flag is being accessed for legacy reasons.
@@ -112,6 +116,11 @@
public static final int CREDENTIAL_TYPE_PASSWORD = 2;
+ /**
+ * Special user id for triggering the FRP verification flow.
+ */
+ public static final int USER_FRP = UserHandle.USER_NULL + 1;
+
@Deprecated
public final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
public final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
@@ -295,24 +304,39 @@
}
public void reportFailedPasswordAttempt(int userId) {
+ if (userId == USER_FRP && frpCredentialEnabled()) {
+ return;
+ }
getDevicePolicyManager().reportFailedPasswordAttempt(userId);
getTrustManager().reportUnlockAttempt(false /* authenticated */, userId);
}
public void reportSuccessfulPasswordAttempt(int userId) {
+ if (userId == USER_FRP && frpCredentialEnabled()) {
+ return;
+ }
getDevicePolicyManager().reportSuccessfulPasswordAttempt(userId);
getTrustManager().reportUnlockAttempt(true /* authenticated */, userId);
}
public void reportPasswordLockout(int timeoutMs, int userId) {
+ if (userId == USER_FRP && frpCredentialEnabled()) {
+ return;
+ }
getTrustManager().reportUnlockLockout(timeoutMs, userId);
}
public int getCurrentFailedPasswordAttempts(int userId) {
+ if (userId == USER_FRP && frpCredentialEnabled()) {
+ return 0;
+ }
return getDevicePolicyManager().getCurrentFailedPasswordAttempts(userId);
}
public int getMaximumFailedPasswordsForWipe(int userId) {
+ if (userId == USER_FRP && frpCredentialEnabled()) {
+ return 0;
+ }
return getDevicePolicyManager().getMaximumFailedPasswordsForWipe(
null /* componentName */, userId);
}
@@ -539,6 +563,14 @@
}
/**
+ * Records that the user has chosen a pattern at some time, even if the pattern is
+ * currently cleared.
+ */
+ public void reportPatternWasChosen(int userId) {
+ setBoolean(PATTERN_EVER_CHOSEN_KEY, true, userId);
+ }
+
+ /**
* Used by device policy manager to validate the current password
* information it has.
*/
@@ -578,7 +610,7 @@
try{
getLockSettings().setLockCredential(null, CREDENTIAL_TYPE_NONE, savedCredential,
- userHandle);
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userHandle);
} catch (RemoteException e) {
// well, we tried...
}
@@ -643,7 +675,7 @@
setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
getLockSettings().setLockCredential(patternToString(pattern), CREDENTIAL_TYPE_PATTERN,
- savedPattern, userId);
+ savedPattern, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
// Update the device encryption password.
if (userId == UserHandle.USER_SYSTEM
@@ -656,7 +688,7 @@
}
}
- setBoolean(PATTERN_EVER_CHOSEN_KEY, true, userId);
+ reportPatternWasChosen(userId);
onAfterChangingPassword(userId);
} catch (RemoteException re) {
Log.e(TAG, "Couldn't save lock pattern " + re);
@@ -757,10 +789,10 @@
* password.
* @param password The password to save
* @param savedPassword The previously saved lock password, or null if none
- * @param quality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
+ * @param requestedQuality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
* @param userHandle The userId of the user to change the password for
*/
- public void saveLockPassword(String password, String savedPassword, int quality,
+ public void saveLockPassword(String password, String savedPassword, int requestedQuality,
int userHandle) {
try {
if (password == null || password.length() < MIN_LOCK_PASSWORD_SIZE) {
@@ -769,9 +801,9 @@
}
final int computedQuality = PasswordMetrics.computeForPassword(password).quality;
- setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle);
+ setLong(PASSWORD_TYPE_KEY, Math.max(requestedQuality, computedQuality), userHandle);
getLockSettings().setLockCredential(password, CREDENTIAL_TYPE_PASSWORD, savedPassword,
- userHandle);
+ requestedQuality, userHandle);
updateEncryptionPasswordIfNeeded(password, computedQuality, userHandle);
updatePasswordHistory(password, userHandle);
@@ -1125,6 +1157,10 @@
}
}
+ public boolean isVisiblePatternEverChosen(int userId) {
+ return getString(Settings.Secure.LOCK_PATTERN_VISIBLE, userId) != null;
+ }
+
/**
* Set whether the visible password is enabled for cryptkeeper screen.
*/
@@ -1252,6 +1288,10 @@
return getBoolean(LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, true, userId);
}
+ public boolean isPowerButtonInstantlyLocksEverChosen(int userId) {
+ return getString(LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, userId) != null;
+ }
+
public void setEnabledTrustAgents(Collection<ComponentName> activeTrustAgents, int userId) {
StringBuilder sb = new StringBuilder();
for (ComponentName cn : activeTrustAgents) {
@@ -1458,12 +1498,13 @@
}
final int computedQuality = PasswordMetrics.computeForPassword(credential).quality;
+ int quality = Math.max(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
+ computedQuality);
if (!getLockSettings().setLockCredentialWithToken(credential, type, tokenHandle,
- token, userId)) {
+ token, quality, userId)) {
return false;
}
- setLong(PASSWORD_TYPE_KEY, Math.max(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
- computedQuality), userId);
+ setLong(PASSWORD_TYPE_KEY, quality, userId);
updateEncryptionPasswordIfNeeded(credential, computedQuality, userId);
updatePasswordHistory(credential, userId);
@@ -1472,7 +1513,8 @@
throw new IllegalArgumentException("password must be emtpy for NONE type");
}
if (!getLockSettings().setLockCredentialWithToken(null, CREDENTIAL_TYPE_NONE,
- tokenHandle, token, userId)) {
+ tokenHandle, token, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ userId)) {
return false;
}
setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
@@ -1672,7 +1714,19 @@
setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1L, UserHandle.USER_SYSTEM);
}
+ public void disableSyntheticPassword() {
+ setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0L, UserHandle.USER_SYSTEM);
+ }
+
public boolean isSyntheticPasswordEnabled() {
return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0;
}
+
+ public static boolean userOwnsFrpCredential(UserInfo info) {
+ return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled();
+ }
+
+ public static boolean frpCredentialEnabled() {
+ return FRP_CREDENTIAL_ENABLED;
+ }
}
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index cc1c65e..32a7a2d 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -39,6 +39,7 @@
import android.util.AttributeSet;
import android.util.IntArray;
import android.util.Log;
+import android.util.SparseArray;
import android.view.DisplayListCanvas;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
@@ -54,7 +55,6 @@
import com.android.internal.R;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
/**
@@ -1340,8 +1340,7 @@
private final class PatternExploreByTouchHelper extends ExploreByTouchHelper {
private Rect mTempRect = new Rect();
- private HashMap<Integer, VirtualViewContainer> mItems = new HashMap<Integer,
- VirtualViewContainer>();
+ private final SparseArray<VirtualViewContainer> mItems = new SparseArray<>();
class VirtualViewContainer {
public VirtualViewContainer(CharSequence description) {
@@ -1352,6 +1351,9 @@
public PatternExploreByTouchHelper(View forView) {
super(forView);
+ for (int i = VIRTUAL_BASE_VIEW_ID; i < VIRTUAL_BASE_VIEW_ID + 9; i++) {
+ mItems.put(i, new VirtualViewContainer(getTextForVirtualView(i)));
+ }
}
@Override
@@ -1369,10 +1371,6 @@
return;
}
for (int i = VIRTUAL_BASE_VIEW_ID; i < VIRTUAL_BASE_VIEW_ID + 9; i++) {
- if (!mItems.containsKey(i)) {
- VirtualViewContainer item = new VirtualViewContainer(getTextForVirtualView(i));
- mItems.put(i, item);
- }
// Add all views. As views are added to the pattern, we remove them
// from notification by making them non-clickable below.
virtualViewIds.add(i);
@@ -1383,9 +1381,9 @@
protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
if (DEBUG_A11Y) Log.v(TAG, "onPopulateEventForVirtualView(" + virtualViewId + ")");
// Announce this view
- if (mItems.containsKey(virtualViewId)) {
- CharSequence contentDescription = mItems.get(virtualViewId).description;
- event.getText().add(contentDescription);
+ VirtualViewContainer container = mItems.get(virtualViewId);
+ if (container != null) {
+ event.getText().add(container.description);
}
}
diff --git a/core/java/com/android/internal/widget/VerifyCredentialResponse.java b/core/java/com/android/internal/widget/VerifyCredentialResponse.java
index 48109ca..ad6020c 100644
--- a/core/java/com/android/internal/widget/VerifyCredentialResponse.java
+++ b/core/java/com/android/internal/widget/VerifyCredentialResponse.java
@@ -18,6 +18,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import android.service.gatekeeper.GateKeeperResponse;
+import android.util.Slog;
/**
* Response object for a ILockSettings credential verification request.
@@ -32,6 +34,7 @@
public static final VerifyCredentialResponse OK = new VerifyCredentialResponse();
public static final VerifyCredentialResponse ERROR
= new VerifyCredentialResponse(RESPONSE_ERROR, 0, null);
+ private static final String TAG = "VerifyCredentialResponse";
private int mResponseCode;
private byte[] mPayload;
@@ -123,4 +126,29 @@
private void setPayload(byte[] payload) {
mPayload = payload;
}
+
+ public VerifyCredentialResponse stripPayload() {
+ return new VerifyCredentialResponse(mResponseCode, mTimeout, new byte[0]);
+ }
+
+ public static VerifyCredentialResponse fromGateKeeperResponse(
+ GateKeeperResponse gateKeeperResponse) {
+ VerifyCredentialResponse response;
+ int responseCode = gateKeeperResponse.getResponseCode();
+ if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
+ response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout());
+ } else if (responseCode == GateKeeperResponse.RESPONSE_OK) {
+ byte[] token = gateKeeperResponse.getPayload();
+ if (token == null) {
+ // something's wrong if there's no payload with a challenge
+ Slog.e(TAG, "verifyChallenge response had no associated payload");
+ response = VerifyCredentialResponse.ERROR;
+ } else {
+ response = new VerifyCredentialResponse(token);
+ }
+ } else {
+ response = VerifyCredentialResponse.ERROR;
+ }
+ return response;
+ }
}
diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java
index c1f443f..2cf58d7 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -438,6 +438,7 @@
String currentPass = "";
boolean foundTreeOptimization = false;
boolean foundQuotaFix = false;
+ boolean foundTimestampAdjustment = false;
boolean foundOtherFix = false;
String otherFixLine = null;
for (int i = startLineNumber; i < endLineNumber; i++) {
@@ -470,6 +471,16 @@
}
} else if (line.startsWith("Update quota info") && currentPass.equals("5")) {
// follows "[QUOTA WARNING]", ignore
+ } else if (line.startsWith("Timestamp(s) on inode") &&
+ line.contains("beyond 2310-04-04 are likely pre-1970") &&
+ currentPass.equals("1")) {
+ Slog.i(TAG, "fs_stat, partition:" + partition + " found timestamp adjustment:"
+ + line);
+ // followed by next line, "Fix? yes"
+ if (lines[i + 1].contains("Fix? yes")) {
+ i++;
+ }
+ foundTimestampAdjustment = true;
} else {
line = line.trim();
// ignore empty msg or any msg before Pass 1
@@ -480,15 +491,17 @@
}
}
}
- if (!foundOtherFix && foundTreeOptimization && foundQuotaFix) {
- // not a real fix, so clear it.
- Slog.i(TAG, "fs_stat, partition:" + partition +
- " quota fix due to tree optimization");
- stat &= ~FS_STAT_FS_FIXED;
- } else {
+ if (foundOtherFix) {
if (otherFixLine != null) {
Slog.i(TAG, "fs_stat, partition:" + partition + " fix:" + otherFixLine);
}
+ } else if (foundQuotaFix && !foundTreeOptimization) {
+ Slog.i(TAG, "fs_stat, got quota fix without tree optimization, partition:" +
+ partition);
+ } else if ((foundTreeOptimization && foundQuotaFix) || foundTimestampAdjustment) {
+ // not a real fix, so clear it.
+ Slog.i(TAG, "fs_stat, partition:" + partition + " fix ignored");
+ stat &= ~FS_STAT_FS_FIXED;
}
}
return stat;
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
new file mode 100644
index 0000000..7e71ce9
--- /dev/null
+++ b/core/jni/Android.bp
@@ -0,0 +1,303 @@
+cc_library_shared {
+ name: "libandroid_runtime",
+
+ cflags: [
+ "-Wno-unused-parameter",
+ "-Wno-non-virtual-dtor",
+ "-Wno-maybe-uninitialized",
+ "-Wno-parentheses",
+
+ "-DGL_GLEXT_PROTOTYPES",
+ "-DEGL_EGLEXT_PROTOTYPES",
+
+ "-DU_USING_ICU_NAMESPACE=0",
+
+ "-Wall",
+ "-Werror",
+ "-Wno-error=deprecated-declarations",
+ "-Wunused",
+ "-Wunreachable-code",
+
+ // necessary for Clang as the GL bindings need to turn
+ // off a GCC warning that Clang doesn't know.
+ "-Wno-unknown-pragmas",
+
+ // TODO: Linear blending should be enabled by default, but we are
+ // TODO: making it an opt-in while it's a work in progress
+ //"-DANDROID_ENABLE_LINEAR_BLENDING",
+ ],
+
+ cppflags: ["-Wno-conversion-null"],
+
+ srcs: [
+ "AndroidRuntime.cpp",
+ "com_android_internal_content_NativeLibraryHelper.cpp",
+ "com_google_android_gles_jni_EGLImpl.cpp",
+ "com_google_android_gles_jni_GLImpl.cpp", // TODO: .arm
+ "android_app_Activity.cpp",
+ "android_app_ApplicationLoaders.cpp",
+ "android_app_NativeActivity.cpp",
+ "android_app_admin_SecurityLog.cpp",
+ "android_opengl_EGL14.cpp",
+ "android_opengl_EGLExt.cpp",
+ "android_opengl_GLES10.cpp",
+ "android_opengl_GLES10Ext.cpp",
+ "android_opengl_GLES11.cpp",
+ "android_opengl_GLES11Ext.cpp",
+ "android_opengl_GLES20.cpp",
+ "android_opengl_GLES30.cpp",
+ "android_opengl_GLES31.cpp",
+ "android_opengl_GLES31Ext.cpp",
+ "android_opengl_GLES32.cpp",
+ "android_database_CursorWindow.cpp",
+ "android_database_SQLiteCommon.cpp",
+ "android_database_SQLiteConnection.cpp",
+ "android_database_SQLiteGlobal.cpp",
+ "android_database_SQLiteDebug.cpp",
+ "android_graphics_drawable_AnimatedVectorDrawable.cpp",
+ "android_graphics_drawable_VectorDrawable.cpp",
+ "android_view_DisplayEventReceiver.cpp",
+ "android_view_DisplayListCanvas.cpp",
+ "android_view_HardwareLayer.cpp",
+ "android_view_InputChannel.cpp",
+ "android_view_InputDevice.cpp",
+ "android_view_InputEventReceiver.cpp",
+ "android_view_InputEventSender.cpp",
+ "android_view_InputQueue.cpp",
+ "android_view_KeyCharacterMap.cpp",
+ "android_view_KeyEvent.cpp",
+ "android_view_MotionEvent.cpp",
+ "android_view_PointerIcon.cpp",
+ "android_view_RenderNode.cpp",
+ "android_view_RenderNodeAnimator.cpp",
+ "android_view_Surface.cpp",
+ "android_view_SurfaceControl.cpp",
+ "android_view_SurfaceSession.cpp",
+ "android_view_TextureView.cpp",
+ "android_view_ThreadedRenderer.cpp",
+ "android_view_VelocityTracker.cpp",
+ "android_text_AndroidCharacter.cpp",
+ "android_text_AndroidBidi.cpp",
+ "android_text_StaticLayout.cpp",
+ "android_os_Debug.cpp",
+ "android_os_GraphicsEnvironment.cpp",
+ "android_os_HwBinder.cpp",
+ "android_os_HwBlob.cpp",
+ "android_os_HwParcel.cpp",
+ "android_os_HwRemoteBinder.cpp",
+ "android_os_MemoryFile.cpp",
+ "android_os_MessageQueue.cpp",
+ "android_os_Parcel.cpp",
+ "android_os_SELinux.cpp",
+ "android_os_seccomp.cpp",
+ "android_os_SystemClock.cpp",
+ "android_os_SystemProperties.cpp",
+ "android_os_Trace.cpp",
+ "android_os_UEventObserver.cpp",
+ "android_os_VintfObject.cpp",
+ "android_os_VintfRuntimeInfo.cpp",
+ "android_net_LocalSocketImpl.cpp",
+ "android_net_NetUtils.cpp",
+ "android_net_TrafficStats.cpp",
+ "android_nio_utils.cpp",
+ "android_util_AssetManager.cpp",
+ "android_util_Binder.cpp",
+ "android_util_EventLog.cpp",
+ "android_util_MemoryIntArray.cpp",
+ "android_util_Log.cpp",
+ "android_util_PathParser.cpp",
+ "android_util_Process.cpp",
+ "android_util_StringBlock.cpp",
+ "android_util_XmlBlock.cpp",
+ "android_util_jar_StrictJarFile.cpp",
+ "android_graphics_Canvas.cpp",
+ "android_graphics_Picture.cpp",
+ "android/graphics/Bitmap.cpp",
+ "android/graphics/BitmapFactory.cpp",
+ "android/graphics/Camera.cpp",
+ "android/graphics/CanvasProperty.cpp",
+ "android/graphics/ColorFilter.cpp",
+ "android/graphics/DrawFilter.cpp",
+ "android/graphics/FontFamily.cpp",
+ "android/graphics/FontUtils.cpp",
+ "android/graphics/CreateJavaOutputStreamAdaptor.cpp",
+ "android/graphics/GIFMovie.cpp",
+ "android/graphics/GraphicBuffer.cpp",
+ "android/graphics/Graphics.cpp",
+ "android/graphics/HarfBuzzNGFaceSkia.cpp",
+ "android/graphics/Interpolator.cpp",
+ "android/graphics/MaskFilter.cpp",
+ "android/graphics/Matrix.cpp",
+ "android/graphics/Movie.cpp",
+ "android/graphics/MovieImpl.cpp",
+ "android/graphics/NinePatch.cpp",
+ "android/graphics/NinePatchPeeker.cpp",
+ "android/graphics/Paint.cpp",
+ "android/graphics/Path.cpp",
+ "android/graphics/PathMeasure.cpp",
+ "android/graphics/PathEffect.cpp",
+ "android/graphics/Picture.cpp",
+ "android/graphics/BitmapRegionDecoder.cpp",
+ "android/graphics/Region.cpp",
+ "android/graphics/Shader.cpp",
+ "android/graphics/SurfaceTexture.cpp",
+ "android/graphics/Typeface.cpp",
+ "android/graphics/Utils.cpp",
+ "android/graphics/YuvToJpegEncoder.cpp",
+ "android/graphics/pdf/PdfDocument.cpp",
+ "android/graphics/pdf/PdfEditor.cpp",
+ "android/graphics/pdf/PdfRenderer.cpp",
+ "android/graphics/pdf/PdfUtils.cpp",
+ "android_media_AudioRecord.cpp",
+ "android_media_AudioSystem.cpp",
+ "android_media_AudioTrack.cpp",
+ "android_media_DeviceCallback.cpp",
+ "android_media_JetPlayer.cpp",
+ "android_media_RemoteDisplay.cpp",
+ "android_media_ToneGenerator.cpp",
+ "android_hardware_Camera.cpp",
+ "android_hardware_camera2_CameraMetadata.cpp",
+ "android_hardware_camera2_legacy_LegacyCameraDevice.cpp",
+ "android_hardware_camera2_legacy_PerfMeasurement.cpp",
+ "android_hardware_camera2_DngCreator.cpp",
+ "android_hardware_display_DisplayViewport.cpp",
+ "android_hardware_HardwareBuffer.cpp",
+ "android_hardware_Radio.cpp",
+ "android_hardware_SensorManager.cpp",
+ "android_hardware_SerialPort.cpp",
+ "android_hardware_SoundTrigger.cpp",
+ "android_hardware_UsbDevice.cpp",
+ "android_hardware_UsbDeviceConnection.cpp",
+ "android_hardware_UsbRequest.cpp",
+ "android_hardware_location_ActivityRecognitionHardware.cpp",
+ "android_util_FileObserver.cpp",
+ "android/opengl/poly_clip.cpp", // TODO: .arm
+ "android/opengl/util.cpp",
+ "android_server_NetworkManagementSocketTagger.cpp",
+ "android_server_Watchdog.cpp",
+ "android_ddm_DdmHandleNativeHeap.cpp",
+ "android_backup_BackupDataInput.cpp",
+ "android_backup_BackupDataOutput.cpp",
+ "android_backup_FileBackupHelperBase.cpp",
+ "android_backup_BackupHelperDispatcher.cpp",
+ "android_app_backup_FullBackup.cpp",
+ "android_content_res_ObbScanner.cpp",
+ "android_content_res_Configuration.cpp",
+ "android_animation_PropertyValuesHolder.cpp",
+ "com_android_internal_net_NetworkStatsFactory.cpp",
+ "com_android_internal_os_FuseAppLoop.cpp",
+ "com_android_internal_os_PathClassLoaderFactory.cpp",
+ "com_android_internal_os_Zygote.cpp",
+ "com_android_internal_util_VirtualRefBasePtr.cpp",
+ "com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp",
+ "hwbinder/EphemeralStorage.cpp",
+ "fd_utils.cpp",
+ ],
+
+ include_dirs: [
+ // we need to access the private Bionic header
+ // <bionic_tls.h> in com_google_android_gles_jni_GLImpl.cpp
+ "bionic/libc/private",
+
+ "external/skia/include/private",
+ "external/skia/src/codec",
+ "external/skia/src/core",
+ "external/skia/src/effects",
+ "external/skia/src/image",
+ "external/skia/src/images",
+ "frameworks/base/media/jni",
+ "libcore/include",
+ "system/media/camera/include",
+ "system/media/private/camera/include",
+ ],
+
+ static_libs: [
+ "libgif",
+ "libseccomp_policy",
+ "libselinux",
+ "libcrypto",
+ "libgrallocusage",
+ ],
+
+ shared_libs: [
+ "libmemtrack",
+ "libandroidfw",
+ "libappfuse",
+ "libbase",
+ "libnativehelper",
+ "liblog",
+ "libcutils",
+ "libdebuggerd_client",
+ "libutils",
+ "libbinder",
+ "libui",
+ "libgraphicsenv",
+ "libgui",
+ "libsensor",
+ "libinput",
+ "libcamera_client",
+ "libcamera_metadata",
+ "libskia",
+ "libsqlite",
+ "libEGL",
+ "libGLESv1_CM",
+ "libGLESv2",
+ "libvulkan",
+ "libziparchive",
+ "libETC1",
+ "libhardware",
+ "libhardware_legacy",
+ "libselinux",
+ "libicuuc",
+ "libmedia",
+ "libaudioclient",
+ "libjpeg",
+ "libusbhost",
+ "libharfbuzz_ng",
+ "libz",
+ "libpdfium",
+ "libimg_utils",
+ "libnetd_client",
+ "libradio",
+ "libsoundtrigger",
+ "libminikin",
+ "libprocessgroup",
+ "libnativebridge",
+ "libradio_metadata",
+ "libnativeloader",
+ "libmemunreachable",
+ "libhidlbase",
+ "libhidltransport",
+ "libhwbinder",
+ "libvintf",
+ "libnativewindow",
+
+ "libhwui",
+ "libdl",
+ ],
+
+ local_include_dirs: ["android/graphics"],
+ export_include_dirs: [
+ ".",
+ "include",
+ ],
+ export_shared_lib_headers: [
+ // AndroidRuntime.h depends on nativehelper/jni.h
+ "libnativehelper",
+
+ // our headers include libnativewindow's public headers
+ "libnativewindow",
+
+ // GraphicsJNI.h includes hwui headers
+ "libhwui",
+ ],
+
+ product_variables: {
+ debuggable: {
+ cflags: ["-D__ANDROID_DEBUGGABLE__"]
+ },
+ treble: {
+ cflags: ["-D__ANDROID_TREBLE__"]
+ },
+ },
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
deleted file mode 100644
index eb11182..0000000
--- a/core/jni/Android.mk
+++ /dev/null
@@ -1,330 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_CFLAGS += -DHAVE_CONFIG_H -DKHTML_NO_EXCEPTIONS -DGKWQ_NO_JAVA
-LOCAL_CFLAGS += -DNO_SUPPORT_JS_BINDING -DQT_NO_WHEELEVENT -DKHTML_NO_XBL
-LOCAL_CFLAGS += -U__APPLE__
-LOCAL_CFLAGS += -Wno-unused-parameter
-LOCAL_CFLAGS += -Wno-non-virtual-dtor
-LOCAL_CFLAGS += -Wno-maybe-uninitialized -Wno-parentheses
-LOCAL_CPPFLAGS += -Wno-conversion-null
-
-ifeq ($(TARGET_ARCH), arm)
- LOCAL_CFLAGS += -DPACKED="__attribute__ ((packed))"
-else
- LOCAL_CFLAGS += -DPACKED=""
-endif
-
-ifneq ($(ENABLE_CPUSETS),)
- LOCAL_CFLAGS += -DENABLE_CPUSETS
-endif
-
-# TODO: Linear blending should be enabled by default, but we are
-# TODO: making it an opt-in while it's a work in progress
-# TODO: The final test should be:
-# TODO: ifneq ($(TARGET_ENABLE_LINEAR_BLENDING),false)
-ifeq ($(TARGET_ENABLE_LINEAR_BLENDING),true)
- LOCAL_CFLAGS += -DANDROID_ENABLE_LINEAR_BLENDING
-endif
-
-LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
-
-LOCAL_CFLAGS += -DU_USING_ICU_NAMESPACE=0
-
-LOCAL_SRC_FILES:= \
- AndroidRuntime.cpp \
- com_android_internal_content_NativeLibraryHelper.cpp \
- com_google_android_gles_jni_EGLImpl.cpp \
- com_google_android_gles_jni_GLImpl.cpp.arm \
- android_app_Activity.cpp \
- android_app_ApplicationLoaders.cpp \
- android_app_NativeActivity.cpp \
- android_app_admin_SecurityLog.cpp \
- android_opengl_EGL14.cpp \
- android_opengl_EGLExt.cpp \
- android_opengl_GLES10.cpp \
- android_opengl_GLES10Ext.cpp \
- android_opengl_GLES11.cpp \
- android_opengl_GLES11Ext.cpp \
- android_opengl_GLES20.cpp \
- android_opengl_GLES30.cpp \
- android_opengl_GLES31.cpp \
- android_opengl_GLES31Ext.cpp \
- android_opengl_GLES32.cpp \
- android_database_CursorWindow.cpp \
- android_database_SQLiteCommon.cpp \
- android_database_SQLiteConnection.cpp \
- android_database_SQLiteGlobal.cpp \
- android_database_SQLiteDebug.cpp \
- android_graphics_drawable_AnimatedVectorDrawable.cpp \
- android_graphics_drawable_VectorDrawable.cpp \
- android_view_DisplayEventReceiver.cpp \
- android_view_DisplayListCanvas.cpp \
- android_view_HardwareLayer.cpp \
- android_view_InputChannel.cpp \
- android_view_InputDevice.cpp \
- android_view_InputEventReceiver.cpp \
- android_view_InputEventSender.cpp \
- android_view_InputQueue.cpp \
- android_view_KeyCharacterMap.cpp \
- android_view_KeyEvent.cpp \
- android_view_MotionEvent.cpp \
- android_view_PointerIcon.cpp \
- android_view_RenderNode.cpp \
- android_view_RenderNodeAnimator.cpp \
- android_view_Surface.cpp \
- android_view_SurfaceControl.cpp \
- android_view_SurfaceSession.cpp \
- android_view_TextureView.cpp \
- android_view_ThreadedRenderer.cpp \
- android_view_VelocityTracker.cpp \
- android_text_AndroidCharacter.cpp \
- android_text_AndroidBidi.cpp \
- android_text_StaticLayout.cpp \
- android_os_Debug.cpp \
- android_os_GraphicsEnvironment.cpp \
- android_os_HwBinder.cpp \
- android_os_HwBlob.cpp \
- android_os_HwParcel.cpp \
- android_os_HwRemoteBinder.cpp \
- android_os_MemoryFile.cpp \
- android_os_MessageQueue.cpp \
- android_os_Parcel.cpp \
- android_os_SELinux.cpp \
- android_os_seccomp.cpp \
- android_os_SystemClock.cpp \
- android_os_SystemProperties.cpp \
- android_os_Trace.cpp \
- android_os_UEventObserver.cpp \
- android_os_VintfObject.cpp \
- android_os_VintfRuntimeInfo.cpp \
- android_net_LocalSocketImpl.cpp \
- android_net_NetUtils.cpp \
- android_net_TrafficStats.cpp \
- android_nio_utils.cpp \
- android_util_AssetManager.cpp \
- android_util_Binder.cpp \
- android_util_EventLog.cpp \
- android_util_MemoryIntArray.cpp \
- android_util_Log.cpp \
- android_util_PathParser.cpp \
- android_util_Process.cpp \
- android_util_StringBlock.cpp \
- android_util_XmlBlock.cpp \
- android_util_jar_StrictJarFile.cpp \
- android_graphics_Canvas.cpp \
- android_graphics_Picture.cpp \
- android/graphics/Bitmap.cpp \
- android/graphics/BitmapFactory.cpp \
- android/graphics/Camera.cpp \
- android/graphics/CanvasProperty.cpp \
- android/graphics/ColorFilter.cpp \
- android/graphics/DrawFilter.cpp \
- android/graphics/FontFamily.cpp \
- android/graphics/FontUtils.cpp \
- android/graphics/CreateJavaOutputStreamAdaptor.cpp \
- android/graphics/GIFMovie.cpp \
- android/graphics/GraphicBuffer.cpp \
- android/graphics/Graphics.cpp \
- android/graphics/HarfBuzzNGFaceSkia.cpp \
- android/graphics/Interpolator.cpp \
- android/graphics/MaskFilter.cpp \
- android/graphics/Matrix.cpp \
- android/graphics/Movie.cpp \
- android/graphics/MovieImpl.cpp \
- android/graphics/NinePatch.cpp \
- android/graphics/NinePatchPeeker.cpp \
- android/graphics/Paint.cpp \
- android/graphics/Path.cpp \
- android/graphics/PathMeasure.cpp \
- android/graphics/PathEffect.cpp \
- android/graphics/Picture.cpp \
- android/graphics/BitmapRegionDecoder.cpp \
- android/graphics/Region.cpp \
- android/graphics/Shader.cpp \
- android/graphics/SurfaceTexture.cpp \
- android/graphics/Typeface.cpp \
- android/graphics/Utils.cpp \
- android/graphics/YuvToJpegEncoder.cpp \
- android/graphics/pdf/PdfDocument.cpp \
- android/graphics/pdf/PdfEditor.cpp \
- android/graphics/pdf/PdfRenderer.cpp \
- android/graphics/pdf/PdfUtils.cpp \
- android_media_AudioRecord.cpp \
- android_media_AudioSystem.cpp \
- android_media_AudioTrack.cpp \
- android_media_DeviceCallback.cpp \
- android_media_JetPlayer.cpp \
- android_media_RemoteDisplay.cpp \
- android_media_ToneGenerator.cpp \
- android_hardware_Camera.cpp \
- android_hardware_camera2_CameraMetadata.cpp \
- android_hardware_camera2_legacy_LegacyCameraDevice.cpp \
- android_hardware_camera2_legacy_PerfMeasurement.cpp \
- android_hardware_camera2_DngCreator.cpp \
- android_hardware_display_DisplayViewport.cpp \
- android_hardware_HardwareBuffer.cpp \
- android_hardware_Radio.cpp \
- android_hardware_SensorManager.cpp \
- android_hardware_SerialPort.cpp \
- android_hardware_SoundTrigger.cpp \
- android_hardware_UsbDevice.cpp \
- android_hardware_UsbDeviceConnection.cpp \
- android_hardware_UsbRequest.cpp \
- android_hardware_location_ActivityRecognitionHardware.cpp \
- android_util_FileObserver.cpp \
- android/opengl/poly_clip.cpp.arm \
- android/opengl/util.cpp \
- android_server_NetworkManagementSocketTagger.cpp \
- android_server_Watchdog.cpp \
- android_ddm_DdmHandleNativeHeap.cpp \
- android_backup_BackupDataInput.cpp \
- android_backup_BackupDataOutput.cpp \
- android_backup_FileBackupHelperBase.cpp \
- android_backup_BackupHelperDispatcher.cpp \
- android_app_backup_FullBackup.cpp \
- android_content_res_ObbScanner.cpp \
- android_content_res_Configuration.cpp \
- android_animation_PropertyValuesHolder.cpp \
- com_android_internal_net_NetworkStatsFactory.cpp \
- com_android_internal_os_FuseAppLoop.cpp \
- com_android_internal_os_PathClassLoaderFactory.cpp \
- com_android_internal_os_Zygote.cpp \
- com_android_internal_util_VirtualRefBasePtr.cpp \
- com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp \
- hwbinder/EphemeralStorage.cpp \
- fd_utils.cpp \
-
-LOCAL_C_INCLUDES += \
- $(LOCAL_PATH)/include \
- $(JNI_H_INCLUDE) \
- $(LOCAL_PATH)/android/graphics \
- $(LOCAL_PATH)/../../libs/hwui \
- $(LOCAL_PATH)/../../../native/vulkan/include \
- $(call include-path-for, bluedroid) \
- $(call include-path-for, libhardware)/hardware \
- $(call include-path-for, libhardware_legacy)/hardware_legacy \
- $(TOP)/frameworks/base/media/jni \
- $(TOP)/frameworks/rs/cpp \
- $(TOP)/frameworks/rs \
- $(TOP)/system/core/base/include \
- $(TOP)/system/core/include \
- $(TOP)/system/core/libappfuse/include \
- $(TOP)/system/media/camera/include \
- $(TOP)/system/media/private/camera/include \
- $(TOP)/system/netd/include \
- external/giflib \
- external/pdfium/public \
- external/skia/include/private \
- external/skia/src/codec \
- external/skia/src/core \
- external/skia/src/effects \
- external/skia/src/image \
- external/skia/src/images \
- external/sqlite/dist \
- external/sqlite/android \
- external/tremor/Tremor \
- external/harfbuzz_ng/src \
- libcore/include \
- $(call include-path-for, audio-utils) \
- frameworks/minikin/include \
- external/freetype/include
-# TODO: clean up Minikin so it doesn't need the freetype include
-
-LOCAL_STATIC_LIBRARIES := \
- libgif \
- libseccomp_policy \
- libselinux \
- libcrypto \
- libgrallocusage \
-
-LOCAL_SHARED_LIBRARIES := \
- libmemtrack \
- libandroidfw \
- libappfuse \
- libbase \
- libnativehelper \
- liblog \
- libcutils \
- libdebuggerd_client \
- libutils \
- libbinder \
- libui \
- libgui \
- libsensor \
- libinput \
- libcamera_client \
- libcamera_metadata \
- libskia \
- libsqlite \
- libEGL \
- libGLESv1_CM \
- libGLESv2 \
- libvulkan \
- libziparchive \
- libETC1 \
- libhardware \
- libhardware_legacy \
- libselinux \
- libicuuc \
- libmedia \
- libaudioclient \
- libjpeg \
- libusbhost \
- libharfbuzz_ng \
- libz \
- libpdfium \
- libimg_utils \
- libnetd_client \
- libradio \
- libsoundtrigger \
- libminikin \
- libprocessgroup \
- libnativebridge \
- libradio_metadata \
- libnativeloader \
- libmemunreachable \
- libhidlbase \
- libhidltransport \
- libhwbinder \
- libvintf \
- libnativewindow \
-
-LOCAL_SHARED_LIBRARIES += \
- libhwui \
- libdl \
-
-# our headers include libnativewindow's public headers
-LOCAL_EXPORT_SHARED_LIBRARY_HEADERS := \
- libnativewindow \
-
-# we need to access the private Bionic header
-# <bionic_tls.h> in com_google_android_gles_jni_GLImpl.cpp
-LOCAL_C_INCLUDES += bionic/libc/private
-
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-
-# AndroidRuntime.h depends on nativehelper/jni.h
-LOCAL_EXPORT_C_INCLUDE_DIRS += libnativehelper/include
-
-LOCAL_MODULE:= libandroid_runtime
-
-# -Wno-unknown-pragmas: necessary for Clang as the GL bindings need to turn
-# off a GCC warning that Clang doesn't know.
-LOCAL_CFLAGS += -Wall -Werror -Wno-error=deprecated-declarations -Wunused -Wunreachable-code \
- -Wno-unknown-pragmas
-
-# -Wno-c++11-extensions: Clang warns about Skia using the C++11 override keyword, but this project
-# is not being compiled with that level. Remove once this has changed.
-LOCAL_CLANG_CFLAGS += -Wno-c++11-extensions
-
-ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
-LOCAL_CFLAGS += -D__ANDROID_DEBUGGABLE__
-endif
-ifneq (,$(filter true, $(PRODUCT_FULL_TREBLE)))
-LOCAL_CFLAGS += -D__ANDROID_TREBLE__
-endif
-
-include $(BUILD_SHARED_LIBRARY)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 785fd2c..1adc6dd 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -265,8 +265,6 @@
mArgBlockLength(argBlockLength)
{
SkGraphics::Init();
- // There is also a global font cache, but its budget is specified by
- // SK_DEFAULT_FONT_CACHE_COUNT_LIMIT and SK_DEFAULT_FONT_CACHE_LIMIT.
// Pre-allocate enough space to hold a fair number of options.
mOptions.setCapacity(20);
@@ -277,7 +275,6 @@
AndroidRuntime::~AndroidRuntime()
{
- SkGraphics::Term();
}
/*
@@ -679,7 +676,15 @@
executionMode = kEMJitCompiler;
}
- parseRuntimeOption("dalvik.vm.stack-trace-file", stackTraceFileBuf, "-Xstacktracefile:");
+ // If dalvik.vm.stack-trace-dir is set, it enables the "new" stack trace
+ // dump scheme and a new file is created for each stack dump. If it isn't set,
+ // the old scheme is enabled.
+ property_get("dalvik.vm.stack-trace-dir", propBuf, "");
+ if (strlen(propBuf) > 0) {
+ addOption("-Xusetombstonedtraces");
+ } else {
+ parseRuntimeOption("dalvik.vm.stack-trace-file", stackTraceFileBuf, "-Xstacktracefile:");
+ }
strcpy(jniOptsBuf, "-Xjniopts:");
if (parseRuntimeOption("dalvik.vm.jniopts", jniOptsBuf, "-Xjniopts:")) {
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index b171a7e..be8b9de 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -278,9 +278,7 @@
if (!localBitmap->valid()) return nullptr;
SkPixelRef& pixelRef = localBitmap->bitmap();
- pixelRef.lockPixels();
if (!pixelRef.pixels()) {
- pixelRef.unlockPixels();
return nullptr;
}
pixelRef.ref();
@@ -298,7 +296,6 @@
SkPixelRef& pixelRef = localBitmap->bitmap();
pixelRef.notifyPixelsChanged();
- pixelRef.unlockPixels();
pixelRef.unref();
return true;
}
@@ -331,11 +328,12 @@
uint64_t* d = (uint64_t*)dst;
for (int i = 0; i < width; i++) {
- const float* color = SkColor4f::FromColor(*src++).vec();
+ const SkColor4f color = SkColor4f::FromColor(*src++);
uint16_t* scratch = reinterpret_cast<uint16_t*>(d++);
- for (int i = 0; i < 4; ++i) {
- scratch[i] = SkFloatToHalf(color[i]);
- }
+ scratch[0] = SkFloatToHalf(color.fR);
+ scratch[1] = SkFloatToHalf(color.fG);
+ scratch[2] = SkFloatToHalf(color.fB);
+ scratch[3] = SkFloatToHalf(color.fA);
}
}
@@ -438,7 +436,6 @@
bool GraphicsJNI::SetPixels(JNIEnv* env, jintArray srcColors, int srcOffset, int srcStride,
int x, int y, int width, int height, const SkBitmap& dstBitmap) {
- SkAutoLockPixels alp(dstBitmap);
void* dst = dstBitmap.getPixels();
FromColorProc proc = ChooseFromColorProc(dstBitmap);
@@ -755,31 +752,53 @@
static bool bitmapCopyTo(SkBitmap* dst, SkColorType dstCT, const SkBitmap& src,
SkBitmap::Allocator* alloc) {
+ LOG_ALWAYS_FATAL_IF(kIndex_8_SkColorType == dstCT, "Error, cannot copyTo kIndex8.");
+
+ SkPixmap srcPM;
+ if (!src.peekPixels(&srcPM)) {
+ return false;
+ }
+
+ SkImageInfo dstInfo = srcPM.info().makeColorType(dstCT);
+ switch (dstCT) {
+ case kRGB_565_SkColorType:
+ // copyTo() has never been strict on alpha type. Here we set the src to opaque to
+ // allow the call to readPixels() to succeed and preserve this lenient behavior.
+ if (kOpaque_SkAlphaType != srcPM.alphaType()) {
+ srcPM = SkPixmap(srcPM.info().makeAlphaType(kOpaque_SkAlphaType), srcPM.addr(),
+ srcPM.rowBytes(), srcPM.ctable());
+ dstInfo = dstInfo.makeAlphaType(kOpaque_SkAlphaType);
+ }
+ break;
+ case kRGBA_F16_SkColorType:
+ // The caller does not have an opportunity to pass a dst color space. Assume that
+ // they want linear sRGB.
+ dstInfo = dstInfo.makeColorSpace(SkColorSpace::MakeSRGBLinear());
+
+ if (!srcPM.colorSpace()) {
+ // Skia needs a color space to convert to F16. nullptr should be treated as sRGB.
+ srcPM.setColorSpace(SkColorSpace::MakeSRGB());
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!dst->setInfo(dstInfo)) {
+ return false;
+ }
+ if (!dst->tryAllocPixels(alloc, nullptr)) {
+ return false;
+ }
+
// Skia does not support copying from kAlpha8 to types that are not alpha only.
// We will handle this case here.
- if (kAlpha_8_SkColorType == src.colorType() && kAlpha_8_SkColorType != dstCT) {
- SkAutoPixmapUnlock srcUnlocker;
- if (!src.requestLock(&srcUnlocker)) {
- return false;
- }
- SkPixmap srcPixmap = srcUnlocker.pixmap();
-
- SkImageInfo dstInfo = src.info().makeColorType(dstCT);
- if (dstCT == kRGBA_F16_SkColorType) {
- dstInfo = dstInfo.makeColorSpace(SkColorSpace::MakeSRGBLinear());
- }
- if (!dst->setInfo(dstInfo)) {
- return false;
- }
- if (!dst->tryAllocPixels(alloc, nullptr)) {
- return false;
- }
-
+ if (kAlpha_8_SkColorType == srcPM.colorType() && kAlpha_8_SkColorType != dstCT) {
switch (dstCT) {
case kRGBA_8888_SkColorType:
case kBGRA_8888_SkColorType: {
for (int y = 0; y < src.height(); y++) {
- const uint8_t* srcRow = srcPixmap.addr8(0, y);
+ const uint8_t* srcRow = srcPM.addr8(0, y);
uint32_t* dstRow = dst->getAddr32(0, y);
ToColor_SA8(dstRow, srcRow, src.width(), nullptr);
}
@@ -794,7 +813,7 @@
}
case kRGBA_F16_SkColorType: {
for (int y = 0; y < src.height(); y++) {
- const uint8_t* srcRow = srcPixmap.addr8(0, y);
+ const uint8_t* srcRow = srcPM.addr8(0, y);
void* dstRow = dst->getAddr(0, y);
ToF16_SA8(dstRow, srcRow, src.width());
}
@@ -805,7 +824,25 @@
}
}
- return src.copyTo(dst, dstCT, alloc);
+ SkPixmap dstPM;
+ if (!dst->peekPixels(&dstPM)) {
+ return false;
+ }
+
+ // Skia needs a color space to convert from F16. nullptr should be treated as sRGB.
+ if (kRGBA_F16_SkColorType == srcPM.colorType() && !dstPM.colorSpace()) {
+ dstPM.setColorSpace(SkColorSpace::MakeSRGB());
+ }
+
+ // readPixels does not support color spaces with parametric transfer functions. This
+ // works around that restriction when the color spaces are equal.
+ if (kRGBA_F16_SkColorType != dstCT && kRGBA_F16_SkColorType != srcPM.colorType() &&
+ dstPM.colorSpace() == srcPM.colorSpace()) {
+ dstPM.setColorSpace(nullptr);
+ srcPM.setColorSpace(nullptr);
+ }
+
+ return srcPM.readPixels(dstPM);
}
static jobject Bitmap_copy(JNIEnv* env, jobject, jlong srcHandle,
@@ -1074,7 +1111,7 @@
return NULL;
}
- SkColorTable* ctable = NULL;
+ sk_sp<SkColorTable> ctable = NULL;
if (colorType == kIndex_8_SkColorType) {
int count = p->readInt32();
if (count < 0 || count > 256) {
@@ -1088,7 +1125,7 @@
if (src == NULL) {
return NULL;
}
- ctable = new SkColorTable(src, count);
+ ctable = SkColorTable::Make(src, count);
}
}
@@ -1097,7 +1134,6 @@
android::Parcel::ReadableBlob blob;
android::status_t status = p->readBlob(size, &blob);
if (status) {
- SkSafeUnref(ctable);
doThrowRE(env, "Could not read bitmap blob.");
return NULL;
}
@@ -1118,15 +1154,13 @@
if (dupFd < 0) {
ALOGE("Error allocating dup fd. Error:%d", errno);
blob.release();
- SkSafeUnref(ctable);
doThrowRE(env, "Could not allocate dup blob fd.");
return NULL;
}
// Map the pixels in place and take ownership of the ashmem region.
nativeBitmap = sk_sp<Bitmap>(GraphicsJNI::mapAshmemBitmap(env, bitmap.get(),
- ctable, dupFd, const_cast<void*>(blob.data()), size, !isMutable));
- SkSafeUnref(ctable);
+ ctable.get(), dupFd, const_cast<void*>(blob.data()), size, !isMutable));
if (!nativeBitmap) {
close(dupFd);
blob.release();
@@ -1152,15 +1186,12 @@
// Copy the pixels into a new buffer.
nativeBitmap = Bitmap::allocateHeapBitmap(bitmap.get(), ctable);
- SkSafeUnref(ctable);
if (!nativeBitmap) {
blob.release();
doThrowRE(env, "Could not allocate java pixel ref.");
return NULL;
}
- bitmap->lockPixels();
memcpy(bitmap->getPixels(), blob.data(), size);
- bitmap->unlockPixels();
// Release the blob handle.
blob.release();
@@ -1211,7 +1242,6 @@
if (bitmap.colorType() == kIndex_8_SkColorType) {
// The bitmap needs to be locked to access its color table.
- SkAutoLockPixels alp(bitmap);
SkColorTable* ctable = bitmap.getColorTable();
if (ctable != NULL) {
int count = ctable->count();
@@ -1258,14 +1288,12 @@
return JNI_FALSE;
}
- bitmap.lockPixels();
const void* pSrc = bitmap.getPixels();
if (pSrc == NULL) {
memset(blob.data(), 0, size);
} else {
memcpy(blob.data(), pSrc, size);
}
- bitmap.unlockPixels();
blob.release();
return JNI_TRUE;
@@ -1355,7 +1383,6 @@
jint x, jint y) {
SkBitmap bitmap;
reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap);
- SkAutoLockPixels alp(bitmap);
ToColorProc proc = ChooseToColorProc(bitmap);
if (NULL == proc) {
@@ -1387,7 +1414,6 @@
jint x, jint y, jint width, jint height) {
SkBitmap bitmap;
reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap);
- SkAutoLockPixels alp(bitmap);
ToColorProc proc = ChooseToColorProc(bitmap);
if (NULL == proc) {
@@ -1436,7 +1462,6 @@
SkBitmap bitmap;
reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap);
SkColor color = static_cast<SkColor>(colorHandle);
- SkAutoLockPixels alp(bitmap);
if (NULL == bitmap.getPixels()) {
return;
}
@@ -1473,7 +1498,6 @@
jlong bitmapHandle, jobject jbuffer) {
SkBitmap bitmap;
reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap);
- SkAutoLockPixels alp(bitmap);
const void* src = bitmap.getPixels();
if (NULL != src) {
@@ -1488,7 +1512,6 @@
jlong bitmapHandle, jobject jbuffer) {
SkBitmap bitmap;
reinterpret_cast<BitmapWrapper*>(bitmapHandle)->getSkBitmap(&bitmap);
- SkAutoLockPixels alp(bitmap);
void* dst = bitmap.getPixels();
if (NULL != dst) {
@@ -1523,9 +1546,6 @@
return JNI_FALSE;
}
- SkAutoLockPixels alp0(bm0);
- SkAutoLockPixels alp1(bm1);
-
// if we can't load the pixels, return false
if (NULL == bm0.getPixels() || NULL == bm1.getPixels()) {
return JNI_FALSE;
@@ -1593,7 +1613,7 @@
SkBitmap result;
HeapAllocator allocator;
- if (!src.copyTo(&result, hwuiBitmap.info().colorType(), &allocator)) {
+ if (!bitmapCopyTo(&result, hwuiBitmap.info().colorType(), src, &allocator)) {
doThrowRE(env, "Could not copy a hardware bitmap.");
return NULL;
}
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index e714671..5a25d5e 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -195,13 +195,8 @@
return false;
}
- mBitmap->reconfigure(info, bitmap->rowBytes(), ctable);
- mBitmap->ref();
- bitmap->setPixelRef(mBitmap)->unref();
-
- // since we're already allocated, we lockPixels right away
- // HeapAllocator behaves this way too
- bitmap->lockPixels();
+ mBitmap->reconfigure(info, bitmap->rowBytes(), sk_ref_sp(ctable));
+ bitmap->setPixelRef(sk_ref_sp(mBitmap), 0, 0);
return true;
}
diff --git a/core/jni/android/graphics/GraphicBuffer.cpp b/core/jni/android/graphics/GraphicBuffer.cpp
index 73e53c6..1017cba 100644
--- a/core/jni/android/graphics/GraphicBuffer.cpp
+++ b/core/jni/android/graphics/GraphicBuffer.cpp
@@ -18,6 +18,7 @@
#include "jni.h"
#include "JNIHelp.h"
+#include <inttypes.h>
#include "android_os_Parcel.h"
#include "GraphicBuffer.h"
@@ -27,6 +28,8 @@
#include <binder/Parcel.h>
+#include <log/log.h>
+
#include <ui/GraphicBuffer.h>
#include <ui/PixelFormat.h>
@@ -90,9 +93,15 @@
class GraphicBufferWrapper {
public:
explicit GraphicBufferWrapper(const sp<GraphicBuffer>& buffer): buffer(buffer) {
+ LOG_ALWAYS_FATAL_IF(buffer == nullptr, "creating a null GraphicBuffer");
+ }
+ const sp<GraphicBuffer>& get() const {
+ return buffer;
}
- sp<GraphicBuffer> buffer;
+private:
+ // make sure this is immutable
+ sp<GraphicBuffer> const buffer;
};
// ----------------------------------------------------------------------------
@@ -102,6 +111,8 @@
static jlong android_graphics_GraphicBuffer_wrap(JNIEnv* env, jobject clazz,
jlong unwrapped) {
sp<GraphicBuffer> b(reinterpret_cast<GraphicBuffer*>(unwrapped));
+ LOG_ALWAYS_FATAL_IF(b == nullptr,
+ "*** android_graphics_GraphicBuffer_wrap() invalid state, b is null, unwrapped=%#" PRIx64, unwrapped);
GraphicBufferWrapper* wrapper = new GraphicBufferWrapper(b);
return reinterpret_cast<jlong>(wrapper);
}
@@ -159,7 +170,7 @@
return JNI_FALSE;
}
- sp<GraphicBuffer> buffer(wrapper->buffer);
+ sp<GraphicBuffer> buffer(wrapper->get());
Rect rect(Rect::EMPTY_RECT);
if (dirtyRect) {
@@ -217,7 +228,7 @@
nativeCanvas->setBitmap(SkBitmap());
if (wrapper) {
- status_t status = wrapper->buffer->unlock();
+ status_t status = wrapper->get()->unlock();
return status == 0 ? JNI_TRUE : JNI_FALSE;
}
@@ -230,11 +241,12 @@
static void android_graphics_GraphicBuffer_write(JNIEnv* env, jobject clazz,
jlong wrapperHandle, jobject dest) {
+
GraphicBufferWrapper* wrapper =
reinterpret_cast<GraphicBufferWrapper*>(wrapperHandle);
Parcel* parcel = parcelForJavaObject(env, dest);
if (parcel) {
- parcel->write(*wrapper->buffer);
+ parcel->write(*wrapper->get());
}
}
@@ -260,7 +272,7 @@
jlong nativeObject = env->GetLongField(obj, gGraphicBufferClassInfo.mNativeObject);
GraphicBufferWrapper* wrapper = (GraphicBufferWrapper*) nativeObject;
if (wrapper != NULL) {
- sp<GraphicBuffer> buffer(wrapper->buffer);
+ sp<GraphicBuffer> buffer(wrapper->get());
return buffer;
}
}
@@ -271,7 +283,7 @@
GraphicBufferWrapper* wrapper = new GraphicBufferWrapper(buffer);
jobject obj = env->NewObject(gGraphicBufferClassInfo.mClass,
gGraphicBufferClassInfo.mConstructorMethodID, buffer->getWidth(), buffer->getHeight(),
- buffer->getPixelFormat(), buffer->getUsage(), reinterpret_cast<jlong>(wrapper));
+ buffer->getPixelFormat(), (jint)buffer->getUsage(), reinterpret_cast<jlong>(wrapper));
return obj;
}
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index b11fd4f..5a2238f 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -456,15 +456,11 @@
// attempting to compute our own.
const size_t rowBytes = bitmap->rowBytes();
- auto wrapper = new android::Bitmap(addr, fd, size, info, rowBytes, ctable);
+ auto wrapper = new android::Bitmap(addr, fd, size, info, rowBytes, sk_ref_sp(ctable));
wrapper->getSkBitmap(bitmap);
if (readOnly) {
bitmap->pixelRef()->setImmutable();
}
- // since we're already allocated, we lockPixels right away
- // HeapAllocator behaves this way too
- bitmap->lockPixels();
-
return wrapper;
}
@@ -614,7 +610,7 @@
///////////////////////////////////////////////////////////////////////////////
bool HeapAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
- mStorage = android::Bitmap::allocateHeapBitmap(bitmap, ctable);
+ mStorage = android::Bitmap::allocateHeapBitmap(bitmap, sk_ref_sp(ctable));
return !!mStorage;
}
@@ -662,20 +658,18 @@
// storage needs
mRecycledBitmap->reconfigure(
mRecycledBitmap->info().makeColorSpace(bitmap->refColorSpace()),
- rowBytes, ctable);
+ rowBytes, sk_ref_sp(ctable));
// Give the bitmap the same pixelRef as mRecycledBitmap.
// skbug.com/4538: We also need to make sure that the rowBytes on the pixel ref
// match the rowBytes on the bitmap.
bitmap->setInfo(bitmap->info(), rowBytes);
- mRecycledBitmap->ref();
- bitmap->setPixelRef(mRecycledBitmap)->unref();
+ bitmap->setPixelRef(sk_ref_sp(mRecycledBitmap), 0, 0);
// Make sure that the recycled bitmap has the correct alpha type.
mRecycledBitmap->setAlphaType(bitmap->alphaType());
bitmap->notifyPixelsChanged();
- bitmap->lockPixels();
mNeedsCopy = false;
// TODO: If the dimensions of the SkBitmap are smaller than those of
@@ -723,7 +717,7 @@
}
bool AshmemPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
- mStorage = android::Bitmap::allocateAshmemBitmap(bitmap, ctable);
+ mStorage = android::Bitmap::allocateAshmemBitmap(bitmap, sk_ref_sp(ctable));
return !!mStorage;
}
diff --git a/core/jni/android/graphics/Movie.h b/core/jni/android/graphics/Movie.h
index c0fbe4f..736890d 100644
--- a/core/jni/android/graphics/Movie.h
+++ b/core/jni/android/graphics/Movie.h
@@ -10,8 +10,9 @@
#ifndef Movie_DEFINED
#define Movie_DEFINED
-#include "SkRefCnt.h"
+#include "SkBitmap.h"
#include "SkCanvas.h"
+#include "SkRefCnt.h"
class SkStreamRewindable;
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index 5f803da..f04abec 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -63,14 +63,17 @@
static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jobject jbitmap,
jint tileModeX, jint tileModeY) {
const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
- SkBitmap bitmap;
+ sk_sp<SkImage> image;
if (jbitmap) {
// Only pass a valid SkBitmap object to the constructor if the Bitmap exists. Otherwise,
// we'll pass an empty SkBitmap to avoid crashing/excepting for compatibility.
- android::bitmap::toBitmap(env, jbitmap).getSkBitmapForShaders(&bitmap);
+ image = android::bitmap::toBitmap(env, jbitmap).makeImage();
}
- sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
+ if (!image.get()) {
+ SkBitmap bitmap;
+ image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
+ }
sk_sp<SkShader> baseShader = image->makeShader(
(SkShader::TileMode)tileModeX, (SkShader::TileMode)tileModeY);
diff --git a/core/jni/android/graphics/pdf/PdfRenderer.cpp b/core/jni/android/graphics/pdf/PdfRenderer.cpp
index 1001c05..33bbdc4 100644
--- a/core/jni/android/graphics/pdf/PdfRenderer.cpp
+++ b/core/jni/android/graphics/pdf/PdfRenderer.cpp
@@ -83,8 +83,6 @@
SkBitmap skBitmap;
GraphicsJNI::getSkBitmap(env, jbitmap, &skBitmap);
- SkAutoLockPixels alp(skBitmap);
-
const int stride = skBitmap.width() * 4;
FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(skBitmap.width(), skBitmap.height(),
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index d4735ec..1370e61 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -739,7 +739,6 @@
int err = checkFormat(colorType, internalformat, type);
if (err)
return err;
- bitmap.lockPixels();
const int w = bitmap.width();
const int h = bitmap.height();
const void* p = bitmap.getPixels();
@@ -766,7 +765,6 @@
glTexImage2D(target, level, internalformat, w, h, border, internalformat, type, p);
}
error:
- bitmap.unlockPixels();
return err;
}
@@ -785,12 +783,10 @@
int err = checkFormat(colorType, format, type);
if (err)
return err;
- bitmap.lockPixels();
const int w = bitmap.width();
const int h = bitmap.height();
const void* p = bitmap.getPixels();
glTexSubImage2D(target, level, xoffset, yoffset, w, h, format, type, p);
- bitmap.unlockPixels();
return 0;
}
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index 253daaa..c0f719e 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -28,6 +28,7 @@
#include "SkDrawFilter.h"
#include "SkGraphics.h"
#include "SkRegion.h"
+#include "SkVertices.h"
namespace android {
@@ -311,15 +312,15 @@
}
static void drawVertices(JNIEnv* env, jobject, jlong canvasHandle,
- jint modeHandle, jint vertexCount,
+ jint modeHandle, jint floatCount,
jfloatArray jverts, jint vertIndex,
jfloatArray jtexs, jint texIndex,
jintArray jcolors, jint colorIndex,
jshortArray jindices, jint indexIndex,
jint indexCount, jlong paintHandle) {
- AutoJavaFloatArray vertA(env, jverts, vertIndex + vertexCount);
- AutoJavaFloatArray texA(env, jtexs, texIndex + vertexCount);
- AutoJavaIntArray colorA(env, jcolors, colorIndex + vertexCount);
+ AutoJavaFloatArray vertA(env, jverts, vertIndex + floatCount);
+ AutoJavaFloatArray texA(env, jtexs, texIndex + floatCount);
+ AutoJavaIntArray colorA(env, jcolors, colorIndex + floatCount);
AutoJavaShortArray indexA(env, jindices, indexIndex + indexCount);
const float* verts = vertA.ptr() + vertIndex;
@@ -334,10 +335,15 @@
indices = (const uint16_t*)(indexA.ptr() + indexIndex);
}
- SkCanvas::VertexMode mode = static_cast<SkCanvas::VertexMode>(modeHandle);
+ int vertexCount = floatCount >> 1; // 2 floats per SkPoint
+ SkVertices::VertexMode mode = static_cast<SkVertices::VertexMode>(modeHandle);
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- get_canvas(canvasHandle)->drawVertices(mode, vertexCount, verts, texs, colors,
- indices, indexCount, *paint);
+ get_canvas(canvasHandle)->drawVertices(SkVertices::MakeCopy(mode, vertexCount,
+ reinterpret_cast<const SkPoint*>(verts),
+ reinterpret_cast<const SkPoint*>(texs),
+ reinterpret_cast<const SkColor*>(colors),
+ indexCount, indices).get(),
+ SkBlendMode::kModulate, *paint);
}
static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
diff --git a/core/jni/android_hardware_Radio.cpp b/core/jni/android_hardware_Radio.cpp
index 18cf8ca..39e615c 100644
--- a/core/jni/android_hardware_Radio.cpp
+++ b/core/jni/android_hardware_Radio.cpp
@@ -325,13 +325,16 @@
ALOGV("%s channel %d tuned %d", __FUNCTION__, nProgramInfo->channel, nProgramInfo->tuned);
+ int flags = 0; // TODO(b/32621193): pass from the HAL
+ jstring jVendorExension = env->NewStringUTF(""); // TODO(b/32621193): pass from the HAL
*jProgramInfo = env->NewObject(gRadioProgramInfoClass, gRadioProgramInfoCstor,
nProgramInfo->channel, nProgramInfo->sub_channel,
nProgramInfo->tuned, nProgramInfo->stereo,
nProgramInfo->digital, nProgramInfo->signal_strength,
- jMetadata);
+ jMetadata, flags, jVendorExension);
env->DeleteLocalRef(jMetadata);
+ env->DeleteLocalRef(jVendorExension);
return (jint)RADIO_STATUS_OK;
}
@@ -443,19 +446,22 @@
jstring jProduct = env->NewStringUTF(nModules[i].product);
jstring jVersion = env->NewStringUTF(nModules[i].version);
jstring jSerial = env->NewStringUTF(nModules[i].serial);
+ bool isBgscanSupported = false; // TODO(b/32621193): pass from the HAL
+ jstring jVendorExension = env->NewStringUTF(""); // TODO(b/32621193): pass from the HAL
jobject jModule = env->NewObject(gModulePropertiesClass, gModulePropertiesCstor,
nModules[i].handle, nModules[i].class_id,
jImplementor, jProduct, jVersion, jSerial,
nModules[i].num_tuners,
nModules[i].num_audio_sources,
nModules[i].supports_capture,
- jBands);
+ jBands, isBgscanSupported, jVendorExension);
env->DeleteLocalRef(jImplementor);
env->DeleteLocalRef(jProduct);
env->DeleteLocalRef(jVersion);
env->DeleteLocalRef(jSerial);
env->DeleteLocalRef(jBands);
+ env->DeleteLocalRef(jVendorExension);
if (jModule == NULL) {
continue;
}
@@ -882,7 +888,7 @@
jclass modulePropertiesClass = FindClassOrDie(env, kModulePropertiesClassPathName);
gModulePropertiesClass = MakeGlobalRefOrDie(env, modulePropertiesClass);
gModulePropertiesCstor = GetMethodIDOrDie(env, modulePropertiesClass, "<init>",
- "(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IIZ[Landroid/hardware/radio/RadioManager$BandDescriptor;)V");
+ "(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IIZ[Landroid/hardware/radio/RadioManager$BandDescriptor;ZLjava/lang/String;)V");
jclass bandDescriptorClass = FindClassOrDie(env, kRadioBandDescriptorClassPathName);
gRadioBandDescriptorClass = MakeGlobalRefOrDie(env, bandDescriptorClass);
@@ -932,7 +938,7 @@
jclass programInfoClass = FindClassOrDie(env, kRadioProgramInfoClassPathName);
gRadioProgramInfoClass = MakeGlobalRefOrDie(env, programInfoClass);
gRadioProgramInfoCstor = GetMethodIDOrDie(env, programInfoClass, "<init>",
- "(IIZZZILandroid/hardware/radio/RadioMetadata;)V");
+ "(IIZZZILandroid/hardware/radio/RadioMetadata;ILjava/lang/String;)V");
jclass metadataClass = FindClassOrDie(env, kRadioMetadataClassPathName);
gRadioMetadataClass = MakeGlobalRefOrDie(env, metadataClass);
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index f1b19e17..e90a41c 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -270,7 +270,7 @@
}
static jint nativeSetOperationParameter(JNIEnv *_env, jclass _this, jlong sensorManager,
- jint type, jfloatArray floats, jintArray ints) {
+ jint handle, jint type, jfloatArray floats, jintArray ints) {
SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
Vector<float> floatVector;
Vector<int32_t> int32Vector;
@@ -285,7 +285,7 @@
_env->GetIntArrayRegion(ints, 0, _env->GetArrayLength(ints), int32Vector.editArray());
}
- return mgr->setOperationParameter(type, floatVector, int32Vector);
+ return mgr->setOperationParameter(handle, type, floatVector, int32Vector);
}
//----------------------------------------------------------------------------
@@ -512,7 +512,7 @@
(void*)nativeConfigDirectChannel },
{"nativeSetOperationParameter",
- "(JI[F[I)I",
+ "(JII[F[I)I",
(void*)nativeSetOperationParameter },
};
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index c11ce0f..d8eb7d2 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -442,10 +442,12 @@
if (threadRet != 0) {
close(writeFd);
+ close(readFd);
jniThrowExceptionFmt(env, "java/io/IOException",
"Failed to create thread for writing (errno = %#x, message = '%s')",
threadRet, strerror(threadRet));
+ return;
}
}
@@ -476,6 +478,8 @@
} else if (!logLine.isEmpty()) {
ALOGD("%s", logLine.string());
}
+
+ close(readFd);
}
int res;
diff --git a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
index b23757e..637234f 100644
--- a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
+++ b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
@@ -22,7 +22,7 @@
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
-// #include "activity_recognition.h"
+// #include <hardware/activity_recognition.h>
// The activity recognition HAL is being deprecated. This means -
// i) Android framework code shall not depend on activity recognition
// being provided through the activity_recognition.h interface.
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 2cfaeeb..3e9ffce 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -28,7 +28,6 @@
#include <utils/Log.h>
#include <media/AudioSystem.h>
#include <media/AudioTrack.h>
-#include <audio_utils/primitives.h>
#include <binder/MemoryHeapBase.h>
#include <binder/MemoryBase.h>
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index de9445d..76140c0 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -33,12 +33,14 @@
#include <string>
#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
#include <debuggerd/client.h>
#include <log/log.h>
#include <utils/misc.h>
#include <utils/String8.h>
#include "JNIHelp.h"
+#include "ScopedUtfChars.h"
#include "jni.h"
#include <memtrack/memtrack.h>
#include <memunreachable/memunreachable.h>
@@ -1008,30 +1010,34 @@
ALOGD("Native heap dump complete.\n");
}
-static void android_os_Debug_dumpNativeBacktraceToFileTimeout(JNIEnv* env, jobject clazz,
- jint pid, jstring fileName, jint timeoutSecs)
-{
- if (fileName == NULL) {
- jniThrowNullPointerException(env, "file == null");
- return;
- }
- const jchar* str = env->GetStringCritical(fileName, 0);
- String8 fileName8;
- if (str) {
- fileName8 = String8(reinterpret_cast<const char16_t*>(str),
- env->GetStringLength(fileName));
- env->ReleaseStringCritical(fileName, str);
+static bool dumpTraces(JNIEnv* env, jint pid, jstring fileName, jint timeoutSecs,
+ DebuggerdDumpType dumpType) {
+ const ScopedUtfChars fileNameChars(env, fileName);
+ if (fileNameChars.c_str() == nullptr) {
+ return false;
}
- int fd = open(fileName8.string(), O_CREAT | O_WRONLY | O_NOFOLLOW | O_CLOEXEC | O_APPEND, 0666);
+ android::base::unique_fd fd(open(fileNameChars.c_str(),
+ O_CREAT | O_WRONLY | O_NOFOLLOW | O_CLOEXEC | O_APPEND,
+ 0666));
if (fd < 0) {
- fprintf(stderr, "Can't open %s: %s\n", fileName8.string(), strerror(errno));
- return;
+ fprintf(stderr, "Can't open %s: %s\n", fileNameChars.c_str(), strerror(errno));
+ return false;
}
- dump_backtrace_to_file_timeout(pid, fd, timeoutSecs);
+ return (dump_backtrace_to_file_timeout(pid, dumpType, timeoutSecs, fd) == 0);
+}
- close(fd);
+static jboolean android_os_Debug_dumpJavaBacktraceToFileTimeout(JNIEnv* env, jobject clazz,
+ jint pid, jstring fileName, jint timeoutSecs) {
+ const bool ret = dumpTraces(env, pid, fileName, timeoutSecs, kDebuggerdJavaBacktrace);
+ return ret ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean android_os_Debug_dumpNativeBacktraceToFileTimeout(JNIEnv* env, jobject clazz,
+ jint pid, jstring fileName, jint timeoutSecs) {
+ const bool ret = dumpTraces(env, pid, fileName, timeoutSecs, kDebuggerdNativeBacktrace);
+ return ret ? JNI_TRUE : JNI_FALSE;
}
static jstring android_os_Debug_getUnreachableMemory(JNIEnv* env, jobject clazz,
@@ -1074,7 +1080,9 @@
(void*)android_os_Debug_getProxyObjectCount },
{ "getBinderDeathObjectCount", "()I",
(void*)android_os_Debug_getDeathObjectCount },
- { "dumpNativeBacktraceToFileTimeout", "(ILjava/lang/String;I)V",
+ { "dumpJavaBacktraceToFileTimeout", "(ILjava/lang/String;I)Z",
+ (void*)android_os_Debug_dumpJavaBacktraceToFileTimeout },
+ { "dumpNativeBacktraceToFileTimeout", "(ILjava/lang/String;I)Z",
(void*)android_os_Debug_dumpNativeBacktraceToFileTimeout },
{ "getUnreachableMemory", "(IZ)Ljava/lang/String;",
(void*)android_os_Debug_getUnreachableMemory },
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index 399dec8..f749488 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -16,7 +16,7 @@
#define LOG_TAG "GraphicsEnvironment"
-#include <ui/GraphicsEnv.h>
+#include <graphicsenv/GraphicsEnv.h>
#include <nativehelper/ScopedUtfChars.h>
#include "core_jni_helpers.h"
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index d740a76..cd7aae7 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -164,8 +164,8 @@
}
}
-static void android_os_Parcel_writeNative(JNIEnv* env, jclass clazz, jlong nativePtr, jobject data,
- jint offset, jint length)
+static void android_os_Parcel_writeByteArray(JNIEnv* env, jclass clazz, jlong nativePtr,
+ jobject data, jint offset, jint length)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel == NULL) {
@@ -346,6 +346,28 @@
return ret;
}
+static jboolean android_os_Parcel_readByteArray(JNIEnv* env, jclass clazz, jlong nativePtr,
+ jobject dest, jint destLen)
+{
+ jboolean ret = JNI_FALSE;
+ Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+ if (parcel == NULL) {
+ return ret;
+ }
+
+ int32_t len = parcel->readInt32();
+ if (len >= 0 && len <= (int32_t)parcel->dataAvail() && len == destLen) {
+ jbyte* ar = (jbyte*)env->GetPrimitiveArrayCritical((jarray)dest, 0);
+ if (ar) {
+ const void* data = parcel->readInplace(len);
+ memcpy(ar, data, len);
+ env->ReleasePrimitiveArrayCritical((jarray)dest, ar, 0);
+ ret = JNI_TRUE;
+ }
+ }
+ return ret;
+}
+
static jbyteArray android_os_Parcel_readBlob(JNIEnv* env, jclass clazz, jlong nativePtr)
{
jbyteArray ret = NULL;
@@ -757,7 +779,7 @@
// @FastNative
{"nativeRestoreAllowFds", "(JZ)V", (void*)android_os_Parcel_restoreAllowFds},
- {"nativeWriteByteArray", "(J[BII)V", (void*)android_os_Parcel_writeNative},
+ {"nativeWriteByteArray", "(J[BII)V", (void*)android_os_Parcel_writeByteArray},
{"nativeWriteBlob", "(J[BII)V", (void*)android_os_Parcel_writeBlob},
// @FastNative
{"nativeWriteInt", "(JI)V", (void*)android_os_Parcel_writeInt},
@@ -772,6 +794,7 @@
{"nativeWriteFileDescriptor", "(JLjava/io/FileDescriptor;)J", (void*)android_os_Parcel_writeFileDescriptor},
{"nativeCreateByteArray", "(J)[B", (void*)android_os_Parcel_createByteArray},
+ {"nativeReadByteArray", "(J[BI)Z", (void*)android_os_Parcel_readByteArray},
{"nativeReadBlob", "(J)[B", (void*)android_os_Parcel_readBlob},
// @FastNative
{"nativeReadInt", "(J)I", (void*)android_os_Parcel_readInt},
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index e8c5771..f34b138 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -356,6 +356,7 @@
case SP_FOREGROUND:
case SP_AUDIO_APP:
case SP_AUDIO_SYS:
+ case SP_RT_APP:
filename = "/dev/cpuset/foreground/cpus";
break;
case SP_TOP_APP:
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 2eada3e..831393b 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -47,7 +47,7 @@
class NativeDisplayEventReceiver : public DisplayEventDispatcher {
public:
NativeDisplayEventReceiver(JNIEnv* env,
- jobject receiverWeak, const sp<MessageQueue>& messageQueue);
+ jobject receiverWeak, const sp<MessageQueue>& messageQueue, jint vsyncSource);
void dispose();
@@ -65,8 +65,9 @@
NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env,
- jobject receiverWeak, const sp<MessageQueue>& messageQueue) :
- DisplayEventDispatcher(messageQueue->getLooper()),
+ jobject receiverWeak, const sp<MessageQueue>& messageQueue, jint vsyncSource) :
+ DisplayEventDispatcher(messageQueue->getLooper(),
+ static_cast<ISurfaceComposer::VsyncSource>(vsyncSource)),
mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
mMessageQueue(messageQueue) {
ALOGV("receiver %p ~ Initializing display event receiver.", this);
@@ -113,7 +114,7 @@
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
- jobject messageQueueObj) {
+ jobject messageQueueObj, jint vsyncSource) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
if (messageQueue == NULL) {
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
@@ -121,7 +122,7 @@
}
sp<NativeDisplayEventReceiver> receiver = new NativeDisplayEventReceiver(env,
- receiverWeak, messageQueue);
+ receiverWeak, messageQueue, vsyncSource);
status_t status = receiver->initialize();
if (status) {
String8 message;
@@ -156,7 +157,7 @@
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "nativeInit",
- "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;)J",
+ "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;I)J",
(void*)nativeInit },
{ "nativeDispose",
"(J)V",
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 5839fd5..bde9134 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -135,6 +135,7 @@
case PublicFormat::DEPTH16:
return HAL_PIXEL_FORMAT_Y16;
case PublicFormat::RAW_SENSOR:
+ case PublicFormat::RAW_DEPTH:
return HAL_PIXEL_FORMAT_RAW16;
default:
// Most formats map 1:1
@@ -149,6 +150,7 @@
return HAL_DATASPACE_V0_JFIF;
case PublicFormat::DEPTH_POINT_CLOUD:
case PublicFormat::DEPTH16:
+ case PublicFormat::RAW_DEPTH:
return HAL_DATASPACE_DEPTH;
case PublicFormat::RAW_SENSOR:
case PublicFormat::RAW_PRIVATE:
@@ -182,8 +184,12 @@
// Enums overlap in both name and value
return static_cast<PublicFormat>(format);
case HAL_PIXEL_FORMAT_RAW16:
- // Name differs, though value is the same
- return PublicFormat::RAW_SENSOR;
+ switch (dataSpace) {
+ case HAL_DATASPACE_DEPTH:
+ return PublicFormat::RAW_DEPTH;
+ default:
+ return PublicFormat::RAW_SENSOR;
+ }
case HAL_PIXEL_FORMAT_RAW_OPAQUE:
// Name differs, though value is the same
return PublicFormat::RAW_PRIVATE;
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 8b82314..97aa562 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -166,7 +166,7 @@
buffer->getWidth(),
buffer->getHeight(),
buffer->getPixelFormat(),
- buffer->getUsage(),
+ (jint)buffer->getUsage(),
(jlong)buffer.get());
}
@@ -221,11 +221,20 @@
return NULL;
}
}
+
+ sk_sp<SkColorSpace> colorSpace;
+ if (screenshot->getDataSpace() == HAL_DATASPACE_DISPLAY_P3) {
+ colorSpace = SkColorSpace::MakeRGB(
+ SkColorSpace::kSRGB_RenderTargetGamma, SkColorSpace::kDCIP3_D65_Gamut);
+ } else {
+ colorSpace = SkColorSpace::MakeSRGB();
+ }
+
SkImageInfo screenshotInfo = SkImageInfo::Make(screenshot->getWidth(),
screenshot->getHeight(),
colorType,
alphaType,
- GraphicsJNI::defaultColorSpace());
+ colorSpace);
const size_t rowBytes =
screenshot->getStride() * android::bytesPerPixel(screenshot->getFormat());
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 438b123..2657140 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -581,18 +581,6 @@
mObserver->decStrong(nullptr);
}
-static jboolean android_view_ThreadedRenderer_supportsOpenGL(JNIEnv* env, jobject clazz) {
- char prop[PROPERTY_VALUE_MAX];
- if (property_get("ro.kernel.qemu", prop, NULL) == 0) {
- // not in the emulator
- return JNI_TRUE;
- }
- // In the emulator this property will be set > 0 when OpenGL ES 2.0 is
- // enabled, 0 otherwise. On old emulator versions it will be undefined.
- property_get("qemu.gles", prop, "0");
- return atoi(prop) > 0 ? JNI_TRUE : JNI_FALSE;
-}
-
static void android_view_ThreadedRenderer_rotateProcessStatsBuffer(JNIEnv* env, jobject clazz) {
RenderProxy::rotateProcessStatsBuffer();
}
@@ -693,6 +681,12 @@
proxy->setOpaque(opaque);
}
+static void android_view_ThreadedRenderer_setWideGamut(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jboolean wideGamut) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ proxy->setWideGamut(wideGamut);
+}
+
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
LOG_ALWAYS_FATAL_IF(frameInfoSize != UI_THREAD_FRAME_INFO_SIZE,
@@ -979,7 +973,6 @@
const char* const kClassPathName = "android/view/ThreadedRenderer";
static const JNINativeMethod gMethods[] = {
- { "nSupportsOpenGL", "()Z", (void*) android_view_ThreadedRenderer_supportsOpenGL },
{ "nRotateProcessStatsBuffer", "()V", (void*) android_view_ThreadedRenderer_rotateProcessStatsBuffer },
{ "nSetProcessStatsBuffer", "(I)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer },
{ "nGetRenderThreadTid", "(J)I", (void*) android_view_ThreadedRenderer_getRenderThreadTid },
@@ -995,6 +988,7 @@
{ "nSetup", "(JFII)V", (void*) android_view_ThreadedRenderer_setup },
{ "nSetLightCenter", "(JFFF)V", (void*) android_view_ThreadedRenderer_setLightCenter },
{ "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque },
+ { "nSetWideGamut", "(JZ)V", (void*) android_view_ThreadedRenderer_setWideGamut },
{ "nSyncAndDrawFrame", "(J[JI)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
{ "nDestroy", "(JJ)V", (void*) android_view_ThreadedRenderer_destroy },
{ "nRegisterAnimatingRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_registerAnimatingRenderNode },
diff --git a/core/jni/com_google_android_gles_jni_EGLImpl.cpp b/core/jni/com_google_android_gles_jni_EGLImpl.cpp
index 1ac05f9..c00d698 100644
--- a/core/jni/com_google_android_gles_jni_EGLImpl.cpp
+++ b/core/jni/com_google_android_gles_jni_EGLImpl.cpp
@@ -45,7 +45,6 @@
static jfieldID gDisplay_EGLDisplayFieldID;
static jfieldID gContext_EGLContextFieldID;
static jfieldID gSurface_EGLSurfaceFieldID;
-static jfieldID gSurface_NativePixelRefFieldID;
static jfieldID gConfig_EGLConfigFieldID;
static inline EGLDisplay getDisplay(JNIEnv* env, jobject o) {
@@ -84,7 +83,6 @@
jclass surface_class = _env->FindClass("com/google/android/gles_jni/EGLSurfaceImpl");
gSurface_EGLSurfaceFieldID = _env->GetFieldID(surface_class, "mEGLSurface", "J");
- gSurface_NativePixelRefFieldID = _env->GetFieldID(surface_class, "mNativePixelRef", "J");
}
static const jint gNull_attrib_base[] = {EGL_NONE};
@@ -398,15 +396,6 @@
}
EGLDisplay dpy = getDisplay(_env, display);
EGLSurface sur = getSurface(_env, surface);
-
- if (sur) {
- SkPixelRef* ref = (SkPixelRef*)(_env->GetLongField(surface,
- gSurface_NativePixelRefFieldID));
- if (ref) {
- ref->unlockPixels();
- SkSafeUnref(ref);
- }
- }
return EglBoolToJBool(eglDestroySurface(dpy, sur));
}
diff --git a/core/jni/include/android_runtime/android_view_Surface.h b/core/jni/include/android_runtime/android_view_Surface.h
index 2641ab8..36487b3 100644
--- a/core/jni/include/android_runtime/android_view_Surface.h
+++ b/core/jni/include/android_runtime/android_view_Surface.h
@@ -53,6 +53,7 @@
RGBA_1010102 = 0x2b,
JPEG = 0x100,
DEPTH_POINT_CLOUD = 0x101,
+ RAW_DEPTH = 0x1002, // @hide
YV12 = 0x32315659,
Y8 = 0x20203859, // @hide
Y16 = 0x20363159, // @hide
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 2042bf62..0ee71bd 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -22,7 +22,6 @@
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/service/appwidget.proto";
import "frameworks/base/core/proto/android/service/battery.proto";
-import "frameworks/base/core/proto/android/service/graphicsstats.proto";
import "frameworks/base/core/proto/android/service/fingerprint.proto";
import "frameworks/base/core/proto/android/service/diskstats.proto";
import "frameworks/base/core/proto/android/service/netstats.proto";
@@ -66,5 +65,4 @@
android.service.notification.NotificationServiceDumpProto notification = 3004;
android.service.pm.PackageServiceDumpProto package = 3008;
android.service.power.PowerServiceDumpProto power = 3009;
- android.service.GraphicsStatsServiceDumpProto graphicsstats = 3005;
}
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
index 6dbfe48..b8679b0 100644
--- a/core/proto/android/service/graphicsstats.proto
+++ b/core/proto/android/service/graphicsstats.proto
@@ -20,6 +20,7 @@
option java_multiple_files = true;
option java_outer_classname = "GraphicsStatsServiceProto";
+option optimize_for = LITE_RUNTIME;
message GraphicsStatsServiceDumpProto {
repeated GraphicsStatsProto stats = 1;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f8044e2..004ecfc 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -285,6 +285,7 @@
<protected-broadcast android:name="android.btopp.intent.action.STOP_HANDOVER_TRANSFER" />
<protected-broadcast android:name="android.nfc.handover.intent.action.HANDOVER_SEND" />
<protected-broadcast android:name="android.nfc.handover.intent.action.HANDOVER_SEND_MULTIPLE" />
+ <protected-broadcast android:name="com.android.nfc.handover.action.CANCEL_HANDOVER_TRANSFER" />
<protected-broadcast android:name="android.intent.action.CLEAR_DNS_CACHE" />
<protected-broadcast android:name="android.intent.action.PROXY_CHANGE" />
@@ -1321,6 +1322,27 @@
<permission android:name="android.permission.NETWORK_SETTINGS"
android:protectionLevel="signature" />
+ <!-- #SystemApi @hide Allows applications to access information about LoWPAN interfaces.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.ACCESS_LOWPAN_STATE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- #SystemApi @hide Allows applications to change LoWPAN connectivity state.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.CHANGE_LOWPAN_STATE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- #SystemApi @hide Allows applications to read LoWPAN credential.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.READ_LOWPAN_CREDENTIAL"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- #SystemApi @hide Allows a service to register or unregister
+ new LoWPAN interfaces.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_LOWPAN_INTERFACES"
+ android:protectionLevel="signature|privileged" />
+
<!-- ======================================= -->
<!-- Permissions for short range, peripheral networks -->
<!-- ======================================= -->
@@ -1676,6 +1698,21 @@
<permission android:name="android.permission.BIND_IMS_SERVICE"
android:protectionLevel="signature|privileged" />
+ <!-- Allows an application to manage embedded subscriptions (those on a eUICC) through
+ EuiccManager APIs.
+ <p>Protection level: signature|privileged|development
+ TODO(b/35851809): Mark this as a SystemApi.
+ @hide -->
+ <permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- Must be required by an EuiccService to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ TODO(b/35851809): Mark this as a SystemApi.
+ @hide -->
+ <permission android:name="android.permission.BIND_EUICC_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- ================================== -->
<!-- Permissions for sdcard interaction -->
@@ -1773,6 +1810,10 @@
<permission android:name="android.permission.MANAGE_USERS"
android:protectionLevel="signature|privileged" />
+ <!-- @hide Allows an application to configure the assist gesture -->
+ <permission android:name="android.permission.CONFIGURE_ASSIST_GESTURE"
+ android:protectionLevel="signature" />
+
<!-- @hide Allows an application to create, remove users and get the list of
users on the device. Applications holding this permission can only create restricted,
guest, managed, demo, and ephemeral users. For creating other kind of users,
@@ -1885,6 +1926,11 @@
android:description="@string/permdesc_useDataInBackground"
android:protectionLevel="signature" />
+ <!-- @hide Allows an application to set display offsets for the screen.
+ This permission is not available to third party applications. -->
+ <permission android:name="android.permission.SET_DISPLAY_OFFSET"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows a companion app to run in the background.
<p>Protection level: normal
-->
@@ -1901,10 +1947,6 @@
android:description="@string/permdesc_useDataInBackground"
android:protectionLevel="normal" />
- <!-- @hide Allows an application to set display offsets for the screen.
- This permission is not available to third party applications. -->
- <permission android:name="android.permission.SET_DISPLAY_OFFSET"
- android:protectionLevel="signature|privileged" />
<!-- ================================== -->
<!-- Permissions affecting the system wallpaper -->
@@ -2199,6 +2241,22 @@
<permission android:name="android.permission.UPDATE_CONFIG"
android:protectionLevel="signature|privileged" />
+ <!-- Allows a time zone rule updater application to request
+ the system installs / uninstalls timezone rules.
+ <p>An application requesting this permission is responsible for
+ verifying the source and integrity of the update before passing
+ it off to the installer components.
+ @hide -->
+ <permission android:name="android.permission.UPDATE_TIME_ZONE_RULES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by a time zone rule updater application,
+ to ensure that only the system can trigger it.
+ @hide -->
+ <permission android:name="android.permission.TRIGGER_TIME_ZONE_RULES_CHECK"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.TRIGGER_TIME_ZONE_RULES_CHECK"/>
+
<!-- Allows the system to reset throttling in shortcut manager.
@hide -->
<permission android:name="android.permission.RESET_SHORTCUT_MANAGER_THROTTLING"
@@ -2733,6 +2791,13 @@
<permission android:name="android.permission.ACCESS_INPUT_FLINGER"
android:protectionLevel="signature" />
+ <!-- Allows an application to disable/enable input devices.
+ Could be used to prevent unwanted touch events
+ on a touchscreen, for example during swimming or rain.
+ @hide -->
+ <permission android:name="android.permission.DISABLE_INPUT_DEVICE"
+ android:protectionLevel="signature" />
+
<!-- Allows an application to configure and connect to Wifi displays
@hide -->
<permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY"
diff --git a/core/res/res/drawable/ic_reply_notification.xml b/core/res/res/drawable/ic_reply_notification.xml
new file mode 100644
index 0000000..88b8c5b
--- /dev/null
+++ b/core/res/res/drawable/ic_reply_notification.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="10dp"
+ android:height="10dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M10.0,9.0L10.0,5.0l-7.0,7.0 7.0,7.0l0.0,-4.1c5.0,0.0 8.5,1.6 11.0,5.1 -1.0,-5.0 -4.0,-10.0 -11.0,-11.0z"/>
+ <path
+ android:pathData="M0 0h24v24H0z"
+ android:fillColor="#00000000"/>
+</vector>
diff --git a/core/res/res/drawable/notification_reply_background.xml b/core/res/res/drawable/notification_reply_background.xml
new file mode 100644
index 0000000..08e22f2
--- /dev/null
+++ b/core/res/res/drawable/notification_reply_background.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <solid
+ android:color="#ff757575"/>
+ <size
+ android:width="16dp"
+ android:height="16dp"/>
+</shape>
diff --git a/core/res/res/drawable/sym_def_app_icon.xml b/core/res/res/drawable/sym_def_app_icon.xml
index 9c02402..129d38a 100644
--- a/core/res/res/drawable/sym_def_app_icon.xml
+++ b/core/res/res/drawable/sym_def_app_icon.xml
@@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
- <background android:drawable="@android:color/white" />
+ <background android:drawable="@drawable/sym_def_app_icon_background" />
<foreground>
- <inset android:inset="27.7%">
- <bitmap android:src="@mipmap/sym_def_app_icon"/>
- </inset>
+ <bitmap android:src="@mipmap/sym_def_app_icon_foreground"/>
</foreground>
</adaptive-icon>
diff --git a/core/res/res/drawable/sym_def_app_icon_background.xml b/core/res/res/drawable/sym_def_app_icon_background.xml
new file mode 100644
index 0000000..5755210
--- /dev/null
+++ b/core/res/res/drawable/sym_def_app_icon_background.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector
+ android:height="108dp"
+ android:width="108dp"
+ android:viewportHeight="108.0"
+ android:viewportWidth="108.0"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/material_deep_teal_500"
+ android:pathData="M0,0h108v108h-108z"
+ android:strokeColor="#66FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M19,0L19,108"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M9,0L9,108"
+ android:strokeColor="#66FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M39,0L39,108"
+ android:strokeColor="#66FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M29,0L29,108"
+ android:strokeColor="#66FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M59,0L59,108"
+ android:strokeColor="#66FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M49,0L49,108"
+ android:strokeColor="#66FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M79,0L79,108"
+ android:strokeColor="#66FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M69,0L69,108"
+ android:strokeColor="#66FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M89,0L89,108"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M99,0L99,108"
+ android:strokeColor="#66FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,89L108,89"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,99L108,99"
+ android:strokeColor="#66FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,69L108,69"
+ android:strokeColor="#66FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,79L108,79"
+ android:strokeColor="#66FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,49L108,49"
+ android:strokeColor="#66FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,59L108,59"
+ android:strokeColor="#66FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,29L108,29"
+ android:strokeColor="#66FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,39L108,39"
+ android:strokeColor="#66FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,19L108,19"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,9L108,9"
+ android:strokeColor="#66FFFFFF" android:strokeWidth="0.8"/>
+</vector>
+
diff --git a/core/res/res/layout/notification_material_media_action.xml b/core/res/res/layout/notification_material_media_action.xml
index 19a6f84..900ca2d 100644
--- a/core/res/res/layout/notification_material_media_action.xml
+++ b/core/res/res/layout/notification_material_media_action.xml
@@ -15,11 +15,12 @@
~ limitations under the License
-->
-<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
+<ImageButton
+ xmlns:android="http://schemas.android.com/apk/res/android"
style="@android:style/Widget.Material.Button.Borderless.Small"
android:id="@+id/action0"
- android:layout_width="48dp"
- android:layout_height="48dp"
+ android:layout_width="@dimen/media_notification_action_button_size"
+ android:layout_height="@dimen/media_notification_action_button_size"
android:paddingBottom="8dp"
android:paddingTop="8dp"
android:paddingStart="8dp"
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 5a2bf4e..f0c980c 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -28,15 +28,15 @@
android:id="@+id/icon"
android:layout_width="?attr/notificationHeaderIconSize"
android:layout_height="?attr/notificationHeaderIconSize"
- android:layout_marginEnd="3dp"
+ android:layout_marginEnd="@dimen/notification_header_icon_margin_end"
/>
<TextView
android:id="@+id/app_name_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="3dp"
- android:layout_marginEnd="2dp"
+ android:layout_marginStart="@dimen/notification_header_app_name_margin_start"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:singleLine="true"
/>
<TextView
@@ -44,8 +44,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="2dp"
- android:layout_marginEnd="2dp"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:text="@string/notification_header_divider_symbol"
android:visibility="gone"/>
<TextView
@@ -53,8 +53,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="2dp"
- android:layout_marginEnd="2dp"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:visibility="gone"
android:singleLine="true"/>
<TextView
@@ -62,8 +62,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?attr/notificationHeaderTextAppearance"
- android:layout_marginStart="2dp"
- android:layout_marginEnd="2dp"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:text="@string/notification_header_divider_symbol"
android:singleLine="true"
android:visibility="gone"/>
@@ -73,8 +73,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:layout_marginStart="2dp"
- android:layout_marginEnd="2dp"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:showRelative="true"
android:singleLine="true"
android:visibility="gone" />
@@ -82,21 +82,22 @@
android:id="@+id/chronometer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="2dp"
- android:layout_marginEnd="2dp"
+ android:layout_marginStart="@dimen/notification_header_separating_margin"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:layout="@layout/notification_template_part_chronometer"
android:visibility="gone"
/>
<com.android.internal.widget.NotificationExpandButton
android:id="@+id/expand_button"
android:background="@null"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingTop="1dp"
+ android:layout_width="@dimen/notification_header_expand_icon_size"
+ android:layout_height="@dimen/notification_header_expand_icon_size"
+ android:paddingTop="@dimen/notification_expand_button_padding_top"
android:visibility="gone"
android:contentDescription="@string/expand_button_content_description_collapsed"
/>
- <ImageView android:id="@+id/profile_badge"
+ <ImageView
+ android:id="@+id/profile_badge"
android:layout_width="@dimen/notification_badge_size"
android:layout_height="@dimen/notification_badge_size"
android:layout_gravity="center"
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index ccd26fb..353a1a5 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -15,12 +15,12 @@
~ limitations under the License
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:tag="base"
- >
+ android:tag="base" >
<include layout="@layout/notification_template_header" />
<LinearLayout
android:id="@+id/notification_main_column"
@@ -31,19 +31,14 @@
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:layout_marginTop="@dimen/notification_content_margin_top"
android:layout_marginBottom="@dimen/notification_content_margin_bottom"
- android:orientation="vertical"
- >
+ android:orientation="vertical" >
<include layout="@layout/notification_template_part_line1" />
<include layout="@layout/notification_template_text" />
+ <include
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_progress_bar_height"
+ android:layout_marginTop="@dimen/notification_progress_margin_top"
+ layout="@layout/notification_template_progress" />
</LinearLayout>
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:layout_marginStart="@dimen/notification_content_margin_start"
- android:layout_marginBottom="15dp"
- android:layout_marginEnd="@dimen/notification_content_margin_end" >
- <include layout="@layout/notification_template_progress" />
- </FrameLayout>
<include layout="@layout/notification_template_right_icon" />
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml
index 8b0b476..6b1049a 100644
--- a/core/res/res/layout/notification_template_material_big_base.xml
+++ b/core/res/res/layout/notification_template_material_big_base.xml
@@ -20,51 +20,42 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
- android:tag="big"
- >
+ android:tag="big" >
<LinearLayout
android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/notification_action_list_height"
- android:orientation="vertical"
- >
+ android:orientation="vertical" >
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="top"
- >
+ android:layout_gravity="top" >
<include layout="@layout/notification_template_header" />
<LinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="top"
+ android:layout_height="wrap_content"
android:layout_marginStart="@dimen/notification_content_margin_start"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:layout_marginTop="@dimen/notification_content_margin_top"
android:layout_marginBottom="@dimen/notification_content_margin_bottom"
- android:orientation="vertical"
- >
+ android:orientation="vertical" >
<include layout="@layout/notification_template_part_line1" />
<include layout="@layout/notification_template_text" />
+ <include
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_progress_bar_height"
+ android:layout_marginTop="@dimen/notification_progress_margin_top"
+ layout="@layout/notification_template_progress" />
</LinearLayout>
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:layout_marginStart="@dimen/notification_content_margin_start"
- android:layout_marginBottom="15dp"
- android:layout_marginEnd="@dimen/notification_content_margin_end">
- <include layout="@layout/notification_template_progress" />
- </FrameLayout>
<include layout="@layout/notification_template_right_icon" />
</FrameLayout>
- <ViewStub android:layout="@layout/notification_material_reply_text"
- android:id="@+id/notification_material_reply_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- />
+ <ViewStub
+ android:layout="@layout/notification_material_reply_text"
+ android:id="@+id/notification_material_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
</LinearLayout>
<include layout="@layout/notification_material_action_list" />
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml
index 532f28e..cc4dd387 100644
--- a/core/res/res/layout/notification_template_material_big_media.xml
+++ b/core/res/res/layout/notification_template_material_big_media.xml
@@ -16,7 +16,8 @@
-->
<!-- Layout for the expanded media notification -->
-<com.android.internal.widget.MediaNotificationView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.MediaNotificationView
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -32,7 +33,7 @@
/>
<include layout="@layout/notification_template_header"
android:layout_width="match_parent"
- android:layout_height="53dp"
+ android:layout_height="@dimen/media_notification_header_height"
android:layout_gravity="start"/>
<LinearLayout
android:layout_width="match_parent"
@@ -55,15 +56,9 @@
</LinearLayout>
<LinearLayout
android:id="@+id/media_actions"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="-21dp"
- android:paddingStart="8dp"
- android:paddingBottom="12dp"
- android:gravity="top"
android:orientation="horizontal"
android:layoutDirection="ltr"
- >
+ style="@style/NotificationMediaActionContainer" >
<!-- media buttons will be added here -->
</LinearLayout>
</LinearLayout>
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index 4be53e0..2c69b90 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -31,8 +31,8 @@
android:scaleType="centerCrop"
/>
<include layout="@layout/notification_template_header"
- android:layout_width="fill_parent"
- android:layout_height="53dp" />
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/media_notification_header_height" />
<LinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
@@ -61,7 +61,7 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginStart="10dp"
- android:layout_marginBottom="12dp"
+ android:layout_marginBottom="@dimen/media_notification_actions_padding_bottom"
android:layoutDirection="ltr"
android:orientation="horizontal"
>
diff --git a/core/res/res/layout/notification_template_progressbar.xml b/core/res/res/layout/notification_template_progressbar.xml
index 61480b8..98e2c03 100644
--- a/core/res/res/layout/notification_template_progressbar.xml
+++ b/core/res/res/layout/notification_template_progressbar.xml
@@ -18,6 +18,6 @@
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/progress"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="@dimen/notification_progress_bar_height"
style="@style/Widget.Material.Light.ProgressBar.Horizontal"
/>
diff --git a/core/res/res/layout/notification_template_right_icon.xml b/core/res/res/layout/notification_template_right_icon.xml
index 65a5015..aa4e05b 100644
--- a/core/res/res/layout/notification_template_right_icon.xml
+++ b/core/res/res/layout/notification_template_right_icon.xml
@@ -15,13 +15,27 @@
~ limitations under the License
-->
-<ImageView android:id="@+id/right_icon" xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="40dp"
- android:layout_height="40dp"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
- android:layout_marginTop="36dp"
- android:layout_gravity="top|end"
- android:scaleType="centerCrop"
- />
-
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/right_icon_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|end">
+ <ImageView android:id="@+id/right_icon"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_gravity="top|end"
+ android:layout_marginTop="36dp"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:scaleType="centerCrop"/>
+ <ImageView android:id="@+id/reply_icon_action"
+ android:layout_width="16dp"
+ android:layout_height="16dp"
+ android:layout_gravity="top|end"
+ android:layout_marginTop="64dp"
+ android:layout_marginEnd="12dp"
+ android:background="@drawable/notification_reply_background"
+ android:src="@drawable/ic_reply_notification"
+ android:scaleType="center"
+ visiblity="gone"/>
+</FrameLayout>
diff --git a/core/res/res/mipmap-hdpi/sym_def_app_icon_foreground.png b/core/res/res/mipmap-hdpi/sym_def_app_icon_foreground.png
new file mode 100644
index 0000000..4e526c9
--- /dev/null
+++ b/core/res/res/mipmap-hdpi/sym_def_app_icon_foreground.png
Binary files differ
diff --git a/core/res/res/mipmap-hdpi/sym_def_app_icon_maskable.png b/core/res/res/mipmap-hdpi/sym_def_app_icon_maskable.png
deleted file mode 100644
index bf58413..0000000
--- a/core/res/res/mipmap-hdpi/sym_def_app_icon_maskable.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/mipmap-mdpi/sym_def_app_icon_foreground.png b/core/res/res/mipmap-mdpi/sym_def_app_icon_foreground.png
new file mode 100644
index 0000000..2c38c71
--- /dev/null
+++ b/core/res/res/mipmap-mdpi/sym_def_app_icon_foreground.png
Binary files differ
diff --git a/core/res/res/mipmap-mdpi/sym_def_app_icon_maskable.png b/core/res/res/mipmap-mdpi/sym_def_app_icon_maskable.png
deleted file mode 100644
index 47f6f81..0000000
--- a/core/res/res/mipmap-mdpi/sym_def_app_icon_maskable.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/mipmap-xhdpi/sym_def_app_icon_foreground.png b/core/res/res/mipmap-xhdpi/sym_def_app_icon_foreground.png
new file mode 100644
index 0000000..072467e
--- /dev/null
+++ b/core/res/res/mipmap-xhdpi/sym_def_app_icon_foreground.png
Binary files differ
diff --git a/core/res/res/mipmap-xhdpi/sym_def_app_icon_maskable.png b/core/res/res/mipmap-xhdpi/sym_def_app_icon_maskable.png
deleted file mode 100644
index f60b896..0000000
--- a/core/res/res/mipmap-xhdpi/sym_def_app_icon_maskable.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/mipmap-xxhdpi/sym_def_app_icon_foreground.png b/core/res/res/mipmap-xxhdpi/sym_def_app_icon_foreground.png
new file mode 100644
index 0000000..78a6b7a
--- /dev/null
+++ b/core/res/res/mipmap-xxhdpi/sym_def_app_icon_foreground.png
Binary files differ
diff --git a/core/res/res/mipmap-xxhdpi/sym_def_app_icon_maskable.png b/core/res/res/mipmap-xxhdpi/sym_def_app_icon_maskable.png
deleted file mode 100644
index 6d4f315..0000000
--- a/core/res/res/mipmap-xxhdpi/sym_def_app_icon_maskable.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/mipmap-xxxhdpi/sym_def_app_icon_foreground.png b/core/res/res/mipmap-xxxhdpi/sym_def_app_icon_foreground.png
new file mode 100644
index 0000000..68ebe33
--- /dev/null
+++ b/core/res/res/mipmap-xxxhdpi/sym_def_app_icon_foreground.png
Binary files differ
diff --git a/core/res/res/mipmap-xxxhdpi/sym_def_app_icon_maskable.png b/core/res/res/mipmap-xxxhdpi/sym_def_app_icon_maskable.png
deleted file mode 100644
index 08e54d0..0000000
--- a/core/res/res/mipmap-xxxhdpi/sym_def_app_icon_maskable.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index f77980a..9b838d5 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB vir MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Gekoppel aan \'n USB-toebehoorsel"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Tik vir meer opsies."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Oudiobykomstigheid word nie gesteun nie"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Tik vir meer inligting"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB-ontfouter gekoppel"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Tik om USB-ontfouting te deaktiveer."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Kies om USB-ontfouting te deaktiveer."</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index bbcc81d..00e3ece 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"ዩኤስቢ ለMIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"ለUSB ተቀጥላ ተያይዟል"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"ለተጨማሪ አማራጮች መታ ያድርጉ።"</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"የኦዲዮ መለዋወጫ አይደገፍም"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"ተጨማሪ መረጃ ለማግኘት መታ ያድርጉ"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB አድስ ተያይዟል"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"የዩኤስቢ ማረሚያን ለማሰናከል መታ ያድርጉ።"</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"USB ማረሚያ ላለማንቃት ምረጥ።"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index ae35122..158f4e8 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1276,6 +1276,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB لـ MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"الاتصال بجهاز USB ملحق"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"انقر للحصول على المزيد من الخيارات."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"ملحق الصوت غير متوافق"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"انقر للحصول على مزيد من المعلومات"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"تم توصيل تصحيح أخطاء USB"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"انقر لتعطيل تصحيح أخطاء USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"اختيار تعطيل تصحيح أخطاء USB."</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index b70ad39..2bce668 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI üçün USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB aksesuara qoşuldu"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Əlavə seçimlər üçün tıklayın."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Audio aksesuar dəstəklənmir"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Daha çox məlumat üçün klikləyin"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB sazlama qoşuludur"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB debaqı deaktivasiya etmək üçün tıklayın."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"USb debaqı deaktivasiya etməyi seçin."</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 06dc7e7..6203907 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -1210,6 +1210,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB za MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Povezano sa USB dodatkom"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Dodirnite za još opcija."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Dodatna oprema za audio sadržaj nije podržana"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Dodirnite za više informacija"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Otklanjanje grešaka sa USB-a je uspostavljeno"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Dodirnite da biste onemogućili otklanjanje grešaka sa USB-a."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Izaberite da biste onemogućili otklanjanja grešaka sa USB-a."</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index fbeb50c..db163bf 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -1232,6 +1232,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB для MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Падключаны да USB-прылады"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Дакраніцеся, каб атрымаць іншыя параметры."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Аксесуар аўдыя не падтрымліваецца"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Націсніце, каб атрымаць дадатковую інфармацыю"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Адладка па USB падключана"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Дакраніцеся, каб адключыць адладку па USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Выберыце, каб адключыць адладку USB."</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index c54379a..95e41a1 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB за MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Установена е връзка с аксесоар за USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Докоснете за още опции."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Аудиоаксесоарът не се поддържа"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Докоснете за още информация"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Отстраняване на грешки през USB"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Докоснете, за да деактивирате отстраняването на грешки през USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Изберете, за да деактивирате отстраняването на грешки през USB."</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index c3c8e46..bd5a5b8 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI এর জন্য USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"একটি USB যন্ত্রাংশতে সংযুক্ত হয়েছে"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"আরো বিকল্পের জন্য আলতো চাপুন৷"</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"অডিও অ্যাক্সেসরি সমর্থিত নয়"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"আরও তথ্যের জন্য ট্যাপ করুন"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB ডিবাগিং সংযুক্ত হয়েছে"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB ডিবাগিং অক্ষম করতে আলতো চাপুন৷"</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 3c1dbdb..63a3945 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -1214,6 +1214,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB za MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Uspostavljena veza sa USB pohranom"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Dodirnite za više opcija."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Audio pribor nije podržan"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Dodirnite za više informacija"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Otklanjanje grešaka putem uređaja spojenog na USB je uspostavljeno"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Dodirnite da onemogućite otklanjanje grešaka putem uređaja spojenog na USB."</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 8804d48..c5f8d55 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB per a MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Connectat a un accessori USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Toca per veure més opcions."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"L\'accessori d\'àudio no és compatible"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Toca per obtenir més informació"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Depuració USB activada"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Toca per desactivar la depuració USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Selecciona per desactivar la depuració USB"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 785e32f..b77f6c2 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1232,6 +1232,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB v režimu MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Připojeno k perifernímu zařízení USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Klepnutím zobrazíte další možnosti."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Zvukové příslušenství není podporováno"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Klepnutím zobrazíte další informace"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Ladění přes USB připojeno"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Klepnutím zakážete ladění USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Vyberte, chcete-li zakázat ladění USB."</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index f9bfc51..87ad291 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB til MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Tilsluttet et USB-ekstraudstyr"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Tryk for at se flere muligheder."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Lydtilbehøret understøttes ikke"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Tryk for at få flere oplysninger"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB-fejlretning er tilsluttet"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Tryk for at deaktivere fejlretning via USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Vælg for at deaktivere USB-fejlretning."</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index d90f6f3..be1c307 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB für MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Mit USB-Zubehör verbunden"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Für weitere Optionen tippen."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Audiozubehör wird nicht unterstützt"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Tippen, um weitere Informationen zu erhalten"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB-Debugging aktiviert"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Zum Deaktivieren von USB-Debugging tippen."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"USB-Debugging deaktivieren: auswählen"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index ff86202..70286ca 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB για MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Σύνδεση σε αξεσουάρ USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Πατήστε για περισσότερες επιλογές."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Το εξάρτημα ήχου δεν υποστηρίζεται"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Πατήστε για να δείτε περισσότερες πληροφορίες"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Συνδέθηκε ο εντοπισμός σφαλμάτων USB"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Πατήστε για απενεργοποίηση του εντοπισμού σφαλμάτων USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Επιλογή για απενεργοποίηση του εντοπισμού σφαλμάτων USB."</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 5b03ddb..e3b72b0 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB for MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Connected to a USB accessory"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Tap for more options."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Audio accessory not supported"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Tap for more info"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB debugging connected"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Tap to disable USB debugging."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Select to disable USB debugging."</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 5b03ddb..e3b72b0 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB for MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Connected to a USB accessory"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Tap for more options."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Audio accessory not supported"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Tap for more info"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB debugging connected"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Tap to disable USB debugging."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Select to disable USB debugging."</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 5b03ddb..e3b72b0 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB for MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Connected to a USB accessory"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Tap for more options."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Audio accessory not supported"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Tap for more info"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB debugging connected"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Tap to disable USB debugging."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Select to disable USB debugging."</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index d85c4b7..cdda07b 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB para MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Conectado a un accesorio USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Presiona para ver más opciones."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"El accesorio de audio no es compatible"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Presiona para obtener más información"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Depuración por USB conectada"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Presiona para inhabilitar la depuración por USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Seleccionar para desactivar la depuración por USB"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index a989f59..d2dd87b 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB para MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Conectado a un accesorio USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Toca para ver más opciones."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Accesorio de audio no compatible"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Toca para obtener más información"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Depuración USB habilitada"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Toca para inhabilitar la depuración USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Seleccionar para inhabilitar la depuración USB"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 7d58cd1..c603b1d 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB MIDI jaoks"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Ühendatud USB-lisaseadmega"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Puudutage lisavalikute nägemiseks."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Helitarvikut ei toetata"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Puudutage lisateabe saamiseks"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB-silumine ühendatud"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Puudutage USB-silumise keelamiseks."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Valige USB silumise keelamiseks"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 05f6dde..7d0ddaa 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI modurako USBa"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB osagarri batera konektatuta"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Sakatu aukera gehiago ikusteko."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Ez da onartzen audio-osagarri hori"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Informazio gehiago lortzeko, sakatu hau"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB arazketa konektatuta"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Sakatu USB arazketa desgaitzeko."</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 0b1f62f..dc8c5fd 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB برای MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"به یک وسیله جانبی USB وصل شده است"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"برای گزینههای بیشتر ضربه بزنید."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"وسیله جانبی صوتی پشتیبانی نمیشود"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"برای اطلاعات بیشتر ضربه بزنید"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"اشکالزدایی USB متصل شد"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"برای غیرفعال کردن اشکالزدایی USB ضربه بزنید."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"انتخاب کنید تا رفع عیب USB غیرفعال شود."</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index d0270c7..58783de 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB on MIDI-tilassa"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Liitetty USB-laitteeseen"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Näet lisää vaihtoehtoja napauttamalla."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Äänilisälaitetta ei tueta."</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Saat lisätietoja napauttamalla."</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB-vianetsintä yhdistetty"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Poista USB-vianetsintä käytöstä napauttamalla."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Poista USB-vianetsintä käytöstä valitsemalla tämä."</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index dcb8f53..c527eba 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB pour MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Connecté à un accessoire USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Touchez pour afficher plus d\'options."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Accessoire audio non pris en charge"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Touchez l\'écran pour obtenir plus d\'information"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Débogage USB connecté"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Touchez pour désactiver le débogage USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Sélectionnez cette option pour désactiver le débogage USB."</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index ccaed18..2f5c44f 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB en mode MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Connecté à un accessoire USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Appuyez ici pour plus d\'options."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Accessoire audio non compatible"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Appuyer pour afficher plus d\'informations"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Débogage USB activé"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Appuyez ici pour désactiver le débogage USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Sélectionnez cette option pour désactiver le débogage USB."</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 16c9943..f733680 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB para MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Conectado a un accesorio USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Toca para ver máis opcións."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"O accesorio de audio non é compatible"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Toca para obter máis información"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Depuración USB conectada"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Toca para desactivar a depuración de erros de USB."</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 3fcbc97..341e17e 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI માટે USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB ઍક્સેસરીથી કનેક્ટ થયાં"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"વધુ વિકલ્પો માટે ટૅપ કરો."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"ઑડિયો ઍક્સેસરી સમર્થિત નથી"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"વધુ માહિતી માટે ટૅપ કરો"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB ડીબગિંગ કનેક્ટ થયું."</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB ડીબગિંગ અક્ષમ કરવા માટે ટૅપ કરો."</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index d8cab55..ba658e2 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI के लिए USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB सहायक सामग्री से कनेक्ट किया गया"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"अधिक विकल्पों के लिए टैप करें."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"ऑडियो एक्सेसरी इस पर काम नहीं करती है"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"अधिक जानकारी के लिए टैप करें"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB डीबग कनेक्ट किया गया"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB डीबग करना अक्षम करने के लिए टैप करें."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"USB डीबग करना अक्षम करने के लिए चुनें."</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 657456c..65e6708 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1210,6 +1210,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB za MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Spojen na USB pribor"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Dodirnite za više opcija."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Audiododatak nije podržan"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Dodirnite za više informacija"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Priključen je alat za otklanjanje pogrešaka USB-om"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Dodirnite da biste onemogućili otklanjanje pogrešaka putem USB-a."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Odaberite da biste onemogućili rješavanje programske pogreške na USB-u."</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 0e05296..9e47afc 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB MIDI-hez"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Csatlakoztatva egy USB-kiegészítőhöz"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Koppintson a további beállítások megjelenítéséhez."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Ez az audiotartozék nem támogatott"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Koppintson, ha további információra van szüksége"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB hibakereső csatlakoztatva"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Koppintson az USB fejlesztő mód kikapcsolásához."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Válassza ezt az USB hibakeresés kikapcsolásához."</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 22dbe67f..4a11c14 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI-ի USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Կապակցված է USB լրասարքի"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Հպեք՝ լրացուցիչ ընտրանքների համար:"</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Աուդիո սարքը չի աջակցվում"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Հպեք՝ լրացուցիչ տեղեկություններ ստանալու համար"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB վրիպազերծումը միացված է"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Հպեք՝ USB վրիպազերծումն անջատելու համար:"</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Ընտրել` USB կարգաբերումը կասեցնելու համար:"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 3c20118..9175507 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB untuk MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Tersambung ke aksesori USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Ketuk untuk opsi lainnya."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Aksesori audio tidak didukung"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Tap untuk info selengkapnya"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Debugging USB terhubung"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Ketuk untuk menonaktifkan debug USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Pilih untuk menonaktifkan debugging USB."</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 38bc24b..7694e9a 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB fyrir MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Tengt við USB-aukabúnað"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Ýttu til að sjá fleiri valkosti."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Hljóðaukabúnaður er ekki studdur"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Ýttu til að fá frekari upplýsingar"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB-villuleit tengd"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Ýttu til að slökkva á USB-villuleit."</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index be2dd22..e6fd76b 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB per la modalità MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Collegato a un accessorio USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Tocca per altre opzioni."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Accessorio audio non supportato"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Tocca per ulteriori informazioni"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Debug USB collegato"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Tocca per disattivare il debug USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Seleziona per disattivare il debug USB."</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 93d3df9..c6336fd 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1232,6 +1232,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB ל-MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"מחובר לאביזר USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"הקש לקבלת אפשרויות נוספות."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"אביזר אודיו אינו נתמך"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"הקש לקבלת מידע נוסף"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"ניפוי באגים של USB מחובר"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"הקש כדי להשבית ניפוי באגים של USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"בחר להשבית ניפוי באגים ב-USB."</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index d4745df..abfb474 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USBをMIDIに使用"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USBアクセサリを接続しました"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"タップしてその他のオプションを表示します。"</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"オーディオ アクセサリがサポートされていません"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"タップして詳細をご確認ください"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USBデバッグが接続されました"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"タップして USB デバッグを無効にします。"</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"USBデバッグを無効にする場合に選択します。"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index aa92609..b08bfa6 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB MIDI-სთვის"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"დაკავშირებულია USB აქსესუართან"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"შეეხეთ დამატებითი ვარიანტების სანახავად."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"აუდიო აქსესუარი მხარდაუჭერელია"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"შეეხეთ დამატებითი ინფორმაციისთვის"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB გამართვა შეერთებულია"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"შეეხეთ USB-გამართვის გასათიშად."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"მონიშნეთ რათა შეწყვიტოთ USB-ის გამართვა"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 50f136f..d80fdb5 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI режиміне арналған USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB жабдығына қосылған"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Қосымша опциялар үшін түртіңіз."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Аудио аксессуарға қолдау көрсетілмейді"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Қосымша ақпарат алу үшін түртіңіз"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB жөндеу қосылған"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB түзетуін өшіру үшін түртіңіз."</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 9022e3d..1e17005 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -1190,6 +1190,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB សម្រាប់ MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"បានភ្ជាប់ឧបករណ៍យូអេសប៊ី"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"ប៉ះសម្រាប់ជម្រើសជាច្រើនទៀត"</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"មិនស្គាល់គ្រឿងបរិក្ខារសំឡេងទេ"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"ចុចដើម្បីទទួលព័ត៌មានបន្ថែម"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"បានភ្ជាប់ការកែកំហុសយូអេសប៊ី"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"ប៉ះដើម្បីបិទដំណើរការកែកំហុសយូអេសប៊ី"</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"ជ្រើស ដើម្បីបិទការកែកំហុសយូអេសប៊ី។"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 6f0ed95..db06af7 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI ಗೆ USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB ಪರಿಕರಕ್ಕೆ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"ಹೆಚ್ಚಿನ ಆಯ್ಕೆಗಳಿಗೆ ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"ಆಡಿಯೋ ಪರಿಕರಗಳು ಬೆಂಬಲಿತವಾಗಿಲ್ಲ"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"ಹೆಚ್ಚಿನ ಮಾಹಿತಿಗಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB ಡೀಬಗಿಂಗ್ ಸಂಪರ್ಕ"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB ಡೀಬಗ್ ಮಾಡುವಿಕೆಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 5cf961b..1fdfb91 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI용 USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB 액세서리에 연결됨"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"옵션을 더 보려면 탭하세요."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"지원되지 않는 오디오 액세서리"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"자세한 내용은 탭하여 확인하세요."</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB 디버깅 연결됨"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB 디버깅을 사용하지 않으려면 탭하세요."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"USB 디버깅을 사용하지 않으려면 선택합니다."</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 460306c..b744f98 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI үчүн USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB аксессуарга байланышты"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Кошумча параметрлерди ачуу үчүн таптап коюңуз."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Аудио шайманы колдоого алынбайт"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Кеңири маалымат алуу үчүн таптап коюңуз"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Мүчүлүштүктөрдү USB аркылуу оңдоо иштетилген"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Мүчүлштктрдү USB аркл оңдну өчр үчн тийп коюңуз."</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 9bbb0a98..58872dc 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB ສຳລັບ MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"ເຊື່ອມຕໍ່ກັບອຸປະກອນເສີມ USB ແລ້ວ"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"ແຕະເພື່ອເບິ່ງຕົວເລືອກເພີ່ມເຕີມ."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"ບໍ່ຮອງຮັບອຸປະກອນເສີມສຽງ"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"ແຕະເພື່ອເບິ່ງຂໍ້ມູນ"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"ເຊື່ອມຕໍ່ການດີບັ໊ກຜ່ານ USB ແລ້ວ"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"ແຕະເພື່ອປິດການດີບັກຜ່ານ USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"ເລືອກເພື່ອປິດການດີບັ໊ກຜ່ານ USB."</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 8c6175e..c53e5ba 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1232,6 +1232,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB (MIDI)"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Prijungta prie USB priedo"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Palieskite, kad būtų rodoma daugiau parinkčių."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Garso įrašo priedas nepalaikomas"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Palieskite, kad sužinotumėte daugiau informacijos"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB derinimas prijungtas"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Palieskite, kad išjungtumėte USB derinimą."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Pasirinkite, kas išjungtumėte USB derinimą."</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index d090739..a3a91eb 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1210,6 +1210,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB savienojums MIDI režīmā"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Ir izveidots savienojums ar USB piederumu."</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Pieskarieties, lai skatītu citas iespējas."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Audio piederums netiek atbalstīts"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Pieskarieties, lai uzzinātu vairāk"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB atkļūdošana ir pievienota."</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Pieskarieties, lai atspējotu USB atkļūdošanu."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Atlasiet, lai atspējotu USB atkļūdošanu."</string>
diff --git a/core/res/res/values-mcc302-mnc220/config.xml b/core/res/res/values-mcc302-mnc220/config.xml
index 422f7c9..9a3d736 100644
--- a/core/res/res/values-mcc302-mnc220/config.xml
+++ b/core/res/res/values-mcc302-mnc220/config.xml
@@ -38,19 +38,13 @@
<string-array translatable="false" name="config_gpsParameters">
<item>SUPL_HOST=supl.telusmobility.com</item>
<item>SUPL_PORT=7275</item>
- <item>XTRA_SERVER_1=http://xtrapath1.izatcloud.net/xtra3grc.bin</item>
- <item>XTRA_SERVER_2=http://xtrapath2.izatcloud.net/xtra3grc.bin</item>
- <item>XTRA_SERVER_3=http://xtrapath3.izatcloud.net/xtra3grc.bin</item>
- <item>NTP_SERVER=north-america.pool.ntp.org</item>
- <item>SUPL_MODE=1</item>
<item>SUPL_VER=0x20000</item>
- <item>LPP_PROFILE=2</item>
- <item>NMEA_PROVIDER=0</item>
- <item>A_GLONASS_POS_PROTOCOL_SELECT=0</item>
- <item>ERR_ESTIMATE=0</item>
- <item>INTERMEDIATE_POS=0</item>
- <item>GPS_LOCK=0</item>
+ <item>SUPL_MODE=1</item>
<item>SUPL_ES=0</item>
+ <item>LPP_PROFILE=3</item>
+ <item>USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL=1</item>
+ <item>A_GLONASS_POS_PROTOCOL_SELECT=0</item>
+ <item>GPS_LOCK=0</item>
</string-array>
</resources>
diff --git a/core/res/res/values-mcc302-mnc221/config.xml b/core/res/res/values-mcc302-mnc221/config.xml
index 1444250..007fd04 100644
--- a/core/res/res/values-mcc302-mnc221/config.xml
+++ b/core/res/res/values-mcc302-mnc221/config.xml
@@ -36,19 +36,13 @@
<string-array translatable="false" name="config_gpsParameters">
<item>SUPL_HOST=supl.telusmobility.com</item>
<item>SUPL_PORT=7275</item>
- <item>XTRA_SERVER_1=http://xtrapath1.izatcloud.net/xtra3grc.bin</item>
- <item>XTRA_SERVER_2=http://xtrapath2.izatcloud.net/xtra3grc.bin</item>
- <item>XTRA_SERVER_3=http://xtrapath3.izatcloud.net/xtra3grc.bin</item>
- <item>NTP_SERVER=north-america.pool.ntp.org</item>
- <item>SUPL_MODE=1</item>
<item>SUPL_VER=0x20000</item>
- <item>LPP_PROFILE=2</item>
- <item>NMEA_PROVIDER=0</item>
- <item>A_GLONASS_POS_PROTOCOL_SELECT=0</item>
- <item>ERR_ESTIMATE=0</item>
- <item>INTERMEDIATE_POS=0</item>
- <item>GPS_LOCK=0</item>
+ <item>SUPL_MODE=1</item>
<item>SUPL_ES=0</item>
+ <item>LPP_PROFILE=3</item>
+ <item>USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL=1</item>
+ <item>A_GLONASS_POS_PROTOCOL_SELECT=0</item>
+ <item>GPS_LOCK=0</item>
</string-array>
</resources>
diff --git a/core/res/res/values-mcc302-mnc370/config.xml b/core/res/res/values-mcc302-mnc370/config.xml
index 05265c7..f3ae851 100644
--- a/core/res/res/values-mcc302-mnc370/config.xml
+++ b/core/res/res/values-mcc302-mnc370/config.xml
@@ -43,23 +43,17 @@
<item>302780</item>
</string-array>
- <!-- Values for GPS configuration (Rogers) -->
+ <!-- Values for GPS configuration (Rogers) -->
<string-array translatable="false" name="config_gpsParameters">
<item>SUPL_HOST=supl.google.com</item>
<item>SUPL_PORT=7275</item>
- <item>XTRA_SERVER_1=http://xtrapath1.izatcloud.net/xtra3grc.bin</item>
- <item>XTRA_SERVER_2=http://xtrapath2.izatcloud.net/xtra3grc.bin</item>
- <item>XTRA_SERVER_3=http://xtrapath3.izatcloud.net/xtra3grc.bin</item>
- <item>NTP_SERVER=north-america.pool.ntp.org</item>
- <item>SUPL_MODE=1</item>
<item>SUPL_VER=0x20000</item>
- <item>LPP_PROFILE=2</item>
- <item>NMEA_PROVIDER=0</item>
- <item>A_GLONASS_POS_PROTOCOL_SELECT=0</item>
- <item>ERR_ESTIMATE=0</item>
- <item>INTERMEDIATE_POS=0</item>
- <item>GPS_LOCK=0</item>
+ <item>SUPL_MODE=1</item>
<item>SUPL_ES=0</item>
+ <item>LPP_PROFILE=2</item>
+ <item>USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL=1</item>
+ <item>A_GLONASS_POS_PROTOCOL_SELECT=0</item>
+ <item>GPS_LOCK=0</item>
</string-array>
</resources>
diff --git a/core/res/res/values-mcc302-mnc610/config.xml b/core/res/res/values-mcc302-mnc610/config.xml
index 0af2c39..44dbdf7 100644
--- a/core/res/res/values-mcc302-mnc610/config.xml
+++ b/core/res/res/values-mcc302-mnc610/config.xml
@@ -31,18 +31,13 @@
<string-array translatable="false" name="config_gpsParameters">
<item>SUPL_HOST=supl.google.com</item>
<item>SUPL_PORT=7275</item>
- <item>XTRA_SERVER_1=http://xtrapath1.izatcloud.net/xtra3grc.bin</item>
- <item>XTRA_SERVER_2=http://xtrapath2.izatcloud.net/xtra3grc.bin</item>
- <item>XTRA_SERVER_3=http://xtrapath3.izatcloud.net/xtra3grc.bin</item>
- <item>NTP_SERVER=north-america.pool.ntp.org</item>
- <item>SUPL_MODE=1</item>
<item>SUPL_VER=0x20000</item>
- <item>LPP_PROFILE=2</item>
- <item>NMEA_PROVIDER=0</item>
- <item>A_GLONASS_POS_PROTOCOL_SELECT=0</item>
- <item>ERR_ESTIMATE=0</item>
- <item>INTERMEDIATE_POS=0</item>
- <item>GPS_LOCK=0</item>
+ <item>SUPL_MODE=1</item>
<item>SUPL_ES=0</item>
+ <item>LPP_PROFILE=2</item>
+ <item>USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL=1</item>
+ <item>A_GLONASS_POS_PROTOCOL_SELECT=0</item>
+ <item>GPS_LOCK=0</item>
</string-array>
+
</resources>
diff --git a/core/res/res/values-mcc302-mnc640/config.xml b/core/res/res/values-mcc302-mnc640/config.xml
index e005bc0..f6b2b4d 100644
--- a/core/res/res/values-mcc302-mnc640/config.xml
+++ b/core/res/res/values-mcc302-mnc640/config.xml
@@ -27,18 +27,13 @@
<string-array translatable="false" name="config_gpsParameters">
<item>SUPL_HOST=supl.google.com</item>
<item>SUPL_PORT=7275</item>
- <item>XTRA_SERVER_1=http://xtrapath1.izatcloud.net/xtra3grc.bin</item>
- <item>XTRA_SERVER_2=http://xtrapath2.izatcloud.net/xtra3grc.bin</item>
- <item>XTRA_SERVER_3=http://xtrapath3.izatcloud.net/xtra3grc.bin</item>
- <item>NTP_SERVER=north-america.pool.ntp.org</item>
- <item>SUPL_MODE=1</item>
<item>SUPL_VER=0x20000</item>
- <item>LPP_PROFILE=2</item>
- <item>NMEA_PROVIDER=0</item>
- <item>A_GLONASS_POS_PROTOCOL_SELECT=0</item>
- <item>ERR_ESTIMATE=0</item>
- <item>INTERMEDIATE_POS=0</item>
- <item>GPS_LOCK=0</item>
+ <item>SUPL_MODE=1</item>
<item>SUPL_ES=0</item>
+ <item>LPP_PROFILE=2</item>
+ <item>USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL=1</item>
+ <item>A_GLONASS_POS_PROTOCOL_SELECT=0</item>
+ <item>GPS_LOCK=0</item>
</string-array>
+
</resources>
diff --git a/core/res/res/values-mcc302-mnc720/config.xml b/core/res/res/values-mcc302-mnc720/config.xml
index 7a3540a..0b8c778 100644
--- a/core/res/res/values-mcc302-mnc720/config.xml
+++ b/core/res/res/values-mcc302-mnc720/config.xml
@@ -45,23 +45,17 @@
<item>302780</item>
</string-array>
- <!-- Values for GPS configuration (Rogers) -->
+ <!-- Values for GPS configuration (Rogers) -->
<string-array translatable="false" name="config_gpsParameters">
<item>SUPL_HOST=supl.google.com</item>
<item>SUPL_PORT=7275</item>
- <item>XTRA_SERVER_1=http://xtrapath1.izatcloud.net/xtra3grc.bin</item>
- <item>XTRA_SERVER_2=http://xtrapath2.izatcloud.net/xtra3grc.bin</item>
- <item>XTRA_SERVER_3=http://xtrapath3.izatcloud.net/xtra3grc.bin</item>
- <item>NTP_SERVER=north-america.pool.ntp.org</item>
- <item>SUPL_MODE=1</item>
<item>SUPL_VER=0x20000</item>
- <item>LPP_PROFILE=2</item>
- <item>NMEA_PROVIDER=0</item>
- <item>A_GLONASS_POS_PROTOCOL_SELECT=0</item>
- <item>ERR_ESTIMATE=0</item>
- <item>INTERMEDIATE_POS=0</item>
- <item>GPS_LOCK=0</item>
+ <item>SUPL_MODE=1</item>
<item>SUPL_ES=0</item>
+ <item>LPP_PROFILE=2</item>
+ <item>USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL=1</item>
+ <item>A_GLONASS_POS_PROTOCOL_SELECT=0</item>
+ <item>GPS_LOCK=0</item>
</string-array>
</resources>
diff --git a/core/res/res/values-mcc505-mnc01/config.xml b/core/res/res/values-mcc505-mnc01/config.xml
index 1705e4a..ff06585 100644
--- a/core/res/res/values-mcc505-mnc01/config.xml
+++ b/core/res/res/values-mcc505-mnc01/config.xml
@@ -40,6 +40,16 @@
<item>Telstra Tethering,telstra.internet,,,,,,,,,505,01,,DUN</item>
</string-array>
+ <!--Thresholds for LTE dbm in status bar-->
+ <integer-array translatable="false" name="config_lteDbmThresholds">
+ <item>-140</item> <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
+ <item>-120</item> <!-- SIGNAL_STRENGTH_POOR -->
+ <item>-115</item> <!-- SIGNAL_STRENGTH_MODERATE -->
+ <item>-100</item> <!-- SIGNAL_STRENGTH_GOOD -->
+ <item>-90</item> <!-- SIGNAL_STRENGTH_GREAT -->
+ <item>-44</item>
+ </integer-array>
+
<!-- Configure mobile network MTU. Carrier specific value is set here.
-->
<integer name="config_mobile_mtu">1400</integer>
diff --git a/core/res/res/values-mcc505-mnc11/config.xml b/core/res/res/values-mcc505-mnc11/config.xml
new file mode 100644
index 0000000..6d085c1
--- /dev/null
+++ b/core/res/res/values-mcc505-mnc11/config.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You my 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!--Thresholds for LTE dbm in status bar-->
+ <integer-array translatable="false" name="config_lteDbmThresholds">
+ <item>-140</item> <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
+ <item>-120</item> <!-- SIGNAL_STRENGTH_POOR -->
+ <item>-115</item> <!-- SIGNAL_STRENGTH_MODERATE -->
+ <item>-100</item> <!-- SIGNAL_STRENGTH_GOOD -->
+ <item>-90</item> <!-- SIGNAL_STRENGTH_GREAT -->
+ <item>-44</item>
+ </integer-array>
+</resources>
diff --git a/core/res/res/values-mcc505-mnc71/config.xml b/core/res/res/values-mcc505-mnc71/config.xml
new file mode 100644
index 0000000..6d085c1
--- /dev/null
+++ b/core/res/res/values-mcc505-mnc71/config.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You my 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!--Thresholds for LTE dbm in status bar-->
+ <integer-array translatable="false" name="config_lteDbmThresholds">
+ <item>-140</item> <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
+ <item>-120</item> <!-- SIGNAL_STRENGTH_POOR -->
+ <item>-115</item> <!-- SIGNAL_STRENGTH_MODERATE -->
+ <item>-100</item> <!-- SIGNAL_STRENGTH_GOOD -->
+ <item>-90</item> <!-- SIGNAL_STRENGTH_GREAT -->
+ <item>-44</item>
+ </integer-array>
+</resources>
diff --git a/core/res/res/values-mcc505-mnc72/config.xml b/core/res/res/values-mcc505-mnc72/config.xml
new file mode 100644
index 0000000..6d085c1
--- /dev/null
+++ b/core/res/res/values-mcc505-mnc72/config.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You my 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!--Thresholds for LTE dbm in status bar-->
+ <integer-array translatable="false" name="config_lteDbmThresholds">
+ <item>-140</item> <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
+ <item>-120</item> <!-- SIGNAL_STRENGTH_POOR -->
+ <item>-115</item> <!-- SIGNAL_STRENGTH_MODERATE -->
+ <item>-100</item> <!-- SIGNAL_STRENGTH_GOOD -->
+ <item>-90</item> <!-- SIGNAL_STRENGTH_GREAT -->
+ <item>-44</item>
+ </integer-array>
+</resources>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index f48345b..9d9a2fe 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB за МИДИ"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Поврзан со USB додаток"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Допрете за повеќе опции."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Аудиододатокот не е поддржан"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Допрете за повеќе информации"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Поврзано е отстранување грешки преку USB"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Допрете за да се оневозможи отстранувањето грешки преку USB."</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index cd8e8c9..92cfb6b 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI-യ്ക്കായുള്ള USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"ഒരു USB ആക്സസ്സറി കണക്റ്റുചെയ്തു"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"കൂടുതൽ ഓപ്ഷനുകൾക്ക് ടാപ്പുചെയ്യുക."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"ഓഡിയോ ആക്സസറി പ്രവർത്തിക്കുകയില്ല"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"കൂടുതൽ വിവരങ്ങൾക്ക് ടാപ്പുചെയ്യുക"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB ഡീബഗ്ഗിംഗ് കണക്റ്റുചെയ്തു"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB ഡീബഗ്ഗിംഗ് പ്രവർത്തനരഹിതമാക്കാൻ ടാപ്പുചെയ്യുക."</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 81f5eaa..cfdd9be 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI-ийн USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB төхөөрөмжид холбогдов"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Бусад сонголтыг харахын тулд товшино уу."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Аудио хэрэгслийг дэмжээгүй байна"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Дэлгэрэнгүйг үзэхийн тулд товшино уу"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB дебаг холбогдсон"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB-н алдаа засварлахыг идэвхгүй болгохын тулд товшино уу."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"USB дебаг хийхийг идэвхгүй болгох бол сонгоно уу."</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index cbeaacd..e43a3c9 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI साठी USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB उपसाधनावर कनेक्ट केले"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"अधिक पर्यायांसाठी टॅप करा."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"ऑडिओ अॅक्सेसरी समर्थित नाही"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"आणखी माहितीसाठी येथे टॅप करा"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB डीबग करणे कनेक्ट केले"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB डीबग करणे अक्षम करण्यासाठी टॅप करा."</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 2a3c046..267dd2f 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB untuk MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Disambungkan kepada aksesori USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Ketik untuk mendapatkan lagi pilihan."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Aksesori audio tidak disokong"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Ketik untuk mendapatkan maklumat lanjut"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Penyahpepijatan USB disambungkan"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Ketik untuk melumpuhkan penyahpepijatan USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Pilih untuk melumpuhkan penyahpepijatan USB."</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 993c5cf..a1e7558 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI အတွက် USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USBတွဲဖက်ပစ္စည်းအား ချိတ်ဆက်ထားသည်"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"နောက်ထပ်ရွေးချယ်စရာများအတွက် တို့ပါ။"</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"အသံ ဆက်စပ်ပစ္စည်းကို မပံ့ပိုးပါ"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"နောက်ထပ် အချက်အလက်များအတွက် တို့ပါ"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB အမှားစစ်ခြင်းအား ချိတ်ဆက်ထားသည်"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB ဆက်သွယ်ရေးစနစ်ကို ပိတ်ရန် တို့ပါ။"</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index b88ff35..aafa411 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB for MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Koblet til et USB-tilbehør"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Trykk for å få flere alternativ."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Lydtilbehøret støttes ikke"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Trykk for mer informasjon"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB-feilsøking tilkoblet"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Trykk for å slå av feilsøking via USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Velg for å deaktivere USB-debugging."</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 7734681..826a424 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1194,6 +1194,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI को लागि USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB सहायकमा जोडिएको छ"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"थप विकल्पहरूका लागि ट्याप गर्नुहोस्।"</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"अडियोको सहायक सामग्रीलाई समर्थन छैन"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"थप जानकारीका लागि ट्याप गर्नुहोस्"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB डिबग गर्ने जडित छ"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB डिबगिङलाई असक्षम गर्न ट्याप गर्नुहोस्।"</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"USB डिबगिङ असक्षम पार्न चयन गर्नुहोस्।"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index b174572..ed38f62 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB voor MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Aangesloten op een USB-accessoire"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Tik voor meer opties."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Audioaccessoire niet ondersteund"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Tik voor meer informatie"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB-foutopsporing verbonden"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Tik om USB-foutopsporing uit te schakelen."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Selecteer deze optie om USB-foutopsporing uit te schakelen."</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 4160344..c456e2f 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI ਲਈ USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"ਇੱਕ USB ਐਕਸੈਸਰੀ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"ਹੋਰ ਵਿਕਲਪਾਂ ਲਈ ਟੈਪ ਕਰੋ।"</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"ਔਡੀਓ ਉਪਸਾਧਨ ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB ਡੀਬਗਿੰਗ ਕਨੈਕਟ ਕੀਤੀ"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB ਡੀਬੱਗਿੰਗ ਨੂੰ ਅਯੋਗ ਬਣਾਉਣ ਲਈ ਟੈਪ ਕਰੋ।"</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 209bd21..aa323ea 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1232,6 +1232,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB w trybie MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Podłączono akcesorium USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Kliknij, by wyświetlić więcej opcji."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Nieobsługiwane akcesorium audio"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Kliknij, aby uzyskać więcej informacji"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Podłączono moduł debugowania USB"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Kliknij, by wyłączyć debugowanie USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Wybierz, aby wyłączyć debugowanie USB."</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 076e728..5bf1f8d 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB para MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Conectado a um acessório USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Toque para ver mais opções."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Acessório de áudio não compatível"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Toque para mais informações"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Depuração USB conectada"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Toque para desativar a depuração do USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Selecione para desativar a depuração USB."</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 15e1e3c..e1afe56 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB para MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Ligado a um acessório USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Toque para obter mais opções."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Acessório de áudio não suportado"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Toque para obter mais informações"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Depuração USB ligada"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Toque para desativar a depuração USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Seleccione para desativar depuração USB."</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 076e728..5bf1f8d 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB para MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Conectado a um acessório USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Toque para ver mais opções."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Acessório de áudio não compatível"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Toque para mais informações"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Depuração USB conectada"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Toque para desativar a depuração do USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Selecione para desativar a depuração USB."</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index cefbccc..9d17566 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1210,6 +1210,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"Conexiune USB pentru MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Conectat la un accesoriu USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Atingeți pentru mai multe opțiuni."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Accesoriul audio nu este acceptat"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Atingeți pentru mai multe informații"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Depanarea USB este conectată"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Atingeți ca să dezactivați remedierea erorilor prin USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Selectați pentru a dezactiva depanarea USB."</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index d6dd1ee..b0c8ea2 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1232,6 +1232,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI через USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB-устройство подключено"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Нажмите, чтобы показать дополнительные параметры."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Аудиоустройство не поддерживается"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Нажмите, чтобы получить дополнительную информацию"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Отладка по USB разрешена"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Нажмите, чтобы отключить отладку по USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Нажмите, чтобы отключить отладку USB."</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index adcfad9..5aa8788 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -1190,6 +1190,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI සඳහා USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB මෙවලමකට සම්බන්ධිතයි"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"තවත් විකල්ප සඳහා තට්ටු කරන්න."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"ශ්රව්ය ආයිත්තම සහාය නොදක්වයි"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"තවත් තොරතුරු සඳහා තට්ටු කරන්න"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB නිදොස්කරණය සම්බන්ධිතයි"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB නිදොස්කරණය අබල කිරීමට තට්ටු කරන්න."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"USB නිදොස්කරණය අබල කිරීමට තෝරන්න."</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index c413155..d79caec 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1232,6 +1232,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB na pripojenie zariadenia MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Pripojené k periférnemu zariadeniu USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Klepnutím zobrazíte ďalšie možnosti."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Zvukové príslušenstvo nie je podporované"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Ďalšie informácie zobrazíte klepnutím"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Ladenie cez USB pripojené"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Klepnutím zakážete ladenie cez USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Výberom zakážete ladenie USB."</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index fb24d43..5257d6f 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1232,6 +1232,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB za MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Priključen na dodatek USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Dotaknite se za več možnosti."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Zvočna oprema ni podprta"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Dotaknite se za več informacij"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Iskanje napak prek USB je povezano"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Dotaknite se za izklop odpravljanja napak prek USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Izberite, če želite onemogočiti iskanje in odpravljanje napak prek vrat USB."</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 47d1d0c..b92732d 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB për MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"U lidh me një ndihmës USB-je"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Trokit për më shumë opsione."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Aksesori i audios nuk mbështetet"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Trokit për më shumë informacion"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Korrigjuesi i USB-së i lidhur"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Trokit për të çaktivizuar korrigjimin e gabimeve të USB-së."</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 031291d..b9e2b31 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1210,6 +1210,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB за MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Повезано са USB додатком"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Додирните за још опција."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Додатна опрема за аудио садржај није подржана"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Додирните за више информација"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Отклањање грешака са USB-а је успостављено"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Додирните да бисте онемогућили отклањање грешака са USB-а."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Изаберите да бисте онемогућили отклањања грешака са USB-а."</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index ffd4f11..8062acb 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB för MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Ansluten till ett USB-tillbehör"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Tryck för fler alternativ."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Ljudtillbehöret stöds inte"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Tryck för mer information"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB-felsökning ansluten"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Tryck om du vill inaktivera USB-felsökning."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Välj att inaktivera USB-felsökning."</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 910949c..4d50e91 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1186,6 +1186,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB kwa ajili ya MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Imeunganishwa kwa kifuasi cha USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Gonga ili upate chaguo zaidi."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Kifuasi cha sauti hakiwezi kutumika"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Gonga ili upate maelezo zaidi"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Utatuaji wa USB umeunganishwa"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Gonga ili uzime utatuaji wa USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Chagua ili kulemaza utatuaji USB."</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 41cf95b..91196b9 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB, MIDIக்கு மட்டும்"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB துணைக்கருவியுடன் இணைக்கப்பட்டுள்ளது"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"மேலும் விருப்பங்களுக்கு, தட்டவும்."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"ஆடியோ துணைக்கருவி ஆதரிக்கப்படவில்லை"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"மேலும் தகவலுக்கு, தட்டவும்"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB பிழைதிருத்தம் இணைக்கப்பட்டது"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB பிழை திருத்தத்தை முடக்க, தட்டவும்."</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 6d43b2b..ddf6397 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI కోసం USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB ఉపకరణానికి కనెక్ట్ చేయబడింది"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"మరిన్ని ఎంపికల కోసం నొక్కండి."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"ఆడియో ఉపకరణానికి మద్దతు లేదు"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"మరింత సమాచారం కోసం నొక్కండి"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB డీబగ్గింగ్ కనెక్ట్ చేయబడింది"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB డీబగ్గింగ్ను నిలిపివేయడానికి నొక్కండి."</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 6b2c4c6..86f1be3 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB สำหรับ MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"เชื่อมต่อกับอุปกรณ์เสริม USB แล้ว"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"แตะเพื่อดูตัวเลือกเพิ่มเติม"</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"ไม่รองรับอุปกรณ์เสริมสำหรับเสียง"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"แตะเพื่อดูข้อมูลเพิ่มเติม"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"เชื่อมต่อการแก้ไขข้อบกพร่อง USB แล้ว"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"แตะเพื่อปิดใช้การแก้ไขข้อบกพร่องของ USB"</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"เลือกเพื่อปิดใช้งานการแก้ไขข้อบกพร่อง USB"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 7cc10be82..5405d03 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB para sa MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Nakakonekta sa isang accessory ng USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"I-tap para sa higit pang mga opsyon."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Hindi sinusuportahan ang audio accessory"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"I-tap para sa higit pang impormasyon"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Konektado ang debugging ng USB"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"I-tap upang i-disable ang pag-debug ng USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Piliin upang i-disable ang debugging ng USB."</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 2096a93..936f827 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI için USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB aksesuarına bağlandı"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Diğer seçenekler için dokunun."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Ses aksesuarı desteklenmiyor"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Daha fazla bilgi için dokunun"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB hata ayıklaması bağlandı"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB hata ayıklama özelliğini devre dışı bırakmak için dokunun."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"USB hata ayıklamasını devre dışı bırakmak için tıklayın."</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index f635ec4..0b5677e 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1232,6 +1232,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB для режиму MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Під’єднано до аксесуара USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Торкніться, щоб переглянути більше опцій."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Аксесуар для аудіо не підтримується"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Торкніться, щоб дізнатися більше"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Налагодження USB завершено"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Торкніться, щоб вимкнути налагодження USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Вибер., щоб вимкн. налагодж. USB."</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 7f007be..038baaf 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI کیلئے USB"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"ایک USB لوازم سے مربوط ہے"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"مزید اختیارات کیلئے تھپتھپائیں۔"</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"آڈیو لوازم تعاون یافتہ نہیں ہے"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"مزید معلومات کے لیے تھپتھپائيں"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB ڈیبگ کرنا مربوط ہو گیا"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"USB ڈیبگنگ کو غیر فعال کرنے کیلئے تھپتھپائیں۔"</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 4351ab2..3974ab3 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB orqali MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB jihozga ulangan"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Boshqa parametrlarini ko‘rish uchun bosing."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Audio aksessuar ishlamaydi"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Tafsilotlar uchun bosing"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"USB orqali nosozliklarni tuzatish"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Faolsizlantirish uchun bu yerga bosing."</string>
<!-- no translation found for adb_active_notification_message (8470296818270110396) -->
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index f134eca..4d39e34 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB cho MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Đã kết nối với phụ kiện USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Nhấn để biết thêm tùy chọn."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Phụ kiện âm thanh không được hỗ trợ"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Nhấn để biết thêm thông tin"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Gỡ lỗi USB đã được kết nối"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Nhấn để vô hiệu hóa gỡ lỗi USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Chọn để vô hiệu hóa gỡ lỗi USB."</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index fda476d..1d2aa50 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"正在通过 USB 连接到 MIDI 接口"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"已连接到USB配件"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"点按即可查看更多选项。"</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"音频配件不受支持"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"点按即可了解详情"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"已连接到USB调试"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"点按即可停用 USB 调试功能。"</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"选择停用USB调试。"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 797a195..09c7c35 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"已連接到一個 USB 配件"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"輕按即可查看更多選項。"</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"不支援的音訊配件"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"輕按即可瞭解詳情"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"已連接 USB 偵錯工具"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"輕按即可停用 USB 偵錯功能。"</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"選取即可停用 USB 偵錯。"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 9008d8e..3379ea5 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"USB MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"已連接 USB 配件"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"輕觸即可查看更多選項。"</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"不支援的音訊配件"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"輕觸即可瞭解詳情"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"已連接 USB 偵錯工具"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"輕觸即可停用 USB 偵錯。"</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"選取以停用 USB 偵錯。"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index c1bff23..34d191b 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1188,6 +1188,8 @@
<string name="usb_midi_notification_title" msgid="4850904915889144654">"I-USB ye-MIDI"</string>
<string name="usb_accessory_notification_title" msgid="7848236974087653666">"Ixhunywe ku-accessory ye-USB"</string>
<string name="usb_notification_message" msgid="3370903770828407960">"Thepha ngezinketho eziningi."</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2256529893240208458">"Insiza yomsindo ayisekelwa"</string>
+ <string name="usb_unsupported_audio_accessory_message" msgid="7811865061127547035">"Thepha ngolwazi olungeziwe"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Ukulungisa iphutha le-USB kuxhunyiwe"</string>
<string name="adb_active_notification_message" msgid="4948470599328424059">"Thepha ukuze ukhubaze ukususa isiphazamisi se-USB."</string>
<string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Khetha ukuvimbela ukulungisa iphutha le-USB."</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 86a6c20..b8c9933 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -431,9 +431,6 @@
<!-- Boolean indicating whether the wifi chipset has dual frequency band support -->
<bool translatable="false" name="config_wifi_dual_band_support">false</bool>
- <!-- Boolean indicating whether Hotspot 2.0/Passpoint and ANQP queries is enabled -->
- <bool translatable="false" name="config_wifi_hotspot2_enabled">false</bool>
-
<!-- Boolean indicating whether 802.11r Fast BSS Transition is enabled on this platform -->
<bool translatable="false" name="config_wifi_fast_bss_transition_enabled">false</bool>
@@ -478,6 +475,9 @@
<integer translatable="false" name="config_wifi_framework_SECURITY_AWARD">80</integer>
<!-- Integer specifying the base interval in seconds for the exponential backoff scan for autojoin -->
<integer translatable="false" name="config_wifi_framework_exponential_backoff_scan_base_interval">20</integer>
+ <!-- Integers specifying the max packet Tx/Rx rates for full scan -->
+ <integer translatable="false" name="config_wifi_framework_max_tx_rate_for_full_scan">8</integer>
+ <integer translatable="false" name="config_wifi_framework_max_rx_rate_for_full_scan">16</integer>
<!-- Integer parameters of the wifi to cellular handover feature
wifi should not stick to bad networks -->
<integer translatable="false" name="config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz">-82</integer>
@@ -965,14 +965,6 @@
<item>10</item>
</integer-array>
- <!-- Vibrator pattern for feedback about a context click -->
- <integer-array name="config_contextClickVibePattern">
- <item>0</item>
- <item>1</item>
- <item>20</item>
- <item>21</item>
- </integer-array>
-
<bool name="config_use_strict_phone_number_comparation">false</bool>
<!-- Display low battery warning when battery level dips to this value.
@@ -1279,6 +1271,49 @@
<!-- True if WallpaperService is enabled -->
<bool name="config_enableWallpaperService">true</bool>
+ <!-- Enables the TimeZoneRuleManager service. This is the master switch for the updateable time
+ zone update mechanism. -->
+ <bool name="config_enableUpdateableTimeZoneRules">false</bool>
+
+ <!-- Enables APK-based time zone update triggering. Set this to false when updates are triggered
+ via external events and not by APK updates. For example, if an updater checks with a server
+ on a regular schedule.
+ [This is only used if config_enableUpdateableTimeZoneRules is true.] -->
+ <bool name="config_timeZoneRulesUpdateTrackingEnabled">false</bool>
+
+ <!-- The package of the time zone rules updater application. Expected to be the same
+ for all Android devices that support APK-based time zone rule updates.
+ A package-targeted android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK intent
+ will be sent to the updater app if the system server detects an update to the updater or
+ data app packages.
+ The package referenced here must have the android.permission.UPDATE_TIME_ZONE_RULES
+ permission.
+ [This is only used if config_enableUpdateableTimeZoneRules and
+ config_timeZoneRulesUpdateTrackingEnabled are true.] -->
+ <string name="config_timeZoneRulesUpdaterPackage" translateable="false"></string>
+
+ <!-- The package of the time zone rules data application. Expected to be configured
+ by OEMs to reference their own priv-app APK package.
+ A package-targeted android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK intent
+ will be sent to the updater app if the system server detects an update to the updater or
+ data app packages.
+ [This is only used if config_enableUpdateableTimeZoneRules and
+ config_timeZoneRulesUpdateTrackingEnabled are true.] -->
+ <string name="config_timeZoneRulesDataPackage" translateable="false"></string>
+
+ <!-- The allowed time in milliseconds between an update check intent being broadcast and the
+ response being considered overdue. Reliability triggers will not fire in this time.
+ [This is only used if config_enableUpdateableTimeZoneRules and
+ config_timeZoneRulesUpdateTrackingEnabled are true.] -->
+ <!-- 5 minutes -->
+ <integer name="config_timeZoneRulesCheckTimeMillisAllowed">300000</integer>
+
+ <!-- The number of times a time zone update check is allowed to fail before the system will stop
+ reacting to reliability triggers.
+ [This is only used if config_enableUpdateableTimeZoneRules and
+ config_timeZoneRulesUpdateTrackingEnabled are true.] -->
+ <integer name="config_timeZoneRulesCheckRetryCount">5</integer>
+
<!-- Whether to enable network location overlay which allows network
location provider to be replaced by an app at run-time. When disabled,
only the config_networkLocationProviderPackageName package will be
@@ -1799,6 +1834,11 @@
<!-- Type of the double tap sensor. Empty if double tap is not supported. -->
<string name="config_dozeDoubleTapSensorType" translatable="false"></string>
+ <!-- Control whether the always on display mode is available. This should only be enabled on
+ devices where the display has be tuned to be power efficient in DOZE and/or DOZE_SUSPEND
+ states. -->
+ <bool name="config_dozeAlwaysOnDisplayAvailable">false</bool>
+
<!-- Power Management: Specifies whether to decouple the auto-suspend state of the
device from the display on/off state.
@@ -2362,9 +2402,13 @@
<string-array translatable="false" name="config_gpsParameters">
<item>SUPL_HOST=supl.google.com</item>
<item>SUPL_PORT=7275</item>
- <item>NTP_SERVER=north-america.pool.ntp.org</item>
<item>SUPL_VER=0x20000</item>
<item>SUPL_MODE=1</item>
+ <item>SUPL_ES=0</item>
+ <item>LPP_PROFILE=0</item>
+ <item>USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL=1</item>
+ <item>A_GLONASS_POS_PROTOCOL_SELECT=0</item>
+ <item>GPS_LOCK=0</item>
</string-array>
<!-- Sprint need a 70 ms delay for 3way call -->
@@ -2536,6 +2580,10 @@
<!-- The default vibration strength, must be between 1 and 255 inclusive. -->
<integer name="config_defaultVibrationAmplitude">255</integer>
+ <!-- If the device should still vibrate even in low power mode, for certain priority vibrations
+ (e.g. accessibility, alarms). This is mainly for Wear devices that don't have speakers. -->
+ <bool name="config_allowPriorityVibrationsInLowPowerMode">false</bool>
+
<!-- Number of retries Cell Data should attempt for a given error code before
restarting the modem.
Error codes not listed will not lead to modem restarts.
@@ -2827,6 +2875,11 @@
<!-- TODO(b/35230407) complete the link field -->
<bool name="config_allowEscrowTokenForTrustAgent">false</bool>
+ <!-- A flattened ComponentName which corresponds to the only trust agent that should be enabled
+ by default. If the default value is used, or set to an empty string, the restriction will
+ not be applied. -->
+ <string name="config_defaultTrustAgent" translatable="false"></string>
+
<!-- Colon separated list of package names that should be granted Notification Listener access -->
<string name="config_defaultListenerAccessPackages" translatable="false"></string>
@@ -2848,6 +2901,11 @@
<!-- Whether the device uses the default focus highlight when focus state isn't specified. -->
<bool name="config_useDefaultFocusHighlight">true</bool>
+ <!-- Flag indicating that the entire notification header can be clicked to expand the
+ notification. If false, then the expand icon has to be clicked in order for the expand
+ to occur. The expand button will have increased touch boundaries to accomodate this. -->
+ <bool name="config_notificationHeaderClickableForExpand">false</bool>
+
<!-- Configuration for automotive -->
<bool name="enable_pbap_pce_profile">false</bool>
@@ -2899,6 +2957,18 @@
relative to the overall voice call stream volume [0..100] -->
<integer name="config_inCallNotificationVolumeRelative">67</integer>
+ <!-- The OEM specified sensor type for the lift trigger to launch the camera app. -->
+ <integer name="config_cameraLiftTriggerSensorType">-1</integer>
+ <!-- The OEM specified sensor string type for the gesture to launch camera app, this value
+ must match the value of config_cameraLiftTriggerSensorType in OEM's HAL -->
+ <string translatable="false" name="config_cameraLiftTriggerSensorStringType"></string>
+
+ <!-- Default number of days to retain for the automatic storage manager. -->
+ <integer translatable="false" name="config_storageManagerDaystoRetainDefault">90</integer>
+
+ <!-- Name of a font family to use for headlines. If empty, falls back to platform default -->
+ <string name="config_headlineFontFamily" translatable="false"></string>
+
<!-- An array of packages that need to be treated as type system in battery settings -->
<string-array translatable="false" name="config_batteryPackageTypeSystem">
<item>com.android.providers.calendar</item>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index ece0e82..5e2334d 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -177,6 +177,12 @@
<!-- height of the content margin on the bottom -->
<dimen name="notification_content_margin_bottom">16dp</dimen>
+ <!-- The height of the progress bar. -->
+ <dimen name="notification_progress_bar_height">15dp</dimen>
+
+ <!-- The top margin before the notification progress bar. -->
+ <dimen name="notification_progress_margin_top">8dp</dimen>
+
<!-- height of the notification header (for icon and package name) -->
<dimen name="notification_header_height">48dp</dimen>
@@ -185,20 +191,40 @@
<!-- The top padding for the notification header -->
<dimen name="notification_header_padding_top">10dp</dimen>
+
<!-- The bottom padding for the notification header -->
<dimen name="notification_header_padding_bottom">11dp</dimen>
+ <!-- The margin at the bottom of the notification header. -->
+ <dimen name="notification_header_margin_bottom">5dp</dimen>
+
+ <!-- The end margin after the application icon in the notification header -->
+ <dimen name="notification_header_icon_margin_end">3dp</dimen>
+
<!-- size (width and height) of the icon in the notification header -->
<dimen name="notification_header_icon_size">18dp</dimen>
<!-- size (width and height) of the icon in the notification header -->
<dimen name="notification_header_icon_size_ambient">20dp</dimen>
+ <!-- The margin before the start of the app name in the header. -->
+ <dimen name="notification_header_app_name_margin_start">3dp</dimen>
+
+ <!-- The margin before and after each of the items in the notification header. -->
+ <dimen name="notification_header_separating_margin">2dp</dimen>
+
+ <!-- The absolute size of the notification expand icon. -2 for wrap_content. -->
+ <dimen name="notification_header_expand_icon_size">-2px</dimen>
+
+ <!-- The top padding for the notification expand button. -->
+ <dimen name="notification_expand_button_padding_top">1dp</dimen>
+
<!-- Height of a small notification in the status bar -->
<dimen name="notification_min_height">92dp</dimen>
<!-- The width of the big icons in notifications. -->
<dimen name="notification_large_icon_width">64dp</dimen>
+
<!-- The width of the big icons in notifications. -->
<dimen name="notification_large_icon_height">64dp</dimen>
@@ -211,9 +237,21 @@
<!-- The small size of the image if the height drawing doesn't work anymore -->
<dimen name="media_notification_expanded_image_small_size">72dp</dimen>
+ <!-- The size of the media actions in the media notification. -->
+ <dimen name="media_notification_action_button_size">48dp</dimen>
+
+ <!-- The bottom padding for the media actions container. -->
+ <dimen name="media_notification_actions_padding_bottom">12dp</dimen>
+
+ <!-- The maximum size of the image in the expanded media notification -->
+ <dimen name="media_notification_expanded_image_max_size">94dp</dimen>
+
<!-- The maximum size of the image in the expanded media notification -->
<dimen name="media_notification_expanded_image_margin_bottom">20dp</dimen>
+ <!-- The absolute height for the header in a media notification. -->
+ <dimen name="media_notification_header_height">53dp</dimen>
+
<!-- The margin of the content to an image-->
<dimen name="notification_content_image_margin_end">8dp</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 90ece60..97b6940 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -191,21 +191,15 @@
<!-- Notification title to tell the user that data service is blocked by access control. -->
<string name="RestrictedOnDataTitle">No data service</string>
- <!-- Notification title to tell the user that emergency service is blocked by access control. -->
- <string name="RestrictedOnEmergencyTitle">No emergency service</string>
+ <!-- Notification title to tell the user that emergency calling is blocked by access control. -->
+ <string name="RestrictedOnEmergencyTitle">No emergency calling</string>
<!-- Notification title to tell the user that normal service is blocked by access control. -->
<string name="RestrictedOnNormalTitle">No voice service</string>
<!-- Notification title to tell the user that all emergency and normal voice services are blocked by access control. -->
<string name="RestrictedOnAllVoiceTitle">No voice/emergency service</string>
- <!-- Notification content to tell the user that data service is blocked by access control. -->
- <string name="RestrictedOnDataContent">Your carrier has temporarily suspended data service at this location</string>
- <!-- Notification content to tell the user that emergency service is blocked by access control. -->
- <string name="RestrictedOnEmergencyContent">Your carrier has temporarily suspended emergency calls at this location</string>
- <!-- Notification content to tell the user that normal service is blocked by access control. -->
- <string name="RestrictedOnNormalContent">Your carrier has temporarily suspended voice calls at this location</string>
- <!-- Notification content to tell the user that all emergency and normal voice services are blocked by access control. -->
- <string name="RestrictedOnAllVoiceContent">Your carrier has temporarily suspended voice and emergency calls at this location</string>
+ <!-- Notification content to tell the user that voice/data/emergency service is blocked by access control. -->
+ <string name="RestrictedStateContent">Temporarily not offered by the mobile network at your location</string>
<!-- Displayed to tell the user that they should switch their network preference. -->
<string name="NetworkPreferenceSwitchTitle">Can\u2019t reach network</string>
@@ -3176,6 +3170,11 @@
<string name="usb_accessory_notification_title">Connected to a USB accessory</string>
<!-- See USB_PREFERENCES. This is the message. -->
<string name="usb_notification_message">Tap for more options.</string>
+ <!-- USB_PREFERENCES: Notification for when a type-c USB audio accessory is attached but not supported. This is the title -->
+ <string name="usb_unsupported_audio_accessory_title">Audio accessory not supported</string>
+ <!-- Message of notification shown when a type-c USB audio accessory is attached but not supported. -->
+ <string name="usb_unsupported_audio_accessory_message">Tap for more info</string>
+
<!-- Title of notification shown when ADB is actively connected to the phone. -->
<string name="adb_active_notification_title">USB debugging connected</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 690b051..5c6e3df 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -931,7 +931,7 @@
<item name="textStyle">normal</item>
<item name="textColor">?textColorPrimaryInverse</item>
<item name="textColorHint">?textColorHintInverse</item>
- </style>
+ </style>
<!-- @hide -->
<style name="TextAppearance.SearchResult.Title">
@@ -1492,4 +1492,15 @@
<item name="background">@drawable/autofill_dataset_picker_background</item>
</style>
+ <!-- The style for the container of media actions in a notification. -->
+ <!-- @hide -->
+ <style name="NotificationMediaActionContainer">
+ <item name="layout_width">wrap_content</item>
+ <item name="layout_height">wrap_content</item>
+ <item name="layout_marginTop">-21dp</item>
+ <item name="paddingStart">8dp</item>
+ <item name="paddingBottom">@dimen/media_notification_actions_padding_bottom</item>
+ <item name="gravity">top</item>
+ </style>
+
</resources>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index ec16611..c783ea8 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -1290,7 +1290,7 @@
<style name="Notification.Header" parent="">
<item name="paddingTop">@dimen/notification_header_padding_top</item>
<item name="paddingBottom">@dimen/notification_header_padding_bottom</item>
- <item name="layout_marginBottom">5dp</item>
+ <item name="layout_marginBottom">@dimen/notification_header_margin_bottom</item>
<item name="paddingStart">@dimen/notification_content_margin_start</item>
<item name="paddingEnd">16dp</item>
</style>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2372427..241886c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -284,6 +284,12 @@
<java-symbol type="bool" name="split_action_bar_is_narrow" />
<java-symbol type="bool" name="config_useVolumeKeySounds" />
<java-symbol type="bool" name="config_enableWallpaperService" />
+ <java-symbol type="bool" name="config_enableUpdateableTimeZoneRules" />
+ <java-symbol type="bool" name="config_timeZoneRulesUpdateTrackingEnabled" />
+ <java-symbol type="string" name="config_timeZoneRulesUpdaterPackage" />
+ <java-symbol type="string" name="config_timeZoneRulesDataPackage" />
+ <java-symbol type="integer" name="config_timeZoneRulesCheckTimeMillisAllowed" />
+ <java-symbol type="integer" name="config_timeZoneRulesCheckRetryCount" />
<java-symbol type="bool" name="config_sendAudioBecomingNoisy" />
<java-symbol type="bool" name="config_enableScreenshotChord" />
<java-symbol type="bool" name="config_bluetooth_default_profiles" />
@@ -366,6 +372,8 @@
<java-symbol type="integer" name="config_wifi_tx_cur_ma" />
<java-symbol type="integer" name="config_wifi_operating_voltage_mv" />
<java-symbol type="string" name="config_wifi_framework_sap_2G_channel_list" />
+ <java-symbol type="integer" name="config_wifi_framework_max_tx_rate_for_full_scan" />
+ <java-symbol type="integer" name="config_wifi_framework_max_rx_rate_for_full_scan" />
<java-symbol type="bool" name="config_wifi_framework_cellular_handover_enable_user_triggered_adjustment" />
<java-symbol type="integer" name="config_wifi_framework_associated_full_scan_tx_packet_threshold" />
@@ -521,10 +529,7 @@
<java-symbol type="string" name="RestrictedOnDataTitle" />
<java-symbol type="string" name="RestrictedOnEmergencyTitle" />
<java-symbol type="string" name="RestrictedOnNormalTitle" />
- <java-symbol type="string" name="RestrictedOnAllVoiceContent" />
- <java-symbol type="string" name="RestrictedOnDataContent" />
- <java-symbol type="string" name="RestrictedOnEmergencyContent" />
- <java-symbol type="string" name="RestrictedOnNormalContent" />
+ <java-symbol type="string" name="RestrictedStateContent" />
<java-symbol type="string" name="notification_channel_network_alert" />
<java-symbol type="string" name="notification_channel_call_forward" />
<java-symbol type="string" name="notification_channel_emergency_callback" />
@@ -1473,7 +1478,6 @@
<java-symbol type="xml" name="password_kbd_symbols" />
<java-symbol type="xml" name="password_kbd_symbols_shift" />
<java-symbol type="xml" name="power_profile" />
- <java-symbol type="xml" name="time_zones_by_country" />
<java-symbol type="xml" name="sms_short_codes" />
<java-symbol type="xml" name="audio_assets" />
<java-symbol type="xml" name="global_keys" />
@@ -1551,7 +1555,6 @@
<java-symbol type="array" name="config_longPressVibePattern" />
<java-symbol type="array" name="config_safeModeDisabledVibePattern" />
<java-symbol type="array" name="config_safeModeEnabledVibePattern" />
- <java-symbol type="array" name="config_contextClickVibePattern" />
<java-symbol type="array" name="config_virtualKeyVibePattern" />
<java-symbol type="attr" name="actionModePopupWindowStyle" />
<java-symbol type="attr" name="dialogCustomTitleDecorLayout" />
@@ -1745,6 +1748,7 @@
<java-symbol type="bool" name="config_animateScreenLights" />
<java-symbol type="bool" name="config_automatic_brightness_available" />
<java-symbol type="bool" name="config_autoBrightnessResetAmbientLuxAfterWarmUp" />
+ <java-symbol type="bool" name="config_notificationHeaderClickableForExpand" />
<java-symbol type="bool" name="config_dozeAfterScreenOff" />
<java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" />
<java-symbol type="bool" name="config_enableFusedLocationOverlay" />
@@ -1771,7 +1775,6 @@
<java-symbol type="bool" name="config_supportLongPressPowerWhenNonInteractive" />
<java-symbol type="bool" name="config_wifi_background_scan_support" />
<java-symbol type="bool" name="config_wifi_dual_band_support" />
- <java-symbol type="bool" name="config_wifi_hotspot2_enabled" />
<java-symbol type="bool" name="config_wifi_fast_bss_transition_enabled" />
<java-symbol type="bool" name="config_wimaxEnabled" />
<java-symbol type="bool" name="show_ongoing_ime_switcher" />
@@ -1809,6 +1812,7 @@
<java-symbol type="dimen" name="default_minimal_size_pip_resizable_task" />
<java-symbol type="dimen" name="task_height_of_minimized_mode" />
<java-symbol type="fraction" name="config_screenAutoBrightnessDozeScaleFactor" />
+ <java-symbol type="bool" name="config_allowPriorityVibrationsInLowPowerMode" />
<java-symbol type="fraction" name="config_autoBrightnessAdjustmentMaxGamma" />
<java-symbol type="integer" name="config_autoBrightnessAmbientLightHorizon"/>
<java-symbol type="integer" name="config_autoBrightnessBrighteningLightDebounce"/>
@@ -1961,6 +1965,8 @@
<java-symbol type="string" name="usb_ptp_notification_title" />
<java-symbol type="string" name="usb_midi_notification_title" />
<java-symbol type="string" name="usb_supplying_notification_title" />
+ <java-symbol type="string" name="usb_unsupported_audio_accessory_title" />
+ <java-symbol type="string" name="usb_unsupported_audio_accessory_message" />
<java-symbol type="string" name="config_UsbDeviceConnectionHandling_component" />
<java-symbol type="string" name="vpn_text" />
<java-symbol type="string" name="vpn_text_long" />
@@ -2516,6 +2522,8 @@
<java-symbol type="integer" name="config_cameraLaunchGestureSensorType" />
<java-symbol type="string" name="config_cameraLaunchGestureSensorStringType" />
<java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" />
+ <java-symbol type="integer" name="config_cameraLiftTriggerSensorType" />
+ <java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" />
<java-symbol type="drawable" name="platlogo_m" />
@@ -2558,6 +2566,13 @@
<java-symbol type="dimen" name="notification_content_margin_top" />
<java-symbol type="dimen" name="notification_content_margin_bottom" />
<java-symbol type="dimen" name="notification_header_background_height" />
+ <java-symbol type="dimen" name="notification_header_height" />
+ <java-symbol type="dimen" name="notification_header_expand_icon_size" />
+ <java-symbol type="dimen" name="notification_expand_button_padding_top" />
+ <java-symbol type="dimen" name="notification_header_icon_margin_end" />
+ <java-symbol type="dimen" name="notification_header_icon_size" />
+ <java-symbol type="dimen" name="notification_header_app_name_margin_start" />
+ <java-symbol type="dimen" name="notification_header_separating_margin" />
<java-symbol type="string" name="default_notification_channel_label" />
<java-symbol type="string" name="importance_from_user" />
<java-symbol type="string" name="importance_from_person" />
@@ -2923,8 +2938,11 @@
<!-- android.service.trust -->
<java-symbol type="bool" name="config_allowEscrowTokenForTrustAgent"/>
+ <java-symbol type="string" name="config_defaultTrustAgent" />
<!-- Time picker -->
+ <java-symbol type="id" name="right_icon_container"/>
+ <java-symbol type="id" name="reply_icon_action"/>
<java-symbol type="id" name="toggle_mode"/>
<java-symbol type="id" name="input_mode"/>
<java-symbol type="id" name="input_header"/>
@@ -2991,13 +3009,9 @@
<!-- ETWS primary messages -->
<java-symbol type="string" name="etws_primary_default_message_earthquake" />
-
<java-symbol type="string" name="etws_primary_default_message_tsunami" />
-
<java-symbol type="string" name="etws_primary_default_message_earthquake_and_tsunami" />
-
<java-symbol type="string" name="etws_primary_default_message_test" />
-
<java-symbol type="string" name="etws_primary_default_message_others" />
<java-symbol type="bool" name="config_quickSettingsSupported" />
@@ -3023,8 +3037,11 @@
<java-symbol type="array" name="config_allowedSecureInstantAppSettings" />
<java-symbol type="bool" name="config_handleVolumeKeysInWindowManager" />
-
<java-symbol type="integer" name="config_inCallNotificationVolumeRelative" />
+ <java-symbol type="bool" name="config_dozeAlwaysOnDisplayAvailable" />
+ <java-symbol type="integer" name="config_storageManagerDaystoRetainDefault" />
+ <java-symbol type="string" name="config_headlineFontFamily" />
+
<java-symbol type="drawable" name="stat_sys_vitals" />
<java-symbol type="array" name="config_batteryPackageTypeSystem" />
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 1ae922a..644638d 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -55,7 +55,7 @@
<shortcode country="be" premium="\\d{4}" free="8\\d{3}|116\\d{3}" />
<!-- Bulgaria: 4-5 digits, plus EU -->
- <shortcode country="bg" pattern="\\d{4,5}" premium="18(?:16|423)|19(?:1[56]|35)" free="116\\d{3}" />
+ <shortcode country="bg" pattern="\\d{4,5}" premium="18(?:16|423)|19(?:1[56]|35)" free="116\\d{3}|1988|1490" />
<!-- Bahrain: 1-5 digits (standard system default, not country specific) -->
<shortcode country="bh" pattern="\\d{1,5}" free="81181" />
@@ -95,7 +95,7 @@
<!-- Spain: 5-6 digits: 25xxx, 27xxx, 280xx, 35xxx, 37xxx, 795xxx, 797xxx, 995xxx, 997xxx, plus EU.
http://www.legallink.es/?q=en/content/which-current-regulatory-status-premium-rate-services-spain -->
- <shortcode country="es" premium="[23][57]\\d{3}|280\\d{2}|[79]9[57]\\d{3}" free="116\\d{3}|22791|222145" />
+ <shortcode country="es" premium="[23][57]\\d{3}|280\\d{2}|[79]9[57]\\d{3}" free="116\\d{3}|22791|222145|22189" />
<!-- Finland: 5-6 digits, premium 0600, 0700: http://en.wikipedia.org/wiki/Telephone_numbers_in_Finland -->
<shortcode country="fi" pattern="\\d{5,6}" premium="0600.*|0700.*|171(?:59|63)" free="116\\d{3}|14789" />
@@ -103,12 +103,12 @@
<!-- France: 5 digits, free: 3xxxx, premium [4-8]xxxx, plus EU:
http://clients.txtnation.com/entries/161972-france-premium-sms-short-code-requirements,
visual voicemail code for Orange: 21101 -->
- <shortcode country="fr" premium="[4-8]\\d{4}" free="3\\d{4}|116\\d{3}|21101" />
+ <shortcode country="fr" premium="[4-8]\\d{4}" free="3\\d{4}|116\\d{3}|21101|20366" />
<!-- United Kingdom (Great Britain): 4-6 digits, common codes [5-8]xxxx, plus EU:
http://www.short-codes.com/media/Co-regulatoryCodeofPracticeforcommonshortcodes170206.pdf,
visual voicemail code for EE: 887 -->
- <shortcode country="gb" pattern="\\d{4,6}" premium="[5-8]\\d{4}" free="116\\d{3}|2020|35890|61002|61202|887|83669|34664|40406" />
+ <shortcode country="gb" pattern="\\d{4,6}" premium="[5-8]\\d{4}" free="116\\d{3}|2020|35890|61002|61202|887|83669|34664|40406|60174" />
<!-- Georgia: 4 digits, known premium codes listed -->
<shortcode country="ge" pattern="\\d{4}" premium="801[234]|888[239]" />
@@ -116,12 +116,15 @@
<!-- Greece: 5 digits (54xxx, 19yxx, x=0-9, y=0-5): http://www.cmtelecom.com/premium-sms/greece -->
<shortcode country="gr" pattern="\\d{5}" premium="54\\d{3}|19[0-5]\\d{2}" free="116\\d{3}|12115" />
+ <!-- Croatia -->
+ <shortcode country="hr" free="13062" />
+
<!-- Hungary: 4 or 10 digits starting with 1 or 0, plus EU:
http://clients.txtnation.com/entries/209633-hungary-premium-sms-short-code-regulations -->
<shortcode country="hu" pattern="[01](?:\\d{3}|\\d{9})" premium="0691227910|1784" free="116\\d{3}" />
<!-- India: 1-5 digits (standard system default, not country specific) -->
- <shortcode country="in" pattern="\\d{1,5}" free="59336" />
+ <shortcode country="in" pattern="\\d{1,5}" free="59336|53969" />
<!-- Indonesia: 1-5 digits (standard system default, not country specific) -->
<shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645" />
@@ -153,45 +156,48 @@
<shortcode country="kw" pattern="\\d{1,5}" free="1378|50420|94006" />
<!-- Lithuania: 3-5 digits, known premium codes listed, plus EU -->
- <shortcode country="lt" pattern="\\d{3,5}" premium="13[89]1|1394|16[34]5" free="116\\d{3}" />
+ <shortcode country="lt" pattern="\\d{3,5}" premium="13[89]1|1394|16[34]5" free="116\\d{3}|1399" />
<!-- Luxembourg: 5 digits, 6xxxx, plus EU:
http://www.luxgsm.lu/assets/files/filepage/file_1253803400.pdf -->
- <shortcode country="lu" premium="6\\d{4}" free="116\\d{3}" />
+ <shortcode country="lu" premium="6\\d{4}" free="116\\d{3}|60231" />
<!-- Latvia: 4 digits, known premium codes listed, plus EU -->
- <shortcode country="lv" pattern="\\d{4}" premium="18(?:19|63|7[1-4])" free="116\\d{3}" />
+ <shortcode country="lv" pattern="\\d{4}" premium="18(?:19|63|7[1-4])" free="116\\d{3}|1399" />
<!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
<shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="46645|5050|26259|50025|50052" />
<!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->
- <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099" />
+ <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288" />
<!-- The Netherlands, 4 digits, known premium codes listed, plus EU -->
- <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223|6225" />
+ <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223|6225|2223" />
<!-- Norway: 4-5 digits (not confirmed), known premium codes listed -->
<shortcode country="no" pattern="\\d{4,5}" premium="2201|222[67]" />
<!-- New Zealand: 3-4 digits, known premium codes listed -->
- <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" />
+ <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="3067|3068|4053" />
<!-- Philippines -->
<shortcode country="ph" free="2147|5495|5496" />
+ <!-- Pakistan -->
+ <shortcode country="pk" free="2057" />
+
<!-- Poland: 4-5 digits (not confirmed), known premium codes listed, plus EU -->
<shortcode country="pl" pattern="\\d{4,5}" premium="74240|79(?:10|866)|92525" free="116\\d{3}|8012|80921" />
<!-- Portugal: 5 digits, plus EU:
http://clients.txtnation.com/entries/158326-portugal-premium-sms-short-code-regulations -->
- <shortcode country="pt" premium="6[1289]\\d{3}" free="116\\d{3}" />
+ <shortcode country="pt" premium="6[1289]\\d{3}" free="116\\d{3}|1262" />
<!-- Qatar: 1-5 digits (standard system default, not country specific) -->
<shortcode country="qa" pattern="\\d{1,5}" free="92451" />
<!-- Romania: 4 digits, plus EU: http://www.simplus.ro/en/resources/glossary-of-terms/ -->
- <shortcode country="ro" pattern="\\d{4}" premium="12(?:63|66|88)|13(?:14|80)" free="116\\d{3}|3654" />
+ <shortcode country="ro" pattern="\\d{4}" premium="12(?:63|66|88)|13(?:14|80)" free="116\\d{3}|3654|8360" />
<!-- Russia: 4 digits, known premium codes listed: http://smscoin.net/info/pricing-russia/ -->
<shortcode country="ru" pattern="\\d{4}" premium="1(?:1[56]1|899)|2(?:09[57]|322|47[46]|880|990)|3[589]33|4161|44(?:4[3-9]|81)|77(?:33|81)|8424" />
@@ -207,7 +213,7 @@
<shortcode country="sg" pattern="7\\d{4}" premium="73800" standard="74688|70134" />
<!-- Slovenia: 4 digits (premium=3xxx, 6xxx, 8xxx), plus EU: http://www.cmtelecom.com/premium-sms/slovenia -->
- <shortcode country="si" pattern="\\d{4}" premium="[368]\\d{3}" free="116\\d{3}" />
+ <shortcode country="si" pattern="\\d{4}" premium="[368]\\d{3}" free="116\\d{3}|3133" />
<!-- Slovakia: 4 digits (premium), plus EU: http://www.cmtelecom.com/premium-sms/slovakia -->
<shortcode country="sk" premium="\\d{4}" free="116\\d{3}|8000" />
@@ -219,7 +225,7 @@
<shortcode country="tj" pattern="\\d{4}" premium="11[3-7]1|4161|4333|444[689]" />
<!-- Turkey -->
- <shortcode country="tr" free="7529|5528" />
+ <shortcode country="tr" free="7529|5528|6493" />
<!-- Ukraine: 4 digits, known premium codes listed -->
<shortcode country="ua" pattern="\\d{4}" premium="444[3-9]|70[579]4|7540" />
diff --git a/core/res/res/xml/time_zones_by_country.xml b/core/res/res/xml/time_zones_by_country.xml
deleted file mode 100644
index 22bfea1..0000000
--- a/core/res/res/xml/time_zones_by_country.xml
+++ /dev/null
@@ -1,1372 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2006, 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.
-*/
--->
-<timezones>
- <!-- ANDORRA, 1:00 -->
-
- <timezone code="ad">Europe/Andorra</timezone>
-
- <!-- UNITED ARAB EMIRATES, 4:00 -->
-
- <timezone code="ae">Asia/Dubai</timezone>
-
- <!-- AFGHANISTAN, 4:30 -->
-
- <timezone code="af">Asia/Kabul</timezone>
-
- <!-- ANTIGUA AND BARBUDA, -4:00 -->
-
- <timezone code="ag">America/Antigua</timezone>
-
- <!-- ANGUILLA, -4:00 -->
-
- <timezone code="ai">America/Anguilla</timezone>
-
- <!-- ALBANIA, 1:00 -->
-
- <timezone code="al">Europe/Tirane</timezone>
-
- <!-- ARMENIA, 4:00 -->
-
- <timezone code="am">Asia/Yerevan</timezone>
-
- <!-- ANGOLA, 1:00 -->
-
- <timezone code="ao">Africa/Luanda</timezone>
-
- <!-- ANTARCTICA, 12:00 -->
-
- <timezone code="aq">Antarctica/McMurdo</timezone>
-
- <!-- ANTARCTICA, 10:00 -->
-
- <timezone code="aq">Antarctica/DumontDUrville</timezone>
-
- <!-- ANTARCTICA, 8:00 -->
-
- <timezone code="aq">Antarctica/Casey</timezone>
-
- <!-- ANTARCTICA, 7:00 -->
-
- <timezone code="aq">Antarctica/Davis</timezone>
-
- <!-- ANTARCTICA, 5:00 -->
-
- <timezone code="aq">Antarctica/Mawson</timezone>
-
- <!-- ANTARCTICA, 6:00 -->
-
- <timezone code="aq">Antarctica/Vostok</timezone>
-
- <!-- ANTARCTICA, 3:00 -->
-
- <timezone code="aq">Antarctica/Syowa</timezone>
-
- <!-- ANTARCTICA, 0:00 -->
-
- <timezone code="aq">Antarctica/Troll</timezone>
-
- <!-- ANTARCTICA, -3:00 -->
-
- <timezone code="aq">Antarctica/Rothera</timezone>
-
- <!-- ANTARCTICA, -4:00 -->
-
- <timezone code="aq">Antarctica/Palmer</timezone>
-
- <!-- ARGENTINA, -3:00 -->
-
- <timezone code="ar">America/Argentina/Buenos_Aires</timezone>
- <timezone code="ar">America/Argentina/Cordoba</timezone>
- <timezone code="ar">America/Argentina/Salta</timezone>
- <timezone code="ar">America/Argentina/Jujuy</timezone>
- <timezone code="ar">America/Argentina/Tucuman</timezone>
- <timezone code="ar">America/Argentina/Catamarca</timezone>
- <timezone code="ar">America/Argentina/La_Rioja</timezone>
- <timezone code="ar">America/Argentina/San_Juan</timezone>
- <timezone code="ar">America/Argentina/Mendoza</timezone>
- <timezone code="ar">America/Argentina/San_Luis</timezone>
- <timezone code="ar">America/Argentina/Rio_Gallegos</timezone>
- <timezone code="ar">America/Argentina/Ushuaia</timezone>
-
- <!-- AMERICAN SAMOA, -11:00 -->
-
- <timezone code="as">Pacific/Pago_Pago</timezone>
-
- <!-- AUSTRIA, 1:00 -->
-
- <timezone code="at">Europe/Vienna</timezone>
-
- <!-- AUSTRALIA, 10:00 -->
-
- <timezone code="au">Australia/Sydney</timezone>
- <timezone code="au">Australia/Melbourne</timezone>
- <timezone code="au">Australia/Brisbane</timezone>
- <timezone code="au">Australia/Hobart</timezone>
- <timezone code="au">Australia/Currie</timezone>
- <timezone code="au">Australia/Lindeman</timezone>
-
- <!-- AUSTRALIA, 11:00 -->
- <timezone code="au">Antarctica/Macquarie</timezone>
-
- <!-- AUSTRALIA, 10:30 -->
-
- <timezone code="au">Australia/Lord_Howe</timezone>
-
- <!-- AUSTRALIA, 9:30 -->
-
- <timezone code="au">Australia/Adelaide</timezone>
- <timezone code="au">Australia/Broken_Hill</timezone>
- <timezone code="au">Australia/Darwin</timezone>
-
- <!-- AUSTRALIA, 8:00 -->
-
- <timezone code="au">Australia/Perth</timezone>
-
- <!-- AUSTRALIA, 8:45 -->
-
- <timezone code="au">Australia/Eucla</timezone>
-
- <!-- ARUBA, -4:00 -->
-
- <timezone code="aw">America/Aruba</timezone>
-
- <!-- ALAND ISLANDS, 2:00 -->
-
- <timezone code="ax">Europe/Mariehamn</timezone>
-
- <!-- AZERBAIJAN, 4:00 -->
-
- <timezone code="az">Asia/Baku</timezone>
-
- <!-- BOSNIA AND HERZEGOVINA, 1:00 -->
-
- <timezone code="ba">Europe/Sarajevo</timezone>
-
- <!-- BARBADOS, -4:00 -->
-
- <timezone code="bb">America/Barbados</timezone>
-
- <!-- BANGLADESH, 6:00 -->
-
- <timezone code="bd">Asia/Dhaka</timezone>
-
- <!-- BELGIUM, 1:00 -->
-
- <timezone code="be">Europe/Brussels</timezone>
-
- <!-- BURKINA FASO, 0:00 -->
-
- <timezone code="bf">Africa/Ouagadougou</timezone>
-
- <!-- BULGARIA, 2:00 -->
-
- <timezone code="bg">Europe/Sofia</timezone>
-
- <!-- BAHRAIN, 3:00 -->
-
- <timezone code="bh">Asia/Bahrain</timezone>
-
- <!-- BURUNDI, 2:00 -->
-
- <timezone code="bi">Africa/Bujumbura</timezone>
-
- <!-- BENIN, 1:00 -->
-
- <timezone code="bj">Africa/Porto-Novo</timezone>
-
- <!-- Saint Barthélemy, -4:00 -->
-
- <timezone code="bl">America/St_Barthelemy</timezone>
-
- <!-- BERMUDA, -4:00 -->
-
- <timezone code="bm">Atlantic/Bermuda</timezone>
-
- <!-- BRUNEI DARUSSALAM, 8:00 -->
-
- <timezone code="bn">Asia/Brunei</timezone>
-
- <!-- BOLIVIA, -4:00 -->
-
- <timezone code="bo">America/La_Paz</timezone>
-
- <!-- Caribbean Netherlands, -4:00 -->
-
- <timezone code="bq">America/Kralendijk</timezone>
-
- <!-- BRAZIL, -2:00 -->
-
- <timezone code="br">America/Noronha</timezone>
-
- <!-- BRAZIL, -3:00 -->
-
- <timezone code="br">America/Sao_Paulo</timezone>
- <timezone code="br">America/Belem</timezone>
- <timezone code="br">America/Fortaleza</timezone>
- <timezone code="br">America/Recife</timezone>
- <timezone code="br">America/Araguaina</timezone>
- <timezone code="br">America/Maceio</timezone>
- <timezone code="br">America/Bahia</timezone>
- <timezone code="br">America/Santarem</timezone>
-
- <!-- BRAZIL, -4:00 -->
-
- <timezone code="br">America/Manaus</timezone>
- <timezone code="br">America/Campo_Grande</timezone>
- <timezone code="br">America/Cuiaba</timezone>
- <timezone code="br">America/Porto_Velho</timezone>
- <timezone code="br">America/Boa_Vista</timezone>
-
- <!-- BRAZIL, -5:00 -->
-
- <timezone code="br">America/Eirunepe</timezone>
- <timezone code="br">America/Rio_Branco</timezone>
-
- <!-- BAHAMAS, -5:00 -->
-
- <timezone code="bs">America/Nassau</timezone>
-
- <!-- BHUTAN, 6:00 -->
-
- <timezone code="bt">Asia/Thimphu</timezone>
-
- <!-- BOTSWANA, 2:00 -->
-
- <timezone code="bw">Africa/Gaborone</timezone>
-
- <!-- BELARUS, 3:00 -->
-
- <timezone code="by">Europe/Minsk</timezone>
-
- <!-- BELIZE, -6:00 -->
-
- <timezone code="bz">America/Belize</timezone>
-
- <!-- CANADA, -3:30 -->
-
- <timezone code="ca">America/St_Johns</timezone>
-
- <!-- CANADA, -4:00 -->
-
- <timezone code="ca">America/Halifax</timezone>
- <timezone code="ca">America/Glace_Bay</timezone>
- <timezone code="ca">America/Moncton</timezone>
- <timezone code="ca">America/Goose_Bay</timezone>
- <timezone code="ca">America/Blanc-Sablon</timezone>
-
- <!-- CANADA, -5:00 -->
-
- <timezone code="ca">America/Toronto</timezone>
- <timezone code="ca">America/Nipigon</timezone>
- <timezone code="ca">America/Thunder_Bay</timezone>
- <timezone code="ca">America/Iqaluit</timezone>
- <timezone code="ca">America/Pangnirtung</timezone>
- <timezone code="ca">America/Atikokan</timezone>
-
- <!-- CANADA, -6:00 -->
-
- <timezone code="ca">America/Winnipeg</timezone>
- <timezone code="ca">America/Regina</timezone>
- <timezone code="ca">America/Rankin_Inlet</timezone>
- <timezone code="ca">America/Rainy_River</timezone>
- <timezone code="ca">America/Swift_Current</timezone>
- <timezone code="ca">America/Resolute</timezone>
-
- <!-- CANADA, -7:00 -->
-
- <timezone code="ca">America/Edmonton</timezone>
- <timezone code="ca">America/Cambridge_Bay</timezone>
- <timezone code="ca">America/Yellowknife</timezone>
- <timezone code="ca">America/Inuvik</timezone>
- <timezone code="ca">America/Dawson_Creek</timezone>
- <timezone code="ca">America/Creston</timezone>
- <timezone code="ca">America/Fort_Nelson</timezone>
-
- <!-- CANADA, -8:00 -->
-
- <timezone code="ca">America/Vancouver</timezone>
- <timezone code="ca">America/Whitehorse</timezone>
- <timezone code="ca">America/Dawson</timezone>
-
- <!-- COCOS (KEELING) ISLANDS, 6:30 -->
-
- <timezone code="cc">Indian/Cocos</timezone>
-
- <!-- CONGO, THE DEMOCRATIC REPUBLIC OF THE, 2:00 -->
-
- <timezone code="cd">Africa/Lubumbashi</timezone>
-
- <!-- CONGO, THE DEMOCRATIC REPUBLIC OF THE, 1:00 -->
-
- <timezone code="cd">Africa/Kinshasa</timezone>
-
- <!-- CENTRAL AFRICAN REPUBLIC, 1:00 -->
-
- <timezone code="cf">Africa/Bangui</timezone>
-
- <!-- CONGO, 1:00 -->
-
- <timezone code="cg">Africa/Brazzaville</timezone>
-
- <!-- SWITZERLAND, 1:00 -->
-
- <timezone code="ch">Europe/Zurich</timezone>
-
- <!-- COTE D'IVOIRE, 0:00 -->
-
- <timezone code="ci">Africa/Abidjan</timezone>
-
- <!-- COOK ISLANDS, -10:00 -->
-
- <timezone code="ck">Pacific/Rarotonga</timezone>
-
- <!-- CHILE, -3:00 -->
-
- <timezone code="cl">America/Punta_Arenas</timezone>
-
- <!-- CHILE, -4:00 -->
-
- <timezone code="cl">America/Santiago</timezone>
-
- <!-- CHILE, -6:00 -->
-
- <timezone code="cl">Pacific/Easter</timezone>
-
- <!-- CAMEROON, 1:00 -->
-
- <timezone code="cm">Africa/Douala</timezone>
-
- <!-- CHINA, 8:00 -->
-
- <timezone code="cn">Asia/Shanghai</timezone>
-
- <!-- CHINA, 6:00 -->
-
- <timezone code="cn">Asia/Urumqi</timezone>
-
- <!-- COLOMBIA, -5:00 -->
-
- <timezone code="co">America/Bogota</timezone>
-
- <!-- COSTA RICA, -6:00 -->
-
- <timezone code="cr">America/Costa_Rica</timezone>
-
- <!-- CUBA, -5:00 -->
-
- <timezone code="cu">America/Havana</timezone>
-
- <!-- CAPE VERDE, -1:00 -->
-
- <timezone code="cv">Atlantic/Cape_Verde</timezone>
-
- <!-- Curaçao, -4:00 -->
-
- <timezone code="cw">America/Curacao</timezone>
-
- <!-- CHRISTMAS ISLAND, 7:00 -->
-
- <timezone code="cx">Indian/Christmas</timezone>
-
- <!-- CYPRUS, 2:00 -->
-
- <timezone code="cy">Asia/Nicosia</timezone>
-
- <!-- CYPRUS, 3:00 -->
-
- <timezone code="cy">Asia/Famagusta</timezone>
-
- <!-- CZECH REPUBLIC, 1:00 -->
-
- <timezone code="cz">Europe/Prague</timezone>
-
- <!-- GERMANY, 1:00 -->
-
- <timezone code="de">Europe/Berlin</timezone>
- <timezone code="de">Europe/Busingen</timezone>
-
- <!-- DJIBOUTI, 3:00 -->
-
- <timezone code="dj">Africa/Djibouti</timezone>
-
- <!-- DENMARK, 1:00 -->
-
- <timezone code="dk">Europe/Copenhagen</timezone>
-
- <!-- DOMINICA, -4:00 -->
-
- <timezone code="dm">America/Dominica</timezone>
-
- <!-- DOMINICAN REPUBLIC, -4:00 -->
-
- <timezone code="do">America/Santo_Domingo</timezone>
-
- <!-- ALGERIA, 1:00 -->
-
- <timezone code="dz">Africa/Algiers</timezone>
-
- <!-- ECUADOR, -5:00 -->
-
- <timezone code="ec">America/Guayaquil</timezone>
-
- <!-- ECUADOR, -6:00 -->
-
- <timezone code="ec">Pacific/Galapagos</timezone>
-
- <!-- ESTONIA, 2:00 -->
-
- <timezone code="ee">Europe/Tallinn</timezone>
-
- <!-- EGYPT, 2:00 -->
-
- <timezone code="eg">Africa/Cairo</timezone>
-
- <!-- WESTERN SAHARA, 0:00 -->
-
- <timezone code="eh">Africa/El_Aaiun</timezone>
-
- <!-- ERITREA, 3:00 -->
-
- <timezone code="er">Africa/Asmara</timezone>
-
- <!-- SPAIN, 1:00 -->
-
- <timezone code="es">Europe/Madrid</timezone>
- <timezone code="es">Africa/Ceuta</timezone>
-
- <!-- SPAIN, 0:00 -->
-
- <timezone code="es">Atlantic/Canary</timezone>
-
- <!-- ETHIOPIA, 3:00 -->
-
- <timezone code="et">Africa/Addis_Ababa</timezone>
-
- <!-- FINLAND, 2:00 -->
-
- <timezone code="fi">Europe/Helsinki</timezone>
-
- <!-- FIJI, 12:00 -->
-
- <timezone code="fj">Pacific/Fiji</timezone>
-
- <!-- FALKLAND ISLANDS (MALVINAS), -3:00 -->
-
- <timezone code="fk">Atlantic/Stanley</timezone>
-
- <!-- MICRONESIA, FEDERATED STATES OF, 11:00 -->
-
- <timezone code="fm">Pacific/Pohnpei</timezone>
- <timezone code="fm">Pacific/Kosrae</timezone>
-
- <!-- MICRONESIA, FEDERATED STATES OF, 10:00 -->
-
- <timezone code="fm">Pacific/Chuuk</timezone>
-
- <!-- FAROE ISLANDS, 0:00 -->
-
- <timezone code="fo">Atlantic/Faroe</timezone>
-
- <!-- FRANCE, 1:00 -->
-
- <timezone code="fr">Europe/Paris</timezone>
-
- <!-- GABON, 1:00 -->
-
- <timezone code="ga">Africa/Libreville</timezone>
-
- <!-- UNITED KINGDOM, 0:00 -->
-
- <timezone code="gb">Europe/London</timezone>
-
- <!-- GRENADA, -4:00 -->
-
- <timezone code="gd">America/Grenada</timezone>
-
- <!-- GEORGIA, 4:00 -->
-
- <timezone code="ge">Asia/Tbilisi</timezone>
-
- <!-- FRENCH GUIANA, -3:00 -->
-
- <timezone code="gf">America/Cayenne</timezone>
-
- <!-- GUERNSEY, 0:00 -->
-
- <timezone code="gg">Europe/Guernsey</timezone>
-
- <!-- GHANA, 0:00 -->
-
- <timezone code="gh">Africa/Accra</timezone>
-
- <!-- GIBRALTAR, 1:00 -->
-
- <timezone code="gi">Europe/Gibraltar</timezone>
-
- <!-- GREENLAND, 0:00 -->
-
- <timezone code="gl">America/Danmarkshavn</timezone>
-
- <!-- GREENLAND, -1:00 -->
-
- <timezone code="gl">America/Scoresbysund</timezone>
-
- <!-- GREENLAND, -3:00 -->
-
- <timezone code="gl">America/Godthab</timezone>
-
- <!-- GREENLAND, -4:00 -->
-
- <timezone code="gl">America/Thule</timezone>
-
- <!-- GAMBIA, 0:00 -->
-
- <timezone code="gm">Africa/Banjul</timezone>
-
- <!-- GUINEA, 0:00 -->
-
- <timezone code="gn">Africa/Conakry</timezone>
-
- <!-- GUADELOUPE, -4:00 -->
-
- <timezone code="gp">America/Guadeloupe</timezone>
-
- <!-- EQUATORIAL GUINEA, 1:00 -->
-
- <timezone code="gq">Africa/Malabo</timezone>
-
- <!-- GREECE, 2:00 -->
-
- <timezone code="gr">Europe/Athens</timezone>
-
- <!-- SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS, -2:00 -->
-
- <timezone code="gs">Atlantic/South_Georgia</timezone>
-
- <!-- GUATEMALA, -6:00 -->
-
- <timezone code="gt">America/Guatemala</timezone>
-
- <!-- GUAM, 10:00 -->
-
- <timezone code="gu">Pacific/Guam</timezone>
-
- <!-- GUINEA-BISSAU, 0:00 -->
-
- <timezone code="gw">Africa/Bissau</timezone>
-
- <!-- GUYANA, -4:00 -->
-
- <timezone code="gy">America/Guyana</timezone>
-
- <!-- HONG KONG, 8:00 -->
-
- <timezone code="hk">Asia/Hong_Kong</timezone>
-
- <!-- HONDURAS, -6:00 -->
-
- <timezone code="hn">America/Tegucigalpa</timezone>
-
- <!-- CROATIA, 1:00 -->
-
- <timezone code="hr">Europe/Zagreb</timezone>
-
- <!-- HAITI, -5:00 -->
-
- <timezone code="ht">America/Port-au-Prince</timezone>
-
- <!-- HUNGARY, 1:00 -->
-
- <timezone code="hu">Europe/Budapest</timezone>
-
- <!-- INDONESIA, 9:00 -->
-
- <timezone code="id">Asia/Jayapura</timezone>
-
- <!-- INDONESIA, 8:00 -->
-
- <timezone code="id">Asia/Makassar</timezone>
-
- <!-- INDONESIA, 7:00 -->
-
- <timezone code="id">Asia/Jakarta</timezone>
- <timezone code="id">Asia/Pontianak</timezone>
-
- <!-- IRELAND, 0:00 -->
-
- <timezone code="ie">Europe/Dublin</timezone>
-
- <!-- ISRAEL, 2:00 -->
-
- <timezone code="il">Asia/Jerusalem</timezone>
-
- <!-- ISLE OF MAN, 0:00 -->
-
- <timezone code="im">Europe/Isle_of_Man</timezone>
-
- <!-- INDIA, 5:30 -->
-
- <timezone code="in">Asia/Kolkata</timezone>
-
- <!-- BRITISH INDIAN OCEAN TERRITORY, 6:00 -->
-
- <timezone code="io">Indian/Chagos</timezone>
-
- <!-- IRAQ, 3:00 -->
-
- <timezone code="iq">Asia/Baghdad</timezone>
-
- <!-- IRAN, ISLAMIC REPUBLIC OF, 3:30 -->
-
- <timezone code="ir">Asia/Tehran</timezone>
-
- <!-- ICELAND, 0:00 -->
-
- <timezone code="is">Atlantic/Reykjavik</timezone>
-
- <!-- ITALY, 1:00 -->
-
- <timezone code="it">Europe/Rome</timezone>
-
- <!-- JERSEY, 0:00 -->
-
- <timezone code="je">Europe/Jersey</timezone>
-
- <!-- JAMAICA, -5:00 -->
-
- <timezone code="jm">America/Jamaica</timezone>
-
- <!-- JORDAN, 2:00 -->
-
- <timezone code="jo">Asia/Amman</timezone>
-
- <!-- JAPAN, 9:00 -->
-
- <timezone code="jp">Asia/Tokyo</timezone>
-
- <!-- KENYA, 3:00 -->
-
- <timezone code="ke">Africa/Nairobi</timezone>
-
- <!-- KYRGYZSTAN, 6:00 -->
-
- <timezone code="kg">Asia/Bishkek</timezone>
-
- <!-- CAMBODIA, 7:00 -->
-
- <timezone code="kh">Asia/Phnom_Penh</timezone>
-
- <!-- KIRIBATI, 14:00 -->
-
- <timezone code="ki">Pacific/Kiritimati</timezone>
-
- <!-- KIRIBATI, 13:00 -->
-
- <timezone code="ki">Pacific/Enderbury</timezone>
-
- <!-- KIRIBATI, 12:00 -->
-
- <timezone code="ki">Pacific/Tarawa</timezone>
-
- <!-- COMOROS, 3:00 -->
-
- <timezone code="km">Indian/Comoro</timezone>
-
- <!-- SAINT KITTS AND NEVIS, -4:00 -->
-
- <timezone code="kn">America/St_Kitts</timezone>
-
- <!-- KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF, 8:30 -->
-
- <timezone code="kp">Asia/Pyongyang</timezone>
-
- <!-- KOREA, REPUBLIC OF, 9:00 -->
-
- <timezone code="kr">Asia/Seoul</timezone>
-
- <!-- KUWAIT, 3:00 -->
-
- <timezone code="kw">Asia/Kuwait</timezone>
-
- <!-- CAYMAN ISLANDS, -5:00 -->
-
- <timezone code="ky">America/Cayman</timezone>
-
- <!-- KAZAKHSTAN, 6:00 -->
-
- <timezone code="kz">Asia/Almaty</timezone>
- <timezone code="kz">Asia/Qyzylorda</timezone>
-
- <!-- KAZAKHSTAN, 5:00 -->
-
- <timezone code="kz">Asia/Aqtau</timezone>
- <timezone code="kz">Asia/Oral</timezone>
- <timezone code="kz">Asia/Aqtobe</timezone>
- <timezone code="kz">Asia/Atyrau</timezone>
-
- <!-- LAO PEOPLE'S DEMOCRATIC REPUBLIC, 7:00 -->
-
- <timezone code="la">Asia/Vientiane</timezone>
-
- <!-- LEBANON, 2:00 -->
-
- <timezone code="lb">Asia/Beirut</timezone>
-
- <!-- SAINT LUCIA, -4:00 -->
-
- <timezone code="lc">America/St_Lucia</timezone>
-
- <!-- LIECHTENSTEIN, 1:00 -->
-
- <timezone code="li">Europe/Vaduz</timezone>
-
- <!-- SRI LANKA, 5:30 -->
-
- <timezone code="lk">Asia/Colombo</timezone>
-
- <!-- LIBERIA, 0:00 -->
-
- <timezone code="lr">Africa/Monrovia</timezone>
-
- <!-- LESOTHO, 2:00 -->
-
- <timezone code="ls">Africa/Maseru</timezone>
-
- <!-- LITHUANIA, 2:00 -->
-
- <timezone code="lt">Europe/Vilnius</timezone>
-
- <!-- LUXEMBOURG, 1:00 -->
-
- <timezone code="lu">Europe/Luxembourg</timezone>
-
- <!-- LATVIA, 2:00 -->
-
- <timezone code="lv">Europe/Riga</timezone>
-
- <!-- LIBYAN ARAB JAMAHIRIYA, 2:00 -->
-
- <timezone code="ly">Africa/Tripoli</timezone>
-
- <!-- MOROCCO, 0:00 -->
-
- <timezone code="ma">Africa/Casablanca</timezone>
-
- <!-- MONACO, 1:00 -->
-
- <timezone code="mc">Europe/Monaco</timezone>
-
- <!-- MOLDOVA, 2:00 -->
-
- <timezone code="md">Europe/Chisinau</timezone>
-
- <!-- MONTENEGRO, 1:00 -->
-
- <timezone code="me">Europe/Podgorica</timezone>
-
- <!-- Collectivity of Saint Martin, -4:00 -->
-
- <timezone code="mf">America/Marigot</timezone>
-
- <!-- MADAGASCAR, 3:00 -->
-
- <timezone code="mg">Indian/Antananarivo</timezone>
-
- <!-- MARSHALL ISLANDS, 12:00 -->
-
- <timezone code="mh">Pacific/Majuro</timezone>
- <timezone code="mh">Pacific/Kwajalein</timezone>
-
- <!-- MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF, 1:00 -->
-
- <timezone code="mk">Europe/Skopje</timezone>
-
- <!-- MALI, 0:00 -->
-
- <timezone code="ml">Africa/Bamako</timezone>
-
- <!-- MYANMAR, 6:30 -->
-
- <timezone code="mm">Asia/Yangon</timezone>
-
- <!-- MONGOLIA, 8:00 -->
-
- <timezone code="mn">Asia/Choibalsan</timezone>
- <timezone code="mn">Asia/Ulaanbaatar</timezone>
-
- <!-- MONGOLIA, 7:00 -->
-
- <timezone code="mn">Asia/Hovd</timezone>
-
- <!-- MACAO, 8:00 -->
-
- <timezone code="mo">Asia/Macau</timezone>
-
- <!-- NORTHERN MARIANA ISLANDS, 10:00 -->
-
- <timezone code="mp">Pacific/Saipan</timezone>
-
- <!-- MARTINIQUE, -4:00 -->
-
- <timezone code="mq">America/Martinique</timezone>
-
- <!-- MAURITANIA, 0:00 -->
-
- <timezone code="mr">Africa/Nouakchott</timezone>
-
- <!-- MONTSERRAT, -4:00 -->
-
- <timezone code="ms">America/Montserrat</timezone>
-
- <!-- MALTA, 1:00 -->
-
- <timezone code="mt">Europe/Malta</timezone>
-
- <!-- MAURITIUS, 4:00 -->
-
- <timezone code="mu">Indian/Mauritius</timezone>
-
- <!-- MALDIVES, 5:00 -->
-
- <timezone code="mv">Indian/Maldives</timezone>
-
- <!-- MALAWI, 2:00 -->
-
- <timezone code="mw">Africa/Blantyre</timezone>
-
- <!-- MEXICO, -6:00 -->
-
- <timezone code="mx">America/Mexico_City</timezone>
- <timezone code="mx">America/Merida</timezone>
- <timezone code="mx">America/Monterrey</timezone>
- <timezone code="mx">America/Matamoros</timezone>
- <timezone code="mx">America/Bahia_Banderas</timezone>
-
- <!-- MEXICO, -5:00 -->
-
- <timezone code="mx">America/Cancun</timezone>
-
- <!-- MEXICO, -7:00 -->
-
- <timezone code="mx">America/Chihuahua</timezone>
- <timezone code="mx">America/Hermosillo</timezone>
- <timezone code="mx">America/Mazatlan</timezone>
- <timezone code="mx">America/Ojinaga</timezone>
-
- <!-- MEXICO, -8:00 -->
-
- <timezone code="mx">America/Tijuana</timezone>
-
- <!-- MALAYSIA, 8:00 -->
-
- <timezone code="my">Asia/Kuala_Lumpur</timezone>
- <timezone code="my">Asia/Kuching</timezone>
-
- <!-- MOZAMBIQUE, 2:00 -->
-
- <timezone code="mz">Africa/Maputo</timezone>
-
- <!-- NAMIBIA, 1:00 -->
-
- <timezone code="na">Africa/Windhoek</timezone>
-
- <!-- NEW CALEDONIA, 11:00 -->
-
- <timezone code="nc">Pacific/Noumea</timezone>
-
- <!-- NIGER, 1:00 -->
-
- <timezone code="ne">Africa/Niamey</timezone>
-
- <!-- NORFOLK ISLAND, 11:30 -->
-
- <timezone code="nf">Pacific/Norfolk</timezone>
-
- <!-- NIGERIA, 1:00 -->
-
- <timezone code="ng">Africa/Lagos</timezone>
-
- <!-- NICARAGUA, -6:00 -->
-
- <timezone code="ni">America/Managua</timezone>
-
- <!-- NETHERLANDS, 1:00 -->
-
- <timezone code="nl">Europe/Amsterdam</timezone>
-
- <!-- NORWAY, 1:00 -->
-
- <timezone code="no">Europe/Oslo</timezone>
-
- <!-- NEPAL, 5:45 -->
-
- <timezone code="np">Asia/Kathmandu</timezone>
-
- <!-- NAURU, 12:00 -->
-
- <timezone code="nr">Pacific/Nauru</timezone>
-
- <!-- NIUE, -11:00 -->
-
- <timezone code="nu">Pacific/Niue</timezone>
-
- <!-- NEW ZEALAND, 12:00 -->
-
- <timezone code="nz">Pacific/Auckland</timezone>
-
- <!-- NEW ZEALAND, 12:45 -->
-
- <timezone code="nz">Pacific/Chatham</timezone>
-
- <!-- OMAN, 4:00 -->
-
- <timezone code="om">Asia/Muscat</timezone>
-
- <!-- PANAMA, -5:00 -->
-
- <timezone code="pa">America/Panama</timezone>
-
- <!-- PERU, -5:00 -->
-
- <timezone code="pe">America/Lima</timezone>
-
- <!-- FRENCH POLYNESIA, -9:00 -->
-
- <timezone code="pf">Pacific/Gambier</timezone>
-
- <!-- FRENCH POLYNESIA, -9:30 -->
-
- <timezone code="pf">Pacific/Marquesas</timezone>
-
- <!-- FRENCH POLYNESIA, -10:00 -->
-
- <timezone code="pf">Pacific/Tahiti</timezone>
-
- <!-- PAPUA NEW GUINEA, 10:00 -->
-
- <timezone code="pg">Pacific/Port_Moresby</timezone>
-
- <!-- PAPUA NEW GUINEA, 11:00 -->
-
- <timezone code="pg">Pacific/Bougainville</timezone>
-
- <!-- PHILIPPINES, 8:00 -->
-
- <timezone code="ph">Asia/Manila</timezone>
-
- <!-- PAKISTAN, 5:00 -->
-
- <timezone code="pk">Asia/Karachi</timezone>
-
- <!-- POLAND, 1:00 -->
-
- <timezone code="pl">Europe/Warsaw</timezone>
-
- <!-- SAINT PIERRE AND MIQUELON, -3:00 -->
-
- <timezone code="pm">America/Miquelon</timezone>
-
- <!-- PITCAIRN, -8:00 -->
-
- <timezone code="pn">Pacific/Pitcairn</timezone>
-
- <!-- PUERTO RICO, -4:00 -->
-
- <timezone code="pr">America/Puerto_Rico</timezone>
-
- <!-- PALESTINE, 2:00 -->
-
- <timezone code="ps">Asia/Gaza</timezone>
- <timezone code="ps">Asia/Hebron</timezone>
-
- <!-- PORTUGAL, 0:00 -->
-
- <timezone code="pt">Europe/Lisbon</timezone>
- <timezone code="pt">Atlantic/Madeira</timezone>
-
- <!-- PORTUGAL, -1:00 -->
-
- <timezone code="pt">Atlantic/Azores</timezone>
-
- <!-- PALAU, 9:00 -->
-
- <timezone code="pw">Pacific/Palau</timezone>
-
- <!-- PARAGUAY, -4:00 -->
-
- <timezone code="py">America/Asuncion</timezone>
-
- <!-- QATAR, 3:00 -->
-
- <timezone code="qa">Asia/Qatar</timezone>
-
- <!-- REUNION, 4:00 -->
-
- <timezone code="re">Indian/Reunion</timezone>
-
- <!-- ROMANIA, 2:00 -->
-
- <timezone code="ro">Europe/Bucharest</timezone>
-
- <!-- SERBIA, 1:00 -->
-
- <timezone code="rs">Europe/Belgrade</timezone>
-
- <!-- RUSSIAN FEDERATION, 12:00 -->
-
- <timezone code="ru">Asia/Kamchatka</timezone>
- <timezone code="ru">Asia/Anadyr</timezone>
-
- <!-- RUSSIAN FEDERATION, 11:00 -->
-
- <timezone code="ru">Asia/Magadan</timezone>
- <timezone code="ru">Asia/Sakhalin</timezone>
- <timezone code="ru">Asia/Srednekolymsk</timezone>
-
- <!-- RUSSIAN FEDERATION, 10:00 -->
-
- <timezone code="ru">Asia/Vladivostok</timezone>
- <timezone code="ru">Asia/Ust-Nera</timezone>
-
- <!-- RUSSIAN FEDERATION, 9:00 -->
-
- <timezone code="ru">Asia/Yakutsk</timezone>
- <timezone code="ru">Asia/Chita</timezone>
- <timezone code="ru">Asia/Khandyga</timezone>
-
- <!-- RUSSIAN FEDERATION, 8:00 -->
-
- <timezone code="ru">Asia/Irkutsk</timezone>
-
- <!-- RUSSIAN FEDERATION, 7:00 -->
-
- <timezone code="ru">Asia/Krasnoyarsk</timezone>
- <timezone code="ru">Asia/Novosibirsk</timezone>
- <timezone code="ru">Asia/Barnaul</timezone>
- <timezone code="ru">Asia/Novokuznetsk</timezone>
- <timezone code="ru">Asia/Tomsk</timezone>
-
- <!-- RUSSIAN FEDERATION, 6:00 -->
-
- <timezone code="ru">Asia/Omsk</timezone>
-
- <!-- RUSSIAN FEDERATION, 5:00 -->
-
- <timezone code="ru">Asia/Yekaterinburg</timezone>
-
- <!-- RUSSIAN FEDERATION, 4:00 -->
-
- <timezone code="ru">Europe/Samara</timezone>
- <timezone code="ru">Europe/Astrakhan</timezone>
- <timezone code="ru">Europe/Ulyanovsk</timezone>
- <timezone code="ru">Europe/Saratov</timezone>
-
- <!-- RUSSIAN FEDERATION, 3:00 -->
-
- <timezone code="ru">Europe/Moscow</timezone>
- <timezone code="ru">Europe/Volgograd</timezone>
- <timezone code="ru">Europe/Kirov</timezone>
- <timezone code="ru">Europe/Simferopol</timezone>
-
- <!-- RUSSIAN FEDERATION, 2:00 -->
-
- <timezone code="ru">Europe/Kaliningrad</timezone>
-
- <!-- RWANDA, 2:00 -->
-
- <timezone code="rw">Africa/Kigali</timezone>
-
- <!-- SAUDI ARABIA, 3:00 -->
-
- <timezone code="sa">Asia/Riyadh</timezone>
-
- <!-- SOLOMON ISLANDS, 11:00 -->
-
- <timezone code="sb">Pacific/Guadalcanal</timezone>
-
- <!-- SEYCHELLES, 4:00 -->
-
- <timezone code="sc">Indian/Mahe</timezone>
-
- <!-- SUDAN, 3:00 -->
-
- <timezone code="sd">Africa/Khartoum</timezone>
-
- <!-- SWEDEN, 1:00 -->
-
- <timezone code="se">Europe/Stockholm</timezone>
-
- <!-- SINGAPORE, 8:00 -->
-
- <timezone code="sg">Asia/Singapore</timezone>
-
- <!-- SAINT HELENA, 0:00 -->
-
- <timezone code="sh">Atlantic/St_Helena</timezone>
-
- <!-- SLOVENIA, 1:00 -->
-
- <timezone code="si">Europe/Ljubljana</timezone>
-
- <!-- SVALBARD AND JAN MAYEN, 1:00 -->
-
- <timezone code="sj">Arctic/Longyearbyen</timezone>
-
- <!-- SLOVAKIA, 1:00 -->
-
- <timezone code="sk">Europe/Bratislava</timezone>
-
- <!-- SIERRA LEONE, 0:00 -->
-
- <timezone code="sl">Africa/Freetown</timezone>
-
- <!-- SAN MARINO, 1:00 -->
-
- <timezone code="sm">Europe/San_Marino</timezone>
-
- <!-- SENEGAL, 0:00 -->
-
- <timezone code="sn">Africa/Dakar</timezone>
-
- <!-- SOMALIA, 3:00 -->
-
- <timezone code="so">Africa/Mogadishu</timezone>
-
- <!-- SURINAME, -3:00 -->
-
- <timezone code="sr">America/Paramaribo</timezone>
-
- <!-- South Sudan, 3:00 -->
-
- <timezone code="ss">Africa/Juba</timezone>
-
- <!-- SAO TOME AND PRINCIPE, 0:00 -->
-
- <timezone code="st">Africa/Sao_Tome</timezone>
-
- <!-- EL SALVADOR, -6:00 -->
-
- <timezone code="sv">America/El_Salvador</timezone>
-
- <!-- Sint Maarten, -4:00 -->
-
- <timezone code="sx">America/Lower_Princes</timezone>
-
- <!-- SYRIAN ARAB REPUBLIC, 2:00 -->
-
- <timezone code="sy">Asia/Damascus</timezone>
-
- <!-- SWAZILAND, 2:00 -->
-
- <timezone code="sz">Africa/Mbabane</timezone>
-
- <!-- TURKS AND CAICOS ISLANDS, -4:00 -->
-
- <timezone code="tc">America/Grand_Turk</timezone>
-
- <!-- CHAD, 1:00 -->
-
- <timezone code="td">Africa/Ndjamena</timezone>
-
- <!-- FRENCH SOUTHERN TERRITORIES, 5:00 -->
-
- <timezone code="tf">Indian/Kerguelen</timezone>
-
- <!-- TOGO, 0:00 -->
-
- <timezone code="tg">Africa/Lome</timezone>
-
- <!-- THAILAND, 7:00 -->
-
- <timezone code="th">Asia/Bangkok</timezone>
-
- <!-- TAJIKISTAN, 5:00 -->
-
- <timezone code="tj">Asia/Dushanbe</timezone>
-
- <!-- TOKELAU, +13:00 -->
-
- <timezone code="tk">Pacific/Fakaofo</timezone>
-
- <!-- TIMOR-LESTE, 9:00 -->
-
- <timezone code="tl">Asia/Dili</timezone>
-
- <!-- TURKMENISTAN, 5:00 -->
-
- <timezone code="tm">Asia/Ashgabat</timezone>
-
- <!-- TUNISIA, 1:00 -->
-
- <timezone code="tn">Africa/Tunis</timezone>
-
- <!-- TONGA, 13:00 -->
-
- <timezone code="to">Pacific/Tongatapu</timezone>
-
- <!-- TURKEY, 3:00 -->
-
- <timezone code="tr">Europe/Istanbul</timezone>
-
- <!-- TRINIDAD AND TOBAGO, -4:00 -->
-
- <timezone code="tt">America/Port_of_Spain</timezone>
-
- <!-- TUVALU, 12:00 -->
-
- <timezone code="tv">Pacific/Funafuti</timezone>
-
- <!-- TAIWAN, PROVINCE OF CHINA, 8:00 -->
-
- <timezone code="tw">Asia/Taipei</timezone>
-
- <!-- TANZANIA, UNITED REPUBLIC OF, 3:00 -->
-
- <timezone code="tz">Africa/Dar_es_Salaam</timezone>
-
- <!-- UKRAINE, 2:00 -->
-
- <timezone code="ua">Europe/Kiev</timezone>
- <timezone code="ua">Europe/Uzhgorod</timezone>
- <timezone code="ua">Europe/Zaporozhye</timezone>
-
- <!-- UGANDA, 3:00 -->
-
- <timezone code="ug">Africa/Kampala</timezone>
-
- <!-- UNITED STATES MINOR OUTLYING ISLANDS, 12:00 -->
-
- <timezone code="um">Pacific/Wake</timezone>
-
- <!-- UNITED STATES MINOR OUTLYING ISLANDS, -11:00 -->
-
- <timezone code="um">Pacific/Midway</timezone>
-
- <!-- UNITED STATES, -5:00 -->
-
- <timezone code="us">America/New_York</timezone>
- <timezone code="us">America/Detroit</timezone>
- <timezone code="us">America/Kentucky/Louisville</timezone>
- <timezone code="us">America/Kentucky/Monticello</timezone>
- <timezone code="us">America/Indiana/Indianapolis</timezone>
- <timezone code="us">America/Indiana/Vincennes</timezone>
- <timezone code="us">America/Indiana/Winamac</timezone>
- <timezone code="us">America/Indiana/Marengo</timezone>
- <timezone code="us">America/Indiana/Petersburg</timezone>
- <timezone code="us">America/Indiana/Vevay</timezone>
-
- <!-- UNITED STATES, -6:00 -->
-
- <timezone code="us">America/Chicago</timezone>
- <timezone code="us">America/Indiana/Knox</timezone>
- <timezone code="us">America/Menominee</timezone>
- <timezone code="us">America/North_Dakota/Center</timezone>
- <timezone code="us">America/North_Dakota/New_Salem</timezone>
- <timezone code="us">America/Indiana/Tell_City</timezone>
- <timezone code="us">America/North_Dakota/Beulah</timezone>
-
- <!-- UNITED STATES, -7:00 -->
-
- <timezone code="us">America/Denver</timezone>
- <timezone code="us">America/Boise</timezone>
- <timezone code="us">America/Phoenix</timezone>
-
- <!-- UNITED STATES, -8:00 -->
-
- <timezone code="us">America/Los_Angeles</timezone>
-
- <!-- UNITED STATES, -9:00 -->
-
- <timezone code="us">America/Anchorage</timezone>
- <timezone code="us">America/Juneau</timezone>
- <timezone code="us">America/Yakutat</timezone>
- <timezone code="us">America/Nome</timezone>
- <timezone code="us">America/Metlakatla</timezone>
- <timezone code="us">America/Sitka</timezone>
-
- <!-- UNITED STATES, -10:00 -->
-
- <timezone code="us">Pacific/Honolulu</timezone>
- <timezone code="us">America/Adak</timezone>
-
- <!-- URUGUAY, -3:00 -->
-
- <timezone code="uy">America/Montevideo</timezone>
-
- <!-- UZBEKISTAN, 5:00 -->
-
- <timezone code="uz">Asia/Tashkent</timezone>
- <timezone code="uz">Asia/Samarkand</timezone>
-
- <!-- HOLY SEE (VATICAN CITY STATE), 1:00 -->
-
- <timezone code="va">Europe/Vatican</timezone>
-
- <!-- SAINT VINCENT AND THE GRENADINES, -4:00 -->
-
- <timezone code="vc">America/St_Vincent</timezone>
-
- <!-- VENEZUELA, -4:00 -->
-
- <timezone code="ve">America/Caracas</timezone>
-
- <!-- VIRGIN ISLANDS, BRITISH, -4:00 -->
-
- <timezone code="vg">America/Tortola</timezone>
-
- <!-- VIRGIN ISLANDS, U.S., -4:00 -->
-
- <timezone code="vi">America/St_Thomas</timezone>
-
- <!-- VIET NAM, 7:00 -->
-
- <timezone code="vn">Asia/Ho_Chi_Minh</timezone>
-
- <!-- VANUATU, 11:00 -->
-
- <timezone code="vu">Pacific/Efate</timezone>
-
- <!-- WALLIS AND FUTUNA, 12:00 -->
-
- <timezone code="wf">Pacific/Wallis</timezone>
-
- <!-- SAMOA, 13:00 -->
-
- <timezone code="ws">Pacific/Apia</timezone>
-
- <!-- YEMEN, 3:00 -->
-
- <timezone code="ye">Asia/Aden</timezone>
-
- <!-- MAYOTTE, 3:00 -->
-
- <timezone code="yt">Indian/Mayotte</timezone>
-
- <!-- SOUTH AFRICA, 2:00 -->
-
- <timezone code="za">Africa/Johannesburg</timezone>
-
- <!-- ZAMBIA, 2:00 -->
-
- <timezone code="zm">Africa/Lusaka</timezone>
-
- <!-- ZIMBABWE, 2:00 -->
-
- <timezone code="zw">Africa/Harare</timezone>
-</timezones>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 94a515b..e127896 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1172,6 +1172,7 @@
</activity>
<activity android:name="com.android.internal.app.ChooserWrapperActivity"/>
<activity android:name="com.android.internal.app.ResolverWrapperActivity"/>
+ <activity android:name="com.android.internal.app.IntentForwarderActivityTest$IntentForwarderWrapperActivity"/>
<receiver android:name="android.app.activity.AbortReceiver">
<intent-filter android:priority="1">
@@ -1309,14 +1310,16 @@
</intent-filter>
</activity>
- <activity android:name="android.animation.BasicAnimatorActivity">
+ <activity android:name="android.animation.BasicAnimatorActivity"
+ android:screenOrientation="locked">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
</intent-filter>
</activity>
- <activity android:name="android.animation.AnimatorSetActivity">
+ <activity android:name="android.animation.AnimatorSetActivity"
+ android:screenOrientation="locked">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index ed96bc1..23d70b8 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -20,7 +20,7 @@
<option name="test-suite-tag" value="apct" />
<option name="test-tag" value="FrameworksCoreTests" />
- <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.frameworks.coretests" />
<option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
</test>
diff --git a/core/tests/coretests/fonts_readme.txt b/core/tests/coretests/fonts_readme.txt
new file mode 100644
index 0000000..f0de576
--- /dev/null
+++ b/core/tests/coretests/fonts_readme.txt
@@ -0,0 +1,15 @@
+All fonts included in this project follow the below copyright and licensing:
+
+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.
\ No newline at end of file
diff --git a/core/tests/coretests/res/font/samplefont.ttf b/core/tests/coretests/res/font/samplefont.ttf
index 2852302..5fccad2 100644
--- a/core/tests/coretests/res/font/samplefont.ttf
+++ b/core/tests/coretests/res/font/samplefont.ttf
Binary files differ
diff --git a/core/tests/coretests/res/font/samplefont2.ttf b/core/tests/coretests/res/font/samplefont2.ttf
index 2852302..f31b8a9 100644
--- a/core/tests/coretests/res/font/samplefont2.ttf
+++ b/core/tests/coretests/res/font/samplefont2.ttf
Binary files differ
diff --git a/core/tests/coretests/res/font/samplefont3.ttf b/core/tests/coretests/res/font/samplefont3.ttf
index 2852302..9c850ab 100644
--- a/core/tests/coretests/res/font/samplefont3.ttf
+++ b/core/tests/coretests/res/font/samplefont3.ttf
Binary files differ
diff --git a/core/tests/coretests/res/font/samplefont4.ttf b/core/tests/coretests/res/font/samplefont4.ttf
index 2852302..9c850ab 100644
--- a/core/tests/coretests/res/font/samplefont4.ttf
+++ b/core/tests/coretests/res/font/samplefont4.ttf
Binary files differ
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index e9e3a18..2470e87 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -16,16 +16,14 @@
package android.app.admin;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import static org.junit.Assert.assertEquals;
import android.os.Parcel;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.junit.runner.RunWith;
/** Unit tests for {@link PasswordMetrics}. */
@RunWith(AndroidJUnit4.class)
@@ -43,20 +41,6 @@
assertEquals(0, metrics.numeric);
assertEquals(0, metrics.symbols);
assertEquals(0, metrics.nonLetter);
- assertTrue("default constructor does not produce default metrics", metrics.isDefault());
- }
-
- @Test
- public void testIsNotDefault() {
- final PasswordMetrics metrics = new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, 12);
- assertFalse("non-default metrics are repoted as default", metrics.isDefault());
- }
-
- @Test
- public void testComputeForEmptyPassword() {
- final PasswordMetrics metrics = PasswordMetrics.computeForPassword("");
- assertTrue("empty password has default metrics", metrics.isDefault());
}
@Test
diff --git a/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
new file mode 100644
index 0000000..9bbcd3d
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.timezone;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link DistroFormatVersion}.
+ */
+// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
+public class DistroFormatVersionTest {
+
+ @Test
+ public void equalsAndHashCode() {
+ DistroFormatVersion one = new DistroFormatVersion(1, 2);
+ assertEqualsContract(one, one);
+
+ DistroFormatVersion two = new DistroFormatVersion(1, 2);
+ assertEqualsContract(one, two);
+
+ DistroFormatVersion three = new DistroFormatVersion(2, 1);
+ assertFalse(one.equals(three));
+ }
+
+ @Test
+ public void parcelable() {
+ DistroFormatVersion version = new DistroFormatVersion(2, 3);
+
+ Parcel parcel = Parcel.obtain();
+ version.writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+
+ DistroFormatVersion newVersion = DistroFormatVersion.CREATOR.createFromParcel(parcel);
+
+ assertEquals(version, newVersion);
+ }
+
+ @Test
+ public void supportsVersion() {
+ DistroFormatVersion deviceVersion = new DistroFormatVersion(2, 2);
+ assertTrue(deviceVersion.supports(deviceVersion));
+
+ DistroFormatVersion sameVersion = new DistroFormatVersion(2, 2);
+ assertTrue(deviceVersion.supports(sameVersion));
+
+ // Minor versions are backwards compatible.
+ DistroFormatVersion sameMajorNewerMinor = new DistroFormatVersion(2, 3);
+ assertTrue(deviceVersion.supports(sameMajorNewerMinor));
+ DistroFormatVersion sameMajorOlderMinor = new DistroFormatVersion(2, 1);
+ assertFalse(deviceVersion.supports(sameMajorOlderMinor));
+
+ // Major versions are not backwards compatible.
+ DistroFormatVersion newerMajor = new DistroFormatVersion(1, 2);
+ assertFalse(deviceVersion.supports(newerMajor));
+ DistroFormatVersion olderMajor = new DistroFormatVersion(3, 2);
+ assertFalse(deviceVersion.supports(olderMajor));
+ }
+
+ private static void assertEqualsContract(DistroFormatVersion one, DistroFormatVersion two) {
+ assertEquals(one, two);
+ assertEquals(one.hashCode(), two.hashCode());
+ }
+}
diff --git a/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
new file mode 100644
index 0000000..2fbc9a1
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.timezone;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link DistroRulesVersion}.
+ */
+// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
+public class DistroRulesVersionTest {
+
+ @Test
+ public void equalsAndHashCode() {
+ DistroRulesVersion one = new DistroRulesVersion("2016a", 2);
+ assertEqualsContract(one, one);
+
+ DistroRulesVersion two = new DistroRulesVersion("2016a", 2);
+ assertEqualsContract(one, two);
+
+ DistroRulesVersion three = new DistroRulesVersion("2016b", 1);
+ assertFalse(one.equals(three));
+ }
+
+ @Test
+ public void parcelable() {
+ DistroRulesVersion version = new DistroRulesVersion("2016a", 2);
+
+ Parcel parcel = Parcel.obtain();
+ version.writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+
+ DistroRulesVersion newVersion = DistroRulesVersion.CREATOR.createFromParcel(parcel);
+
+ assertEquals(version, newVersion);
+ }
+
+ @Test
+ public void isOlderThan() {
+ DistroRulesVersion deviceVersion = new DistroRulesVersion("2016b", 2);
+ assertFalse(deviceVersion.isOlderThan(deviceVersion));
+
+ DistroRulesVersion sameVersion = new DistroRulesVersion("2016b", 2);
+ assertFalse(deviceVersion.isOlderThan(sameVersion));
+
+ DistroRulesVersion sameRulesNewerRevision = new DistroRulesVersion("2016b", 3);
+ assertTrue(deviceVersion.isOlderThan(sameRulesNewerRevision));
+
+ DistroRulesVersion sameRulesOlderRevision = new DistroRulesVersion("2016b", 1);
+ assertFalse(deviceVersion.isOlderThan(sameRulesOlderRevision));
+
+ DistroRulesVersion newerRules = new DistroRulesVersion("2016c", 2);
+ assertTrue(deviceVersion.isOlderThan(newerRules));
+
+ DistroRulesVersion olderRules = new DistroRulesVersion("2016a", 2);
+ assertFalse(deviceVersion.isOlderThan(olderRules));
+ }
+
+ private static void assertEqualsContract(DistroRulesVersion one, DistroRulesVersion two) {
+ assertEquals(one, two);
+ assertEquals(one.hashCode(), two.hashCode());
+ }
+}
diff --git a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
new file mode 100644
index 0000000..a9357c9
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.timezone;
+
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link RulesState}.
+ */
+// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
+public class RulesStateTest {
+
+ @Test
+ public void equalsAndHashCode() {
+ RulesState one = new RulesState(
+ "2016a", formatVersion(1, 2), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+ assertEqualsContract(one, one);
+
+ RulesState two = new RulesState(
+ "2016a", formatVersion(1, 2), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+ assertEqualsContract(one, two);
+
+ RulesState differentSystemRules = new RulesState(
+ "2016b", formatVersion(1, 2), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+ assertFalse(one.equals(differentSystemRules));
+
+ RulesState differentFormatVersion = new RulesState(
+ "2016a", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+ assertFalse(one.equals(differentFormatVersion));
+
+ RulesState differentOperationInProgress = new RulesState(
+ "2016a", formatVersion(1, 1), true /* operationInProgress */,
+ RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
+ assertFalse(one.equals(differentOperationInProgress));
+
+ RulesState differentStagedOperation = new RulesState(
+ "2016a", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_UNINSTALL, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+ assertFalse(one.equals(differentStagedOperation));
+
+ RulesState differentStagedInstallVersion = new RulesState(
+ "2016a", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 4),
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+ assertFalse(one.equals(differentStagedInstallVersion));
+
+ RulesState differentInstalled = new RulesState(
+ "2016a", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+ RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
+ assertFalse(one.equals(differentInstalled));
+
+ RulesState differentInstalledVersion = new RulesState(
+ "2016a", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 3));
+ assertFalse(one.equals(differentInstalledVersion));
+ }
+
+ @Test
+ public void parcelable() {
+ RulesState rulesState1 = new RulesState(
+ "2016a", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016b", 2),
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 3));
+ checkParcelableRoundTrip(rulesState1);
+
+ RulesState rulesStateWithNulls = new RulesState(
+ "2016a", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
+ checkParcelableRoundTrip(rulesStateWithNulls);
+
+ RulesState rulesStateWithUnknowns = new RulesState(
+ "2016a", formatVersion(1, 1), true /* operationInProgress */,
+ RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
+ checkParcelableRoundTrip(rulesStateWithNulls);
+ }
+
+ private static void checkParcelableRoundTrip(RulesState rulesState) {
+ Parcel parcel = Parcel.obtain();
+ rulesState.writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+
+ RulesState newVersion = RulesState.CREATOR.createFromParcel(parcel);
+
+ assertEquals(rulesState, newVersion);
+ }
+
+ @Test
+ public void isSystemVersionOlderThan() {
+ RulesState rulesState = new RulesState(
+ "2016b", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 3));
+ assertFalse(rulesState.isSystemVersionOlderThan(rulesVersion("2016a", 1)));
+ assertFalse(rulesState.isSystemVersionOlderThan(rulesVersion("2016b", 1)));
+ assertTrue(rulesState.isSystemVersionOlderThan(rulesVersion("2016c", 1)));
+ }
+
+ @Test
+ public void isInstalledDistroOlderThan() {
+ RulesState operationInProgress = new RulesState(
+ "2016b", formatVersion(1, 1), true /* operationInProgress */,
+ RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
+ RulesState.STAGED_OPERATION_UNKNOWN, null /* installedDistroRulesVersion */);
+ try {
+ operationInProgress.isInstalledDistroOlderThan(rulesVersion("2016b", 1));
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ RulesState nothingInstalled = new RulesState(
+ "2016b", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
+ try {
+ nothingInstalled.isInstalledDistroOlderThan(rulesVersion("2016b", 1));
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ DistroRulesVersion installedVersion = rulesVersion("2016b", 3);
+ RulesState rulesStateWithInstalledVersion = new RulesState(
+ "2016b", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_INSTALLED, installedVersion);
+
+ DistroRulesVersion olderRules = rulesVersion("2016a", 1);
+ assertEquals(installedVersion.isOlderThan(olderRules),
+ rulesStateWithInstalledVersion.isInstalledDistroOlderThan(olderRules));
+
+ DistroRulesVersion sameRules = rulesVersion("2016b", 1);
+ assertEquals(installedVersion.isOlderThan(sameRules),
+ rulesStateWithInstalledVersion.isInstalledDistroOlderThan(sameRules));
+
+ DistroRulesVersion newerRules = rulesVersion("2016c", 1);
+ assertEquals(installedVersion.isOlderThan(newerRules),
+ rulesStateWithInstalledVersion.isInstalledDistroOlderThan(newerRules));
+ }
+
+ private static void assertEqualsContract(RulesState one, RulesState two) {
+ assertEquals(one, two);
+ assertEquals(one.hashCode(), two.hashCode());
+ }
+
+ private static DistroRulesVersion rulesVersion(String rulesVersion, int revision) {
+ return new DistroRulesVersion(rulesVersion, revision);
+ }
+
+ private static DistroFormatVersion formatVersion(int majorVersion, int minorVersion) {
+ return new DistroFormatVersion(majorVersion, minorVersion);
+ }
+}
diff --git a/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java b/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
new file mode 100644
index 0000000..e7a839c
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.timezone;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+
+import android.content.Context;
+import android.content.Intent;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+
+/**
+ * Tests for {@link RulesUpdaterContract}.
+ */
+// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
+public class RulesUpdaterContractTest {
+
+ @Test
+ public void createUpdaterIntent() throws Exception {
+ String packageName = "foobar";
+ Intent intent = RulesUpdaterContract.createUpdaterIntent(packageName);
+
+ assertEquals(RulesUpdaterContract.ACTION_TRIGGER_RULES_UPDATE_CHECK, intent.getAction());
+ assertEquals(packageName, intent.getPackage());
+ assertEquals(Intent.FLAG_INCLUDE_STOPPED_PACKAGES, intent.getFlags());
+ }
+
+ @Test
+ public void sendBroadcast() throws Exception {
+ String packageName = "foobar";
+ byte[] tokenBytes = new byte[] { 1, 2, 3, 4, 5 };
+
+ Intent expectedIntent = RulesUpdaterContract.createUpdaterIntent(packageName);
+ expectedIntent.putExtra(RulesUpdaterContract.EXTRA_CHECK_TOKEN, tokenBytes);
+
+ Context mockContext = mock(Context.class);
+
+ RulesUpdaterContract.sendBroadcast(mockContext, packageName, tokenBytes);
+
+ verify(mockContext).sendBroadcast(
+ filterEquals(expectedIntent),
+ eq(RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION));
+ }
+
+ /**
+ * Registers a mockito parameter matcher that uses {@link Intent#filterEquals(Intent)}. to
+ * check the parameter against the intent supplied.
+ */
+ private static Intent filterEquals(final Intent expected) {
+ final Matcher<Intent> m = new BaseMatcher<Intent>() {
+ @Override
+ public boolean matches(Object actual) {
+ return actual != null && expected.filterEquals((Intent) actual);
+ }
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(expected.toString());
+ }
+ };
+ return argThat(m);
+ }
+}
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 1ffc1b3..423d45ea 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -36,6 +36,7 @@
import android.content.res.Resources.NotFoundException;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
@@ -64,9 +65,14 @@
import com.android.frameworks.coretests.R;
import com.android.internal.content.PackageHelper;
+import dalvik.system.VMRuntime;
+
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -78,9 +84,9 @@
public static final String TAG = "PackageManagerTests";
- public final long MAX_WAIT_TIME = 25 * 1000;
+ public static final long MAX_WAIT_TIME = 25 * 1000;
- public final long WAIT_TIME_INCR = 5 * 1000;
+ public static final long WAIT_TIME_INCR = 5 * 1000;
private static final String SECURE_CONTAINERS_PREFIX = "/mnt/asec";
@@ -3844,6 +3850,117 @@
PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
}
+ private static class TestDexModuleRegisterCallback
+ extends PackageManager.DexModuleRegisterCallback {
+ private String mDexModulePath = null;
+ private boolean mSuccess = false;
+ private String mMessage = null;
+ CountDownLatch doneSignal = new CountDownLatch(1);
+
+ @Override
+ public void onDexModuleRegistered(String dexModulePath, boolean success, String message) {
+ mDexModulePath = dexModulePath;
+ mSuccess = success;
+ mMessage = message;
+ doneSignal.countDown();
+ }
+
+ boolean waitTillDone() {
+ long startTime = System.currentTimeMillis();
+ while (System.currentTimeMillis() - startTime < MAX_WAIT_TIME) {
+ try {
+ return doneSignal.await(MAX_WAIT_TIME, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Log.i(TAG, "Interrupted during sleep", e);
+ }
+ }
+ return false;
+ }
+
+ }
+
+ // Verify that the base code path cannot be registered.
+ public void testRegisterDexModuleBaseCode() throws Exception {
+ PackageManager pm = getPm();
+ ApplicationInfo info = getContext().getApplicationInfo();
+ TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
+ pm.registerDexModule(info.getBaseCodePath(), callback);
+ assertTrue(callback.waitTillDone());
+ assertEquals(info.getBaseCodePath(), callback.mDexModulePath);
+ assertFalse("BaseCodePath should not be registered", callback.mSuccess);
+ }
+
+ // Verify thatmodules which are not own by the calling package are not registered.
+ public void testRegisterDexModuleNotOwningModule() throws Exception {
+ TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
+ String moduleBelongingToOtherPackage = "/data/user/0/com.google.android.gms/module.apk";
+ getPm().registerDexModule(moduleBelongingToOtherPackage, callback);
+ assertTrue(callback.waitTillDone());
+ assertEquals(moduleBelongingToOtherPackage, callback.mDexModulePath);
+ assertTrue(callback.waitTillDone());
+ assertFalse("Only modules belonging to the calling package can be registered",
+ callback.mSuccess);
+ }
+
+ // Verify that modules owned by the package are successfully registered.
+ public void testRegisterDexModuleSuccessfully() throws Exception {
+ ApplicationInfo info = getContext().getApplicationInfo();
+ // Copy the main apk into the data folder and use it as a "module".
+ File dexModuleDir = new File(info.dataDir, "module-dir");
+ File dexModule = new File(dexModuleDir, "module.apk");
+ try {
+ assertNotNull(FileUtils.createDir(
+ dexModuleDir.getParentFile(), dexModuleDir.getName()));
+ Files.copy(Paths.get(info.getBaseCodePath()), dexModule.toPath(),
+ StandardCopyOption.REPLACE_EXISTING);
+ TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
+ getPm().registerDexModule(dexModule.toString(), callback);
+ assertTrue(callback.waitTillDone());
+ assertEquals(dexModule.toString(), callback.mDexModulePath);
+ assertTrue(callback.waitTillDone());
+ assertTrue(callback.mMessage, callback.mSuccess);
+
+ // NOTE:
+ // This actually verifies internal behaviour which might change. It's not
+ // ideal but it's the best we can do since there's no other place we can currently
+ // write a better test.
+ for(String isa : getAppDexInstructionSets(info)) {
+ Files.exists(Paths.get(dexModuleDir.toString(), "oat", isa, "module.odex"));
+ Files.exists(Paths.get(dexModuleDir.toString(), "oat", isa, "module.vdex"));
+ }
+ } finally {
+ FileUtils.deleteContentsAndDir(dexModuleDir);
+ }
+ }
+
+ // If the module does not exist on disk we should get a failure.
+ public void testRegisterDexModuleNotExists() throws Exception {
+ ApplicationInfo info = getContext().getApplicationInfo();
+ String nonExistentApk = Paths.get(info.dataDir, "non-existent.apk").toString();
+ TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
+ getPm().registerDexModule(nonExistentApk, callback);
+ assertTrue(callback.waitTillDone());
+ assertEquals(nonExistentApk, callback.mDexModulePath);
+ assertTrue(callback.waitTillDone());
+ assertFalse("DexModule registration should fail", callback.mSuccess);
+ }
+
+ // Copied from com.android.server.pm.InstructionSets because we don't have access to it here.
+ private static String[] getAppDexInstructionSets(ApplicationInfo info) {
+ if (info.primaryCpuAbi != null) {
+ if (info.secondaryCpuAbi != null) {
+ return new String[] {
+ VMRuntime.getInstructionSet(info.primaryCpuAbi),
+ VMRuntime.getInstructionSet(info.secondaryCpuAbi) };
+ } else {
+ return new String[] {
+ VMRuntime.getInstructionSet(info.primaryCpuAbi) };
+ }
+ }
+
+ return new String[] { VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]) };
+ }
+
/*---------- Recommended install location tests ----*/
/*
* TODO's
diff --git a/core/tests/coretests/src/android/database/NewDatabasePerformanceTestSuite.java b/core/tests/coretests/src/android/database/NewDatabasePerformanceTestSuite.java
index 31cf515..c9db034 100644
--- a/core/tests/coretests/src/android/database/NewDatabasePerformanceTestSuite.java
+++ b/core/tests/coretests/src/android/database/NewDatabasePerformanceTestSuite.java
@@ -23,68 +23,29 @@
TestSuite suite =
new TestSuite(NewDatabasePerformanceTestSuite.class.getName());
- suite.addTestSuite(NewDatabasePerformanceTests.
- Insert1000.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- InsertIndexed1000.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- Select100.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- SelectStringComparison100.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- SelectIndex100.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- InnerJoin100.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- InnerJoinOneSide100.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- InnerJoinNoIndex100.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- SelectSubQIndex100.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- SelectIndexStringComparison100.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- SelectInteger100.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- SelectString100.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- SelectIntegerIndex100.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- SelectIndexString100.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- SelectStringStartsWith100.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- DeleteIndexed1000.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- Delete1000.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- DeleteWhere1000.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- DeleteIndexWhere1000.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- UpdateIndexWhere1000.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- UpdateWhere1000.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- InsertInteger10000.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- InsertIntegerIndex10000.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- InsertString10000.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- InsertStringIndexed10000.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- SelectStringStartsWith10000.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- SelectStringIndexedStartsWith10000.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- SelectInteger10000.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- SelectIntegerIndexed10000.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- SelectStringContains10000.class);
- suite.addTestSuite(NewDatabasePerformanceTests.
- SelectStringIndexedContains10000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.Insert100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.InsertIndexed100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.Select100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.SelectStringComparison100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.SelectIndex100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.InnerJoin100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.InnerJoinOneSide100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.InnerJoinNoIndex100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.SelectSubQIndex100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.SelectIndexStringComparison100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.SelectInteger100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.SelectString100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.SelectIntegerIndex100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.SelectIndexString100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.SelectStringStartsWith100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.DeleteIndexed100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.Delete100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.DeleteWhere100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.DeleteIndexWhere100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.UpdateIndexWhere100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.UpdateWhere100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.SelectStringContains100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.SelectStringIndexedContains100.class);
return suite;
}
diff --git a/core/tests/coretests/src/android/database/NewDatabasePerformanceTests.java b/core/tests/coretests/src/android/database/NewDatabasePerformanceTests.java
index adf88d2..f8d8e5e 100644
--- a/core/tests/coretests/src/android/database/NewDatabasePerformanceTests.java
+++ b/core/tests/coretests/src/android/database/NewDatabasePerformanceTests.java
@@ -19,6 +19,7 @@
import android.content.ContentValues;
import android.database.sqlite.SQLiteDatabase;
import android.test.PerformanceTestCase;
+import android.util.Log;
import junit.framework.TestCase;
@@ -31,17 +32,20 @@
*/
public class NewDatabasePerformanceTests {
+ private static final String TAG = "NewDatabasePerformanceTests";
// Edit this to change the test run times. The original is 100.
- final static int kMultiplier = 1;
+ private static final int SIZE_MULTIPLIER = 100;
public static class PerformanceBase extends TestCase
implements PerformanceTestCase {
protected static final int CURRENT_DATABASE_VERSION = 42;
protected SQLiteDatabase mDatabase;
protected File mDatabaseFile;
+ private long mSetupFinishedTime;
public void setUp() {
+ long setupStarted = System.currentTimeMillis();
mDatabaseFile = new File("/sdcard", "perf_database_test.db");
if (mDatabaseFile.exists()) {
mDatabaseFile.delete();
@@ -51,9 +55,14 @@
null);
assertTrue(mDatabase != null);
mDatabase.setVersion(CURRENT_DATABASE_VERSION);
+ mSetupFinishedTime = System.currentTimeMillis();
+ Log.i(TAG, "Setup for " + getClass().getSimpleName() + " took "
+ + (mSetupFinishedTime - setupStarted) + " ms");
}
public void tearDown() {
+ long duration = System.currentTimeMillis() - mSetupFinishedTime;
+ Log.i(TAG, "Test " + getClass().getSimpleName() + " took " + duration + " ms");
mDatabase.close();
mDatabaseFile.delete();
}
@@ -100,11 +109,11 @@
}
/**
- * Test 1000 inserts.
+ * Test 100 inserts.
*/
- public static class Insert1000 extends PerformanceBase {
- private static final int SIZE = 10 * kMultiplier;
+ public static class Insert100 extends PerformanceBase {
+ private static final int SIZE = SIZE_MULTIPLIER;
private String[] statements = new String[SIZE];
@@ -132,11 +141,11 @@
}
/**
- * Test 1000 inserts into an indexed table.
+ * Test 100 inserts into an indexed table.
*/
- public static class InsertIndexed1000 extends PerformanceBase {
- private static final int SIZE = 10 * kMultiplier;
+ public static class InsertIndexed100 extends PerformanceBase {
+ private static final int SIZE = SIZE_MULTIPLIER;
private String[] statements = new String[SIZE];
@@ -169,7 +178,8 @@
*/
public static class Select100 extends PerformanceBase {
- private static final int SIZE = 1 * kMultiplier;
+ private static final int SIZE = SIZE_MULTIPLIER;
+ private static final int REPEAT_COUNT = 10;
private static final String[] COLUMNS = {"count(*)", "avg(b)"};
private String[] where = new String[SIZE];
@@ -196,9 +206,11 @@
}
public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase
- .query("t1", COLUMNS, where[i], null, null, null, null);
+ for (int iter = 0; iter < REPEAT_COUNT; iter++) {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase
+ .query("t1", COLUMNS, where[i], null, null, null, null);
+ }
}
}
}
@@ -206,9 +218,9 @@
/**
* 100 SELECTs on a string comparison
*/
-
public static class SelectStringComparison100 extends PerformanceBase {
- private static final int SIZE = 1 * kMultiplier;
+ private static final int SIZE = SIZE_MULTIPLIER;
+ private static final int REPEAT_COUNT = 10;
private static final String[] COLUMNS = {"count(*)", "avg(b)"};
private String[] where = new String[SIZE];
@@ -233,9 +245,11 @@
}
public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase
- .query("t1", COLUMNS, where[i], null, null, null, null);
+ for (int iter = 0; iter < REPEAT_COUNT; iter++) {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase
+ .query("t1", COLUMNS, where[i], null, null, null, null);
+ }
}
}
}
@@ -243,9 +257,9 @@
/**
* 100 SELECTs with an index
*/
-
public static class SelectIndex100 extends PerformanceBase {
- private static final int SIZE = 1 * kMultiplier;
+ private static final int SIZE = SIZE_MULTIPLIER;
+ private static final int REPEAT_COUNT = 10;
private static final String[] COLUMNS = {"count(*)", "avg(b)"};
private String[] where = new String[SIZE];
@@ -273,9 +287,11 @@
}
public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase
- .query("t1", COLUMNS, where[i], null, null, null, null);
+ for (int iter = 0; iter < REPEAT_COUNT; iter++) {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase
+ .query("t1", COLUMNS, where[i], null, null, null, null);
+ }
}
}
}
@@ -283,9 +299,9 @@
/**
* INNER JOIN without an index
*/
-
public static class InnerJoin100 extends PerformanceBase {
- private static final int SIZE = 1 * kMultiplier;
+ private static final int SIZE = SIZE_MULTIPLIER;
+ private static final int REPEAT_COUNT = 10;
private static final String[] COLUMNS = {"t1.a"};
@Override
@@ -312,8 +328,10 @@
}
public void testRun() {
- mDatabase.query("t1 INNER JOIN t2 ON t1.b = t2.b", COLUMNS, null,
- null, null, null, null);
+ for (int iter = 0; iter < REPEAT_COUNT; iter++) {
+ mDatabase.query("t1 INNER JOIN t2 ON t1.b = t2.b", COLUMNS, null,
+ null, null, null, null);
+ }
}
}
@@ -322,7 +340,8 @@
*/
public static class InnerJoinOneSide100 extends PerformanceBase {
- private static final int SIZE = 1 * kMultiplier;
+ private static final int SIZE = SIZE_MULTIPLIER;
+ private static final int REPEAT_COUNT = 10;
private static final String[] COLUMNS = {"t1.a"};
@Override
@@ -351,17 +370,19 @@
}
public void testRun() {
- mDatabase.query("t1 INNER JOIN t2 ON t1.b = t2.b", COLUMNS, null,
- null, null, null, null);
+ for (int iter = 0; iter < REPEAT_COUNT; iter++) {
+ mDatabase.query("t1 INNER JOIN t2 ON t1.b = t2.b", COLUMNS, null,
+ null, null, null, null);
+ }
}
}
/**
* INNER JOIN without an index on one side
*/
-
public static class InnerJoinNoIndex100 extends PerformanceBase {
- private static final int SIZE = 1 * kMultiplier;
+ private static final int SIZE = SIZE_MULTIPLIER;
+ private static final int REPEAT_COUNT = 10;
private static final String[] COLUMNS = {"t1.a"};
@Override
@@ -390,17 +411,19 @@
}
public void testRun() {
- mDatabase.query("t1 INNER JOIN t2 ON t1.c = t2.c", COLUMNS, null,
- null, null, null, null);
+ for (int iter = 0; iter < REPEAT_COUNT; iter++) {
+ mDatabase.query("t1 INNER JOIN t2 ON t1.c = t2.c", COLUMNS, null,
+ null, null, null, null);
+ }
}
}
/**
* 100 SELECTs with subqueries. Subquery is using an index
*/
-
public static class SelectSubQIndex100 extends PerformanceBase {
- private static final int SIZE = 1 * kMultiplier;
+ private static final int SIZE = SIZE_MULTIPLIER;
+ private static final int REPEAT_COUNT = 10;
private static final String[] COLUMNS = {"t1.a"};
private String[] where = new String[SIZE];
@@ -449,9 +472,9 @@
/**
* 100 SELECTs on string comparison with Index
*/
-
public static class SelectIndexStringComparison100 extends PerformanceBase {
- private static final int SIZE = 1 * kMultiplier;
+ private static final int SIZE = SIZE_MULTIPLIER;
+ private static final int REPEAT_COUNT = 10;
private static final String[] COLUMNS = {"count(*)", "avg(b)"};
private String[] where = new String[SIZE];
@@ -477,9 +500,11 @@
}
public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase
- .query("t1", COLUMNS, where[i], null, null, null, null);
+ for (int iter = 0; iter < REPEAT_COUNT; iter++) {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase
+ .query("t1", COLUMNS, where[i], null, null, null, null);
+ }
}
}
}
@@ -487,9 +512,9 @@
/**
* 100 SELECTs on integer
*/
-
public static class SelectInteger100 extends PerformanceBase {
- private static final int SIZE = 1 * kMultiplier;
+ private static final int SIZE = SIZE_MULTIPLIER;
+ private static final int REPEAT_COUNT = 10;
private static final String[] COLUMNS = {"b"};
@Override
@@ -509,8 +534,10 @@
}
public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase.query("t1", COLUMNS, null, null, null, null, null);
+ for (int iter = 0; iter < REPEAT_COUNT; iter++) {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t1", COLUMNS, null, null, null, null, null);
+ }
}
}
}
@@ -518,9 +545,9 @@
/**
* 100 SELECTs on String
*/
-
public static class SelectString100 extends PerformanceBase {
- private static final int SIZE = 1 * kMultiplier;
+ private static final int SIZE = SIZE_MULTIPLIER;
+ private static final int REPEAT_COUNT = 10;
private static final String[] COLUMNS = {"c"};
@Override
@@ -536,12 +563,13 @@
mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ numberName(r) + "')");
}
-
}
public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase.query("t1", COLUMNS, null, null, null, null, null);
+ for (int iter = 0; iter < REPEAT_COUNT; iter++) {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t1", COLUMNS, null, null, null, null, null);
+ }
}
}
}
@@ -549,9 +577,9 @@
/**
* 100 SELECTs on integer with index
*/
-
public static class SelectIntegerIndex100 extends PerformanceBase {
- private static final int SIZE = 1 * kMultiplier;
+ private static final int SIZE = SIZE_MULTIPLIER;
+ private static final int REPEAT_COUNT = 10;
private static final String[] COLUMNS = {"b"};
@Override
@@ -572,8 +600,10 @@
}
public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase.query("t1", COLUMNS, null, null, null, null, null);
+ for (int iter = 0; iter < REPEAT_COUNT; iter++) {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t1", COLUMNS, null, null, null, null, null);
+ }
}
}
}
@@ -581,9 +611,9 @@
/**
* 100 SELECTs on String with index
*/
-
public static class SelectIndexString100 extends PerformanceBase {
- private static final int SIZE = 1 * kMultiplier;
+ private static final int SIZE = SIZE_MULTIPLIER;
+ private static final int REPEAT_COUNT = 10;
private static final String[] COLUMNS = {"c"};
@Override
@@ -600,22 +630,24 @@
mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ numberName(r) + "')");
}
-
}
public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase.query("t1", COLUMNS, null, null, null, null, null);
+ for (int iter = 0; iter < REPEAT_COUNT; iter++) {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t1", COLUMNS, null, null, null, null, null);
+ }
}
}
+
}
/**
* 100 SELECTs on String with starts with
*/
-
public static class SelectStringStartsWith100 extends PerformanceBase {
- private static final int SIZE = 1 * kMultiplier;
+ private static final int SIZE = SIZE_MULTIPLIER;
+ private static final int REPEAT_COUNT = 10;
private static final String[] COLUMNS = {"c"};
private String[] where = new String[SIZE];
@@ -643,19 +675,20 @@
}
public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase
- .query("t1", COLUMNS, where[i], null, null, null, null);
+ for (int iter = 0; iter < REPEAT_COUNT; iter++) {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase
+ .query("t1", COLUMNS, where[i], null, null, null, null);
+ }
}
}
}
/**
- * 1000 Deletes on an indexed table
+ * 100 Deletes on an indexed table
*/
-
- public static class DeleteIndexed1000 extends PerformanceBase {
- private static final int SIZE = 10 * kMultiplier;
+ public static class DeleteIndexed100 extends PerformanceBase {
+ private static final int SIZE = SIZE_MULTIPLIER;
@Override
public void setUp() {
@@ -682,11 +715,10 @@
}
/**
- * 1000 Deletes
+ * 100 Deletes
*/
-
- public static class Delete1000 extends PerformanceBase {
- private static final int SIZE = 10 * kMultiplier;
+ public static class Delete100 extends PerformanceBase {
+ private static final int SIZE = SIZE_MULTIPLIER;
@Override
public void setUp() {
@@ -712,11 +744,10 @@
}
/**
- * 1000 DELETE's without an index with where clause
+ * 100 DELETE's without an index with where clause
*/
-
- public static class DeleteWhere1000 extends PerformanceBase {
- private static final int SIZE = 10 * kMultiplier;
+ public static class DeleteWhere100 extends PerformanceBase {
+ private static final int SIZE = SIZE_MULTIPLIER;
private String[] where = new String[SIZE];
@Override
@@ -748,11 +779,10 @@
}
/**
- * 1000 DELETE's with an index with where clause
+ * 100 DELETE's with an index with where clause
*/
-
- public static class DeleteIndexWhere1000 extends PerformanceBase {
- private static final int SIZE = 10 * kMultiplier;
+ public static class DeleteIndexWhere100 extends PerformanceBase {
+ private static final int SIZE = SIZE_MULTIPLIER;
private String[] where = new String[SIZE];
@Override
@@ -785,11 +815,10 @@
}
/**
- * 1000 update's with an index with where clause
+ * 100 update's with an index with where clause
*/
-
- public static class UpdateIndexWhere1000 extends PerformanceBase {
- private static final int SIZE = 10 * kMultiplier;
+ public static class UpdateIndexWhere100 extends PerformanceBase {
+ private static final int SIZE = SIZE_MULTIPLIER;
private String[] where = new String[SIZE];
ContentValues[] mValues = new ContentValues[SIZE];
@@ -828,11 +857,10 @@
}
/**
- * 1000 update's without an index with where clause
+ * 100 update's without an index with where clause
*/
-
- public static class UpdateWhere1000 extends PerformanceBase {
- private static final int SIZE = 10 * kMultiplier;
+ public static class UpdateWhere100 extends PerformanceBase {
+ private static final int SIZE = SIZE_MULTIPLIER;
private String[] where = new String[SIZE];
ContentValues[] mValues = new ContentValues[SIZE];
@@ -869,284 +897,11 @@
}
/**
- * 10000 inserts for an integer
+ * 100 selects for a String - contains 'e'
*/
-
- public static class InsertInteger10000 extends PerformanceBase {
- private static final int SIZE = 100 * kMultiplier;
- ContentValues[] mValues = new ContentValues[SIZE];
-
- @Override
- public void setUp() {
- super.setUp();
- Random random = new Random(42);
-
- mDatabase
- .execSQL("CREATE TABLE t1(a INTEGER)");
-
- for (int i = 0; i < SIZE; i++) {
- int r = random.nextInt(100000);
- ContentValues b = new ContentValues(1);
- b.put("a", r);
- mValues[i] = b;
- }
- }
-
- public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase.insert("t1", null, mValues[i]);
- }
- }
- }
-
- /**
- * 10000 inserts for an integer -indexed table
- */
-
- public static class InsertIntegerIndex10000 extends PerformanceBase {
- private static final int SIZE = 100 * kMultiplier;
- ContentValues[] mValues = new ContentValues[SIZE];
-
- @Override
- public void setUp() {
- super.setUp();
- Random random = new Random(42);
-
- mDatabase
- .execSQL("CREATE TABLE t1(a INTEGER)");
- mDatabase.execSQL("CREATE INDEX i1a ON t1(a)");
-
- for (int i = 0; i < SIZE; i++) {
- int r = random.nextInt(100000);
- ContentValues b = new ContentValues(1);
- b.put("a", r);
- mValues[i] = b;
- }
- }
-
- public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase.insert("t1", null, mValues[i]);
- }
- }
- }
-
- /**
- * 10000 inserts for a String
- */
-
- public static class InsertString10000 extends PerformanceBase {
- private static final int SIZE = 100 * kMultiplier;
- ContentValues[] mValues = new ContentValues[SIZE];
-
- @Override
- public void setUp() {
- super.setUp();
- Random random = new Random(42);
-
- mDatabase
- .execSQL("CREATE TABLE t1(a VARCHAR(100))");
-
- for (int i = 0; i < SIZE; i++) {
- int r = random.nextInt(100000);
- ContentValues b = new ContentValues(1);
- b.put("a", numberName(r));
- mValues[i] = b;
- }
- }
-
- public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase.insert("t1", null, mValues[i]);
- }
- }
- }
-
- /**
- * 10000 inserts for a String - indexed table
- */
-
- public static class InsertStringIndexed10000 extends PerformanceBase {
- private static final int SIZE = 100 * kMultiplier;
- ContentValues[] mValues = new ContentValues[SIZE];
-
- @Override
- public void setUp() {
- super.setUp();
- Random random = new Random(42);
-
- mDatabase
- .execSQL("CREATE TABLE t1(a VARCHAR(100))");
- mDatabase.execSQL("CREATE INDEX i1a ON t1(a)");
-
- for (int i = 0; i < SIZE; i++) {
- int r = random.nextInt(100000);
- ContentValues b = new ContentValues(1);
- b.put("a", numberName(r));
- mValues[i] = b;
- }
- }
-
- public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase.insert("t1", null, mValues[i]);
- }
- }
- }
-
-
- /**
- * 10000 selects for a String -starts with
- */
-
- public static class SelectStringStartsWith10000 extends PerformanceBase {
- private static final int SIZE = 100 * kMultiplier;
- private static final String[] COLUMNS = {"t3.a"};
- private String[] where = new String[SIZE];
-
- @Override
- public void setUp() {
- super.setUp();
- Random random = new Random(42);
-
- mDatabase
- .execSQL("CREATE TABLE t3(a VARCHAR(100))");
-
- for (int i = 0; i < SIZE; i++) {
- int r = random.nextInt(100000);
- mDatabase.execSQL("INSERT INTO t3 VALUES('"
- + numberName(r) + "')");
- }
-
- for (int i = 0; i < SIZE; i++) {
- int r = random.nextInt(100000);
- where[i] = "a LIKE '" + numberName(r).substring(0, 1) + "*'";
-
- }
- }
-
- public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
- }
- }
- }
-
- /**
- * 10000 selects for a String - indexed table -starts with
- */
-
- public static class SelectStringIndexedStartsWith10000 extends
- PerformanceBase {
- private static final int SIZE = 100 * kMultiplier;
- private static final String[] COLUMNS = {"t3.a"};
- private String[] where = new String[SIZE];
-
- @Override
- public void setUp() {
- super.setUp();
- Random random = new Random(42);
-
- mDatabase
- .execSQL("CREATE TABLE t3(a VARCHAR(100))");
- mDatabase.execSQL("CREATE INDEX i3a ON t3(a)");
-
- for (int i = 0; i < SIZE; i++) {
- int r = random.nextInt(100000);
- mDatabase.execSQL("INSERT INTO t3 VALUES('"
- + numberName(r) + "')");
- }
-
- for (int i = 0; i < SIZE; i++) {
- int r = random.nextInt(100000);
- where[i] = "a LIKE '" + numberName(r).substring(0, 1) + "*'";
-
- }
- }
-
- public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
- }
- }
- }
-
- /**
- * 10000 selects for an integer -
- */
-
- public static class SelectInteger10000 extends PerformanceBase {
- private static final int SIZE = 100 * kMultiplier;
- private static final String[] COLUMNS = {"t4.a"};
- private String[] where = new String[SIZE];
-
- @Override
- public void setUp() {
- super.setUp();
- Random random = new Random(42);
-
- mDatabase
- .execSQL("CREATE TABLE t4(a INTEGER)");
-
- for (int i = 0; i < SIZE; i++) {
- int r = random.nextInt(100000);
- mDatabase.execSQL("INSERT INTO t4 VALUES(" + r + ")");
- int lower = i * 100;
- int upper = (i + 10) * 100;
- where[i] = "a >= " + lower + " AND a < " + upper;
- }
- }
-
- public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase.query("t4", COLUMNS, where[i], null, null, null, null);
- }
- }
- }
-
- /**
- * 10000 selects for an integer -indexed table
- */
-
- public static class SelectIntegerIndexed10000 extends PerformanceBase {
- private static final int SIZE = 100 * kMultiplier;
- private static final String[] COLUMNS = {"t4.a"};
- private String[] where = new String[SIZE];
-
- @Override
- public void setUp() {
- super.setUp();
- Random random = new Random(42);
-
- mDatabase
- .execSQL("CREATE TABLE t4(a INTEGER)");
- mDatabase.execSQL("CREATE INDEX i4a ON t4(a)");
-
- for (int i = 0; i < SIZE; i++) {
- int r = random.nextInt(100000);
- mDatabase.execSQL("INSERT INTO t4 VALUES(" + r + ")");
-
- int lower = i * 100;
- int upper = (i + 10) * 100;
- where[i] = "a >= " + lower + " AND a < " + upper;
- }
-
- }
-
- public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase.query("t4", COLUMNS, where[i], null, null, null, null);
- }
- }
- }
-
-
- /**
- * 10000 selects for a String - contains 'e'
- */
-
- public static class SelectStringContains10000 extends PerformanceBase {
- private static final int SIZE = 100 * kMultiplier;
+ public static class SelectStringContains100 extends PerformanceBase {
+ private static final int SIZE = SIZE_MULTIPLIER;
+ private static final int REPEAT_COUNT = 10;
private static final String[] COLUMNS = {"t3.a"};
private String[] where = new String[SIZE];
@@ -1171,19 +926,20 @@
}
public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
+ for (int iter = 0; iter < REPEAT_COUNT; iter++) {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
+ }
}
}
}
/**
- * 10000 selects for a String - contains 'e'-indexed table
+ * 100 selects for a String - contains 'e'-indexed table
*/
-
- public static class SelectStringIndexedContains10000 extends
- PerformanceBase {
- private static final int SIZE = 100 * kMultiplier;
+ public static class SelectStringIndexedContains100 extends PerformanceBase {
+ private static final int SIZE = SIZE_MULTIPLIER;
+ private static final int REPEAT_COUNT = 10;
private static final String[] COLUMNS = {"t3.a"};
private String[] where = new String[SIZE];
@@ -1209,19 +965,21 @@
}
public void testRun() {
- for (int i = 0; i < SIZE; i++) {
- mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
+ for (int iter = 0; iter < REPEAT_COUNT; iter++) {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
+ }
}
}
}
- public static final String[] ONES =
+ static final String[] ONES =
{"zero", "one", "two", "three", "four", "five", "six", "seven",
"eight", "nine", "ten", "eleven", "twelve", "thirteen",
"fourteen", "fifteen", "sixteen", "seventeen", "eighteen",
"nineteen"};
- public static final String[] TENS =
+ static final String[] TENS =
{"", "ten", "twenty", "thirty", "forty", "fifty", "sixty",
"seventy", "eighty", "ninety"};
}
diff --git a/core/tests/coretests/src/android/database/process_newdb_perf_test_logs.py b/core/tests/coretests/src/android/database/process_newdb_perf_test_logs.py
new file mode 100644
index 0000000..1faeceb
--- /dev/null
+++ b/core/tests/coretests/src/android/database/process_newdb_perf_test_logs.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+
+# 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.
+
+import re
+import sys
+
+def main():
+ args = sys.argv
+ if len(args) <= 1:
+ exit("Usage %s <log_file>" % args[0])
+ with open(args[1], 'r') as f:
+ all_lines = f.readlines()
+ timings = {}
+ running_sum = 0
+ for line in all_lines:
+ regex = r"NewDatabasePerformanceTests: Test (\w+) took (\d+) ms"
+ matches = re.search(regex, line)
+ if matches:
+ test_name = matches.group(1)
+ duration = int(matches.group(2))
+ if not test_name in timings:
+ timings[test_name] = []
+ timings[test_name].append(duration)
+ running_sum += duration
+ if ("TestRunner: run finished:" in line) and (running_sum > 0):
+ test_name = '*** TOTAL ALL TESTS (ms) ***'
+ if not test_name in timings:
+ timings[test_name] = []
+ timings[test_name].append(running_sum)
+ running_sum=0
+
+ for k in sorted(timings):
+ timings_ar = timings[k]
+ print "%s: %s avg: %s" % (k, timings_ar, sum(timings_ar) / float(len(timings_ar)) )
+
+if __name__ == '__main__':
+ main()
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index d875ed4..7dc72db7 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -102,6 +102,7 @@
Settings.Global.APP_IDLE_CONSTANTS,
Settings.Global.ASSISTED_GPS_ENABLED,
Settings.Global.AUDIO_SAFE_VOLUME_STATE,
+ Settings.Global.BACKUP_REFACTORED_SERVICE_DISABLED,
Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD,
Settings.Global.BATTERY_DISCHARGE_THRESHOLD,
Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE,
@@ -412,6 +413,7 @@
Settings.Secure.BACKUP_ENABLED,
Settings.Secure.BACKUP_PROVISIONED,
Settings.Secure.BACKUP_TRANSPORT,
+ Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED, // Candidate for backup?
Settings.Secure.CARRIER_APPS_HANDLED,
Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG,
Settings.Secure.COMPLETED_CATEGORY_PREFIX,
diff --git a/core/tests/coretests/src/android/text/BidiFormatterTest.java b/core/tests/coretests/src/android/text/BidiFormatterTest.java
new file mode 100644
index 0000000..14705ad
--- /dev/null
+++ b/core/tests/coretests/src/android/text/BidiFormatterTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2013 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.text;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BidiFormatterTest {
+ private static final BidiFormatter LTR_FMT = BidiFormatter.getInstance(false /* LTR context */);
+ private static final BidiFormatter RTL_FMT = BidiFormatter.getInstance(true /* RTL context */);
+
+ private static final String EN = "abba";
+ private static final String HE = "\u05E0\u05E1";
+
+ private static final String LRM = "\u200E";
+ private static final String RLM = "\u200F";
+
+ @Test
+ public void testMarkAfter() {
+ assertEquals("uniform dir matches LTR context",
+ "", LTR_FMT.markAfter(EN, TextDirectionHeuristics.LTR));
+ assertEquals("uniform dir matches RTL context",
+ "", RTL_FMT.markAfter(HE, TextDirectionHeuristics.RTL));
+
+ assertEquals("exit dir opposite to LTR context",
+ LRM, LTR_FMT.markAfter(EN + HE, TextDirectionHeuristics.LTR));
+ assertEquals("exit dir opposite to RTL context",
+ RLM, RTL_FMT.markAfter(HE + EN, TextDirectionHeuristics.RTL));
+
+ assertEquals("overall dir (but not exit dir) opposite to LTR context",
+ LRM, LTR_FMT.markAfter(HE + EN, TextDirectionHeuristics.RTL));
+ assertEquals("overall dir (but not exit dir) opposite to RTL context",
+ RLM, RTL_FMT.markAfter(EN + HE, TextDirectionHeuristics.LTR));
+
+ assertEquals("exit dir neutral, overall dir matches LTR context",
+ "", LTR_FMT.markAfter(".", TextDirectionHeuristics.LTR));
+ assertEquals("exit dir neutral, overall dir matches RTL context",
+ "", RTL_FMT.markAfter(".", TextDirectionHeuristics.RTL));
+ }
+
+ @Test
+ public void testMarkBefore() {
+ assertEquals("uniform dir matches LTR context",
+ "", LTR_FMT.markBefore(EN, TextDirectionHeuristics.LTR));
+ assertEquals("uniform dir matches RTL context",
+ "", RTL_FMT.markBefore(HE, TextDirectionHeuristics.RTL));
+
+ assertEquals("entry dir opposite to LTR context",
+ LRM, LTR_FMT.markBefore(HE + EN, TextDirectionHeuristics.LTR));
+ assertEquals("entry dir opposite to RTL context",
+ RLM, RTL_FMT.markBefore(EN + HE, TextDirectionHeuristics.RTL));
+
+ assertEquals("overall dir (but not entry dir) opposite to LTR context",
+ LRM, LTR_FMT.markBefore(EN + HE, TextDirectionHeuristics.RTL));
+ assertEquals("overall dir (but not entry dir) opposite to RTL context",
+ RLM, RTL_FMT.markBefore(HE + EN, TextDirectionHeuristics.LTR));
+
+ assertEquals("exit dir neutral, overall dir matches LTR context",
+ "", LTR_FMT.markBefore(".", TextDirectionHeuristics.LTR));
+ assertEquals("exit dir neutral, overall dir matches RTL context",
+ "", RTL_FMT.markBefore(".", TextDirectionHeuristics.RTL));
+ }
+}
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutBlocksTest.java b/core/tests/coretests/src/android/text/DynamicLayoutBlocksTest.java
index bc9f44d..08e1b5b 100644
--- a/core/tests/coretests/src/android/text/DynamicLayoutBlocksTest.java
+++ b/core/tests/coretests/src/android/text/DynamicLayoutBlocksTest.java
@@ -67,7 +67,10 @@
}
private void update(int startLine, int endLine, int newLineCount) {
- dl.setBlocksDataForTest(initialBlockEnds, initialBlockIndices, initialBlockEnds.length);
+ final int totalLines = initialBlockEnds[initialBlockEnds.length - 1]
+ + newLineCount - endLine + startLine;
+ dl.setBlocksDataForTest(
+ initialBlockEnds, initialBlockIndices, initialBlockEnds.length, totalLines);
checkInvariants();
dl.updateBlocks(startLine, endLine, newLineCount);
}
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutTest.java b/core/tests/coretests/src/android/text/DynamicLayoutTest.java
index da6dc7e..811bf2c 100644
--- a/core/tests/coretests/src/android/text/DynamicLayoutTest.java
+++ b/core/tests/coretests/src/android/text/DynamicLayoutTest.java
@@ -17,15 +17,14 @@
package android.text;
import static android.text.Layout.Alignment.ALIGN_NORMAL;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import android.graphics.Canvas;
+import android.graphics.Paint;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.text.style.ReplacementSpan;
@@ -54,6 +53,16 @@
assertNull(layout.getBlocksAlwaysNeedToBeRedrawn());
}
+ private class MockReplacementSpan extends ReplacementSpan {
+ public int getSize(Paint paint, CharSequence text, int start, int end,
+ Paint.FontMetricsInt fm) {
+ return 10;
+ }
+
+ public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top,
+ int y, int bottom, Paint paint) { }
+ }
+
@Test
public void testGetBlocksAlwaysNeedToBeRedrawn_replacementSpan() {
final SpannableStringBuilder builder = new SpannableStringBuilder();
@@ -66,17 +75,11 @@
builder.append("hijk lmn\n");
assertNull(layout.getBlocksAlwaysNeedToBeRedrawn());
- ReplacementSpan mockReplacementSpan = mock(ReplacementSpan.class);
- when(mockReplacementSpan.getSize(any(), any(), any(), any(), any()))
- .thenReturn(10);
- doNothing().when(mockReplacementSpan)
- .draw(any(), any(), any(), any(), any(), any(), any(), any(), any());
-
- builder.setSpan(mockReplacementSpan, 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ builder.setSpan(new MockReplacementSpan(), 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
assertNotNull(layout.getBlocksAlwaysNeedToBeRedrawn());
assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().contains(0));
- builder.setSpan(mockReplacementSpan, 9, 13, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ builder.setSpan(new MockReplacementSpan(), 9, 13, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().contains(0));
assertTrue(layout.getBlocksAlwaysNeedToBeRedrawn().contains(1));
diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java
new file mode 100644
index 0000000..6d610bb
--- /dev/null
+++ b/core/tests/coretests/src/android/text/LayoutTest.java
@@ -0,0 +1,469 @@
+/*
+ * Copyright (C) 2008 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.text;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.Layout.Alignment;
+import android.text.style.StrikethroughSpan;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LayoutTest {
+ private static final int LINE_COUNT = 5;
+ private static final int LINE_HEIGHT = 12;
+ private static final int LINE_DESCENT = 4;
+ private static final CharSequence LAYOUT_TEXT = "alwei\t;sdfs\ndf @";
+
+ private SpannableString mSpannedText;
+
+ private int mWidth;
+ private Layout.Alignment mAlign;
+ private float mSpacingMult;
+ private float mSpacingAdd;
+ private TextPaint mTextPaint;
+
+ @Before
+ public void setup() {
+ mTextPaint = new TextPaint();
+ mSpannedText = new SpannableString(LAYOUT_TEXT);
+ mSpannedText.setSpan(new StrikethroughSpan(), 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+ mWidth = 11;
+ mAlign = Alignment.ALIGN_CENTER;
+ mSpacingMult = 1;
+ mSpacingAdd = 2;
+ }
+
+ @Test
+ public void testConstructor() {
+ new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, mAlign, mSpacingMult, mSpacingAdd);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructorNull() {
+ new MockLayout(null, null, -1, null, 0, 0);
+ }
+
+ @Test
+ public void testGetText() {
+ CharSequence text = "test case 1";
+ Layout layout = new MockLayout(text, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ assertEquals(text, layout.getText());
+
+ layout = new MockLayout(null, mTextPaint, mWidth, mAlign, mSpacingMult, mSpacingAdd);
+ assertNull(layout.getText());
+ }
+
+ @Test
+ public void testGetPaint() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+
+ assertSame(mTextPaint, layout.getPaint());
+
+ layout = new MockLayout(LAYOUT_TEXT, null, mWidth, mAlign, mSpacingMult, mSpacingAdd);
+ assertNull(layout.getPaint());
+ }
+
+ @Test
+ public void testGetWidth() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, 10,
+ mAlign, mSpacingMult, mSpacingAdd);
+ assertEquals(10, layout.getWidth());
+
+ layout = new MockLayout(LAYOUT_TEXT, mTextPaint, 0, mAlign, mSpacingMult, mSpacingAdd);
+ assertEquals(0, layout.getWidth());
+ }
+
+ @Test
+ public void testGetEllipsizedWidth() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, 15,
+ mAlign, mSpacingMult, mSpacingAdd);
+ assertEquals(15, layout.getEllipsizedWidth());
+
+ layout = new MockLayout(LAYOUT_TEXT, mTextPaint, 0, mAlign, mSpacingMult, mSpacingAdd);
+ assertEquals(0, layout.getEllipsizedWidth());
+ }
+
+ @Test
+ public void testIncreaseWidthTo() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ int oldWidth = layout.getWidth();
+
+ layout.increaseWidthTo(oldWidth);
+ assertEquals(oldWidth, layout.getWidth());
+
+ try {
+ layout.increaseWidthTo(oldWidth - 1);
+ fail("should throw runtime exception attempted to reduce Layout width");
+ } catch (RuntimeException e) {
+ }
+
+ layout.increaseWidthTo(oldWidth + 1);
+ assertEquals(oldWidth + 1, layout.getWidth());
+ }
+
+ @Test
+ public void testGetHeight() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ assertEquals(60, layout.getHeight());
+ }
+
+ @Test
+ public void testGetAlignment() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ assertSame(mAlign, layout.getAlignment());
+
+ layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, null, mSpacingMult, mSpacingAdd);
+ assertNull(layout.getAlignment());
+ }
+
+ @Test
+ public void testGetSpacingMultiplier() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, mAlign, -1, mSpacingAdd);
+ assertEquals(-1.0f, layout.getSpacingMultiplier(), 0.0f);
+
+ layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, mAlign, 5, mSpacingAdd);
+ assertEquals(5.0f, layout.getSpacingMultiplier(), 0.0f);
+ }
+
+ @Test
+ public void testGetSpacingAdd() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, mAlign, mSpacingMult, -1);
+ assertEquals(-1.0f, layout.getSpacingAdd(), 0.0f);
+
+ layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, mAlign, mSpacingMult, 20);
+ assertEquals(20.0f, layout.getSpacingAdd(), 0.0f);
+ }
+
+ @Test
+ public void testGetLineBounds() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ Rect bounds = new Rect();
+
+ assertEquals(32, layout.getLineBounds(2, bounds));
+ assertEquals(0, bounds.left);
+ assertEquals(mWidth, bounds.right);
+ assertEquals(24, bounds.top);
+ assertEquals(36, bounds.bottom);
+ }
+
+ @Test
+ public void testGetLineForVertical() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ assertEquals(0, layout.getLineForVertical(-1));
+ assertEquals(0, layout.getLineForVertical(0));
+ assertEquals(0, layout.getLineForVertical(LINE_COUNT));
+ assertEquals(LINE_COUNT - 1, layout.getLineForVertical(1000));
+ }
+
+ @Test
+ public void testGetLineForOffset() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ assertEquals(0, layout.getLineForOffset(-1));
+ assertEquals(1, layout.getLineForOffset(1));
+ assertEquals(LINE_COUNT - 1, layout.getLineForOffset(LINE_COUNT - 1));
+ assertEquals(LINE_COUNT - 1, layout.getLineForOffset(1000));
+ }
+
+ @Test
+ public void testGetLineEnd() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ assertEquals(2, layout.getLineEnd(1));
+ }
+
+ @Test
+ public void testGetLineVisibleEnd() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+
+ assertEquals(2, layout.getLineVisibleEnd(1));
+ assertEquals(LINE_COUNT, layout.getLineVisibleEnd(LINE_COUNT - 1));
+ assertEquals(LAYOUT_TEXT.length(), layout.getLineVisibleEnd(LAYOUT_TEXT.length() - 1));
+ try {
+ layout.getLineVisibleEnd(LAYOUT_TEXT.length());
+ fail("should throw .StringIndexOutOfBoundsException here");
+ } catch (StringIndexOutOfBoundsException e) {
+ }
+ }
+
+ @Test
+ public void testGetLineBottom() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ assertEquals(LINE_HEIGHT, layout.getLineBottom(0));
+ }
+
+ @Test
+ public void testGetLineBaseline() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ assertEquals(8, layout.getLineBaseline(0));
+ }
+
+ @Test
+ public void testGetLineAscent() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ assertEquals(-8, layout.getLineAscent(0));
+ }
+
+ @Test
+ public void testGetParagraphAlignment() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ assertSame(mAlign, layout.getParagraphAlignment(0));
+
+ layout = new MockLayout(mSpannedText, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ assertSame(mAlign, layout.getParagraphAlignment(0));
+ assertSame(mAlign, layout.getParagraphAlignment(1));
+ }
+
+ @Test
+ public void testGetParagraphLeft() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ assertEquals(0, layout.getParagraphLeft(0));
+ }
+
+ @Test
+ public void testGetParagraphRight() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ assertEquals(mWidth, layout.getParagraphRight(0));
+ }
+
+ @Test
+ public void testIsSpanned() {
+ MockLayout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ // default is not spanned text
+ assertFalse(layout.mockIsSpanned());
+
+ // try to create a spanned text
+ layout = new MockLayout(mSpannedText, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ assertTrue(layout.mockIsSpanned());
+ }
+
+ private static final class MockLayout extends Layout {
+ MockLayout(CharSequence text, TextPaint paint, int width,
+ Alignment align, float spacingmult, float spacingadd) {
+ super(text, paint, width, align, spacingmult, spacingadd);
+ }
+
+ protected boolean mockIsSpanned() {
+ return super.isSpanned();
+ }
+
+ @Override
+ public int getBottomPadding() {
+ return 0;
+ }
+
+ @Override
+ public int getEllipsisCount(int line) {
+ return 0;
+ }
+
+ @Override
+ public int getEllipsisStart(int line) {
+ return 0;
+ }
+
+ @Override
+ public boolean getLineContainsTab(int line) {
+ return false;
+ }
+
+ @Override
+ public int getLineCount() {
+ return LINE_COUNT;
+ }
+
+ @Override
+ public int getLineDescent(int line) {
+ return LINE_DESCENT;
+ }
+
+ @Override
+ public Directions getLineDirections(int line) {
+ return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+ }
+
+ @Override
+ public int getLineStart(int line) {
+ if (line < 0) {
+ return 0;
+ }
+ return line;
+ }
+
+ @Override
+ public int getLineTop(int line) {
+ if (line < 0) {
+ return 0;
+ }
+ return LINE_HEIGHT * (line);
+ }
+
+ @Override
+ public int getParagraphDirection(int line) {
+ return 0;
+ }
+
+ @Override
+ public int getTopPadding() {
+ return 0;
+ }
+ }
+
+ @Test
+ public void testGetLineWidth() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ for (int i = 0; i < LINE_COUNT; i++) {
+ int start = layout.getLineStart(i);
+ int end = layout.getLineEnd(i);
+ String text = LAYOUT_TEXT.toString().substring(start, end);
+ assertEquals(mTextPaint.measureText(text), layout.getLineWidth(i), 1.0f);
+ }
+ }
+
+ @Test
+ public void testGetCursorPath() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ Path path = new Path();
+ final float epsilon = 1.0f;
+ for (int i = 0; i < LINE_COUNT; i++) {
+ layout.getCursorPath(i, path, LAYOUT_TEXT);
+ RectF bounds = new RectF();
+ path.computeBounds(bounds, false);
+ assertTrue(bounds.top >= layout.getLineTop(i) - epsilon);
+ assertTrue(bounds.bottom <= layout.getLineBottom(i) + epsilon);
+ }
+ }
+
+ @Test
+ public void testDraw() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+ final int width = 256;
+ final int height = 256;
+ MockCanvas c = new MockCanvas(width, height);
+ layout.draw(c);
+ List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
+ assertEquals(LINE_COUNT, drawCommands.size());
+ for (int i = 0; i < LINE_COUNT; i++) {
+ MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
+ int start = layout.getLineStart(i);
+ int end = layout.getLineEnd(i);
+ assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text);
+ float expected_y = (i + 1) * LINE_HEIGHT - LINE_DESCENT;
+ assertEquals(expected_y, drawCommand.y, 0.0f);
+ }
+ }
+
+ private final class MockCanvas extends Canvas {
+
+ class DrawCommand {
+ public final String text;
+ public final float x;
+ public final float y;
+
+ DrawCommand(String text, float x, float y) {
+ this.text = text;
+ this.x = x;
+ this.y = y;
+ }
+ }
+
+ List<DrawCommand> mDrawCommands;
+
+ MockCanvas(int width, int height) {
+ super();
+ mDrawCommands = new ArrayList<>();
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ setBitmap(bitmap);
+ }
+
+ // Drawing text with either drawText or drawTextRun is valid; we don't care which.
+ // We also don't care which of the string representations is used.
+
+ @Override
+ public void drawText(String text, int start, int end, float x, float y, Paint p) {
+ mDrawCommands.add(new DrawCommand(text.substring(start, end), x, y));
+ }
+
+ @Override
+ public void drawText(CharSequence text, int start, int end, float x, float y, Paint p) {
+ drawText(text.toString(), start, end, x, y, p);
+ }
+
+ @Override
+ public void drawText(char[] text, int index, int count, float x, float y, Paint p) {
+ mDrawCommands.add(new DrawCommand(new String(text, index, count), x, y));
+ }
+
+ @Override
+ public void drawTextRun(CharSequence text, int start, int end, int contextStart,
+ int contextEnd, float x, float y, boolean isRtl, Paint paint) {
+ drawText(text, start, end, x, y, paint);
+ }
+
+ @Override
+ public void drawTextRun(char[] text, int index, int count, int contextIndex,
+ int contextCount, float x, float y, boolean isRtl, Paint paint) {
+ drawText(text, index, count, x, y, paint);
+ }
+
+ List<DrawCommand> getDrawCommands() {
+ return mDrawCommands;
+ }
+ }
+}
+
diff --git a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
index 1f1a689..04a486e 100644
--- a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
+++ b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
@@ -16,9 +16,46 @@
package android.text;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.text.style.BulletSpan;
+import android.text.style.QuoteSpan;
+import android.text.style.SubscriptSpan;
+import android.text.style.UnderlineSpan;
+
+import org.junit.Test;
+
public class SpannableStringBuilderTest extends SpannableTest {
protected Spannable newSpannableWithText(String text) {
return new SpannableStringBuilder(text);
}
+
+ @Test
+ public void testGetSpans_sortsByPriorityEvenWhenSortParamIsFalse() {
+ String text = "p_in_s";
+ SpannableStringBuilder builder = new SpannableStringBuilder(text);
+ Object first = new SubscriptSpan();
+ Object second = new UnderlineSpan();
+ Object third = new BulletSpan();
+ Object fourth = new QuoteSpan();
+
+ builder.setSpan(first, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ builder.setSpan(second, 1, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ builder.setSpan(third, 2, text.length(), 1 << Spanned.SPAN_PRIORITY_SHIFT);
+ builder.setSpan(fourth, 0, text.length(), 2 << Spanned.SPAN_PRIORITY_SHIFT);
+
+ Object[] spans = builder.getSpans(0, text.length(), Object.class, false);
+
+ assertNotNull(spans);
+ assertEquals(4, spans.length);
+ // priority spans are first
+ assertEquals(fourth, spans[0]);
+ assertEquals(third, spans[1]);
+ // other spans should be there
+ assertEquals(second, spans[2]);
+ assertEquals(first, spans[3]);
+ }
}
diff --git a/core/tests/coretests/src/android/text/StaticLayoutLineBreakingTest.java b/core/tests/coretests/src/android/text/StaticLayoutLineBreakingTest.java
new file mode 100644
index 0000000..2ee855e
--- /dev/null
+++ b/core/tests/coretests/src/android/text/StaticLayoutLineBreakingTest.java
@@ -0,0 +1,461 @@
+/*
+ * Copyright (C) 2012 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.text;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.Layout.Alignment;
+import android.text.style.MetricAffectingSpan;
+import android.util.Log;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StaticLayoutLineBreakingTest {
+ // Span test are currently not supported because text measurement uses the MeasuredText
+ // internal mWorkPaint instead of the provided MockTestPaint.
+ private static final boolean SPAN_TESTS_SUPPORTED = false;
+ private static final boolean DEBUG = false;
+
+ private static final float SPACE_MULTI = 1.0f;
+ private static final float SPACE_ADD = 0.0f;
+ private static final int WIDTH = 100;
+ private static final Alignment ALIGN = Alignment.ALIGN_LEFT;
+
+ private static final char SURR_FIRST = '\uD800';
+ private static final char SURR_SECOND = '\uDF31';
+
+ private static final int[] NO_BREAK = new int[] {};
+
+ private static final TextPaint sTextPaint = new MockTextPaint();
+
+ private static class MockTextPaint extends TextPaint {
+
+ @Override
+ public float getTextRunAdvances(char[] chars, int index, int count,
+ int contextIndex, int contextCount, boolean isRtl, float[] advances,
+ int advancesIndex) {
+
+ // Conditions copy pasted from Paint
+ if (chars == null) {
+ throw new IllegalArgumentException("text cannot be null");
+ }
+
+ if ((index | count | contextIndex | contextCount | advancesIndex
+ | (index - contextIndex) | (contextCount - count)
+ | ((contextIndex + contextCount) - (index + count))
+ | (chars.length - (contextIndex + contextCount))
+ | (advances == null ? 0 :
+ (advances.length - (advancesIndex + count)))) < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ float res = 0.0f;
+
+ if (advances != null) {
+ for (int i = 0; i < count; i++) {
+ float width = getCharWidth(chars[index + i]);
+ advances[advancesIndex + i] = width;
+ res += width;
+ }
+ }
+
+ return res;
+ }
+ }
+
+ private static float getCharWidth(char c) {
+ switch (Character.toUpperCase(c)) {
+ // Roman figures
+ case 'I': return 1.0f;
+ case 'V': return 5.0f;
+ case 'X': return 10.0f;
+ case 'L': return 50.0f;
+ case 'C': return 100.0f; // equals to WIDTH
+ case ' ': return 10.0f;
+ case '_': return 0.0f; // 0-width character
+ case SURR_FIRST: return 7.0f;
+ case SURR_SECOND: return 3.0f; // Sum of SURR_FIRST-SURR_SECOND is 10
+ default: return 10.0f;
+ }
+ }
+
+ private static StaticLayout getStaticLayout(CharSequence source, int width) {
+ return new StaticLayout(source, sTextPaint, width, ALIGN, SPACE_MULTI, SPACE_ADD, false);
+ }
+
+ private static int[] getBreaks(CharSequence source) {
+ return getBreaks(source, WIDTH);
+ }
+
+ private static int[] getBreaks(CharSequence source, int width) {
+ StaticLayout staticLayout = getStaticLayout(source, width);
+
+ int[] breaks = new int[staticLayout.getLineCount() - 1];
+ for (int line = 0; line < breaks.length; line++) {
+ breaks[line] = staticLayout.getLineEnd(line);
+ }
+ return breaks;
+ }
+
+ private static void debugLayout(CharSequence source, StaticLayout staticLayout) {
+ if (DEBUG) {
+ int count = staticLayout.getLineCount();
+ Log.i("SLLBTest", "\"" + source.toString() + "\": "
+ + count + " lines");
+ for (int line = 0; line < count; line++) {
+ int lineStart = staticLayout.getLineStart(line);
+ int lineEnd = staticLayout.getLineEnd(line);
+ Log.i("SLLBTest", "Line " + line + " [" + lineStart + ".."
+ + lineEnd + "]\t" + source.subSequence(lineStart, lineEnd));
+ }
+ }
+ }
+
+ private static void layout(CharSequence source, int[] breaks) {
+ layout(source, breaks, WIDTH);
+ }
+
+ private static void layout(CharSequence source, int[] breaks, int width) {
+ StaticLayout staticLayout = getStaticLayout(source, width);
+
+ debugLayout(source, staticLayout);
+
+ int lineCount = breaks.length + 1;
+ assertEquals("Number of lines", lineCount, staticLayout.getLineCount());
+
+ for (int line = 0; line < lineCount; line++) {
+ int lineStart = staticLayout.getLineStart(line);
+ int lineEnd = staticLayout.getLineEnd(line);
+
+ if (line == 0) {
+ assertEquals("Line start for first line", 0, lineStart);
+ } else {
+ assertEquals("Line start for line " + line, breaks[line - 1], lineStart);
+ }
+
+ if (line == lineCount - 1) {
+ assertEquals("Line end for last line", source.length(), lineEnd);
+ } else {
+ assertEquals("Line end for line " + line, breaks[line], lineEnd);
+ }
+ }
+ }
+
+ private static void layoutMaxLines(CharSequence source, int[] breaks, int maxLines) {
+ StaticLayout staticLayout = new StaticLayout(source, 0, source.length(), sTextPaint, WIDTH,
+ ALIGN, TextDirectionHeuristics.LTR, SPACE_MULTI, SPACE_ADD, false /* includePad */,
+ null, WIDTH, maxLines);
+
+ debugLayout(source, staticLayout);
+
+ final int lineCount = staticLayout.getLineCount();
+
+ for (int line = 0; line < lineCount; line++) {
+ int lineStart = staticLayout.getLineStart(line);
+ int lineEnd = staticLayout.getLineEnd(line);
+
+ if (line == 0) {
+ assertEquals("Line start for first line", 0, lineStart);
+ } else {
+ assertEquals("Line start for line " + line, breaks[line - 1], lineStart);
+ }
+
+ if (line == lineCount - 1 && line != breaks.length - 1) {
+ assertEquals("Line end for last line", source.length(), lineEnd);
+ } else {
+ assertEquals("Line end for line " + line, breaks[line], lineEnd);
+ }
+ }
+ }
+
+ private static final int MAX_SPAN_COUNT = 10;
+ private static final int[] sSpanStarts = new int[MAX_SPAN_COUNT];
+ private static final int[] sSpanEnds = new int[MAX_SPAN_COUNT];
+
+ private static MetricAffectingSpan getMetricAffectingSpan() {
+ return new MetricAffectingSpan() {
+ @Override
+ public void updateDrawState(TextPaint tp) { /* empty */ }
+
+ @Override
+ public void updateMeasureState(TextPaint p) { /* empty */ }
+ };
+ }
+
+ /**
+ * Replaces the "<...>" blocks by spans, assuming non overlapping, correctly defined spans
+ * @param text
+ * @return A CharSequence with '<' '>' replaced by MetricAffectingSpan
+ */
+ private static CharSequence spanify(String text) {
+ int startIndex = text.indexOf('<');
+ if (startIndex < 0) return text;
+
+ int spanCount = 0;
+ do {
+ int endIndex = text.indexOf('>');
+ if (endIndex < 0) throw new IllegalArgumentException("Unbalanced span markers");
+
+ text = text.substring(0, startIndex) + text.substring(startIndex + 1, endIndex)
+ + text.substring(endIndex + 1);
+
+ sSpanStarts[spanCount] = startIndex;
+ sSpanEnds[spanCount] = endIndex - 2;
+ spanCount++;
+
+ startIndex = text.indexOf('<');
+ } while (startIndex >= 0);
+
+ SpannableStringBuilder result = new SpannableStringBuilder(text);
+ for (int i = 0; i < spanCount; i++) {
+ result.setSpan(getMetricAffectingSpan(), sSpanStarts[i], sSpanEnds[i],
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ return result;
+ }
+
+ @Test
+ public void testNoLineBreak() {
+ // Width lower than WIDTH
+ layout("", NO_BREAK);
+ layout("I", NO_BREAK);
+ layout("V", NO_BREAK);
+ layout("X", NO_BREAK);
+ layout("L", NO_BREAK);
+ layout("I VILI", NO_BREAK);
+ layout("XXXX", NO_BREAK);
+ layout("LXXXX", NO_BREAK);
+
+ // Width equal to WIDTH
+ layout("C", NO_BREAK);
+ layout("LL", NO_BREAK);
+ layout("L XXXX", NO_BREAK);
+ layout("XXXXXXXXXX", NO_BREAK);
+ layout("XXX XXXXXX", NO_BREAK);
+ layout("XXX XXXX X", NO_BREAK);
+ layout("XXX XXXXX ", NO_BREAK);
+ layout(" XXXXXXXX ", NO_BREAK);
+ layout(" XX XXX ", NO_BREAK);
+ // 0123456789
+
+ // Width greater than WIDTH, but no break
+ layout(" XX XXX ", NO_BREAK);
+ layout("XX XXX XXX ", NO_BREAK);
+ layout("XX XXX XXX ", NO_BREAK);
+ layout("XXXXXXXXXX ", NO_BREAK);
+ // 01234567890
+ }
+
+ @Test
+ public void testOneLineBreak() {
+ // 01234567890
+ layout("XX XXX XXXX", new int[] {7});
+ layout("XX XXXX XXX", new int[] {8});
+ layout("XX XXXXX XX", new int[] {9});
+ layout("XX XXXXXX X", new int[] {10});
+ // 01234567890
+ layout("XXXXXXXXXXX", new int[] {10});
+ layout("XXXXXXXXX X", new int[] {10});
+ layout("XXXXXXXX XX", new int[] {9});
+ layout("XXXXXXX XXX", new int[] {8});
+ layout("XXXXXX XXXX", new int[] {7});
+ // 01234567890
+ layout("LL LL", new int[] {3});
+ layout("LLLL", new int[] {2});
+ layout("C C", new int[] {2});
+ layout("CC", new int[] {1});
+ }
+
+ @Test
+ public void testSpaceAtBreak() {
+ // 0123456789012
+ layout("XXXX XXXXX X", new int[] {11});
+ layout("XXXXXXXXXX X", new int[] {11});
+ layout("XXXXXXXXXV X", new int[] {11});
+ layout("C X", new int[] {2});
+ }
+
+ @Test
+ public void testMultipleSpacesAtBreak() {
+ // 0123456789012
+ layout("LXX XXXX", new int[] {4});
+ layout("LXX XXXX", new int[] {5});
+ layout("LXX XXXX", new int[] {6});
+ layout("LXX XXXX", new int[] {7});
+ layout("LXX XXXX", new int[] {8});
+ }
+
+ @Test
+ public void testZeroWidthCharacters() {
+ // 0123456789012345678901234
+ layout("X_X_X_X_X_X_X_X_X_X", NO_BREAK);
+ layout("___X_X_X_X_X_X_X_X_X_X___", NO_BREAK);
+ layout("C_X", new int[] {2});
+ layout("C__X", new int[] {3});
+ }
+
+ /**
+ * Note that when the text has spans, StaticLayout does not use the provided TextPaint to
+ * measure text runs anymore. This is probably a bug.
+ * To be able to use the fake sTextPaint and make this test pass, use mPaint instead of
+ * mWorkPaint in MeasuredText#addStyleRun
+ */
+ @Test
+ public void testWithSpans() {
+ if (!SPAN_TESTS_SUPPORTED) return;
+
+ layout(spanify("<012 456 89>"), NO_BREAK);
+ layout(spanify("012 <456> 89"), NO_BREAK);
+ layout(spanify("<012> <456>< 89>"), NO_BREAK);
+ layout(spanify("<012> <456> <89>"), NO_BREAK);
+
+ layout(spanify("<012> <456> <89>012"), new int[] {8});
+ layout(spanify("<012> <456> 89<012>"), new int[] {8});
+ layout(spanify("<012> <456> <89><012>"), new int[] {8});
+ layout(spanify("<012> <456> 89 <123>"), new int[] {11});
+ layout(spanify("<012> <456> 89< 123>"), new int[] {11});
+ layout(spanify("<012> <456> <89> <123>"), new int[] {11});
+ layout(spanify("012 456 89 <LXX> XX XX"), new int[] {11, 18});
+ }
+
+ /*
+ * Adding a span to the string should not change the layout, since the metrics are unchanged.
+ */
+ @Test
+ public void testWithOneSpan() {
+ if (!SPAN_TESTS_SUPPORTED) return;
+
+ String[] texts = new String[] { "0123", "012 456", "012 456 89 123", "012 45678 012",
+ "012 456 89012 456 89012", "0123456789012" };
+
+ MetricAffectingSpan metricAffectingSpan = getMetricAffectingSpan();
+
+ for (String text : texts) {
+ // Get the line breaks without any span
+ int[] breaks = getBreaks(text);
+
+ // Add spans on all possible offsets
+ for (int spanStart = 0; spanStart < text.length(); spanStart++) {
+ for (int spanEnd = spanStart; spanEnd < text.length(); spanEnd++) {
+ SpannableStringBuilder ssb = new SpannableStringBuilder(text);
+ ssb.setSpan(metricAffectingSpan, spanStart, spanEnd,
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ layout(ssb, breaks);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testWithTwoSpans() {
+ if (!SPAN_TESTS_SUPPORTED) return;
+
+ String[] texts = new String[] { "0123", "012 456", "012 456 89 123", "012 45678 012",
+ "012 456 89012 456 89012", "0123456789012" };
+
+ MetricAffectingSpan metricAffectingSpan1 = getMetricAffectingSpan();
+ MetricAffectingSpan metricAffectingSpan2 = getMetricAffectingSpan();
+
+ for (String text : texts) {
+ // Get the line breaks without any span
+ int[] breaks = getBreaks(text);
+
+ // Add spans on all possible offsets
+ for (int spanStart1 = 0; spanStart1 < text.length(); spanStart1++) {
+ for (int spanEnd1 = spanStart1; spanEnd1 < text.length(); spanEnd1++) {
+ SpannableStringBuilder ssb = new SpannableStringBuilder(text);
+ ssb.setSpan(metricAffectingSpan1, spanStart1, spanEnd1,
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+ for (int spanStart2 = 0; spanStart2 < text.length(); spanStart2++) {
+ for (int spanEnd2 = spanStart2; spanEnd2 < text.length(); spanEnd2++) {
+ ssb.setSpan(metricAffectingSpan2, spanStart2, spanEnd2,
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ layout(ssb, breaks);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public static String replace(String string, char c, char r) {
+ return string.replaceAll(String.valueOf(c), String.valueOf(r));
+ }
+
+ @Test
+ public void testWithSurrogate() {
+ layout("LX" + SURR_FIRST + SURR_SECOND, NO_BREAK);
+ layout("LXXXX" + SURR_FIRST + SURR_SECOND, NO_BREAK);
+ // LXXXXI (91) + SURR_FIRST (7) fits. But we should not break the surrogate pair
+ // Bug: surrogate pair is broken, should be 6 (breaking after the 'V')
+ // Maybe not: may be ok if the second character of a pair always has a 0-width
+ layout("LXXXXI" + SURR_FIRST + SURR_SECOND, new int[] {7});
+
+ // LXXXXI (95) + SURR_SECOND (3) fits, but this is not a valid surrogate pair, breaking it
+ layout("LXXXXV" + SURR_SECOND + SURR_FIRST, new int[] {7});
+
+ layout("C" + SURR_FIRST + SURR_SECOND, new int[] {1});
+ }
+
+ @Test
+ public void testNarrowWidth() {
+ int[] widths = new int[] { 0, 4, 10 };
+ String[] texts = new String[] { "", "X", " ", "XX", " X", "XXX" };
+
+ for (String text: texts) {
+ // 15 is such that only one character will fit
+ int[] breaks = getBreaks(text, 15);
+
+ // Width under 15 should all lead to the same line break
+ for (int width: widths) {
+ layout(text, breaks, width);
+ }
+ }
+ }
+
+ @Test
+ public void testNarrowWidthZeroWidth() {
+ int[] widths = new int[] { 1, 4 };
+ for (int width: widths) {
+ layout("X.", new int[] {1}, width);
+ layout("X__", NO_BREAK, width);
+ layout("X__X", new int[] {3}, width);
+ layout("X__X_", new int[] {3}, width);
+
+ layout("_", NO_BREAK, width);
+ layout("__", NO_BREAK, width);
+ layout("_X", new int[] {1}, width);
+ layout("_X_", new int[] {1}, width);
+ layout("__X__", new int[] {2}, width);
+ }
+ }
+
+ @Test
+ public void testMaxLines() {
+ layoutMaxLines("C", NO_BREAK, 1);
+ layoutMaxLines("C C", new int[] {2}, 1);
+ layoutMaxLines("C C", new int[] {2}, 2);
+ layoutMaxLines("CC", new int[] {1}, 1);
+ layoutMaxLines("CC", new int[] {1}, 2);
+ }
+}
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index b7ca219..74a6cc6 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -17,7 +17,9 @@
package android.text;
import static android.text.Layout.Alignment.ALIGN_NORMAL;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import android.graphics.Paint.FontMetricsInt;
import android.support.test.filters.SmallTest;
@@ -26,15 +28,68 @@
import android.text.method.EditorState;
import android.util.Log;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.text.Normalizer;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Tests StaticLayout vertical metrics behavior.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class StaticLayoutTest {
+ private static final float SPACE_MULTI = 1.0f;
+ private static final float SPACE_ADD = 0.0f;
+ private static final int DEFAULT_OUTER_WIDTH = 150;
+
+ private static final CharSequence LAYOUT_TEXT = "CharSe\tq\nChar"
+ + "Sequence\nCharSequence\nHelllo\n, world\nLongLongLong";
+ private static final CharSequence LAYOUT_TEXT_SINGLE_LINE = "CharSequence";
+
+ private static final Alignment DEFAULT_ALIGN = Alignment.ALIGN_CENTER;
+ private static final int ELLIPSIZE_WIDTH = 8;
+
+ private StaticLayout mDefaultLayout;
+ private TextPaint mDefaultPaint;
+
+ @Before
+ public void setup() {
+ mDefaultPaint = new TextPaint();
+ mDefaultLayout = createDefaultStaticLayout();
+ }
+
+ private StaticLayout createDefaultStaticLayout() {
+ return new StaticLayout(LAYOUT_TEXT, mDefaultPaint,
+ DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true);
+ }
+
+ @Test
+ public void testBuilder() {
+ {
+ // Obtain.
+ final StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0,
+ LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ final StaticLayout layout = builder.build();
+ // Check default value.
+ assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR,
+ layout.getTextDirectionHeuristic());
+ }
+ {
+ // setTextDirection.
+ final StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0,
+ LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
+ builder.setTextDirection(TextDirectionHeuristics.RTL);
+ final StaticLayout layout = builder.build();
+ // Always returns TextDirectionHeuristics.FIRSTSTRONG_LTR.
+ assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR,
+ layout.getTextDirectionHeuristic());
+ }
+ }
+
/**
* Basic test showing expected behavior and relationship between font
* metrics and line metrics.
@@ -410,4 +465,210 @@
state.setByString("| U+261D U+1F3FB U+261D U+1F3FB U+261D U+1F3FB");
moveCursorToLeftCursorableOffset(state, paint);
}
+
+ private StaticLayout createEllipsizeStaticLayout(CharSequence text,
+ TextUtils.TruncateAt ellipsize, int maxLines) {
+ return new StaticLayout(text, 0, text.length(),
+ mDefaultPaint, DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN,
+ TextDirectionHeuristics.FIRSTSTRONG_LTR,
+ SPACE_MULTI, SPACE_ADD, true /* include pad */,
+ ellipsize,
+ ELLIPSIZE_WIDTH,
+ maxLines);
+ }
+
+ @Test
+ public void testEllipsis_singleLine() {
+ {
+ // Single line case and TruncateAt.END so that we have some ellipsis
+ StaticLayout layout = createEllipsizeStaticLayout(LAYOUT_TEXT_SINGLE_LINE,
+ TextUtils.TruncateAt.END, 1);
+ assertTrue(layout.getEllipsisCount(0) > 0);
+ }
+ {
+ // Single line case and TruncateAt.MIDDLE so that we have some ellipsis
+ StaticLayout layout = createEllipsizeStaticLayout(LAYOUT_TEXT_SINGLE_LINE,
+ TextUtils.TruncateAt.MIDDLE, 1);
+ assertTrue(layout.getEllipsisCount(0) > 0);
+ }
+ {
+ // Single line case and TruncateAt.END so that we have some ellipsis
+ StaticLayout layout = createEllipsizeStaticLayout(LAYOUT_TEXT_SINGLE_LINE,
+ TextUtils.TruncateAt.END, 1);
+ assertTrue(layout.getEllipsisCount(0) > 0);
+ }
+ {
+ // Single line case and TruncateAt.MARQUEE so that we have NO ellipsis
+ StaticLayout layout = createEllipsizeStaticLayout(LAYOUT_TEXT_SINGLE_LINE,
+ TextUtils.TruncateAt.MARQUEE, 1);
+ assertTrue(layout.getEllipsisCount(0) == 0);
+ }
+ {
+ final String text = "\u3042" // HIRAGANA LETTER A
+ + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";
+ final float textWidth = mDefaultPaint.measureText(text);
+ final int halfWidth = (int) (textWidth / 2.0f);
+ {
+ StaticLayout layout = new StaticLayout(text, 0, text.length(), mDefaultPaint,
+ halfWidth, DEFAULT_ALIGN, TextDirectionHeuristics.FIRSTSTRONG_LTR,
+ SPACE_MULTI, SPACE_ADD, false, TextUtils.TruncateAt.END, halfWidth, 1);
+ assertTrue(layout.getEllipsisCount(0) > 0);
+ assertTrue(layout.getEllipsisStart(0) > 0);
+ }
+ {
+ StaticLayout layout = new StaticLayout(text, 0, text.length(), mDefaultPaint,
+ halfWidth, DEFAULT_ALIGN, TextDirectionHeuristics.FIRSTSTRONG_LTR,
+ SPACE_MULTI, SPACE_ADD, false, TextUtils.TruncateAt.START, halfWidth, 1);
+ assertTrue(layout.getEllipsisCount(0) > 0);
+ assertEquals(0, mDefaultLayout.getEllipsisStart(0));
+ }
+ {
+ StaticLayout layout = new StaticLayout(text, 0, text.length(), mDefaultPaint,
+ halfWidth, DEFAULT_ALIGN, TextDirectionHeuristics.FIRSTSTRONG_LTR,
+ SPACE_MULTI, SPACE_ADD, false, TextUtils.TruncateAt.MIDDLE, halfWidth, 1);
+ assertTrue(layout.getEllipsisCount(0) > 0);
+ assertTrue(layout.getEllipsisStart(0) > 0);
+ }
+ {
+ StaticLayout layout = new StaticLayout(text, 0, text.length(), mDefaultPaint,
+ halfWidth, DEFAULT_ALIGN, TextDirectionHeuristics.FIRSTSTRONG_LTR,
+ SPACE_MULTI, SPACE_ADD, false, TextUtils.TruncateAt.MARQUEE, halfWidth, 1);
+ assertEquals(0, layout.getEllipsisCount(0));
+ }
+ }
+
+ {
+ // The white spaces in this text will be trailing if maxLines is larger than 1, but
+ // width of the trailing white spaces must not be ignored if ellipsis is applied.
+ final String text = "abc def";
+ final float textWidth = mDefaultPaint.measureText(text);
+ final int halfWidth = (int) (textWidth / 2.0f);
+ {
+ StaticLayout layout = new StaticLayout(text, 0, text.length(), mDefaultPaint,
+ halfWidth, DEFAULT_ALIGN, TextDirectionHeuristics.FIRSTSTRONG_LTR,
+ SPACE_MULTI, SPACE_ADD, false, TextUtils.TruncateAt.END, halfWidth, 1);
+ assertTrue(layout.getEllipsisCount(0) > 0);
+ assertTrue(layout.getEllipsisStart(0) > 0);
+ }
+ }
+
+ {
+ // 2 family emojis (11 code units + 11 code units).
+ final String text = "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66"
+ + "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66";
+ final float textWidth = mDefaultPaint.measureText(text);
+
+ final TextUtils.TruncateAt[] kinds = {TextUtils.TruncateAt.START,
+ TextUtils.TruncateAt.MIDDLE, TextUtils.TruncateAt.END};
+ for (final TextUtils.TruncateAt kind : kinds) {
+ for (int i = 0; i <= 8; i++) {
+ int avail = (int) (textWidth * i / 7.0f);
+ StaticLayout layout = new StaticLayout(text, 0, text.length(), mDefaultPaint,
+ avail, DEFAULT_ALIGN, TextDirectionHeuristics.FIRSTSTRONG_LTR,
+ SPACE_MULTI, SPACE_ADD, false, kind, avail, 1);
+
+ assertTrue(layout.getEllipsisCount(0) == text.length()
+ || layout.getEllipsisCount(0) == text.length() / 2
+ || layout.getEllipsisCount(0) == 0);
+ }
+ }
+ }
+ }
+
+ // String wrapper for testing not well known implementation of CharSequence.
+ private class FakeCharSequence implements CharSequence {
+ private String mStr;
+
+ FakeCharSequence(String str) {
+ mStr = str;
+ }
+
+ @Override
+ public char charAt(int index) {
+ return mStr.charAt(index);
+ }
+
+ @Override
+ public int length() {
+ return mStr.length();
+ }
+
+ @Override
+ public CharSequence subSequence(int start, int end) {
+ return mStr.subSequence(start, end);
+ }
+
+ @Override
+ public String toString() {
+ return mStr;
+ }
+ };
+
+ private List<CharSequence> buildTestCharSequences(String testString, Normalizer.Form[] forms) {
+ List<CharSequence> result = new ArrayList<>();
+
+ List<String> normalizedStrings = new ArrayList<>();
+ for (Normalizer.Form form: forms) {
+ normalizedStrings.add(Normalizer.normalize(testString, form));
+ }
+
+ for (String str: normalizedStrings) {
+ result.add(str);
+ result.add(new SpannedString(str));
+ result.add(new SpannableString(str));
+ result.add(new SpannableStringBuilder(str)); // as a GraphicsOperations implementation.
+ result.add(new FakeCharSequence(str)); // as a not well known implementation.
+ }
+ return result;
+ }
+
+ private String buildTestMessage(CharSequence seq) {
+ String normalized;
+ if (Normalizer.isNormalized(seq, Normalizer.Form.NFC)) {
+ normalized = "NFC";
+ } else if (Normalizer.isNormalized(seq, Normalizer.Form.NFD)) {
+ normalized = "NFD";
+ } else if (Normalizer.isNormalized(seq, Normalizer.Form.NFKC)) {
+ normalized = "NFKC";
+ } else if (Normalizer.isNormalized(seq, Normalizer.Form.NFKD)) {
+ normalized = "NFKD";
+ } else {
+ throw new IllegalStateException("Normalized form is not NFC/NFD/NFKC/NFKD");
+ }
+
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < seq.length(); ++i) {
+ builder.append(String.format("0x%04X ", Integer.valueOf(seq.charAt(i))));
+ }
+
+ return "testString: \"" + seq.toString() + "\"[" + builder.toString() + "]"
+ + ", class: " + seq.getClass().getName()
+ + ", Normalization: " + normalized;
+ }
+
+ @Test
+ public void testGetOffset_UNICODE_Hebrew() {
+ String testString = "\u05DE\u05E1\u05E2\u05D3\u05D4"; // Hebrew Characters
+ for (CharSequence seq: buildTestCharSequences(testString, Normalizer.Form.values())) {
+ StaticLayout layout = new StaticLayout(seq, mDefaultPaint,
+ DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN,
+ TextDirectionHeuristics.RTL, SPACE_MULTI, SPACE_ADD, true);
+
+ String testLabel = buildTestMessage(seq);
+
+ assertEquals(testLabel, 1, layout.getOffsetToLeftOf(0));
+ assertEquals(testLabel, 2, layout.getOffsetToLeftOf(1));
+ assertEquals(testLabel, 3, layout.getOffsetToLeftOf(2));
+ assertEquals(testLabel, 4, layout.getOffsetToLeftOf(3));
+ assertEquals(testLabel, 5, layout.getOffsetToLeftOf(4));
+ assertEquals(testLabel, 5, layout.getOffsetToLeftOf(5));
+
+ assertEquals(testLabel, 0, layout.getOffsetToRightOf(0));
+ assertEquals(testLabel, 0, layout.getOffsetToRightOf(1));
+ assertEquals(testLabel, 1, layout.getOffsetToRightOf(2));
+ assertEquals(testLabel, 2, layout.getOffsetToRightOf(3));
+ assertEquals(testLabel, 3, layout.getOffsetToRightOf(4));
+ assertEquals(testLabel, 4, layout.getOffsetToRightOf(5));
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/text/format/DateUtilsTest.java b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
index 9271cb4..6063e1a 100644
--- a/core/tests/coretests/src/android/text/format/DateUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
@@ -24,12 +24,16 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import java.util.Locale;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.text.DateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DateUtilsTest {
@@ -103,6 +107,42 @@
assertEquals("48h", DateUtils.formatDuration(172800000, DateUtils.LENGTH_SHORTEST));
}
+ @Test
+ public void testFormatSameDayTime() {
+ // This test assumes a default DateFormat.is24Hour setting.
+ DateFormat.is24Hour = null;
+ Date date = new Date(109, 0, 19, 3, 30, 15);
+ long fixedTime = date.getTime();
+
+ int currentYear = Calendar.getInstance().get(Calendar.YEAR);
+ Date dateWithCurrentYear = new Date(currentYear - 1900, 0, 19, 3, 30, 15);
+
+ final long dayDuration = 5 * 24 * 60 * 60 * 1000;
+ assertEquals("Saturday, January 24, 2009", DateUtils.formatSameDayTime(
+ fixedTime + dayDuration, fixedTime, java.text.DateFormat.FULL,
+ java.text.DateFormat.FULL));
+ assertEquals("Jan 24, 2009", DateUtils.formatSameDayTime(fixedTime + dayDuration,
+ fixedTime, java.text.DateFormat.DEFAULT, java.text.DateFormat.FULL));
+ assertEquals("January 24, 2009", DateUtils.formatSameDayTime(fixedTime + dayDuration,
+ fixedTime, java.text.DateFormat.LONG, java.text.DateFormat.FULL));
+ assertEquals("Jan 24, 2009", DateUtils.formatSameDayTime(fixedTime + dayDuration,
+ fixedTime, java.text.DateFormat.MEDIUM, java.text.DateFormat.FULL));
+ assertEquals("1/24/09", DateUtils.formatSameDayTime(fixedTime + dayDuration,
+ fixedTime, java.text.DateFormat.SHORT, java.text.DateFormat.FULL));
+
+ final long hourDuration = 2 * 60 * 60 * 1000;
+ assertEquals("5:30:15 AM GMT+00:00", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.FULL));
+ assertEquals("5:30:15 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.DEFAULT));
+ assertEquals("5:30:15 AM GMT+00:00", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.LONG));
+ assertEquals("5:30:15 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.MEDIUM));
+ assertEquals("5:30 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.SHORT));
+ }
+
private void setLocales(LocaleList locales) {
final Resources systemResources = Resources.getSystem();
final Configuration config = new Configuration(systemResources.getConfiguration());
diff --git a/core/tests/coretests/src/android/text/method/WordIteratorTest.java b/core/tests/coretests/src/android/text/method/WordIteratorTest.java
index 3499a74..d1f4f7e 100644
--- a/core/tests/coretests/src/android/text/method/WordIteratorTest.java
+++ b/core/tests/coretests/src/android/text/method/WordIteratorTest.java
@@ -21,19 +21,131 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.support.test.runner.AndroidJUnit4;
import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.text.BreakIterator;
import java.util.Locale;
-import org.junit.Test;
-import org.junit.runner.RunWith;
// TODO(Bug: 24062099): Add more tests for non-ascii text.
@SmallTest
@RunWith(AndroidJUnit4.class)
public class WordIteratorTest {
+ private WordIterator mWordIterator = new WordIterator();
+
+ private void verifyIsWordWithSurrogate(int beginning, int end, int surrogateIndex) {
+ for (int i = beginning; i <= end; i++) {
+ if (i == surrogateIndex) continue;
+ assertEquals(beginning, mWordIterator.getBeginning(i));
+ assertEquals(end, mWordIterator.getEnd(i));
+ }
+ }
+
+ private void setCharSequence(String string) {
+ mWordIterator.setCharSequence(string, 0, string.length());
+ }
+
+ private void verifyIsWord(int beginning, int end) {
+ verifyIsWordWithSurrogate(beginning, end, -1);
+ }
+
+ private void verifyIsNotWord(int beginning, int end) {
+ for (int i = beginning; i <= end; i++) {
+ assertEquals(BreakIterator.DONE, mWordIterator.getBeginning(i));
+ assertEquals(BreakIterator.DONE, mWordIterator.getEnd(i));
+ }
+ }
+
+ @Test
+ public void testEmptyString() {
+ setCharSequence("");
+ assertEquals(BreakIterator.DONE, mWordIterator.following(0));
+ assertEquals(BreakIterator.DONE, mWordIterator.preceding(0));
+
+ assertEquals(BreakIterator.DONE, mWordIterator.getBeginning(0));
+ assertEquals(BreakIterator.DONE, mWordIterator.getEnd(0));
+ }
+
+ @Test
+ public void testOneWord() {
+ setCharSequence("I");
+ verifyIsWord(0, 1);
+
+ setCharSequence("am");
+ verifyIsWord(0, 2);
+
+ setCharSequence("zen");
+ verifyIsWord(0, 3);
+ }
+
+ @Test
+ public void testSpacesOnly() {
+ setCharSequence(" ");
+ verifyIsNotWord(0, 1);
+
+ setCharSequence(", ");
+ verifyIsNotWord(0, 2);
+
+ setCharSequence(":-)");
+ verifyIsNotWord(0, 3);
+ }
+
+ @Test
+ public void testBeginningEnd() {
+ setCharSequence("Well hello, there! ");
+ // 0123456789012345678901
+ verifyIsWord(0, 4);
+ verifyIsWord(5, 10);
+ verifyIsNotWord(11, 13);
+ verifyIsWord(14, 19);
+ verifyIsNotWord(20, 21);
+
+ setCharSequence(" Another - sentence");
+ // 012345678901234567890
+ verifyIsNotWord(0, 1);
+ verifyIsWord(2, 9);
+ verifyIsNotWord(10, 11);
+ verifyIsWord(12, 20);
+
+ setCharSequence("This is \u0644\u0627 tested"); // Lama-aleph
+ // 012345678 9 01234567
+ verifyIsWord(0, 4);
+ verifyIsWord(5, 7);
+ verifyIsWord(8, 10);
+ verifyIsWord(11, 17);
+ }
+
+ @Test
+ public void testSurrogate() {
+ final String gothicBairkan = "\uD800\uDF31";
+
+ setCharSequence("one we" + gothicBairkan + "ird word");
+ // 012345 67 890123456
+
+ verifyIsWord(0, 3);
+ // Skip index 7 (there is no point in starting between the two surrogate characters)
+ verifyIsWordWithSurrogate(4, 11, 7);
+ verifyIsWord(12, 16);
+
+ setCharSequence("one " + gothicBairkan + "xxx word");
+ // 0123 45 678901234
+
+ verifyIsWord(0, 3);
+ verifyIsWordWithSurrogate(4, 9, 5);
+ verifyIsWord(10, 14);
+
+ setCharSequence("one xxx" + gothicBairkan + " word");
+ // 0123456 78 901234
+
+ verifyIsWord(0, 3);
+ verifyIsWordWithSurrogate(4, 9, 8);
+ verifyIsWord(10, 14);
+ }
+
@Test
public void testSetCharSequence() {
final String text = "text";
diff --git a/core/tests/coretests/src/android/text/style/UnderlineSpanTest.java b/core/tests/coretests/src/android/text/style/UnderlineSpanTest.java
new file mode 100644
index 0000000..e5c4b82
--- /dev/null
+++ b/core/tests/coretests/src/android/text/style/UnderlineSpanTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.style;
+
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UnderlineSpanTest {
+ private class RedUnderlineSpan extends UnderlineSpan {
+ @Override
+ public void updateDrawState(TextPaint ds) {
+ ds.setUnderlineText(android.graphics.Color.RED, 1.0f);
+ }
+ }
+
+ // Identical to the normal UnderlineSpan test, except that a subclass of UnderlineSpan is used
+ // that draws a red underline. This shouldn't affect width either.
+ @Test
+ public void testDoesntAffectWidth_colorUnderlineSubclass() {
+ // Roboto kerns between "P" and "."
+ final SpannableString text = new SpannableString("P.");
+ final float origLineWidth = textWidth(text);
+ // Underline just the "P".
+ text.setSpan(new RedUnderlineSpan(), 0, 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ final float underlinedLineWidth = textWidth(text);
+ assertEquals(origLineWidth, underlinedLineWidth, 0.0f);
+ }
+
+ // Measures the width of some potentially-spanned text, assuming it's not too wide.
+ private float textWidth(CharSequence text) {
+ final TextPaint tp = new TextPaint();
+ tp.setTextSize(100.0f); // Large enough so that the difference in kerning is visible.
+ final int largeWidth = 10000; // Enough width so the whole text fits in one line.
+ final StaticLayout layout = StaticLayout.Builder.obtain(
+ text, 0, text.length(), tp, largeWidth).build();
+ return layout.getLineWidth(0);
+ }
+}
diff --git a/core/tests/coretests/src/android/text/util/LinkifyTest.java b/core/tests/coretests/src/android/text/util/LinkifyTest.java
index 23c34085..73ff046 100644
--- a/core/tests/coretests/src/android/text/util/LinkifyTest.java
+++ b/core/tests/coretests/src/android/text/util/LinkifyTest.java
@@ -16,6 +16,7 @@
package android.text.util;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -25,15 +26,20 @@
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.text.Spannable;
+import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
+import android.text.style.URLSpan;
+import android.util.Patterns;
import android.widget.TextView;
-import java.util.Locale;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Locale;
+
/**
* LinkifyTest tests {@link Linkify}.
*/
@@ -98,4 +104,49 @@
overrideConfig.setLocales(LOCALE_LIST_US);
return mContext.createConfigurationContext(overrideConfig);
}
+
+ @Test
+ public void testAddLinks_addsLinksWhenDefaultSchemeIsNull() {
+ Spannable spannable = new SpannableString("any https://android.com any android.com any");
+ Linkify.addLinks(spannable, Patterns.AUTOLINK_WEB_URL, null, null, null);
+
+ URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
+ assertEquals("android.com and https://android.com should be linkified", 2, spans.length);
+ assertEquals("https://android.com", spans[0].getURL());
+ assertEquals("android.com", spans[1].getURL());
+ }
+
+ @Test
+ public void testAddLinks_addsLinksWhenSchemesArrayIsNull() {
+ Spannable spannable = new SpannableString("any https://android.com any android.com any");
+ Linkify.addLinks(spannable, Patterns.AUTOLINK_WEB_URL, "http://", null, null);
+
+ URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
+ assertEquals("android.com and https://android.com should be linkified", 2, spans.length);
+ // expected behavior, passing null schemes array means: prepend defaultScheme to all links.
+ assertEquals("http://https://android.com", spans[0].getURL());
+ assertEquals("http://android.com", spans[1].getURL());
+ }
+
+ @Test
+ public void testAddLinks_prependsDefaultSchemeToBeginingOfLink() {
+ Spannable spannable = new SpannableString("any android.com any");
+ Linkify.addLinks(spannable, Patterns.AUTOLINK_WEB_URL, "http://",
+ new String[] { "http://", "https://"}, null, null);
+
+ URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
+ assertEquals("android.com should be linkified", 1, spans.length);
+ assertEquals("http://android.com", spans[0].getURL());
+ }
+
+ @Test
+ public void testAddLinks_doesNotPrependSchemeIfSchemeExists() {
+ Spannable spannable = new SpannableString("any https://android.com any");
+ Linkify.addLinks(spannable, Patterns.AUTOLINK_WEB_URL, "http://",
+ new String[] { "http://", "https://"}, null, null);
+
+ URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
+ assertEquals("android.com should be linkified", 1, spans.length);
+ assertEquals("https://android.com", spans[0].getURL());
+ }
}
diff --git a/core/tests/coretests/src/android/transition/FadeTransitionTest.java b/core/tests/coretests/src/android/transition/FadeTransitionTest.java
index 0140a04..674b363 100644
--- a/core/tests/coretests/src/android/transition/FadeTransitionTest.java
+++ b/core/tests/coretests/src/android/transition/FadeTransitionTest.java
@@ -48,23 +48,23 @@
View square1 = mActivity.findViewById(R.id.square1);
Fade fadeOut = new Fade(Fade.MODE_OUT);
TransitionLatch latch = setVisibilityInTransition(fadeOut, R.id.square1, View.INVISIBLE);
- assertTrue(latch.startLatch.await(200, TimeUnit.MILLISECONDS));
+ assertTrue(latch.startLatch.await(400, TimeUnit.MILLISECONDS));
assertEquals(View.VISIBLE, square1.getVisibility());
waitForAnimation();
assertFalse(square1.getTransitionAlpha() == 0 || square1.getTransitionAlpha() == 1);
- assertTrue(latch.endLatch.await(400, TimeUnit.MILLISECONDS));
+ assertTrue(latch.endLatch.await(800, TimeUnit.MILLISECONDS));
assertEquals(1.0f, square1.getTransitionAlpha());
assertEquals(View.INVISIBLE, square1.getVisibility());
Fade fadeIn = new Fade(Fade.MODE_IN);
latch = setVisibilityInTransition(fadeIn, R.id.square1, View.VISIBLE);
- assertTrue(latch.startLatch.await(200, TimeUnit.MILLISECONDS));
+ assertTrue(latch.startLatch.await(400, TimeUnit.MILLISECONDS));
assertEquals(View.VISIBLE, square1.getVisibility());
waitForAnimation();
final float transitionAlpha = square1.getTransitionAlpha();
assertTrue("expecting transitionAlpha to be between 0 and 1. Was " + transitionAlpha,
transitionAlpha > 0 && transitionAlpha < 1);
- assertTrue(latch.endLatch.await(400, TimeUnit.MILLISECONDS));
+ assertTrue(latch.endLatch.await(800, TimeUnit.MILLISECONDS));
assertEquals(1.0f, square1.getTransitionAlpha());
assertEquals(View.VISIBLE, square1.getVisibility());
}
@@ -76,14 +76,14 @@
FadeValueCheck fadeOutValueCheck = new FadeValueCheck(square1);
fadeOut.addListener(fadeOutValueCheck);
TransitionLatch outLatch = setVisibilityInTransition(fadeOut, R.id.square1, View.INVISIBLE);
- assertTrue(outLatch.startLatch.await(200, TimeUnit.MILLISECONDS));
+ assertTrue(outLatch.startLatch.await(400, TimeUnit.MILLISECONDS));
waitForAnimation();
Fade fadeIn = new Fade(Fade.MODE_IN);
FadeValueCheck fadeInValueCheck = new FadeValueCheck(square1);
fadeIn.addListener(fadeInValueCheck);
TransitionLatch inLatch = setVisibilityInTransition(fadeIn, R.id.square1, View.VISIBLE);
- assertTrue(inLatch.startLatch.await(200, TimeUnit.MILLISECONDS));
+ assertTrue(inLatch.startLatch.await(400, TimeUnit.MILLISECONDS));
assertEquals(fadeOutValueCheck.pauseTransitionAlpha, fadeInValueCheck.startTransitionAlpha);
assertTrue("expecting transitionAlpha to be between 0 and 1. Was " +
@@ -91,7 +91,7 @@
fadeOutValueCheck.pauseTransitionAlpha > 0 &&
fadeOutValueCheck.pauseTransitionAlpha < 1);
- assertTrue(inLatch.endLatch.await(400, TimeUnit.MILLISECONDS));
+ assertTrue(inLatch.endLatch.await(800, TimeUnit.MILLISECONDS));
assertEquals(1.0f, square1.getTransitionAlpha());
assertEquals(View.VISIBLE, square1.getVisibility());
}
@@ -109,22 +109,22 @@
FadeValueCheck fadeInValueCheck = new FadeValueCheck(square1);
fadeIn.addListener(fadeInValueCheck);
TransitionLatch inLatch = setVisibilityInTransition(fadeIn, R.id.square1, View.VISIBLE);
- assertTrue(inLatch.startLatch.await(200, TimeUnit.MILLISECONDS));
+ assertTrue(inLatch.startLatch.await(400, TimeUnit.MILLISECONDS));
waitForAnimation();
Fade fadeOut = new Fade(Fade.MODE_OUT);
FadeValueCheck fadeOutValueCheck = new FadeValueCheck(square1);
fadeOut.addListener(fadeOutValueCheck);
TransitionLatch outLatch = setVisibilityInTransition(fadeOut, R.id.square1, View.INVISIBLE);
- assertTrue(outLatch.startLatch.await(200, TimeUnit.MILLISECONDS));
+ assertTrue(outLatch.startLatch.await(400, TimeUnit.MILLISECONDS));
- assertEquals(fadeOutValueCheck.pauseTransitionAlpha, fadeInValueCheck.startTransitionAlpha);
+ assertEquals(fadeInValueCheck.pauseTransitionAlpha, fadeOutValueCheck.startTransitionAlpha);
assertTrue("expecting transitionAlpha to be between 0 and 1. Was " +
fadeInValueCheck.pauseTransitionAlpha,
fadeInValueCheck.pauseTransitionAlpha > 0 &&
fadeInValueCheck.pauseTransitionAlpha < 1);
- assertTrue(outLatch.endLatch.await(400, TimeUnit.MILLISECONDS));
+ assertTrue(outLatch.endLatch.await(800, TimeUnit.MILLISECONDS));
assertEquals(1.0f, square1.getTransitionAlpha());
assertEquals(View.INVISIBLE, square1.getVisibility());
}
diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
new file mode 100644
index 0000000..b18fa74
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(AndroidJUnit4.class)
+public class IntentForwarderActivityTest {
+ private static final ComponentName FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME =
+ new ComponentName(
+ "android",
+ IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE
+ );
+ private static final String TYPE_PLAIN_TEXT = "text/plain";
+
+ private static UserInfo MANAGED_PROFILE_INFO = new UserInfo();
+ static {
+ MANAGED_PROFILE_INFO.id = 10;
+ MANAGED_PROFILE_INFO.flags = UserInfo.FLAG_MANAGED_PROFILE;
+ }
+
+ private static UserInfo CURRENT_USER_INFO = new UserInfo();
+ static {
+ CURRENT_USER_INFO.id = UserHandle.myUserId();
+ CURRENT_USER_INFO.flags = 0;
+ }
+
+ private static IntentForwarderActivity.Injector sInjector;
+ private static ComponentName sComponentName;
+
+ @Mock private IPackageManager mIPm;
+ @Mock private PackageManager mPm;
+ @Mock private UserManager mUserManager;
+
+ @Rule
+ public ActivityTestRule<IntentForwarderWrapperActivity> mActivityRule =
+ new ActivityTestRule<>(IntentForwarderWrapperActivity.class, true, false);
+
+ private Context mContext;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = InstrumentationRegistry.getTargetContext();
+ sInjector = new TestInjector();
+ }
+
+ @Test
+ public void forwardToManagedProfile_canForward_sendIntent() throws Exception {
+ sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME;
+
+ // Intent can be forwarded.
+ when(mIPm.canForwardTo(
+ any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);
+
+ // Managed profile exists.
+ List<UserInfo> profiles = new ArrayList<>();
+ profiles.add(CURRENT_USER_INFO);
+ profiles.add(MANAGED_PROFILE_INFO);
+ when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+
+ Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+ intent.setAction(Intent.ACTION_SEND);
+ intent.setType(TYPE_PLAIN_TEXT);
+ IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mIPm).canForwardTo(intentCaptor.capture(), eq(TYPE_PLAIN_TEXT), anyInt(), anyInt());
+ assertEquals(Intent.ACTION_SEND, intentCaptor.getValue().getAction());
+
+ assertEquals(Intent.ACTION_SEND, intentCaptor.getValue().getAction());
+ assertNotNull(activity.mStartActivityIntent);
+ assertEquals(Intent.ACTION_SEND, activity.mStartActivityIntent.getAction());
+ assertNull(activity.mStartActivityIntent.getPackage());
+ assertNull(activity.mStartActivityIntent.getComponent());
+ assertEquals(CURRENT_USER_INFO.id, activity.mStartActivityIntent.getContentUserHint());
+
+ assertEquals(MANAGED_PROFILE_INFO.id, activity.mUserIdActivityLaunchedIn);
+ }
+
+ @Test
+ public void forwardToManagedProfile_cannotForward_sendIntent() throws Exception {
+ sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME;
+
+ // Intent cannot be forwarded.
+ when(mIPm.canForwardTo(
+ any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(false);
+
+ // Managed profile exists.
+ List<UserInfo> profiles = new ArrayList<>();
+ profiles.add(CURRENT_USER_INFO);
+ profiles.add(MANAGED_PROFILE_INFO);
+ when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+
+ // Create ACTION_SEND intent.
+ Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+ intent.setAction(Intent.ACTION_SEND);
+ IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+
+ assertNull(activity.mStartActivityIntent);
+ }
+
+ @Test
+ public void forwardToManagedProfile_noManagedProfile_sendIntent() throws Exception {
+ sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME;
+
+ // Intent can be forwarded.
+ when(mIPm.canForwardTo(
+ any(Intent.class), anyString(), anyInt(), anyInt())).thenReturn(true);
+
+ // Managed profile does not exist.
+ List<UserInfo> profiles = new ArrayList<>();
+ profiles.add(CURRENT_USER_INFO);
+ when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+
+ // Create ACTION_SEND intent.
+ Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+ intent.setAction(Intent.ACTION_SEND);
+ IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+
+ assertNull(activity.mStartActivityIntent);
+ }
+
+ @Test
+ public void forwardToManagedProfile_canForward_chooserIntent() throws Exception {
+ sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME;
+
+ // Intent can be forwarded.
+ when(mIPm.canForwardTo(
+ any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);
+
+ // Manage profile exists.
+ List<UserInfo> profiles = new ArrayList<>();
+ profiles.add(CURRENT_USER_INFO);
+ profiles.add(MANAGED_PROFILE_INFO);
+ when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+
+ // Create chooser Intent
+ Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+ intent.setAction(Intent.ACTION_CHOOSER);
+ Intent sendIntent = new Intent(Intent.ACTION_SEND);
+ sendIntent.setComponent(new ComponentName("xx", "yyy"));
+ sendIntent.setType(TYPE_PLAIN_TEXT);
+ intent.putExtra(Intent.EXTRA_INTENT, sendIntent);
+ IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mIPm).canForwardTo(intentCaptor.capture(), eq(TYPE_PLAIN_TEXT), anyInt(), anyInt());
+ assertEquals(Intent.ACTION_SEND, intentCaptor.getValue().getAction());
+
+ assertNotNull(activity.mStartActivityIntent);
+ assertEquals(Intent.ACTION_CHOOSER, activity.mStartActivityIntent.getAction());
+ assertNull(activity.mStartActivityIntent.getPackage());
+ assertNull(activity.mStartActivityIntent.getComponent());
+
+ Intent innerIntent = activity.mStartActivityIntent.getParcelableExtra(Intent.EXTRA_INTENT);
+ assertNotNull(innerIntent);
+ assertEquals(Intent.ACTION_SEND, innerIntent.getAction());
+ assertNull(innerIntent.getComponent());
+ assertNull(innerIntent.getPackage());
+ assertEquals(CURRENT_USER_INFO.id, innerIntent.getContentUserHint());
+
+ assertEquals(MANAGED_PROFILE_INFO.id, activity.mUserIdActivityLaunchedIn);
+ }
+
+ @Test
+ public void forwardToManagedProfile_canForward_selectorIntent() throws Exception {
+ sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME;
+
+ // Intent can be forwarded.
+ when(mIPm.canForwardTo(
+ any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);
+
+ // Manage profile exists.
+ List<UserInfo> profiles = new ArrayList<>();
+ profiles.add(CURRENT_USER_INFO);
+ profiles.add(MANAGED_PROFILE_INFO);
+ when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+
+ // Create selector intent.
+ Intent intent = Intent.makeMainSelectorActivity(
+ Intent.ACTION_VIEW, Intent.CATEGORY_BROWSABLE);
+ IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mIPm).canForwardTo(
+ intentCaptor.capture(), nullable(String.class), anyInt(), anyInt());
+ assertEquals(Intent.ACTION_VIEW, intentCaptor.getValue().getAction());
+
+ assertNotNull(activity.mStartActivityIntent);
+ assertEquals(Intent.ACTION_MAIN, activity.mStartActivityIntent.getAction());
+ assertNull(activity.mStartActivityIntent.getPackage());
+ assertNull(activity.mStartActivityIntent.getComponent());
+ assertEquals(CURRENT_USER_INFO.id, activity.mStartActivityIntent.getContentUserHint());
+
+ Intent innerIntent = activity.mStartActivityIntent.getSelector();
+ assertNotNull(innerIntent);
+ assertEquals(Intent.ACTION_VIEW, innerIntent.getAction());
+ assertNull(innerIntent.getComponent());
+ assertNull(innerIntent.getPackage());
+
+ assertEquals(MANAGED_PROFILE_INFO.id, activity.mUserIdActivityLaunchedIn);
+ }
+
+
+ public static class IntentForwarderWrapperActivity extends IntentForwarderActivity {
+ private Intent mStartActivityIntent;
+ private int mUserIdActivityLaunchedIn;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ getIntent().setComponent(sComponentName);
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected Injector createInjector() {
+ return sInjector;
+ }
+
+ @Override
+ public void startActivityAsCaller(Intent intent, @Nullable Bundle options, boolean
+ ignoreTargetSecurity, int userId) {
+ mStartActivityIntent = intent;
+ mUserIdActivityLaunchedIn = userId;
+ }
+ }
+
+ class TestInjector implements IntentForwarderActivity.Injector {
+
+ @Override
+ public IPackageManager getIPackageManager() {
+ return mIPm;
+ }
+
+ @Override
+ public UserManager getUserManager() {
+ return mUserManager;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mPm;
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarContainerTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarContainerTest.java
index 1471796..912b7ec 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarContainerTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarContainerTest.java
@@ -60,11 +60,9 @@
TestViewGroup viewGroup = new TestViewGroup(mContext);
viewGroup.addView(mActionBarContainer);
- ActionMode mode = mActionBarContainer.startActionModeForChild(
- null, null, ActionMode.TYPE_FLOATING);
+ mActionBarContainer.startActionModeForChild(null, null, ActionMode.TYPE_FLOATING);
// Should bubble up.
- assertNotNull(mode);
assertTrue(viewGroup.isStartActionModeForChildTypedCalled);
}
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
new file mode 100644
index 0000000..f73950a
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.UserHandle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LockPatternUtilsTest {
+
+ @Test
+ public void testUserFrp_isNotRegularUser() throws Exception {
+ assertTrue(LockPatternUtils.USER_FRP < 0);
+ }
+
+ @Test
+ public void testUserFrp_isNotAReservedSpecialUser() throws Exception {
+ assertNotEquals(UserHandle.USER_NULL, LockPatternUtils.USER_FRP);
+ assertNotEquals(UserHandle.USER_ALL, LockPatternUtils.USER_FRP);
+ assertNotEquals(UserHandle.USER_CURRENT, LockPatternUtils.USER_FRP);
+ assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_FRP);
+ }
+}
diff --git a/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/Android.mk b/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/Android.mk
index c70c1d3..5a545e5 100644
--- a/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/Android.mk
@@ -22,6 +22,8 @@
LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := junit
+
LOCAL_PACKAGE_NAME := ExternalLocAllPermsTestApp
include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk
index 7946e1a..b35cbae 100644
--- a/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk
@@ -20,6 +20,8 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+
LOCAL_SDK_VERSION := current
LOCAL_PACKAGE_NAME := ExternalSharedPermsTestApp
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk
index 657d0a4..06812b5 100644
--- a/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk
@@ -20,6 +20,8 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+
LOCAL_SDK_VERSION := current
LOCAL_PACKAGE_NAME := ExternalSharedPermsBTTestApp
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk
index d4450dc..48bceaf 100644
--- a/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk
@@ -20,6 +20,8 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+
LOCAL_SDK_VERSION := current
LOCAL_PACKAGE_NAME := ExternalSharedPermsDiffKeyTestApp
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk
index 8154862..569d102 100644
--- a/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk
@@ -20,6 +20,8 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+
LOCAL_SDK_VERSION := current
LOCAL_PACKAGE_NAME := ExternalSharedPermsFLTestApp
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
index 14b032e..c66a10c 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
@@ -18,7 +18,7 @@
## The application with a minimal main dex
include $(CLEAR_VARS)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex android-support-multidex-instrumentation android-support-test
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -35,16 +35,11 @@
$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
-LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.preprocessor=true\
- -D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
-#################################
-include $(BUILD_SYSTEM)/configure_local_jack.mk
-#################################
+LOCAL_MIN_SDK_VERSION := 8
-ifdef LOCAL_JACK_ENABLED
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
-endif
+LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex \
+ -D jack.dex.output.multidex.legacy=true
include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml
index 7fff711..98d8f27 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml
@@ -22,7 +22,15 @@
<uses-library android:name="android.test.runner" />
</application>
- <instrumentation android:name="android.test.InstrumentationTestRunner"
+ <instrumentation android:name="com.android.test.runner.MultiDexTestRunner"
+ android:targetPackage="com.android.multidexlegacyandexception"
+ android:label="Test for MultiDexLegacyAndException" />
+
+ <instrumentation android:name="com.android.multidexlegacyandexception.tests.MultiDexAndroidJUnitRunner"
+ android:targetPackage="com.android.multidexlegacyandexception"
+ android:label="Test for MultiDexLegacyAndException" />
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.multidexlegacyandexception"
android:label="Test for MultiDexLegacyAndException" />
</manifest>
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java
deleted file mode 100644
index 8d065a4..0000000
--- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2014 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.multidexlegacyandexception;
-
-/**
- * Run the tests with: <code>adb shell am instrument -w
- com.android.multidexlegacyandexception/android.test.InstrumentationTestRunner
-</code>
- */
-@SuppressWarnings("deprecation")
-public class Test extends android.test.ActivityInstrumentationTestCase2<MainActivity> {
- public Test() {
- super(MainActivity.class);
- }
-
- public void testExceptionInMainDex() {
- assertEquals(10, TestApplication.get(true));
- }
-
- public void testExceptionInSecondaryDex() {
- assertEquals(10, getActivity().get1(true));
- assertEquals(11, getActivity().get2(true));
- }
-
- public void testExceptionInIntermediate() {
- assertEquals(11, IntermediateClass.get3(true));
- assertEquals(11, MiniIntermediateClass.get3(true));
- assertEquals(11, IntermediateClass.get4(true));
- assertEquals(1, IntermediateClass.get5(null));
- assertEquals(10, IntermediateClass.get5(new ExceptionInMainDex()));
- assertEquals(11, IntermediateClass.get5(new ExceptionInSecondaryDex()));
- assertEquals(12, IntermediateClass.get5(new ExceptionInMainDex2()));
- assertEquals(13, IntermediateClass.get5(new ExceptionInSecondaryDex2()));
- assertEquals(14, IntermediateClass.get5(new OutOfMemoryError()));
- assertEquals(17, IntermediateClass.get5(new CaughtOnlyException()));
- assertEquals(39, IntermediateClass.get5(new ExceptionInSecondaryDexWithSuperInMain()));
- assertEquals(23, IntermediateClass.get5(new SuperExceptionInSecondaryDex()));
- assertEquals(23, IntermediateClass.get5(new SuperExceptionInMainDex()));
- assertEquals(23, IntermediateClass.get5(new CaughtOnlyByIntermediateException()));
- assertEquals(37, IntermediateClass.get5(new ArrayIndexOutOfBoundsException()));
- }
-
-}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/ActivityTest.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/ActivityTest.java
new file mode 100644
index 0000000..d6ac3e5
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/ActivityTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014 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.multidexlegacyandexception.tests;
+
+import com.android.multidexlegacyandexception.CaughtOnlyByIntermediateException;
+import com.android.multidexlegacyandexception.CaughtOnlyException;
+import com.android.multidexlegacyandexception.ExceptionInMainDex;
+import com.android.multidexlegacyandexception.ExceptionInMainDex2;
+import com.android.multidexlegacyandexception.ExceptionInSecondaryDex;
+import com.android.multidexlegacyandexception.ExceptionInSecondaryDex2;
+import com.android.multidexlegacyandexception.ExceptionInSecondaryDexWithSuperInMain;
+import com.android.multidexlegacyandexception.IntermediateClass;
+import com.android.multidexlegacyandexception.MainActivity;
+import com.android.multidexlegacyandexception.MiniIntermediateClass;
+import com.android.multidexlegacyandexception.SuperExceptionInMainDex;
+import com.android.multidexlegacyandexception.SuperExceptionInSecondaryDex;
+import com.android.multidexlegacyandexception.TestApplication;
+
+/**
+ * Run the tests with:
+ * <code>
+adb shell am instrument -w com.android.multidexlegacyandexception/com.android.multidexlegacyandexception.tests.MultiDexAndroidJUnitRunner
+</code>
+ */
+@SuppressWarnings("deprecation")
+public class ActivityTest extends android.test.ActivityInstrumentationTestCase2<MainActivity> {
+ public ActivityTest() {
+ super(MainActivity.class);
+ }
+
+ public void testExceptionInMainDex() {
+ assertEquals(10, TestApplication.get(true));
+ }
+
+ public void testExceptionInSecondaryDex() {
+ assertEquals(10, getActivity().get1(true));
+ assertEquals(11, getActivity().get2(true));
+ }
+
+ public void testExceptionInIntermediate() {
+ assertEquals(11, IntermediateClass.get3(true));
+ assertEquals(11, MiniIntermediateClass.get3(true));
+ assertEquals(11, IntermediateClass.get4(true));
+ assertEquals(1, IntermediateClass.get5(null));
+ assertEquals(10, IntermediateClass.get5(new ExceptionInMainDex()));
+ assertEquals(11, IntermediateClass.get5(new ExceptionInSecondaryDex()));
+ assertEquals(12, IntermediateClass.get5(new ExceptionInMainDex2()));
+ assertEquals(13, IntermediateClass.get5(new ExceptionInSecondaryDex2()));
+ assertEquals(14, IntermediateClass.get5(new OutOfMemoryError()));
+ assertEquals(17, IntermediateClass.get5(new CaughtOnlyException()));
+ assertEquals(39, IntermediateClass.get5(new ExceptionInSecondaryDexWithSuperInMain()));
+ assertEquals(23, IntermediateClass.get5(new SuperExceptionInSecondaryDex()));
+ assertEquals(23, IntermediateClass.get5(new SuperExceptionInMainDex()));
+ assertEquals(23, IntermediateClass.get5(new CaughtOnlyByIntermediateException()));
+ assertEquals(37, IntermediateClass.get5(new ArrayIndexOutOfBoundsException()));
+ }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/MultiDexAndroidJUnitRunner.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/MultiDexAndroidJUnitRunner.java
new file mode 100644
index 0000000..758ac1d
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/MultiDexAndroidJUnitRunner.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.multidexlegacyandexception.tests;
+
+import android.os.Bundle;
+import android.support.multidex.MultiDex;
+import android.support.test.runner.AndroidJUnitRunner;
+
+public class MultiDexAndroidJUnitRunner extends AndroidJUnitRunner {
+
+ @Override
+ public void onCreate(Bundle arguments) {
+ MultiDex.installInstrumentation(getContext(), getTargetContext());
+ super.onCreate(arguments);
+ }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivityIntermediate.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivityIntermediate.java
new file mode 100644
index 0000000..3a8acc6
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivityIntermediate.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.multidexlegacyandexception.tests;
+
+public class NoActivityIntermediate {
+
+ public static int get() {
+ return NoActivitySubTest.get();
+ }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivityJUnit3Test.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivityJUnit3Test.java
new file mode 100644
index 0000000..31cb939
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivityJUnit3Test.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2014 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.multidexlegacyandexception.tests;
+
+import android.test.InstrumentationTestCase;
+import com.android.multidexlegacyandexception.CaughtOnlyByIntermediateException;
+import com.android.multidexlegacyandexception.CaughtOnlyException;
+import com.android.multidexlegacyandexception.ExceptionInMainDex;
+import com.android.multidexlegacyandexception.ExceptionInMainDex2;
+import com.android.multidexlegacyandexception.ExceptionInSecondaryDex;
+import com.android.multidexlegacyandexception.ExceptionInSecondaryDex2;
+import com.android.multidexlegacyandexception.ExceptionInSecondaryDexWithSuperInMain;
+import com.android.multidexlegacyandexception.IntermediateClass;
+import com.android.multidexlegacyandexception.MiniIntermediateClass;
+import com.android.multidexlegacyandexception.SuperExceptionInMainDex;
+import com.android.multidexlegacyandexception.SuperExceptionInSecondaryDex;
+import com.android.multidexlegacyandexception.TestApplication;
+import junit.framework.Assert;
+
+/**
+ * Run the tests with: <code>
+adb shell am instrument -w com.android.multidexlegacyandexception/com.android.test.runner.MultiDexTestRunner
+</code>
+ * or <code>
+adb shell am instrument -w com.android.multidexlegacyandexception/com.android.multidexlegacyandexception.tests.MultiDexAndroidJUnitRunner
+</code>
+ */
+@SuppressWarnings("deprecation")
+public class NoActivityJUnit3Test extends InstrumentationTestCase {
+
+ @org.junit.Test
+ public void testExceptionInMainDex() {
+ Assert.assertEquals(10, TestApplication.get(true));
+ }
+
+ @org.junit.Test
+ public void testExceptionInIntermediate() {
+ Assert.assertEquals(11, IntermediateClass.get3(true));
+ Assert.assertEquals(11, MiniIntermediateClass.get3(true));
+ Assert.assertEquals(11, IntermediateClass.get4(true));
+ Assert.assertEquals(1, IntermediateClass.get5(null));
+ Assert.assertEquals(10, IntermediateClass.get5(new ExceptionInMainDex()));
+ Assert.assertEquals(11, IntermediateClass.get5(new ExceptionInSecondaryDex()));
+ Assert.assertEquals(12, IntermediateClass.get5(new ExceptionInMainDex2()));
+ Assert.assertEquals(13, IntermediateClass.get5(new ExceptionInSecondaryDex2()));
+ Assert.assertEquals(14, IntermediateClass.get5(new OutOfMemoryError()));
+ Assert.assertEquals(17, IntermediateClass.get5(new CaughtOnlyException()));
+ Assert.assertEquals(39, IntermediateClass.get5(new ExceptionInSecondaryDexWithSuperInMain()));
+ Assert.assertEquals(23, IntermediateClass.get5(new SuperExceptionInSecondaryDex()));
+ Assert.assertEquals(23, IntermediateClass.get5(new SuperExceptionInMainDex()));
+ Assert.assertEquals(23, IntermediateClass.get5(new CaughtOnlyByIntermediateException()));
+ Assert.assertEquals(37, IntermediateClass.get5(new ArrayIndexOutOfBoundsException()));
+ }
+
+ @org.junit.Test
+ public void testMultidexedTest() {
+ Assert.assertEquals(7, NoActivityIntermediate.get());
+ }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivityJUnit4Test.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivityJUnit4Test.java
new file mode 100644
index 0000000..94a5b7f
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivityJUnit4Test.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.multidexlegacyandexception.tests;
+
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+
+/**
+ * Run the tests with: <code>
+adb shell am instrument -w com.android.multidexlegacyandexception/com.android.test.runner.MultiDexTestRunner
+</code>
+ * or <code>
+adb shell am instrument -w com.android.multidexlegacyandexception/com.android.multidexlegacyandexception.tests.MultiDexAndroidJUnitRunner
+</code>
+ */
+@RunWith(AndroidJUnit4.class)
+public class NoActivityJUnit4Test extends NoActivityJUnit3Test {
+
+ @org.junit.Test
+ public void multidexedTestJUnit4() {
+ super.testMultidexedTest();
+ }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivitySubTest.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivitySubTest.java
new file mode 100644
index 0000000..4885c2e
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivitySubTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.multidexlegacyandexception.tests;
+
+public class NoActivitySubTest {
+
+ public static int get() {
+ return 7;
+ }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/test.jpp b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/test.jpp
deleted file mode 100644
index 0d027ed..0000000
--- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/test.jpp
+++ /dev/null
@@ -1,3 +0,0 @@
-test:
- @@com.android.jack.annotations.ForceInMainDex
- class com.android.multidexlegacyandexception.Test
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk
index 208eceb..2784193 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk
@@ -44,6 +44,8 @@
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
endif
+LOCAL_MIN_SDK_VERSION := 8
+
include $(BUILD_PACKAGE)
ifndef LOCAL_JACK_ENABLED
@@ -85,6 +87,8 @@
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
endif
+LOCAL_MIN_SDK_VERSION := 8
+
include $(BUILD_PACKAGE)
ifndef LOCAL_JACK_ENABLED
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/Android.mk
new file mode 100644
index 0000000..69ec5ce
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/Android.mk
@@ -0,0 +1,40 @@
+# Copyright (C) 2014 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)
+
+
+## The application with a minimal main dex
+include $(CLEAR_VARS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := 8
+
+LOCAL_PACKAGE_NAME := MultiDexLegacyTestAppTests
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_JAVACFLAGS := -nowarn
+
+LOCAL_MIN_SDK_VERSION := 8
+
+LOCAL_INSTRUMENTATION_FOR := MultiDexLegacyTestApp
+
+LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.dex.output.multidex.legacy=true
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/AndroidManifest.xml
new file mode 100644
index 0000000..35369c7
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.multidexlegacytestapp.test"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="8" />
+ <instrumentation
+ android:name="com.android.test.runner.MultiDexTestRunner"
+ android:targetPackage="com.android.multidexlegacytestapp" />
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.multidexlegacytestapp" />
+
+ <application
+ android:label="multidexlegacytestapp.test" >
+ <uses-library android:name="android.test.runner" />
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/src/com/android/multidexlegacytestapp/test/InstrumentationTest.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/src/com/android/multidexlegacytestapp/test/InstrumentationTest.java
new file mode 100644
index 0000000..a0288f2
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/src/com/android/multidexlegacytestapp/test/InstrumentationTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 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.multidexlegacytestapp.test;
+
+import android.test.InstrumentationTestCase;
+import com.android.multidexlegacytestapp.manymethods.Big001;
+import com.android.multidexlegacytestapp.manymethods.Big079;
+import junit.framework.Assert;
+
+/**
+ * Run the tests with: <code>
+adb shell am instrument -w com.android.multidexlegacytestapp.test/com.android.test.runner.MultiDexTestRunner
+</code>
+ */
+public class InstrumentationTest extends InstrumentationTestCase {
+
+ public void testExceptionInMainDex() {
+ Assert.assertEquals(0, new Big001().get0());
+ Assert.assertEquals(100, new Big079().get100());
+ }
+
+ public void testMultidexedTest() {
+ Assert.assertEquals(7, Intermediate.get());
+ }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/src/com/android/multidexlegacytestapp/test/Intermediate.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/src/com/android/multidexlegacytestapp/test/Intermediate.java
new file mode 100644
index 0000000..9a79c7a
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/src/com/android/multidexlegacytestapp/test/Intermediate.java
@@ -0,0 +1,9 @@
+package com.android.multidexlegacytestapp.test;
+
+public class Intermediate {
+
+ public static int get() {
+ return SubTest.get();
+ }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/src/com/android/multidexlegacytestapp/test/SubTest.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/src/com/android/multidexlegacytestapp/test/SubTest.java
new file mode 100644
index 0000000..e28584b
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/src/com/android/multidexlegacytestapp/test/SubTest.java
@@ -0,0 +1,11 @@
+package com.android.multidexlegacytestapp.test;
+
+import com.android.multidexlegacytestapp.manymethods.Big080;
+
+public class SubTest {
+
+ public static int get() {
+ return new Big080().get7();
+ }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk
new file mode 100644
index 0000000..f2bd353
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk
@@ -0,0 +1,63 @@
+# Copyright (C) 2014 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)
+
+
+## The tests with only one dex
+include $(CLEAR_VARS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation android-support-test
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := MultiDexLegacyTestAppTests2
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_JAVACFLAGS := -nowarn
+
+LOCAL_MIN_SDK_VERSION := 8
+
+LOCAL_INSTRUMENTATION_FOR := MultiDexLegacyTestApp
+
+include $(BUILD_PACKAGE)
+
+
+## The tests with a minimal main dex
+include $(CLEAR_VARS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation android-support-test
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := 8
+
+LOCAL_PACKAGE_NAME := MultiDexLegacyTestAppTests2-multidex
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_JAVACFLAGS := -nowarn
+
+LOCAL_MIN_SDK_VERSION := 8
+
+LOCAL_INSTRUMENTATION_FOR := MultiDexLegacyTestApp
+
+LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.dex.output.multidex.legacy=true
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/AndroidManifest.xml
new file mode 100644
index 0000000..1cadfcd
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.multidexlegacytestapp.test2"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="8" />
+ <instrumentation
+ android:name="com.android.multidexlegacytestapp.test2.MultiDexAndroidJUnitRunner"
+ android:targetPackage="com.android.multidexlegacytestapp" />
+
+ <application
+ android:label="multidexlegacytestapp.test" >
+ <uses-library android:name="android.test.runner" />
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java
new file mode 100644
index 0000000..4e6ec14
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 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.multidexlegacytestapp.test2;
+
+import android.support.test.runner.AndroidJUnit4;
+import com.android.multidexlegacytestapp.manymethods.Big001;
+import com.android.multidexlegacytestapp.manymethods.Big079;
+import junit.framework.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Run the tests with: <code>adb shell am instrument -w
+ com.android.multidexlegacytestapp.test2/com.android.multidexlegacytestapp.test2.MultiDexAndroidJUnitRunner
+</code>
+ */
+@RunWith(AndroidJUnit4.class)
+public class InstrumentationTest {
+
+ @Test
+ public void exceptionInMainDex() {
+ Assert.assertEquals(0, new Big001().get0());
+ Assert.assertEquals(100, new Big079().get100());
+ }
+
+ @Test
+ public void multidexedTest() {
+ Assert.assertEquals(7, Intermediate.get());
+ }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/Intermediate.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/Intermediate.java
new file mode 100644
index 0000000..3e5e81e
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/Intermediate.java
@@ -0,0 +1,9 @@
+package com.android.multidexlegacytestapp.test2;
+
+public class Intermediate {
+
+ public static int get() {
+ return SubTest.get();
+ }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java
new file mode 100644
index 0000000..963f904
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java
@@ -0,0 +1,15 @@
+package com.android.multidexlegacytestapp.test2;
+
+import android.os.Bundle;
+import android.support.multidex.MultiDex;
+import android.support.test.runner.AndroidJUnitRunner;
+
+public class MultiDexAndroidJUnitRunner extends AndroidJUnitRunner {
+
+ @Override
+ public void onCreate(Bundle arguments) {
+ MultiDex.installInstrumentation(getContext(), getTargetContext());
+ super.onCreate(arguments);
+ }
+
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/SubTest.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/SubTest.java
new file mode 100644
index 0000000..acff80f
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/SubTest.java
@@ -0,0 +1,11 @@
+package com.android.multidexlegacytestapp.test2;
+
+import com.android.multidexlegacytestapp.manymethods.Big080;
+
+public class SubTest {
+
+ public static int get() {
+ return new Big080().get7();
+ }
+
+}
diff --git a/core/tests/utiltests/Android.mk b/core/tests/utiltests/Android.mk
index a4b0916..f2e02e3 100644
--- a/core/tests/utiltests/Android.mk
+++ b/core/tests/utiltests/Android.mk
@@ -17,7 +17,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
frameworks-base-testutils \
- mockito-target-minus-junit4
+ mockito-target-minus-junit4 \
+ legacy-android-tests
LOCAL_JAVA_LIBRARIES := android.test.runner
@@ -28,3 +29,4 @@
LOCAL_COMPATIBILITY_SUITE := device-tests
include $(BUILD_PACKAGE)
+
diff --git a/core/tests/utiltests/AndroidTest.xml b/core/tests/utiltests/AndroidTest.xml
index 9ebf568..65bb8de 100644
--- a/core/tests/utiltests/AndroidTest.xml
+++ b/core/tests/utiltests/AndroidTest.xml
@@ -20,7 +20,7 @@
<option name="test-suite-tag" value="apct" />
<option name="test-tag" value="FrameworksUtilTests" />
- <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.frameworks.utiltests" />
<option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
</test>
diff --git a/core/tests/utiltests/runtests.sh b/core/tests/utiltests/runtests.sh
new file mode 100755
index 0000000..853119f
--- /dev/null
+++ b/core/tests/utiltests/runtests.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+if [ -z $ANDROID_BUILD_TOP ]; then
+ echo "You need to source and lunch before you can use this script"
+ exit 1
+fi
+
+echo "Running tests"
+
+set -e # fail early
+
+echo "+ mmma -j32 $ANDROID_BUILD_TOP/frameworks/base/core/tests/utiltests"
+# NOTE Don't actually run the command above since this shell doesn't inherit functions from the
+# caller.
+make -j32 -C $ANDROID_BUILD_TOP -f build/core/main.mk MODULES-IN-frameworks-base-core-tests-utiltests
+
+set -x # print commands
+
+adb root
+adb wait-for-device
+
+adb install -r -g "$OUT/data/app/FrameworksUtilTests/FrameworksUtilTests.apk"
+
+adb shell am instrument -w "$@" 'com.android.frameworks.utiltests/android.support.test.runner.AndroidJUnitRunner'
diff --git a/core/tests/utiltests/src/android/util/IntArrayTest.java b/core/tests/utiltests/src/android/util/IntArrayTest.java
new file mode 100644
index 0000000..a6120a1
--- /dev/null
+++ b/core/tests/utiltests/src/android/util/IntArrayTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IntArrayTest {
+
+ @Test
+ public void testIntArray() {
+ IntArray a = new IntArray();
+ a.add(1);
+ a.add(2);
+ a.add(3);
+ verify(new int[]{1, 2, 3}, a);
+
+ IntArray b = IntArray.fromArray(new int[]{4, 5, 6, 7, 8}, 3);
+ a.addAll(b);
+ verify(new int[]{1, 2, 3, 4, 5, 6}, a);
+
+ a.resize(2);
+ verify(new int[]{1, 2}, a);
+
+ a.resize(8);
+ verify(new int[]{1, 2, 0, 0, 0, 0, 0, 0}, a);
+
+ a.set(5, 10);
+ verify(new int[]{1, 2, 0, 0, 0, 10, 0, 0}, a);
+
+ a.add(5, 20);
+ assertEquals(20, a.get(5));
+ assertEquals(5, a.indexOf(20));
+ verify(new int[]{1, 2, 0, 0, 0, 20, 10, 0, 0}, a);
+
+ assertEquals(-1, a.indexOf(99));
+
+ a.resize(15);
+ a.set(14, 30);
+ verify(new int[]{1, 2, 0, 0, 0, 20, 10, 0, 0, 0, 0, 0, 0, 0, 30}, a);
+
+ int[] backingArray = new int[]{1, 2, 3, 4};
+ a = IntArray.wrap(backingArray);
+ a.set(0, 10);
+ assertEquals(10, backingArray[0]);
+ backingArray[1] = 20;
+ backingArray[2] = 30;
+ verify(backingArray, a);
+ assertEquals(2, a.indexOf(30));
+
+ a.resize(2);
+ assertEquals(0, backingArray[2]);
+ assertEquals(0, backingArray[3]);
+
+ a.add(50);
+ verify(new int[]{10, 20, 50}, a);
+ }
+
+ public void verify(int[] expected, IntArray intArray) {
+ assertEquals(expected.length, intArray.size());
+ assertArrayEquals(expected, intArray.toArray());
+ }
+}
diff --git a/core/tests/utiltests/src/android/util/LongArrayTest.java b/core/tests/utiltests/src/android/util/LongArrayTest.java
new file mode 100644
index 0000000..a7afcbd
--- /dev/null
+++ b/core/tests/utiltests/src/android/util/LongArrayTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LongArrayTest {
+
+ @Test
+ public void testLongArray() {
+ LongArray a = new LongArray();
+ a.add(1);
+ a.add(2);
+ a.add(3);
+ verify(new long[]{1, 2, 3}, a);
+
+ LongArray b = LongArray.fromArray(new long[]{4, 5, 6, 7, 8}, 3);
+ a.addAll(b);
+ verify(new long[]{1, 2, 3, 4, 5, 6}, a);
+
+ a.resize(2);
+ verify(new long[]{1, 2}, a);
+
+ a.resize(8);
+ verify(new long[]{1, 2, 0, 0, 0, 0, 0, 0}, a);
+
+ a.set(5, 10);
+ verify(new long[]{1, 2, 0, 0, 0, 10, 0, 0}, a);
+
+ a.add(5, 20);
+ assertEquals(20, a.get(5));
+ assertEquals(5, a.indexOf(20));
+ verify(new long[]{1, 2, 0, 0, 0, 20, 10, 0, 0}, a);
+
+ assertEquals(-1, a.indexOf(99));
+
+ a.resize(15);
+ a.set(14, 30);
+ verify(new long[]{1, 2, 0, 0, 0, 20, 10, 0, 0, 0, 0, 0, 0, 0, 30}, a);
+
+ long[] backingArray = new long[]{1, 2, 3, 4};
+ a = LongArray.wrap(backingArray);
+ a.set(0, 10);
+ assertEquals(10, backingArray[0]);
+ backingArray[1] = 20;
+ backingArray[2] = 30;
+ verify(backingArray, a);
+ assertEquals(2, a.indexOf(30));
+
+ a.resize(2);
+ assertEquals(0, backingArray[2]);
+ assertEquals(0, backingArray[3]);
+
+ a.add(50);
+ verify(new long[]{10, 20, 50}, a);
+ }
+
+ public void verify(long[] expected, LongArray longArrays) {
+ assertEquals(expected.length, longArrays.size());
+ assertArrayEquals(expected, longArrays.toArray());
+ }
+}
diff --git a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
index d29b572..eb2a516 100644
--- a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
@@ -80,6 +80,66 @@
}
/**
+ * Tests {@link StateMachine#toString()}.
+ */
+ class StateMachineToStringTest extends StateMachine {
+ StateMachineToStringTest(String name) {
+ super(name);
+ }
+ }
+
+ class ExampleState extends State {
+ String mName;
+
+ ExampleState(String name) {
+ mName = name;
+ }
+
+ @Override
+ public String getName() {
+ return mName;
+ }
+ }
+
+ @SmallTest
+ public void testToStringSucceedsEvenIfMachineHasNoStates() throws Exception {
+ StateMachine stateMachine = new StateMachineToStringTest("TestStateMachine");
+ assertTrue(stateMachine.toString().contains("TestStateMachine"));
+ }
+
+ @SmallTest
+ public void testToStringSucceedsEvenIfStateHasNoName() throws Exception {
+ StateMachine stateMachine = new StateMachineToStringTest("TestStateMachine");
+ State exampleState = new ExampleState(null);
+ stateMachine.addState(exampleState);
+ stateMachine.setInitialState(exampleState);
+ stateMachine.start();
+ assertTrue(stateMachine.toString().contains("TestStateMachine"));
+ assertTrue(stateMachine.toString().contains("(null)"));
+ }
+
+ @SmallTest
+ public void testToStringIncludesMachineAndStateNames() throws Exception {
+ StateMachine stateMachine = new StateMachineToStringTest("TestStateMachine");
+ State exampleState = new ExampleState("exampleState");
+ stateMachine.addState(exampleState);
+ stateMachine.setInitialState(exampleState);
+ stateMachine.start();
+ assertTrue(stateMachine.toString().contains("TestStateMachine"));
+ assertTrue(stateMachine.toString().contains("exampleState"));
+ }
+
+ @SmallTest
+ public void testToStringDoesNotContainMultipleLines() throws Exception {
+ StateMachine stateMachine = new StateMachineToStringTest("TestStateMachine");
+ State exampleState = new ExampleState("exampleState");
+ stateMachine.addState(exampleState);
+ stateMachine.setInitialState(exampleState);
+ stateMachine.start();
+ assertFalse(stateMachine.toString().contains("\n"));
+ }
+
+ /**
* Tests {@link StateMachine#quit()}.
*/
class StateMachineQuitTest extends StateMachine {
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 344f3c8..19d2a31 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -45,6 +45,7 @@
<permission name="android.permission.BLUETOOTH_STACK" >
<group gid="bluetooth" />
<group gid="wakelock" />
+ <group gid="uhid" />
</permission>
<permission name="android.permission.NET_TUNNELING" >
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index efed165..8bfb6f3 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -162,6 +162,7 @@
<permission name="android.permission.UPDATE_DEVICE_STATS"/>
<permission name="android.permission.UPDATE_LOCK"/>
<permission name="android.permission.WRITE_APN_SETTINGS"/>
+ <permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
<permission name="com.android.voicemail.permission.READ_VOICEMAIL"/>
<permission name="com.android.voicemail.permission.WRITE_VOICEMAIL"/>
@@ -229,6 +230,7 @@
<permission name="android.permission.MANAGE_FINGERPRINT"/>
<permission name="android.permission.MANAGE_USB"/>
<permission name="android.permission.MANAGE_USERS"/>
+ <permission name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" />
<permission name="android.permission.MASTER_CLEAR"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
@@ -313,6 +315,7 @@
<permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
<permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
+ <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
<permission name="android.permission.CONNECTIVITY_INTERNAL"/>
<permission name="android.permission.CONTROL_VPN"/>
<permission name="android.permission.DUMP"/>
diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk
index 5baa75b..7b2fa76 100644
--- a/data/fonts/Android.mk
+++ b/data/fonts/Android.mk
@@ -81,6 +81,33 @@
build-one-font-module :=
font_src_files :=
+################################
+# Copies the font configuration file into system/etc for the product as fonts.xml.
+# In the case where $(ADDITIONAL_FONTS_FILE) is defined, the content of $(ADDITIONAL_FONTS_FILE)
+# is added to the $(AOSP_FONTS_FILE).
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := fonts.xml
+LOCAL_MODULE_CLASS := ETC
+
+AOSP_FONTS_FILE := frameworks/base/data/fonts/fonts.xml
+
+ifdef ADDITIONAL_FONTS_FILE
+ADDITIONAL_FONTS_SCRIPT := frameworks/base/tools/fonts/add_additional_fonts.py
+ADD_ADDITIONAL_FONTS := $(local-generated-sources-dir)/fonts.xml
+
+$(ADD_ADDITIONAL_FONTS): PRIVATE_SCRIPT := $(ADDITIONAL_FONTS_SCRIPT)
+$(ADD_ADDITIONAL_FONTS): PRIVATE_ADDITIONAL_FONTS_FILE := $(ADDITIONAL_FONTS_FILE)
+$(ADD_ADDITIONAL_FONTS): $(ADDITIONAL_FONTS_SCRIPT) $(AOSP_FONTS_FILE) $(ADDITIONAL_FONTS_FILE)
+ rm -f $@
+ python $(PRIVATE_SCRIPT) $@ $(PRIVATE_ADDITIONAL_FONTS_FILE)
+else
+ADD_ADDITIONAL_FONTS := $(AOSP_FONTS_FILE)
+endif
+
+LOCAL_PREBUILT_MODULE_FILE := $(ADD_ADDITIONAL_FONTS)
+
+include $(BUILD_PREBUILT)
# Run sanity tests on fonts on checkbuild
checkbuild: fontchain_lint
diff --git a/data/fonts/fonts.mk b/data/fonts/fonts.mk
index 23c54ae..e884f2f 100644
--- a/data/fonts/fonts.mk
+++ b/data/fonts/fonts.mk
@@ -14,9 +14,7 @@
# Warning: this is actually a product definition, to be inherited from
-PRODUCT_COPY_FILES := \
- frameworks/base/data/fonts/fonts.xml:$(TARGET_COPY_OUT_SYSTEM)/etc/fonts.xml
-
PRODUCT_PACKAGES := \
DroidSansMono.ttf \
- AndroidClock.ttf
+ AndroidClock.ttf \
+ fonts.xml
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 318353f..ae93608 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -51,10 +51,13 @@
<font weight="300" style="italic">RobotoCondensed-LightItalic.ttf</font>
<font weight="400" style="normal">RobotoCondensed-Regular.ttf</font>
<font weight="400" style="italic">RobotoCondensed-Italic.ttf</font>
+ <font weight="500" style="normal">RobotoCondensed-Medium.ttf</font>
+ <font weight="500" style="italic">RobotoCondensed-MediumItalic.ttf</font>
<font weight="700" style="normal">RobotoCondensed-Bold.ttf</font>
<font weight="700" style="italic">RobotoCondensed-BoldItalic.ttf</font>
</family>
<alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
+ <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
<family name="serif">
<font weight="400" style="normal">NotoSerif-Regular.ttf</font>
diff --git a/data/sounds/AudioTv.mk b/data/sounds/AudioTv.mk
deleted file mode 100644
index ee37cb9..0000000
--- a/data/sounds/AudioTv.mk
+++ /dev/null
@@ -1,125 +0,0 @@
-# Copyright 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := frameworks/base/data/sounds
-
-PRODUCT_COPY_FILES += \
- $(LOCAL_PATH)/Alarm_Beep_01.ogg:system/media/audio/alarms/Alarm_Beep_01.ogg \
- $(LOCAL_PATH)/Alarm_Beep_02.ogg:system/media/audio/alarms/Alarm_Beep_02.ogg \
- $(LOCAL_PATH)/Alarm_Beep_03.ogg:system/media/audio/alarms/Alarm_Beep_03.ogg \
- $(LOCAL_PATH)/Alarm_Buzzer.ogg:system/media/audio/alarms/Alarm_Buzzer.ogg \
- $(LOCAL_PATH)/Alarm_Classic.ogg:system/media/audio/alarms/Alarm_Classic.ogg \
- $(LOCAL_PATH)/Alarm_Rooster_02.ogg:system/media/audio/alarms/Alarm_Rooster_02.ogg \
- $(LOCAL_PATH)/alarms/ogg/Argon.ogg:system/media/audio/alarms/Argon.ogg \
- $(LOCAL_PATH)/alarms/ogg/Barium.ogg:system/media/audio/alarms/Barium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Carbon.ogg:system/media/audio/alarms/Carbon.ogg \
- $(LOCAL_PATH)/alarms/ogg/Cesium.ogg:system/media/audio/alarms/Cesium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Fermium.ogg:system/media/audio/alarms/Fermium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Hassium.ogg:system/media/audio/alarms/Hassium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Helium.ogg:system/media/audio/alarms/Helium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Krypton.ogg:system/media/audio/alarms/Krypton.ogg \
- $(LOCAL_PATH)/alarms/ogg/Neon.ogg:system/media/audio/alarms/Neon.ogg \
- $(LOCAL_PATH)/alarms/ogg/Neptunium.ogg:system/media/audio/alarms/Neptunium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Nobelium.ogg:system/media/audio/alarms/Nobelium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:system/media/audio/alarms/Osmium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Oxygen.ogg:system/media/audio/alarms/Oxygen.ogg \
- $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \
- $(LOCAL_PATH)/alarms/ogg/Plutonium.ogg:system/media/audio/alarms/Plutonium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Promethium.ogg:system/media/audio/alarms/Promethium.ogg \
- $(LOCAL_PATH)/alarms/ogg/Scandium.ogg:system/media/audio/alarms/Scandium.ogg \
- $(LOCAL_PATH)/effects/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
- $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
- $(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
- $(LOCAL_PATH)/effects/ogg/Effect_Tick_48k.ogg:system/media/audio/ui/Effect_Tick.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressDelete_120_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressInvalid_120_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressReturn_120_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \
- $(LOCAL_PATH)/effects/ogg/KeypressStandard_120_48k.ogg:system/media/audio/ui/KeypressStandard.ogg \
- $(LOCAL_PATH)/effects/ogg/Lock.ogg:system/media/audio/ui/Lock.ogg \
- $(LOCAL_PATH)/effects/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
- $(LOCAL_PATH)/effects/ogg/Trusted_48k.ogg:system/media/audio/ui/Trusted.ogg \
- $(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \
- $(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \
- $(LOCAL_PATH)/effects/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
- $(LOCAL_PATH)/effects/ogg/VideoStop_48k.ogg:system/media/audio/ui/VideoStop.ogg \
- $(LOCAL_PATH)/effects/ogg/WirelessChargingStarted.ogg:system/media/audio/ui/WirelessChargingStarted.ogg \
- $(LOCAL_PATH)/F1_MissedCall.ogg:system/media/audio/notifications/F1_MissedCall.ogg \
- $(LOCAL_PATH)/F1_New_MMS.ogg:system/media/audio/notifications/F1_New_MMS.ogg \
- $(LOCAL_PATH)/F1_New_SMS.ogg:system/media/audio/notifications/F1_New_SMS.ogg \
- $(LOCAL_PATH)/notifications/Aldebaran.ogg:system/media/audio/notifications/Aldebaran.ogg \
- $(LOCAL_PATH)/notifications/Altair.ogg:system/media/audio/notifications/Altair.ogg \
- $(LOCAL_PATH)/notifications/Antares.ogg:system/media/audio/notifications/Antares.ogg \
- $(LOCAL_PATH)/notifications/arcturus.ogg:system/media/audio/notifications/arcturus.ogg \
- $(LOCAL_PATH)/notifications/Beat_Box_Android.ogg:system/media/audio/notifications/Beat_Box_Android.ogg \
- $(LOCAL_PATH)/notifications/Betelgeuse.ogg:system/media/audio/notifications/Betelgeuse.ogg \
- $(LOCAL_PATH)/notifications/Canopus.ogg:system/media/audio/notifications/Canopus.ogg \
- $(LOCAL_PATH)/notifications/Castor.ogg:system/media/audio/notifications/Castor.ogg \
- $(LOCAL_PATH)/notifications/Cricket.ogg:system/media/audio/notifications/Cricket.ogg \
- $(LOCAL_PATH)/notifications/Deneb.ogg:system/media/audio/notifications/Deneb.ogg \
- $(LOCAL_PATH)/notifications/Doink.ogg:system/media/audio/notifications/Doink.ogg \
- $(LOCAL_PATH)/notifications/Drip.ogg:system/media/audio/notifications/Drip.ogg \
- $(LOCAL_PATH)/notifications/Electra.ogg:system/media/audio/notifications/Electra.ogg \
- $(LOCAL_PATH)/notifications/Fomalhaut.ogg:system/media/audio/notifications/Fomalhaut.ogg \
- $(LOCAL_PATH)/notifications/Heaven.ogg:system/media/audio/notifications/Heaven.ogg \
- $(LOCAL_PATH)/notifications/Merope.ogg:system/media/audio/notifications/Merope.ogg \
- $(LOCAL_PATH)/notifications/moonbeam.ogg:system/media/audio/notifications/moonbeam.ogg \
- $(LOCAL_PATH)/notifications/ogg/Adara.ogg:system/media/audio/notifications/Adara.ogg \
- $(LOCAL_PATH)/notifications/ogg/Alya.ogg:system/media/audio/notifications/Alya.ogg \
- $(LOCAL_PATH)/notifications/ogg/Antimony.ogg:system/media/audio/notifications/Antimony.ogg \
- $(LOCAL_PATH)/notifications/ogg/Arcturus.ogg:system/media/audio/notifications/Arcturus.ogg \
- $(LOCAL_PATH)/notifications/ogg/Argon.ogg:system/media/audio/notifications/Argon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Bellatrix.ogg:system/media/audio/notifications/Bellatrix.ogg \
- $(LOCAL_PATH)/notifications/ogg/Beryllium.ogg:system/media/audio/notifications/Beryllium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Capella.ogg:system/media/audio/notifications/Capella.ogg \
- $(LOCAL_PATH)/notifications/ogg/CetiAlpha.ogg:system/media/audio/notifications/CetiAlpha.ogg \
- $(LOCAL_PATH)/notifications/ogg/Cobalt.ogg:system/media/audio/notifications/Cobalt.ogg \
- $(LOCAL_PATH)/notifications/ogg/Fluorine.ogg:system/media/audio/notifications/Fluorine.ogg \
- $(LOCAL_PATH)/notifications/ogg/Gallium.ogg:system/media/audio/notifications/Gallium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Helium.ogg:system/media/audio/notifications/Helium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Hojus.ogg:system/media/audio/notifications/Hojus.ogg \
- $(LOCAL_PATH)/notifications/ogg/Iridium.ogg:system/media/audio/notifications/Iridium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Krypton.ogg:system/media/audio/notifications/Krypton.ogg \
- $(LOCAL_PATH)/notifications/ogg/Lalande.ogg:system/media/audio/notifications/Lalande.ogg \
- $(LOCAL_PATH)/notifications/ogg/Mira.ogg:system/media/audio/notifications/Mira.ogg \
- $(LOCAL_PATH)/notifications/ogg/Palladium.ogg:system/media/audio/notifications/Palladium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Polaris.ogg:system/media/audio/notifications/Polaris.ogg \
- $(LOCAL_PATH)/notifications/ogg/Pollux.ogg:system/media/audio/notifications/Pollux.ogg \
- $(LOCAL_PATH)/notifications/ogg/Procyon.ogg:system/media/audio/notifications/Procyon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Proxima.ogg:system/media/audio/notifications/Proxima.ogg \
- $(LOCAL_PATH)/notifications/ogg/Radon.ogg:system/media/audio/notifications/Radon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Rubidium.ogg:system/media/audio/notifications/Rubidium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Selenium.ogg:system/media/audio/notifications/Selenium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Shaula.ogg:system/media/audio/notifications/Shaula.ogg \
- $(LOCAL_PATH)/notifications/ogg/Spica.ogg:system/media/audio/notifications/Spica.ogg \
- $(LOCAL_PATH)/notifications/ogg/Strontium.ogg:system/media/audio/notifications/Strontium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Syrma.ogg:system/media/audio/notifications/Syrma.ogg \
- $(LOCAL_PATH)/notifications/ogg/Talitha.ogg:system/media/audio/notifications/Talitha.ogg \
- $(LOCAL_PATH)/notifications/ogg/Tejat.ogg:system/media/audio/notifications/Tejat.ogg \
- $(LOCAL_PATH)/notifications/ogg/Thallium.ogg:system/media/audio/notifications/Thallium.ogg \
- $(LOCAL_PATH)/notifications/ogg/Upsilon.ogg:system/media/audio/notifications/Upsilon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Vega.ogg:system/media/audio/notifications/Vega.ogg \
- $(LOCAL_PATH)/notifications/ogg/Xenon.ogg:system/media/audio/notifications/Xenon.ogg \
- $(LOCAL_PATH)/notifications/ogg/Zirconium.ogg:system/media/audio/notifications/Zirconium.ogg \
- $(LOCAL_PATH)/notifications/pixiedust.ogg:system/media/audio/notifications/pixiedust.ogg \
- $(LOCAL_PATH)/notifications/pizzicato.ogg:system/media/audio/notifications/pizzicato.ogg \
- $(LOCAL_PATH)/notifications/Plastic_Pipe.ogg:system/media/audio/notifications/Plastic_Pipe.ogg \
- $(LOCAL_PATH)/notifications/regulus.ogg:system/media/audio/notifications/regulus.ogg \
- $(LOCAL_PATH)/notifications/sirius.ogg:system/media/audio/notifications/sirius.ogg \
- $(LOCAL_PATH)/notifications/Sirrah.ogg:system/media/audio/notifications/Sirrah.ogg \
- $(LOCAL_PATH)/notifications/SpaceSeed.ogg:system/media/audio/notifications/SpaceSeed.ogg \
- $(LOCAL_PATH)/notifications/TaDa.ogg:system/media/audio/notifications/TaDa.ogg \
- $(LOCAL_PATH)/notifications/Tinkerbell.ogg:system/media/audio/notifications/Tinkerbell.ogg \
- $(LOCAL_PATH)/notifications/tweeters.ogg:system/media/audio/notifications/tweeters.ogg \
- $(LOCAL_PATH)/notifications/vega.ogg:system/media/audio/notifications/vega.ogg
diff --git a/drm/java/android/drm/DrmManagerClient.java b/drm/java/android/drm/DrmManagerClient.java
index 5973d3e..76eab4a 100644
--- a/drm/java/android/drm/DrmManagerClient.java
+++ b/drm/java/android/drm/DrmManagerClient.java
@@ -262,7 +262,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mCloseGuard.warnIfOpen();
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
close();
} finally {
super.finalize();
diff --git a/drm/java/android/drm/DrmUtils.java b/drm/java/android/drm/DrmUtils.java
index 2a86996..60ee6d9 100644
--- a/drm/java/android/drm/DrmUtils.java
+++ b/drm/java/android/drm/DrmUtils.java
@@ -17,6 +17,7 @@
package android.drm;
import java.io.BufferedInputStream;
+import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -79,26 +80,16 @@
file.delete();
}
- private static void quietlyDispose(InputStream stream) {
+ private static void quietlyDispose(Closeable closable) {
try {
- if (null != stream) {
- stream.close();
+ if (null != closable) {
+ closable.close();
}
} catch (IOException e) {
// no need to care, at least as of now
}
}
- private static void quietlyDispose(OutputStream stream) {
- try {
- if (null != stream) {
- stream.close();
- }
- } catch (IOException e) {
- // no need to care
- }
- }
-
/**
* Gets an instance of {@link DrmUtils.ExtendedMetadataParser}, which can be used to parse
* extended metadata embedded in DRM constraint information.
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index a226e85..e3527e3 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -662,6 +662,15 @@
public static final int DEPTH_POINT_CLOUD = 0x101;
/**
+ * Unprocessed implementation-dependent raw
+ * depth measurements, opaque with 16 bit
+ * samples.
+ *
+ * @hide
+ */
+ public static final int RAW_DEPTH = 0x1002;
+
+ /**
* Android private opaque image format.
* <p>
* The choices of the actual format and pixel data layout are entirely up to
@@ -723,6 +732,7 @@
return 24;
case FLEX_RGBA_8888:
return 32;
+ case RAW_DEPTH:
case RAW_SENSOR:
return 16;
case RAW10:
@@ -765,6 +775,7 @@
case DEPTH16:
case DEPTH_POINT_CLOUD:
case PRIVATE:
+ case RAW_DEPTH:
return true;
}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index e3b4740..e1eb69a 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -80,6 +80,11 @@
private String mFontFeatureSettings;
private String mFontVariationSettings;
+ private float mShadowLayerRadius;
+ private float mShadowLayerDx;
+ private float mShadowLayerDy;
+ private int mShadowLayerColor;
+
private static final Object sCacheLock = new Object();
/**
@@ -530,6 +535,11 @@
setElegantTextHeight(false);
mFontFeatureSettings = null;
mFontVariationSettings = null;
+
+ mShadowLayerRadius = 0.0f;
+ mShadowLayerDx = 0.0f;
+ mShadowLayerDy = 0.0f;
+ mShadowLayerColor = 0;
}
/**
@@ -567,6 +577,54 @@
mLocales = paint.mLocales;
mFontFeatureSettings = paint.mFontFeatureSettings;
mFontVariationSettings = paint.mFontVariationSettings;
+
+ mShadowLayerRadius = paint.mShadowLayerRadius;
+ mShadowLayerDx = paint.mShadowLayerDx;
+ mShadowLayerDy = paint.mShadowLayerDy;
+ mShadowLayerColor = paint.mShadowLayerColor;
+ }
+
+ /**
+ * Returns true if all attributes are equal.
+ *
+ * The caller is expected to have checked the trivial cases, like the pointers being equal,
+ * the objects having different classes, or the parameter being null.
+ * @hide
+ */
+ public boolean hasEqualAttributes(@NonNull Paint other) {
+ return mColorFilter == other.mColorFilter
+ && mMaskFilter == other.mMaskFilter
+ && mPathEffect == other.mPathEffect
+ && mShader == other.mShader
+ && mTypeface == other.mTypeface
+ && mXfermode == other.mXfermode
+ && mHasCompatScaling == other.mHasCompatScaling
+ && mCompatScaling == other.mCompatScaling
+ && mInvCompatScaling == other.mInvCompatScaling
+ && mBidiFlags == other.mBidiFlags
+ && mLocales.equals(other.mLocales)
+ && TextUtils.equals(mFontFeatureSettings, other.mFontFeatureSettings)
+ && TextUtils.equals(mFontVariationSettings, other.mFontVariationSettings)
+ && mShadowLayerRadius == other.mShadowLayerRadius
+ && mShadowLayerDx == other.mShadowLayerDx
+ && mShadowLayerDy == other.mShadowLayerDy
+ && mShadowLayerColor == other.mShadowLayerColor
+ && getFlags() == other.getFlags()
+ && getHinting() == other.getHinting()
+ && getStyle() == other.getStyle()
+ && getColor() == other.getColor()
+ && getStrokeWidth() == other.getStrokeWidth()
+ && getStrokeMiter() == other.getStrokeMiter()
+ && getStrokeCap() == other.getStrokeCap()
+ && getStrokeJoin() == other.getStrokeJoin()
+ && getTextAlign() == other.getTextAlign()
+ && isElegantTextHeight() == other.isElegantTextHeight()
+ && getTextSize() == other.getTextSize()
+ && getTextScaleX() == other.getTextScaleX()
+ && getTextSkewX() == other.getTextSkewX()
+ && getLetterSpacing() == other.getLetterSpacing()
+ && getWordSpacing() == other.getWordSpacing()
+ && getHyphenEdit() == other.getHyphenEdit();
}
/** @hide */
@@ -758,6 +816,27 @@
}
/**
+ * Distance from top of the underline to the baseline. Positive values mean below the baseline.
+ * This method returns where the underline should be drawn independent of if the underlineText
+ * bit is set at the moment.
+ * @hide
+ */
+ public float getUnderlinePosition() {
+ // kStdUnderline_Offset = 1/9, defined in SkTextFormatParams.h
+ // TODO: replace with position from post and MVAR tables (b/62353930).
+ return (1.0f / 9.0f) * getTextSize();
+ }
+
+ /**
+ * @hide
+ */
+ public float getUnderlineThickness() {
+ // kStdUnderline_Thickness = 1/18, defined in SkTextFormatParams.h
+ // TODO: replace with thickness from post and MVAR tables (b/62353930).
+ return (1.0f / 18.0f) * getTextSize();
+ }
+
+ /**
* Helper for setFlags(), setting or clearing the UNDERLINE_TEXT_FLAG bit
*
* @param underlineText true to set the underlineText bit in the paint's
@@ -1036,6 +1115,8 @@
// old shader's pointer may be reused by another shader allocation later
if (mShader != shader) {
mNativeShader = -1;
+ // Release any native references to the old shader content
+ nSetShader(mNativePaint, 0);
}
// Defer setting the shader natively until getNativeInstance() is called
mShader = shader;
@@ -1236,6 +1317,10 @@
* opaque, or the alpha from the shadow color if not.
*/
public void setShadowLayer(float radius, float dx, float dy, int shadowColor) {
+ mShadowLayerRadius = radius;
+ mShadowLayerDx = dx;
+ mShadowLayerDy = dy;
+ mShadowLayerColor = shadowColor;
nSetShadowLayer(mNativePaint, radius, dx, dy, shadowColor);
}
@@ -2796,7 +2881,7 @@
private static native int nGetFontMetricsInt(long paintPtr,
long typefacePtr, FontMetricsInt fmi);
-
+
// ---------------- @CriticalNative ------------------------
@CriticalNative
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 00b5eda..90d6ab8 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -834,6 +834,16 @@
final Animator localAnimator = animator.clone();
final String targetName = mTargetNameMap.get(animator);
final Object target = mVectorDrawable.getTargetByName(targetName);
+ if (!mShouldIgnoreInvalidAnim) {
+ if (target == null) {
+ throw new IllegalStateException("Target with the name \"" + targetName
+ + "\" cannot be found in the VectorDrawable to be animated.");
+ } else if (!(target instanceof VectorDrawable.VectorDrawableState)
+ && !(target instanceof VectorDrawable.VObject)) {
+ throw new UnsupportedOperationException("Target should be either VGroup, VPath,"
+ + " or ConstantState, " + target.getClass() + " is not supported");
+ }
+ }
localAnimator.setTarget(target);
return localAnimator;
}
@@ -1321,16 +1331,10 @@
throw new IllegalArgumentException("ClipPath only supports PathData " +
"property");
}
-
}
} else if (target instanceof VectorDrawable.VectorDrawableState) {
createRTAnimatorForRootGroup(values, animator,
(VectorDrawable.VectorDrawableState) target, startTime);
- } else if (!mDrawable.mAnimatedVectorState.mShouldIgnoreInvalidAnim) {
- // Should never get here
- throw new UnsupportedOperationException("Target should be either VGroup, VPath, " +
- "or ConstantState, " + target == null ? "Null target" : target.getClass() +
- " is not supported");
}
}
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 8ce7ed0..aa38f31 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -44,6 +44,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.Arrays;
import java.util.Objects;
/**
@@ -493,7 +494,7 @@
case TYPE_DATA:
return getDataLength() == otherIcon.getDataLength()
&& getDataOffset() == otherIcon.getDataOffset()
- && getDataBytes() == otherIcon.getDataBytes();
+ && Arrays.equals(getDataBytes(), otherIcon.getDataBytes());
case TYPE_RESOURCE:
return getResId() == otherIcon.getResId()
&& Objects.equals(getResPackage(), otherIcon.getResPackage());
diff --git a/graphics/java/android/graphics/pdf/PdfDocument.java b/graphics/java/android/graphics/pdf/PdfDocument.java
index d603436..1b8336f 100644
--- a/graphics/java/android/graphics/pdf/PdfDocument.java
+++ b/graphics/java/android/graphics/pdf/PdfDocument.java
@@ -202,7 +202,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mCloseGuard.warnIfOpen();
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
dispose();
} finally {
super.finalize();
diff --git a/graphics/java/android/graphics/pdf/PdfEditor.java b/graphics/java/android/graphics/pdf/PdfEditor.java
index cd1f8de..245d769 100644
--- a/graphics/java/android/graphics/pdf/PdfEditor.java
+++ b/graphics/java/android/graphics/pdf/PdfEditor.java
@@ -270,7 +270,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mCloseGuard.warnIfOpen();
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
if (mInput != null) {
doClose();
}
diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java
index 7b7a290..b7b81ae 100644
--- a/graphics/java/android/graphics/pdf/PdfRenderer.java
+++ b/graphics/java/android/graphics/pdf/PdfRenderer.java
@@ -230,7 +230,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mCloseGuard.warnIfOpen();
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
if (mInput != null) {
doClose();
}
@@ -444,7 +447,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mCloseGuard.warnIfOpen();
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
if (mNativePage != 0) {
doClose();
}
diff --git a/keystore/tests/Android.mk b/keystore/tests/Android.mk
deleted file mode 100644
index a740b13..0000000
--- a/keystore/tests/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-# We only want this apk build for tests.
-LOCAL_MODULE_TAGS := tests
-LOCAL_CERTIFICATE := platform
-
-LOCAL_JAVA_LIBRARIES := android.test.runner bouncycastle conscrypt
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
-
-# Include all test java files.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := KeyStoreTests
-
-include $(BUILD_PACKAGE)
diff --git a/keystore/tests/AndroidManifest.xml b/keystore/tests/AndroidManifest.xml
deleted file mode 100644
index 415442f..0000000
--- a/keystore/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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="android.security.tests"
- android:sharedUserId="android.uid.system">
-
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="android.security.tests"
- android:label="KeyStore Tests">
- </instrumentation>
-</manifest>
diff --git a/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java b/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java
deleted file mode 100644
index bc8dd13..0000000
--- a/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2012 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.security;
-
-import android.test.AndroidTestCase;
-
-import java.math.BigInteger;
-import java.util.Date;
-
-import javax.security.auth.x500.X500Principal;
-
-public class KeyPairGeneratorSpecTest extends AndroidTestCase {
- private static final String TEST_ALIAS_1 = "test1";
-
- private static final X500Principal TEST_DN_1 = new X500Principal("CN=test1");
-
- private static final long NOW_MILLIS = System.currentTimeMillis();
-
- private static final BigInteger SERIAL_1 = BigInteger.ONE;
-
- /* We have to round this off because X509v3 doesn't store milliseconds. */
- private static final Date NOW = new Date(NOW_MILLIS - (NOW_MILLIS % 1000L));
-
- @SuppressWarnings("deprecation")
- private static final Date NOW_PLUS_10_YEARS = new Date(NOW.getYear() + 10, 0, 1);
-
- public void testConstructor_Success() throws Exception {
- KeyPairGeneratorSpec spec =
- new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1,
- SERIAL_1, NOW, NOW_PLUS_10_YEARS, 0);
-
- assertEquals("Context should be the one specified", getContext(), spec.getContext());
-
- assertEquals("Alias should be the one specified", TEST_ALIAS_1, spec.getKeystoreAlias());
-
- assertEquals("Key algorithm should be the one specified", "RSA", spec.getKeyType());
-
- assertEquals("Key size should be the one specified", 1024, spec.getKeySize());
-
- assertEquals("subjectDN should be the one specified", TEST_DN_1, spec.getSubjectDN());
-
- assertEquals("startDate should be the one specified", NOW, spec.getStartDate());
-
- assertEquals("endDate should be the one specified", NOW_PLUS_10_YEARS, spec.getEndDate());
- }
-
- public void testBuilder_Success() throws Exception {
- KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setKeyType("RSA")
- .setKeySize(1024)
- .setSubject(TEST_DN_1)
- .setSerialNumber(SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .setEncryptionRequired()
- .build();
-
- assertEquals("Context should be the one specified", getContext(), spec.getContext());
-
- assertEquals("Alias should be the one specified", TEST_ALIAS_1, spec.getKeystoreAlias());
-
- assertEquals("Key algorithm should be the one specified", "RSA", spec.getKeyType());
-
- assertEquals("Key size should be the one specified", 1024, spec.getKeySize());
-
- assertEquals("subjectDN should be the one specified", TEST_DN_1, spec.getSubjectDN());
-
- assertEquals("startDate should be the one specified", NOW, spec.getStartDate());
-
- assertEquals("endDate should be the one specified", NOW_PLUS_10_YEARS, spec.getEndDate());
-
- assertEquals("encryption flag should be on", KeyStore.FLAG_ENCRYPTED, spec.getFlags());
- }
-
- public void testConstructor_NullContext_Failure() throws Exception {
- try {
- new KeyPairGeneratorSpec(null, TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1, NOW,
- NOW_PLUS_10_YEARS, 0);
- fail("Should throw IllegalArgumentException when context is null");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testConstructor_NullKeystoreAlias_Failure() throws Exception {
- try {
- new KeyPairGeneratorSpec(getContext(), null, "RSA", 1024, null, TEST_DN_1, SERIAL_1, NOW,
- NOW_PLUS_10_YEARS, 0);
- fail("Should throw IllegalArgumentException when keystoreAlias is null");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testConstructor_NullSubjectDN_Failure() throws Exception {
- try {
- new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, null, SERIAL_1, NOW,
- NOW_PLUS_10_YEARS, 0);
- fail("Should throw IllegalArgumentException when subjectDN is null");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testConstructor_NullSerial_Failure() throws Exception {
- try {
- new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, null, NOW,
- NOW_PLUS_10_YEARS, 0);
- fail("Should throw IllegalArgumentException when startDate is null");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testConstructor_NullStartDate_Failure() throws Exception {
- try {
- new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1,
- null, NOW_PLUS_10_YEARS, 0);
- fail("Should throw IllegalArgumentException when startDate is null");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testConstructor_NullEndDate_Failure() throws Exception {
- try {
- new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1,
- NOW, null, 0);
- fail("Should throw IllegalArgumentException when keystoreAlias is null");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testConstructor_EndBeforeStart_Failure() throws Exception {
- try {
- new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1,
- NOW_PLUS_10_YEARS, NOW, 0);
- fail("Should throw IllegalArgumentException when end is before start");
- } catch (IllegalArgumentException success) {
- }
- }
-}
diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java
deleted file mode 100644
index 319cf32..0000000
--- a/keystore/tests/src/android/security/KeyStoreTest.java
+++ /dev/null
@@ -1,974 +0,0 @@
-/*
- * Copyright (C) 2009 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.security;
-
-import android.app.Activity;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.Process;
-import android.security.keymaster.ExportResult;
-import android.security.keymaster.KeyCharacteristics;
-import android.security.keymaster.KeymasterArguments;
-import android.security.keymaster.KeymasterBlob;
-import android.security.keymaster.KeymasterDefs;
-import android.security.keymaster.OperationResult;
-import android.test.ActivityUnitTestCase;
-import android.test.AssertionFailedError;
-import android.test.MoreAsserts;
-import android.test.suitebuilder.annotation.MediumTest;
-import com.android.org.conscrypt.NativeConstants;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.security.spec.RSAKeyGenParameterSpec;
-
-/**
- * Junit / Instrumentation test case for KeyStore class
- *
- * Running the test suite:
- *
- * runtest keystore-unit
- *
- * Or this individual test case:
- *
- * runtest --path frameworks/base/keystore/tests/src/android/security/KeyStoreTest.java
- */
-@MediumTest
-public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
- private static final String TEST_PASSWD = "12345678";
- private static final String TEST_PASSWD2 = "87654321";
- private static final String TEST_KEYNAME = "test-key";
- private static final String TEST_KEYNAME1 = "test-key.1";
- private static final String TEST_KEYNAME2 = "test-key\02";
- private static final byte[] TEST_KEYVALUE = "test value".getBytes(StandardCharsets.UTF_8);
-
- // "Hello, World" in Chinese
- private static final String TEST_I18N_KEY = "\u4F60\u597D, \u4E16\u754C";
- private static final byte[] TEST_I18N_VALUE = TEST_I18N_KEY.getBytes(StandardCharsets.UTF_8);
-
- // Test vector data for signatures
- private static final int RSA_KEY_SIZE = 1024;
- private static final byte[] TEST_DATA = new byte[RSA_KEY_SIZE / 8];
- static {
- for (int i = 0; i < TEST_DATA.length; i++) {
- TEST_DATA[i] = (byte) i;
- }
- }
-
- private KeyStore mKeyStore = null;
-
- public KeyStoreTest() {
- super(Activity.class);
- }
-
- private static final byte[] PRIVKEY_BYTES = hexToBytes(
- "308204BE020100300D06092A864886F70D0101010500048204A8308204A4020100028201" +
- "0100E0473E8AB8F2284FEB9E742FF9748FA118ED98633C92F52AEB7A2EBE0D3BE60329BE" +
- "766AD10EB6A515D0D2CFD9BEA7930F0C306537899F7958CD3E85B01F8818524D312584A9" +
- "4B251E3625B54141EDBFEE198808E1BB97FC7CB49B9EAAAF68E9C98D7D0EDC53BBC0FA00" +
- "34356D6305FBBCC3C7001405386ABBC873CB0F3EF7425F3D33DF7B315AE036D2A0B66AFD" +
- "47503B169BF36E3B5162515B715FDA83DEAF2C58AEB9ABFB3097C3CC9DD9DBE5EF296C17" +
- "6139028E8A671E63056D45F40188D2C4133490845DE52C2534E9C6B2478C07BDAE928823" +
- "B62D066C7770F9F63F3DBA247F530844747BE7AAA85D853B8BD244ACEC3DE3C89AB46453" +
- "AB4D24C3AC6902030100010282010037784776A5F17698F5AC960DFB83A1B67564E648BD" +
- "0597CF8AB8087186F2669C27A9ECBDD480F0197A80D07309E6C6A96F925331E57F8B4AC6" +
- "F4D45EDA45A23269C09FC428C07A4E6EDF738A15DEC97FABD2F2BB47A14F20EA72FCFE4C" +
- "36E01ADA77BD137CD8D4DA10BB162E94A4662971F175F985FA188F056CB97EE2816F43AB" +
- "9D3747612486CDA8C16196C30818A995EC85D38467791267B3BF21F273710A6925862576" +
- "841C5B6712C12D4BD20A2F3299ADB7C135DA5E9515ABDA76E7CAF2A3BE80551D073B78BF" +
- "1162C48AD2B7F4743A0238EE4D252F7D5E7E6533CCAE64CCB39360075A2FD1E034EC3AE5" +
- "CE9C408CCBF0E25E4114021687B3DD4754AE8102818100F541884BC3737B2922D4119EF4" +
- "5E2DEE2CD4CBB75F45505A157AA5009F99C73A2DF0724AC46024306332EA898177634546" +
- "5DC6DF1E0A6F140AFF3B7396E6A8994AC5DAA96873472FE37749D14EB3E075E629DBEB35" +
- "83338A6F3649D0A2654A7A42FD9AB6BFA4AC4D481D390BB229B064BDC311CC1BE1B63189" +
- "DA7C40CDECF2B102818100EA1A742DDB881CEDB7288C87E38D868DD7A409D15A43F445D5" +
- "377A0B5731DDBFCA2DAF28A8E13CD5C0AFCEC3347D74A39E235A3CD9633F274DE2B94F92" +
- "DF43833911D9E9F1CF58F27DE2E08FF45964C720D3EC2139DC7CAFC912953CDECB2F355A" +
- "2E2C35A50FAD754CB3B23166424BA3B6E3112A2B898C38C5C15EDB238693390281805182" +
- "8F1EC6FD996029901BAF1D7E337BA5F0AF27E984EAD895ACE62BD7DF4EE45A224089F2CC" +
- "151AF3CD173FCE0474BCB04F386A2CDCC0E0036BA2419F54579262D47100BE931984A3EF" +
- "A05BECF141574DC079B3A95C4A83E6C43F3214D6DF32D512DE198085E531E616B83FD7DD" +
- "9D1F4E2607C3333D07C55D107D1D3893587102818100DB4FB50F50DE8EDB53FF34C80931" +
- "88A0512867DA2CCA04897759E587C244010DAF8664D59E8083D16C164789301F67A9F078" +
- "060D834A2ADBD367575B68A8A842C2B02A89B3F31FCCEC8A22FE395795C5C6C7422B4E5D" +
- "74A1E9A8F30E7759B9FC2D639C1F15673E84E93A5EF1506F4315383C38D45CBD1B14048F" +
- "4721DC82326102818100D8114593AF415FB612DBF1923710D54D07486205A76A3B431949" +
- "68C0DFF1F11EF0F61A4A337D5FD3741BBC9640E447B8B6B6C47C3AC1204357D3B0C55BA9" +
- "286BDA73F629296F5FA9146D8976357D3C751E75148696A40B74685C82CE30902D639D72" +
- "4FF24D5E2E9407EE34EDED2E3B4DF65AA9BCFEB6DF28D07BA6903F165768");
-
- private static final byte[] AES256_BYTES = hexToBytes(
- "0CC175B9C0F1B6A831C399E269772661CEC520EA51EA0A47E87295FA3245A605");
-
- private static byte[] hexToBytes(String s) {
- int len = s.length();
- byte[] data = new byte[len / 2];
- for (int i = 0; i < len; i += 2) {
- data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(
- s.charAt(i + 1), 16));
- }
- return data;
- }
-
- @Override
- protected void setUp() throws Exception {
- mKeyStore = KeyStore.getInstance();
- if (mKeyStore.state() != KeyStore.State.UNINITIALIZED) {
- mKeyStore.reset();
- }
- assertEquals("KeyStore should be in an uninitialized state",
- KeyStore.State.UNINITIALIZED, mKeyStore.state());
- super.setUp();
- }
-
- @Override
- protected void tearDown() throws Exception {
- mKeyStore.reset();
- super.tearDown();
- }
-
- public void testState() throws Exception {
- assertEquals(KeyStore.State.UNINITIALIZED, mKeyStore.state());
- }
-
- public void testPassword() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
- assertEquals(KeyStore.State.UNLOCKED, mKeyStore.state());
- }
-
- public void testGet() throws Exception {
- assertNull(mKeyStore.get(TEST_KEYNAME));
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertNull(mKeyStore.get(TEST_KEYNAME));
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
- }
-
- public void testPut() throws Exception {
- assertNull(mKeyStore.get(TEST_KEYNAME));
- assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
- }
-
- public void testPut_grantedUid_Wifi() throws Exception {
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- }
-
- public void testPut_ungrantedUid_Bluetooth() throws Exception {
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- }
-
- public void testI18n() throws Exception {
- assertFalse(mKeyStore.put(TEST_I18N_KEY, TEST_I18N_VALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_I18N_KEY));
- mKeyStore.onUserPasswordChanged(TEST_I18N_KEY);
- assertTrue(mKeyStore.put(TEST_I18N_KEY, TEST_I18N_VALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(mKeyStore.contains(TEST_I18N_KEY));
- }
-
- public void testDelete() throws Exception {
- assertFalse(mKeyStore.delete(TEST_KEYNAME));
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertFalse(mKeyStore.delete(TEST_KEYNAME));
-
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
- assertTrue(mKeyStore.delete(TEST_KEYNAME));
- assertNull(mKeyStore.get(TEST_KEYNAME));
- }
-
- public void testDelete_grantedUid_Wifi() throws Exception {
- assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.WIFI_UID));
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.WIFI_UID));
-
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- assertTrue(mKeyStore.delete(TEST_KEYNAME, Process.WIFI_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- }
-
- public void testDelete_ungrantedUid_Bluetooth() throws Exception {
- assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.BLUETOOTH_UID));
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.BLUETOOTH_UID));
-
- assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.BLUETOOTH_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- }
-
- public void testContains() throws Exception {
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
-
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
-
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- }
-
- public void testContains_grantedUid_Wifi() throws Exception {
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- }
-
- public void testContains_grantedUid_Bluetooth() throws Exception {
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-
- assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- }
-
- public void testList() throws Exception {
- String[] emptyResult = mKeyStore.list(TEST_KEYNAME);
- assertNotNull(emptyResult);
- assertEquals(0, emptyResult.length);
-
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
- mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
-
- String[] results = mKeyStore.list(TEST_KEYNAME);
- assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()),
- TEST_KEYNAME2.substring(TEST_KEYNAME.length()))),
- new HashSet(Arrays.asList(results)));
- }
-
- public void testList_ungrantedUid_Bluetooth() throws Exception {
- String[] results1 = mKeyStore.list(TEST_KEYNAME, Process.BLUETOOTH_UID);
- assertEquals(0, results1.length);
-
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
- mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
-
- String[] results2 = mKeyStore.list(TEST_KEYNAME, Process.BLUETOOTH_UID);
- assertEquals(0, results2.length);
- }
-
- public void testList_grantedUid_Wifi() throws Exception {
- String[] results1 = mKeyStore.list(TEST_KEYNAME, Process.WIFI_UID);
- assertNotNull(results1);
- assertEquals(0, results1.length);
-
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED);
- mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED);
-
- String[] results2 = mKeyStore.list(TEST_KEYNAME, Process.WIFI_UID);
- assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()),
- TEST_KEYNAME2.substring(TEST_KEYNAME.length()))),
- new HashSet(Arrays.asList(results2)));
- }
-
- public void testList_grantedUid_Vpn() throws Exception {
- String[] results1 = mKeyStore.list(TEST_KEYNAME, Process.VPN_UID);
- assertNotNull(results1);
- assertEquals(0, results1.length);
-
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, Process.VPN_UID, KeyStore.FLAG_ENCRYPTED);
- mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, Process.VPN_UID, KeyStore.FLAG_ENCRYPTED);
-
- String[] results2 = mKeyStore.list(TEST_KEYNAME, Process.VPN_UID);
- assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()),
- TEST_KEYNAME2.substring(TEST_KEYNAME.length()))),
- new HashSet(Arrays.asList(results2)));
- }
-
- public void testLock() throws Exception {
- assertFalse(mKeyStore.lock());
-
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertEquals(KeyStore.State.UNLOCKED, mKeyStore.state());
-
- assertTrue(mKeyStore.lock());
- assertEquals(KeyStore.State.LOCKED, mKeyStore.state());
- }
-
- public void testUnlock() throws Exception {
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertEquals(KeyStore.State.UNLOCKED, mKeyStore.state());
- mKeyStore.lock();
-
- assertFalse(mKeyStore.unlock(TEST_PASSWD2));
- assertTrue(mKeyStore.unlock(TEST_PASSWD));
- }
-
- public void testIsEmpty() throws Exception {
- assertTrue(mKeyStore.isEmpty());
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertTrue(mKeyStore.isEmpty());
- mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
- assertFalse(mKeyStore.isEmpty());
- mKeyStore.reset();
- assertTrue(mKeyStore.isEmpty());
- }
-
- public void testGenerate_NotInitialized_Fail() throws Exception {
- assertFalse("Should fail when keystore is not initialized",
- mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
- }
-
- public void testGenerate_Locked_Fail() throws Exception {
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- mKeyStore.lock();
- assertFalse("Should fail when keystore is locked",
- mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
- }
-
- public void testGenerate_Success() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to generate key when unlocked",
- mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- }
-
- public void testGenerate_grantedUid_Wifi_Success() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to generate key when unlocked",
- mKeyStore.generate(TEST_KEYNAME, Process.WIFI_UID, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
- assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
- }
-
- public void testGenerate_ungrantedUid_Bluetooth_Failure() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertFalse(mKeyStore.generate(TEST_KEYNAME, Process.BLUETOOTH_UID,
- NativeConstants.EVP_PKEY_RSA, RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
- }
-
- public void testImport_Success() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
- PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- }
-
- public void testImport_grantedUid_Wifi_Success() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
- PRIVKEY_BYTES, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
- }
-
- public void testImport_ungrantedUid_Bluetooth_Failure() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertFalse(mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES, Process.BLUETOOTH_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
- }
-
- public void testImport_Failure_BadEncoding() throws Exception {
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-
- assertFalse("Invalid DER-encoded key should not be imported", mKeyStore.importKey(
- TEST_KEYNAME, TEST_DATA, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- }
-
- public void testSign_Success() throws Exception {
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-
- assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- final byte[] signature = mKeyStore.sign(TEST_KEYNAME, TEST_DATA);
-
- assertNotNull("Signature should not be null", signature);
- }
-
- public void testVerify_Success() throws Exception {
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-
- assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- final byte[] signature = mKeyStore.sign(TEST_KEYNAME, TEST_DATA);
-
- assertNotNull("Signature should not be null", signature);
-
- assertTrue("Signature should verify with same data",
- mKeyStore.verify(TEST_KEYNAME, TEST_DATA, signature));
- }
-
- public void testSign_NotInitialized_Failure() throws Exception {
- assertNull("Should not be able to sign without first initializing the keystore",
- mKeyStore.sign(TEST_KEYNAME, TEST_DATA));
- }
-
- public void testSign_NotGenerated_Failure() throws Exception {
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-
- assertNull("Should not be able to sign without first generating keys",
- mKeyStore.sign(TEST_KEYNAME, TEST_DATA));
- }
-
- public void testGrant_Generated_Success() throws Exception {
- assertTrue("Password should work for keystore",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to generate key for testcase",
- mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
- assertTrue("Should be able to grant key to other user",
- mKeyStore.grant(TEST_KEYNAME, 0));
- }
-
- public void testGrant_Imported_Success() throws Exception {
- assertTrue("Password should work for keystore", mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to import key for testcase", mKeyStore.importKey(TEST_KEYNAME,
- PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertTrue("Should be able to grant key to other user", mKeyStore.grant(TEST_KEYNAME, 0));
- }
-
- public void testGrant_NoKey_Failure() throws Exception {
- assertTrue("Should be able to unlock keystore for test",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertFalse("Should not be able to grant without first initializing the keystore",
- mKeyStore.grant(TEST_KEYNAME, 0));
- }
-
- public void testGrant_NotInitialized_Failure() throws Exception {
- assertFalse("Should not be able to grant without first initializing the keystore",
- mKeyStore.grant(TEST_KEYNAME, 0));
- }
-
- public void testUngrant_Generated_Success() throws Exception {
- assertTrue("Password should work for keystore",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to generate key for testcase",
- mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
- assertTrue("Should be able to grant key to other user",
- mKeyStore.grant(TEST_KEYNAME, 0));
-
- assertTrue("Should be able to ungrant key to other user",
- mKeyStore.ungrant(TEST_KEYNAME, 0));
- }
-
- public void testUngrant_Imported_Success() throws Exception {
- assertTrue("Password should work for keystore",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to import key for testcase", mKeyStore.importKey(TEST_KEYNAME,
- PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertTrue("Should be able to grant key to other user",
- mKeyStore.grant(TEST_KEYNAME, 0));
-
- assertTrue("Should be able to ungrant key to other user",
- mKeyStore.ungrant(TEST_KEYNAME, 0));
- }
-
- public void testUngrant_NotInitialized_Failure() throws Exception {
- assertFalse("Should fail to ungrant key when keystore not initialized",
- mKeyStore.ungrant(TEST_KEYNAME, 0));
- }
-
- public void testUngrant_NoGrant_Failure() throws Exception {
- assertTrue("Password should work for keystore",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to generate key for testcase",
- mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
- assertFalse("Should not be able to revoke not existent grant",
- mKeyStore.ungrant(TEST_KEYNAME, 0));
- }
-
- public void testUngrant_DoubleUngrant_Failure() throws Exception {
- assertTrue("Password should work for keystore",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to generate key for testcase",
- mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
- assertTrue("Should be able to grant key to other user",
- mKeyStore.grant(TEST_KEYNAME, 0));
-
- assertTrue("Should be able to ungrant key to other user",
- mKeyStore.ungrant(TEST_KEYNAME, 0));
-
- assertFalse("Should fail to ungrant key to other user second time",
- mKeyStore.ungrant(TEST_KEYNAME, 0));
- }
-
- public void testUngrant_DoubleGrantUngrant_Failure() throws Exception {
- assertTrue("Password should work for keystore",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to generate key for testcase",
- mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
- assertTrue("Should be able to grant key to other user",
- mKeyStore.grant(TEST_KEYNAME, 0));
-
- assertTrue("Should be able to grant key to other user a second time",
- mKeyStore.grant(TEST_KEYNAME, 0));
-
- assertTrue("Should be able to ungrant key to other user",
- mKeyStore.ungrant(TEST_KEYNAME, 0));
-
- assertFalse("Should fail to ungrant key to other user second time",
- mKeyStore.ungrant(TEST_KEYNAME, 0));
- }
-
- public void testDuplicate_grantedUid_Wifi_Success() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
-
- assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-
- // source doesn't exist
- assertFalse(mKeyStore.duplicate(TEST_KEYNAME1, -1, TEST_KEYNAME1, Process.WIFI_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME1, Process.WIFI_UID));
-
- // Copy from current UID to granted UID
- assertTrue(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME1, Process.WIFI_UID));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME1));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- assertTrue(mKeyStore.contains(TEST_KEYNAME1, Process.WIFI_UID));
- assertFalse(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME1, Process.WIFI_UID));
-
- // Copy from granted UID to same granted UID
- assertTrue(mKeyStore.duplicate(TEST_KEYNAME1, Process.WIFI_UID, TEST_KEYNAME2,
- Process.WIFI_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- assertTrue(mKeyStore.contains(TEST_KEYNAME1, Process.WIFI_UID));
- assertTrue(mKeyStore.contains(TEST_KEYNAME2, Process.WIFI_UID));
- assertFalse(mKeyStore.duplicate(TEST_KEYNAME1, Process.WIFI_UID, TEST_KEYNAME2,
- Process.WIFI_UID));
-
- assertTrue(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME2, -1));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME1));
- assertTrue(mKeyStore.contains(TEST_KEYNAME2));
- assertFalse(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME2, -1));
- }
-
- public void testDuplicate_ungrantedUid_Bluetooth_Failure() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
-
- assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-
- assertFalse(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME2, Process.BLUETOOTH_UID));
- assertFalse(mKeyStore.duplicate(TEST_KEYNAME, Process.BLUETOOTH_UID, TEST_KEYNAME2,
- Process.BLUETOOTH_UID));
-
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- }
-
- /**
- * The amount of time to allow before and after expected time for variance
- * in timing tests.
- */
- private static final long SLOP_TIME_MILLIS = 15000L;
-
- public void testGetmtime_Success() throws Exception {
- assertTrue("Password should work for keystore",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
- PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- long now = System.currentTimeMillis();
- long actual = mKeyStore.getmtime(TEST_KEYNAME);
-
- long expectedAfter = now - SLOP_TIME_MILLIS;
- long expectedBefore = now + SLOP_TIME_MILLIS;
-
- assertLessThan("Time should be close to current time", expectedBefore, actual);
- assertGreaterThan("Time should be close to current time", expectedAfter, actual);
- }
-
- private static void assertLessThan(String explanation, long expectedBefore, long actual) {
- if (actual >= expectedBefore) {
- throw new AssertionFailedError(explanation + ": actual=" + actual
- + ", expected before: " + expectedBefore);
- }
- }
-
- private static void assertGreaterThan(String explanation, long expectedAfter, long actual) {
- if (actual <= expectedAfter) {
- throw new AssertionFailedError(explanation + ": actual=" + actual
- + ", expected after: " + expectedAfter);
- }
- }
-
- public void testGetmtime_NonExist_Failure() throws Exception {
- assertTrue("Password should work for keystore",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
- PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertEquals("-1 should be returned for non-existent key",
- -1L, mKeyStore.getmtime(TEST_KEYNAME2));
- }
-
- private KeyCharacteristics generateRsaKey(String name) throws Exception {
- KeymasterArguments args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
- args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
- args.addUnsignedLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, RSAKeyGenParameterSpec.F4);
-
- KeyCharacteristics outCharacteristics = new KeyCharacteristics();
- int result = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
- assertEquals("generateRsaKey should succeed", KeyStore.NO_ERROR, result);
- return outCharacteristics;
- }
-
- public void testGenerateKey() throws Exception {
- generateRsaKey("test");
- mKeyStore.delete("test");
- }
-
- public void testGenerateRsaWithEntropy() throws Exception {
- byte[] entropy = new byte[] {1,2,3,4,5};
- String name = "test";
- KeymasterArguments args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
- args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
- args.addUnsignedLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, RSAKeyGenParameterSpec.F4);
-
- KeyCharacteristics outCharacteristics = new KeyCharacteristics();
- int result = mKeyStore.generateKey(name, args, entropy, 0, outCharacteristics);
- assertEquals("generateKey should succeed", KeyStore.NO_ERROR, result);
- }
-
- public void testGenerateAndDelete() throws Exception {
- generateRsaKey("test");
- assertTrue("delete should succeed", mKeyStore.delete("test"));
- }
-
- public void testGetKeyCharacteristicsSuccess() throws Exception {
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- String name = "test";
- KeyCharacteristics gen = generateRsaKey(name);
- KeyCharacteristics call = new KeyCharacteristics();
- int result = mKeyStore.getKeyCharacteristics(name, null, null, call);
- assertEquals("getKeyCharacteristics should succeed", KeyStore.NO_ERROR, result);
- mKeyStore.delete("test");
- }
-
- public void testAppId() throws Exception {
- String name = "test";
- byte[] id = new byte[] {0x01, 0x02, 0x03};
- KeymasterArguments args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
- args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
- args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
- args.addBytes(KeymasterDefs.KM_TAG_APPLICATION_ID, id);
- args.addUnsignedLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, RSAKeyGenParameterSpec.F4);
-
- KeyCharacteristics outCharacteristics = new KeyCharacteristics();
- int result = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
- assertEquals("generateRsaKey should succeed", KeyStore.NO_ERROR, result);
- assertEquals("getKeyCharacteristics should fail without application ID",
- KeymasterDefs.KM_ERROR_INVALID_KEY_BLOB,
- mKeyStore.getKeyCharacteristics(name, null, null, outCharacteristics));
- assertEquals("getKeyCharacteristics should succeed with application ID",
- KeyStore.NO_ERROR,
- mKeyStore.getKeyCharacteristics(name, new KeymasterBlob(id), null,
- outCharacteristics));
- }
-
-
- public void testExportRsa() throws Exception {
- String name = "test";
- generateRsaKey(name);
- ExportResult result = mKeyStore.exportKey(name, KeymasterDefs.KM_KEY_FORMAT_X509, null,
- null);
- assertEquals("Export success", KeyStore.NO_ERROR, result.resultCode);
- // TODO: Verify we have an RSA public key that's well formed.
- }
-
- public void testAesGcmEncryptSuccess() throws Exception {
- String name = "test";
- KeymasterArguments args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
- args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_GCM);
- args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
-
- KeyCharacteristics outCharacteristics = new KeyCharacteristics();
- int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
- assertEquals("Generate should succeed", KeyStore.NO_ERROR, rc);
-
- args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_GCM);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 128);
- OperationResult result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT,
- true, args, null);
- IBinder token = result.token;
- assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
- result = mKeyStore.update(token, null, new byte[] {0x01, 0x02, 0x03, 0x04});
- assertEquals("Update should succeed", KeyStore.NO_ERROR, result.resultCode);
- assertEquals("Finish should succeed", KeyStore.NO_ERROR,
- mKeyStore.finish(token, null, null).resultCode);
- // TODO: Assert that an AEAD tag was returned by finish
- }
-
- public void testBadToken() throws Exception {
- IBinder token = new Binder();
- OperationResult result = mKeyStore.update(token, null, new byte[] {0x01});
- assertEquals("Update with invalid token should fail",
- KeymasterDefs.KM_ERROR_INVALID_OPERATION_HANDLE, result.resultCode);
- }
-
- private int importAesKey(String name, byte[] key, int size, int mode) {
- KeymasterArguments args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mode);
- args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, size);
- args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
- return mKeyStore.importKey(name, args, KeymasterDefs.KM_KEY_FORMAT_RAW, key, 0,
- new KeyCharacteristics());
- }
- private byte[] doOperation(String name, int purpose, byte[] in, KeymasterArguments beginArgs) {
- OperationResult result = mKeyStore.begin(name, purpose,
- true, beginArgs, null);
- assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
- IBinder token = result.token;
- result = mKeyStore.update(token, null, in);
- assertEquals("Update should succeed", KeyStore.NO_ERROR, result.resultCode);
- assertEquals("All data should be consumed", in.length, result.inputConsumed);
- assertEquals("Finish should succeed", KeyStore.NO_ERROR,
- mKeyStore.finish(token, null, null).resultCode);
- return result.output;
- }
-
- public void testImportAes() throws Exception {
- int result = importAesKey("aes", AES256_BYTES, 256, KeymasterDefs.KM_MODE_ECB);
- assertEquals("import should succeed", KeyStore.NO_ERROR, result);
- mKeyStore.delete("aes");
- }
-
- public void testAes256Ecb() throws Exception {
- byte[] key =
- hexToBytes("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
- String name = "aes";
- assertEquals(KeyStore.NO_ERROR, importAesKey(name, key, 256, KeymasterDefs.KM_MODE_ECB));
- byte[][] testVectors = new byte[][] {
- hexToBytes("6bc1bee22e409f96e93d7e117393172a"),
- hexToBytes("ae2d8a571e03ac9c9eb76fac45af8e51"),
- hexToBytes("30c81c46a35ce411e5fbc1191a0a52ef"),
- hexToBytes("f69f2445df4f9b17ad2b417be66c3710")};
- byte[][] cipherVectors = new byte[][] {
- hexToBytes("f3eed1bdb5d2a03c064b5a7e3db181f8"),
- hexToBytes("591ccb10d410ed26dc5ba74a31362870"),
- hexToBytes("b6ed21b99ca6f4f9f153e7b1beafed1d"),
- hexToBytes("23304b7a39f9f3ff067d8d8f9e24ecc7")};
- KeymasterArguments beginArgs = new KeymasterArguments();
- beginArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- beginArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
- beginArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- for (int i = 0; i < testVectors.length; i++) {
- byte[] cipherText = doOperation(name, KeymasterDefs.KM_PURPOSE_ENCRYPT, testVectors[i],
- beginArgs);
- MoreAsserts.assertEquals(cipherVectors[i], cipherText);
- }
- for (int i = 0; i < testVectors.length; i++) {
- byte[] plainText = doOperation(name, KeymasterDefs.KM_PURPOSE_DECRYPT,
- cipherVectors[i], beginArgs);
- MoreAsserts.assertEquals(testVectors[i], plainText);
- }
- }
-
- // This is a very implementation specific test and should be thrown out eventually, however it
- // is nice for now to test that keystore is properly pruning operations.
- public void testOperationPruning() throws Exception {
- String name = "test";
- KeymasterArguments args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
- args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CTR);
- args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
-
- KeyCharacteristics outCharacteristics = new KeyCharacteristics();
- int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
- assertEquals("Generate should succeed", KeyStore.NO_ERROR, rc);
-
- args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CTR);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- OperationResult result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT,
- true, args, null);
- assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
- IBinder first = result.token;
- // Implementation detail: softkeymaster supports 16 concurrent operations
- for (int i = 0; i < 16; i++) {
- result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT, true, args, null);
- assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
- }
- // At this point the first operation should be pruned.
- assertEquals("Operation should be pruned", KeymasterDefs.KM_ERROR_INVALID_OPERATION_HANDLE,
- mKeyStore.update(first, null, new byte[] {0x01}).resultCode);
- }
-
- public void testAuthNeeded() throws Exception {
- String name = "test";
- KeymasterArguments args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_PKCS7);
- args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
- args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
- args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 1);
-
- KeyCharacteristics outCharacteristics = new KeyCharacteristics();
- int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
- assertEquals("Generate should succeed", KeyStore.NO_ERROR, rc);
- OperationResult result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT,
- true, args, null);
- assertEquals("Begin should expect authorization", KeyStore.OP_AUTH_NEEDED,
- result.resultCode);
- IBinder token = result.token;
- result = mKeyStore.update(token, null, new byte[] {0x01, 0x02, 0x03, 0x04});
- assertEquals("Update should require authorization",
- KeymasterDefs.KM_ERROR_KEY_USER_NOT_AUTHENTICATED, result.resultCode);
- }
-
- public void testPasswordRemovalEncryptedEntry() throws Exception {
- mKeyStore.onUserPasswordChanged("test");
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
- mKeyStore.onUserPasswordChanged("");
- // Removing the password should have deleted all entries using FLAG_ENCRYPTED
- assertNull(mKeyStore.get(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
- }
-
- public void testPasswordRemovalUnencryptedEntry() throws Exception {
- mKeyStore.onUserPasswordChanged("test");
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_NONE));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
- mKeyStore.onUserPasswordChanged("");
- // Removing the password should not delete unencrypted entries.
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
- }
-}
diff --git a/keystore/tests/src/android/security/SystemKeyStoreTest.java b/keystore/tests/src/android/security/SystemKeyStoreTest.java
deleted file mode 100644
index ecf7cbc..0000000
--- a/keystore/tests/src/android/security/SystemKeyStoreTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2010 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.security;
-
-import android.app.Activity;
-import android.security.SystemKeyStore;
-import android.test.ActivityUnitTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-
-/**
- * Junit / Instrumentation test case for KeyStore class
- *
- * Running the test suite:
- *
- * runtest keystore-unit
- *
- * Or this individual test case:
- *
- * runtest --path frameworks/base/keystore/tests/src/android/security/SystemKeyStoreTest.java
- */
-@MediumTest
-public class SystemKeyStoreTest extends ActivityUnitTestCase<Activity> {
-
- private static final String keyName = "TestKey";
- private static final String keyName2 = "TestKey2";
- private SystemKeyStore mSysKeyStore = null;
-
- public SystemKeyStoreTest() {
- super(Activity.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- mSysKeyStore = SystemKeyStore.getInstance();
- try {
- mSysKeyStore.deleteKey(keyName);
- mSysKeyStore.deleteKey(keyName2);
- } catch (Exception e) { }
- super.setUp();
- }
-
- @Override
- protected void tearDown() throws Exception {
- try {
- mSysKeyStore.deleteKey(keyName);
- mSysKeyStore.deleteKey(keyName2);
- } catch (Exception e) { }
- super.tearDown();
- }
-
- public void testBasicAccess() throws Exception {
- try {
- byte[] newKey = mSysKeyStore.generateNewKey(128, "AES", keyName);
- assertNotNull(newKey);
- byte[] recKey = mSysKeyStore.retrieveKey(keyName);
- assertEquals(newKey.length, recKey.length);
- for (int i = 0; i < newKey.length; i++) {
- assertEquals(newKey[i], recKey[i]);
- }
- mSysKeyStore.deleteKey(keyName);
- byte[] nullKey = mSysKeyStore.retrieveKey(keyName);
- assertNull(nullKey);
-
- String newKeyStr = mSysKeyStore.generateNewKeyHexString(128, "AES", keyName2);
- assertNotNull(newKeyStr);
- String recKeyStr = mSysKeyStore.retrieveKeyHexString(keyName2);
- assertEquals(newKeyStr, recKeyStr);
-
- mSysKeyStore.deleteKey(keyName2);
- String nullKey2 = mSysKeyStore.retrieveKeyHexString(keyName2);
- assertNull(nullKey2);
- } catch (Exception e) {
- fail();
- }
- }
-}
diff --git a/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java b/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java
deleted file mode 100644
index 1af0b7d..0000000
--- a/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java
+++ /dev/null
@@ -1,432 +0,0 @@
-/*
- * Copyright (C) 2012 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.security.keystore;
-
-import android.security.Credentials;
-import android.security.KeyPairGeneratorSpec;
-import android.security.KeyStore;
-import android.security.keymaster.ExportResult;
-import android.security.keymaster.KeymasterDefs;
-import android.test.AndroidTestCase;
-
-import java.io.ByteArrayInputStream;
-import java.math.BigInteger;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.interfaces.ECKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.RSAKeyGenParameterSpec;
-import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.Date;
-
-import javax.security.auth.x500.X500Principal;
-
-public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
- private android.security.KeyStore mAndroidKeyStore;
-
- private java.security.KeyPairGenerator mGenerator;
-
- private static final String TEST_ALIAS_1 = "test1";
-
- private static final String TEST_ALIAS_2 = "test2";
-
- private static final X500Principal TEST_DN_1 = new X500Principal("CN=test1");
-
- private static final X500Principal TEST_DN_2 = new X500Principal("CN=test2");
-
- private static final BigInteger TEST_SERIAL_1 = BigInteger.ONE;
-
- private static final BigInteger TEST_SERIAL_2 = BigInteger.valueOf(2L);
-
- private static final long NOW_MILLIS = System.currentTimeMillis();
-
- /* We have to round this off because X509v3 doesn't store milliseconds. */
- private static final Date NOW = new Date(NOW_MILLIS - (NOW_MILLIS % 1000L));
-
- @SuppressWarnings("deprecation")
- private static final Date NOW_PLUS_10_YEARS = new Date(NOW.getYear() + 10, 0, 1);
-
- @Override
- protected void setUp() throws Exception {
- mAndroidKeyStore = android.security.KeyStore.getInstance();
-
- assertTrue(mAndroidKeyStore.reset());
-
- assertFalse(mAndroidKeyStore.isUnlocked());
-
- mGenerator = java.security.KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
- }
-
- private void setupPassword() {
- assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111"));
- assertTrue(mAndroidKeyStore.isUnlocked());
-
- String[] aliases = mAndroidKeyStore.list("");
- assertNotNull(aliases);
- assertEquals(0, aliases.length);
- }
-
- public void testKeyPairGenerator_Initialize_Params_Encrypted_Success() throws Exception {
- setupPassword();
-
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .setEncryptionRequired()
- .build());
- }
-
- public void testKeyPairGenerator_Initialize_KeySize_Encrypted_Failure() throws Exception {
- setupPassword();
-
- try {
- mGenerator.initialize(1024);
- fail("KeyPairGenerator should not support setting the key size");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testKeyPairGenerator_Initialize_KeySizeAndSecureRandom_Encrypted_Failure()
- throws Exception {
- setupPassword();
-
- try {
- mGenerator.initialize(1024, new SecureRandom());
- fail("KeyPairGenerator should not support setting the key size");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testKeyPairGenerator_Initialize_ParamsAndSecureRandom_Encrypted_Failure()
- throws Exception {
- setupPassword();
-
- mGenerator.initialize(
- new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setKeyType("RSA")
- .setKeySize(1024)
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .setEncryptionRequired()
- .build(),
- new SecureRandom());
- }
-
- public void testKeyPairGenerator_GenerateKeyPair_Encrypted_Success() throws Exception {
- setupPassword();
-
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .setEncryptionRequired()
- .build());
-
- final KeyPair pair = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair);
-
- assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, NOW,
- NOW_PLUS_10_YEARS);
- }
-
- public void testKeyPairGenerator_GenerateKeyPair_EC_Unencrypted_Success() throws Exception {
- KeyPairGenerator generator = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
- generator.initialize(new KeyGenParameterSpec.Builder(
- TEST_ALIAS_1,
- KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
- .setCertificateSubject(TEST_DN_1)
- .setCertificateSerialNumber(TEST_SERIAL_1)
- .setCertificateNotBefore(NOW)
- .setCertificateNotAfter(NOW_PLUS_10_YEARS)
- .setDigests(KeyProperties.DIGEST_SHA256)
- .build());
-
- final KeyPair pair = generator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair);
-
- assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 256, null, TEST_DN_1, TEST_SERIAL_1, NOW,
- NOW_PLUS_10_YEARS);
- }
-
- public void testKeyPairGenerator_Legacy_GenerateKeyPair_EC_Unencrypted_Success()
- throws Exception {
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setKeyType("EC")
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .build());
-
- final KeyPair pair = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair);
-
- assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 256, null, TEST_DN_1, TEST_SERIAL_1, NOW,
- NOW_PLUS_10_YEARS);
- }
-
- public void testKeyPairGenerator_GenerateKeyPair_EC_P521_Unencrypted_Success() throws Exception {
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setKeyType("EC")
- .setKeySize(521)
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .build());
-
- final KeyPair pair = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair);
-
- assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 521, null, TEST_DN_1, TEST_SERIAL_1, NOW,
- NOW_PLUS_10_YEARS);
- }
-
- public void testKeyPairGenerator_GenerateKeyPair_RSA_Unencrypted_Success() throws Exception {
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .build());
-
- final KeyPair pair = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair);
-
- assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, NOW,
- NOW_PLUS_10_YEARS);
- }
-
- public void testKeyPairGenerator_GenerateKeyPair_RSA_WithParams_Unencrypted_Success()
- throws Exception {
- AlgorithmParameterSpec spec = new RSAKeyGenParameterSpec(1024, BigInteger.valueOf(3L));
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setKeySize(1024)
- .setAlgorithmParameterSpec(spec)
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .build());
-
- final KeyPair pair = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair);
-
- assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 1024, spec, TEST_DN_1, TEST_SERIAL_1, NOW,
- NOW_PLUS_10_YEARS);
- }
-
- public void testKeyPairGenerator_GenerateKeyPair_Replaced_Success() throws Exception {
- // Generate the first key
- {
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .build());
- final KeyPair pair1 = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair1);
- assertKeyPairCorrect(pair1, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1,
- NOW, NOW_PLUS_10_YEARS);
- }
-
- // Replace the original key
- {
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_2)
- .setSubject(TEST_DN_2)
- .setSerialNumber(TEST_SERIAL_2)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .build());
- final KeyPair pair2 = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair2);
- assertKeyPairCorrect(pair2, TEST_ALIAS_2, "RSA", 2048, null, TEST_DN_2, TEST_SERIAL_2,
- NOW, NOW_PLUS_10_YEARS);
- }
- }
-
- public void testKeyPairGenerator_GenerateKeyPair_Replaced_UnencryptedToEncrypted_Success()
- throws Exception {
- // Generate the first key
- {
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .build());
- final KeyPair pair1 = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair1);
- assertKeyPairCorrect(pair1, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1,
- NOW, NOW_PLUS_10_YEARS);
- }
-
- // Attempt to replace previous key
- {
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setSubject(TEST_DN_2)
- .setSerialNumber(TEST_SERIAL_2)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .setEncryptionRequired()
- .build());
- try {
- mGenerator.generateKeyPair();
- fail("Should not be able to generate encrypted key while not initialized");
- } catch (IllegalStateException expected) {
- }
-
- assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111"));
- assertTrue(mAndroidKeyStore.isUnlocked());
-
- final KeyPair pair2 = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair2);
- assertKeyPairCorrect(pair2, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_2, TEST_SERIAL_2,
- NOW, NOW_PLUS_10_YEARS);
- }
- }
-
- private void assertKeyPairCorrect(KeyPair pair, String alias, String keyType, int keySize,
- AlgorithmParameterSpec spec, X500Principal dn, BigInteger serial, Date start, Date end)
- throws Exception {
- final PublicKey pubKey = pair.getPublic();
- assertNotNull("The PublicKey for the KeyPair should be not null", pubKey);
- assertEquals(keyType, pubKey.getAlgorithm());
-
- if ("EC".equalsIgnoreCase(keyType)) {
- assertEquals("Curve should be what was specified during initialization", keySize,
- ((ECPublicKey) pubKey).getParams().getCurve().getField().getFieldSize());
- } else if ("RSA".equalsIgnoreCase(keyType)) {
- RSAPublicKey rsaPubKey = (RSAPublicKey) pubKey;
- assertEquals("Modulus size should be what is specified during initialization",
- (keySize + 7) & ~7, (rsaPubKey.getModulus().bitLength() + 7) & ~7);
- if (spec != null) {
- RSAKeyGenParameterSpec params = (RSAKeyGenParameterSpec) spec;
- assertEquals((keySize + 7) & ~7, (params.getKeysize() + 7) & ~7);
- assertEquals(params.getPublicExponent(), rsaPubKey.getPublicExponent());
- }
- }
-
- final PrivateKey privKey = pair.getPrivate();
- assertNotNull("The PrivateKey for the KeyPair should be not null", privKey);
- assertEquals(keyType, privKey.getAlgorithm());
-
- if ("EC".equalsIgnoreCase(keyType)) {
- assertTrue("EC private key must be instanceof ECKey: " + privKey.getClass().getName(),
- privKey instanceof ECKey);
- assertEquals("Private and public key must have the same EC parameters",
- ((ECKey) pubKey).getParams(), ((ECKey) privKey).getParams());
- } else if ("RSA".equalsIgnoreCase(keyType)) {
- assertTrue("RSA private key must be instance of RSAKey: "
- + privKey.getClass().getName(),
- privKey instanceof RSAKey);
- assertEquals("Private and public key must have the same RSA modulus",
- ((RSAKey) pubKey).getModulus(), ((RSAKey) privKey).getModulus());
- }
-
- final byte[] userCertBytes = mAndroidKeyStore.get(Credentials.USER_CERTIFICATE + alias);
- assertNotNull("The user certificate should exist for the generated entry", userCertBytes);
-
- final CertificateFactory cf = CertificateFactory.getInstance("X.509");
- final Certificate userCert =
- cf.generateCertificate(new ByteArrayInputStream(userCertBytes));
-
- assertTrue("Certificate should be in X.509 format", userCert instanceof X509Certificate);
-
- final X509Certificate x509userCert = (X509Certificate) userCert;
-
- assertEquals(
- "Public key used to sign certificate should have the same algorithm as in KeyPair",
- pubKey.getAlgorithm(), x509userCert.getPublicKey().getAlgorithm());
-
- assertEquals("PublicKey used to sign certificate should match one returned in KeyPair",
- pubKey,
- AndroidKeyStoreProvider.getAndroidKeyStorePublicKey(
- Credentials.USER_PRIVATE_KEY + alias,
- KeyStore.UID_SELF,
- x509userCert.getPublicKey().getAlgorithm(),
- x509userCert.getPublicKey().getEncoded()));
-
- assertEquals("The Subject DN should be the one passed into the params", dn,
- x509userCert.getSubjectDN());
-
- assertEquals("The Issuer DN should be the same as the Subject DN", dn,
- x509userCert.getIssuerDN());
-
- assertEquals("The Serial should be the one passed into the params", serial,
- x509userCert.getSerialNumber());
-
- assertDateEquals("The notBefore date should be the one passed into the params", start,
- x509userCert.getNotBefore());
-
- assertDateEquals("The notAfter date should be the one passed into the params", end,
- x509userCert.getNotAfter());
-
- // Assert that the cert's signature verifies using the public key from generated KeyPair
- x509userCert.verify(pubKey);
- // Assert that the cert's signature verifies using the public key from the cert itself.
- x509userCert.verify(x509userCert.getPublicKey());
-
- final byte[] caCerts = mAndroidKeyStore.get(Credentials.CA_CERTIFICATE + alias);
- assertNull("A list of CA certificates should not exist for the generated entry", caCerts);
-
- ExportResult exportResult = mAndroidKeyStore.exportKey(
- Credentials.USER_PRIVATE_KEY + alias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null);
- assertEquals(KeyStore.NO_ERROR, exportResult.resultCode);
- final byte[] pubKeyBytes = exportResult.exportData;
- assertNotNull("The keystore should return the public key for the generated key",
- pubKeyBytes);
- assertTrue("Public key X.509 format should be as expected",
- Arrays.equals(pubKey.getEncoded(), pubKeyBytes));
- }
-
- private static void assertDateEquals(String message, Date date1, Date date2) throws Exception {
- SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy HH:mm:ss");
-
- String result1 = formatter.format(date1);
- String result2 = formatter.format(date2);
-
- assertEquals(message, result1, result2);
- }
-}
diff --git a/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java b/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java
deleted file mode 100644
index aa718dc..0000000
--- a/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java
+++ /dev/null
@@ -1,2210 +0,0 @@
-/*
- * Copyright (C) 2012 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.security.keystore;
-
-import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
-
-import com.android.org.conscrypt.NativeConstants;
-
-import android.security.Credentials;
-import android.security.KeyStore;
-import android.security.KeyStoreParameter;
-import android.test.AndroidTestCase;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.OutputStream;
-import java.math.BigInteger;
-import java.security.Key;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyStore.Entry;
-import java.security.KeyStore.PrivateKeyEntry;
-import java.security.KeyStore.TrustedCertificateEntry;
-import java.security.KeyStoreException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.interfaces.ECKey;
-import java.security.interfaces.RSAKey;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import javax.security.auth.x500.X500Principal;
-
-public class AndroidKeyStoreTest extends AndroidTestCase {
- private android.security.KeyStore mAndroidKeyStore;
-
- private java.security.KeyStore mKeyStore;
-
- private static final String TEST_ALIAS_1 = "test1";
-
- private static final String TEST_ALIAS_2 = "test2";
-
- private static final String TEST_ALIAS_3 = "test3";
-
- private static final X500Principal TEST_DN_1 = new X500Principal("CN=test1");
-
- private static final X500Principal TEST_DN_2 = new X500Principal("CN=test2");
-
- private static final BigInteger TEST_SERIAL_1 = BigInteger.ONE;
-
- private static final BigInteger TEST_SERIAL_2 = BigInteger.valueOf(2L);
-
- private static final long NOW_MILLIS = System.currentTimeMillis();
-
- /* We have to round this off because X509v3 doesn't store milliseconds. */
- private static final Date NOW = new Date(NOW_MILLIS - (NOW_MILLIS % 1000L));
-
- @SuppressWarnings("deprecation")
- private static final Date NOW_PLUS_10_YEARS = new Date(NOW.getYear() + 10, 0, 1);
-
- /*
- * The keys and certificates below are generated with:
- *
- * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
- * openssl req -newkey rsa:1024 -keyout userkey.pem -nodes -days 3650 -out userkey.req
- * mkdir -p demoCA/newcerts
- * touch demoCA/index.txt
- * echo "01" > demoCA/serial
- * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650
- */
-
- /**
- * Generated from above and converted with:
- *
- * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
- */
- private static final byte[] FAKE_RSA_CA_1 = {
- (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0xce, (byte) 0x30, (byte) 0x82,
- (byte) 0x02, (byte) 0x37, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
- (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xe1, (byte) 0x6a,
- (byte) 0xa2, (byte) 0xf4, (byte) 0x2e, (byte) 0x55, (byte) 0x48, (byte) 0x0a,
- (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
- (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
- (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x4f, (byte) 0x31,
- (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53,
- (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43,
- (byte) 0x41, (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d,
- (byte) 0x4d, (byte) 0x6f, (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61,
- (byte) 0x69, (byte) 0x6e, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65,
- (byte) 0x77, (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12,
- (byte) 0x41, (byte) 0x6e, (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69,
- (byte) 0x64, (byte) 0x20, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74,
- (byte) 0x20, (byte) 0x43, (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73,
- (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x32,
- (byte) 0x30, (byte) 0x38, (byte) 0x31, (byte) 0x34, (byte) 0x31, (byte) 0x36,
- (byte) 0x35, (byte) 0x35, (byte) 0x34, (byte) 0x34, (byte) 0x5a, (byte) 0x17,
- (byte) 0x0d, (byte) 0x32, (byte) 0x32, (byte) 0x30, (byte) 0x38, (byte) 0x31,
- (byte) 0x32, (byte) 0x31, (byte) 0x36, (byte) 0x35, (byte) 0x35, (byte) 0x34,
- (byte) 0x34, (byte) 0x5a, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b,
- (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
- (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31,
- (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41,
- (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d,
- (byte) 0x6f, (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69,
- (byte) 0x6e, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77,
- (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41,
- (byte) 0x6e, (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64,
- (byte) 0x20, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20,
- (byte) 0x43, (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x30,
- (byte) 0x81, (byte) 0x9f, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09,
- (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d,
- (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03,
- (byte) 0x81, (byte) 0x8d, (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89,
- (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xa3, (byte) 0x72,
- (byte) 0xab, (byte) 0xd0, (byte) 0xe4, (byte) 0xad, (byte) 0x2f, (byte) 0xe7,
- (byte) 0xe2, (byte) 0x79, (byte) 0x07, (byte) 0x36, (byte) 0x3d, (byte) 0x0c,
- (byte) 0x8d, (byte) 0x42, (byte) 0x9a, (byte) 0x0a, (byte) 0x33, (byte) 0x64,
- (byte) 0xb3, (byte) 0xcd, (byte) 0xb2, (byte) 0xd7, (byte) 0x3a, (byte) 0x42,
- (byte) 0x06, (byte) 0x77, (byte) 0x45, (byte) 0x29, (byte) 0xe9, (byte) 0xcb,
- (byte) 0xb7, (byte) 0x4a, (byte) 0xd6, (byte) 0xee, (byte) 0xad, (byte) 0x01,
- (byte) 0x91, (byte) 0x9b, (byte) 0x0c, (byte) 0x59, (byte) 0xa1, (byte) 0x03,
- (byte) 0xfa, (byte) 0xf0, (byte) 0x5a, (byte) 0x7c, (byte) 0x4f, (byte) 0xf7,
- (byte) 0x8d, (byte) 0x36, (byte) 0x0f, (byte) 0x1f, (byte) 0x45, (byte) 0x7d,
- (byte) 0x1b, (byte) 0x31, (byte) 0xa1, (byte) 0x35, (byte) 0x0b, (byte) 0x00,
- (byte) 0xed, (byte) 0x7a, (byte) 0xb6, (byte) 0xc8, (byte) 0x4e, (byte) 0xa9,
- (byte) 0x86, (byte) 0x4c, (byte) 0x7b, (byte) 0x99, (byte) 0x57, (byte) 0x41,
- (byte) 0x12, (byte) 0xef, (byte) 0x6b, (byte) 0xbc, (byte) 0x3d, (byte) 0x60,
- (byte) 0xf2, (byte) 0x99, (byte) 0x1a, (byte) 0xcd, (byte) 0xed, (byte) 0x56,
- (byte) 0xa4, (byte) 0xe5, (byte) 0x36, (byte) 0x9f, (byte) 0x24, (byte) 0x1f,
- (byte) 0xdc, (byte) 0x89, (byte) 0x40, (byte) 0xc8, (byte) 0x99, (byte) 0x92,
- (byte) 0xab, (byte) 0x4a, (byte) 0xb5, (byte) 0x61, (byte) 0x45, (byte) 0x62,
- (byte) 0xff, (byte) 0xa3, (byte) 0x45, (byte) 0x65, (byte) 0xaf, (byte) 0xf6,
- (byte) 0x27, (byte) 0x30, (byte) 0x51, (byte) 0x0e, (byte) 0x0e, (byte) 0xeb,
- (byte) 0x79, (byte) 0x0c, (byte) 0xbe, (byte) 0xb3, (byte) 0x0a, (byte) 0x6f,
- (byte) 0x29, (byte) 0x06, (byte) 0xdc, (byte) 0x2f, (byte) 0x6b, (byte) 0x51,
- (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3,
- (byte) 0x81, (byte) 0xb1, (byte) 0x30, (byte) 0x81, (byte) 0xae, (byte) 0x30,
- (byte) 0x1d, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e,
- (byte) 0x04, (byte) 0x16, (byte) 0x04, (byte) 0x14, (byte) 0x33, (byte) 0x05,
- (byte) 0xee, (byte) 0xfe, (byte) 0x6f, (byte) 0x60, (byte) 0xc7, (byte) 0xf9,
- (byte) 0xa9, (byte) 0xd2, (byte) 0x73, (byte) 0x5c, (byte) 0x8f, (byte) 0x6d,
- (byte) 0xa2, (byte) 0x2f, (byte) 0x97, (byte) 0x8e, (byte) 0x5d, (byte) 0x51,
- (byte) 0x30, (byte) 0x7f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d,
- (byte) 0x23, (byte) 0x04, (byte) 0x78, (byte) 0x30, (byte) 0x76, (byte) 0x80,
- (byte) 0x14, (byte) 0x33, (byte) 0x05, (byte) 0xee, (byte) 0xfe, (byte) 0x6f,
- (byte) 0x60, (byte) 0xc7, (byte) 0xf9, (byte) 0xa9, (byte) 0xd2, (byte) 0x73,
- (byte) 0x5c, (byte) 0x8f, (byte) 0x6d, (byte) 0xa2, (byte) 0x2f, (byte) 0x97,
- (byte) 0x8e, (byte) 0x5d, (byte) 0x51, (byte) 0xa1, (byte) 0x53, (byte) 0xa4,
- (byte) 0x51, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
- (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06,
- (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b,
- (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
- (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31,
- (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d, (byte) 0x6f,
- (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69, (byte) 0x6e,
- (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77, (byte) 0x31,
- (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e,
- (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20,
- (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43,
- (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x82, (byte) 0x09,
- (byte) 0x00, (byte) 0xe1, (byte) 0x6a, (byte) 0xa2, (byte) 0xf4, (byte) 0x2e,
- (byte) 0x55, (byte) 0x48, (byte) 0x0a, (byte) 0x30, (byte) 0x0c, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x05,
- (byte) 0x30, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0xff, (byte) 0x30,
- (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48,
- (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05,
- (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x81, (byte) 0x00,
- (byte) 0x8c, (byte) 0x30, (byte) 0x42, (byte) 0xfa, (byte) 0xeb, (byte) 0x1a,
- (byte) 0x26, (byte) 0xeb, (byte) 0xda, (byte) 0x56, (byte) 0x32, (byte) 0xf2,
- (byte) 0x9d, (byte) 0xa5, (byte) 0x24, (byte) 0xd8, (byte) 0x3a, (byte) 0xda,
- (byte) 0x30, (byte) 0xa6, (byte) 0x8b, (byte) 0x46, (byte) 0xfe, (byte) 0xfe,
- (byte) 0xdb, (byte) 0xf1, (byte) 0xe6, (byte) 0xe1, (byte) 0x7c, (byte) 0x1b,
- (byte) 0xe7, (byte) 0x77, (byte) 0x00, (byte) 0xa1, (byte) 0x1c, (byte) 0x19,
- (byte) 0x17, (byte) 0x73, (byte) 0xb0, (byte) 0xf0, (byte) 0x9d, (byte) 0xf3,
- (byte) 0x4f, (byte) 0xb6, (byte) 0xbc, (byte) 0xc7, (byte) 0x47, (byte) 0x85,
- (byte) 0x2a, (byte) 0x4a, (byte) 0xa1, (byte) 0xa5, (byte) 0x58, (byte) 0xf5,
- (byte) 0xc5, (byte) 0x1a, (byte) 0x51, (byte) 0xb1, (byte) 0x04, (byte) 0x80,
- (byte) 0xee, (byte) 0x3a, (byte) 0xec, (byte) 0x2f, (byte) 0xe1, (byte) 0xfd,
- (byte) 0x58, (byte) 0xeb, (byte) 0xed, (byte) 0x82, (byte) 0x9e, (byte) 0x38,
- (byte) 0xa3, (byte) 0x24, (byte) 0x75, (byte) 0xf7, (byte) 0x3e, (byte) 0xc2,
- (byte) 0xc5, (byte) 0x27, (byte) 0xeb, (byte) 0x6f, (byte) 0x7b, (byte) 0x50,
- (byte) 0xda, (byte) 0x43, (byte) 0xdc, (byte) 0x3b, (byte) 0x0b, (byte) 0x6f,
- (byte) 0x78, (byte) 0x8f, (byte) 0xb0, (byte) 0x66, (byte) 0xe1, (byte) 0x12,
- (byte) 0x87, (byte) 0x5f, (byte) 0x97, (byte) 0x7b, (byte) 0xca, (byte) 0x14,
- (byte) 0x79, (byte) 0xf7, (byte) 0xe8, (byte) 0x6c, (byte) 0x72, (byte) 0xdb,
- (byte) 0x91, (byte) 0x65, (byte) 0x17, (byte) 0x54, (byte) 0xe0, (byte) 0x74,
- (byte) 0x1d, (byte) 0xac, (byte) 0x47, (byte) 0x04, (byte) 0x12, (byte) 0xe0,
- (byte) 0xc3, (byte) 0x66, (byte) 0x19, (byte) 0x05, (byte) 0x2e, (byte) 0x7e,
- (byte) 0xf1, (byte) 0x61
- };
-
- /**
- * Generated from above and converted with:
- *
- * openssl pkcs8 -topk8 -outform d -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g'
- */
- private static final byte[] FAKE_RSA_KEY_1 = new byte[] {
- (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x78, (byte) 0x02, (byte) 0x01,
- (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
- (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
- (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x82,
- (byte) 0x02, (byte) 0x62, (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x5e,
- (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x81, (byte) 0x81,
- (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6, (byte) 0x5b,
- (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c, (byte) 0x66,
- (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86, (byte) 0x8a,
- (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3, (byte) 0x02,
- (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08, (byte) 0xf3,
- (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04, (byte) 0x6d,
- (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f, (byte) 0x67,
- (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c, (byte) 0xcb,
- (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30, (byte) 0xe2,
- (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5, (byte) 0x79,
- (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b, (byte) 0xce,
- (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb, (byte) 0x08,
- (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff, (byte) 0x3b,
- (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9, (byte) 0xc4,
- (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29, (byte) 0x0d,
- (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b, (byte) 0x23,
- (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78, (byte) 0x08,
- (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5, (byte) 0xf1,
- (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19, (byte) 0xb4,
- (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03, (byte) 0x16,
- (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce, (byte) 0x9e,
- (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03, (byte) 0x01,
- (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x81, (byte) 0x80, (byte) 0x16,
- (byte) 0x59, (byte) 0xc3, (byte) 0x24, (byte) 0x1d, (byte) 0x33, (byte) 0x98,
- (byte) 0x9c, (byte) 0xc9, (byte) 0xc8, (byte) 0x2c, (byte) 0x88, (byte) 0xbf,
- (byte) 0x0a, (byte) 0x01, (byte) 0xce, (byte) 0xfb, (byte) 0x34, (byte) 0x7a,
- (byte) 0x58, (byte) 0x7a, (byte) 0xb0, (byte) 0xbf, (byte) 0xa6, (byte) 0xb2,
- (byte) 0x60, (byte) 0xbe, (byte) 0x70, (byte) 0x21, (byte) 0xf5, (byte) 0xfc,
- (byte) 0x85, (byte) 0x0d, (byte) 0x33, (byte) 0x58, (byte) 0xa1, (byte) 0xe5,
- (byte) 0x09, (byte) 0x36, (byte) 0x84, (byte) 0xb2, (byte) 0x04, (byte) 0x0a,
- (byte) 0x02, (byte) 0xd3, (byte) 0x88, (byte) 0x1f, (byte) 0x0c, (byte) 0x2b,
- (byte) 0x1d, (byte) 0xe9, (byte) 0x3d, (byte) 0xe7, (byte) 0x79, (byte) 0xf9,
- (byte) 0x32, (byte) 0x5c, (byte) 0x8a, (byte) 0x75, (byte) 0x49, (byte) 0x12,
- (byte) 0xe4, (byte) 0x05, (byte) 0x26, (byte) 0xd4, (byte) 0x2e, (byte) 0x9e,
- (byte) 0x1f, (byte) 0xcc, (byte) 0x54, (byte) 0xad, (byte) 0x33, (byte) 0x8d,
- (byte) 0x99, (byte) 0x00, (byte) 0xdc, (byte) 0xf5, (byte) 0xb4, (byte) 0xa2,
- (byte) 0x2f, (byte) 0xba, (byte) 0xe5, (byte) 0x62, (byte) 0x30, (byte) 0x6d,
- (byte) 0xe6, (byte) 0x3d, (byte) 0xeb, (byte) 0x24, (byte) 0xc2, (byte) 0xdc,
- (byte) 0x5f, (byte) 0xb7, (byte) 0x16, (byte) 0x35, (byte) 0xa3, (byte) 0x98,
- (byte) 0x98, (byte) 0xa8, (byte) 0xef, (byte) 0xe8, (byte) 0xc4, (byte) 0x96,
- (byte) 0x6d, (byte) 0x38, (byte) 0xab, (byte) 0x26, (byte) 0x6d, (byte) 0x30,
- (byte) 0xc2, (byte) 0xa0, (byte) 0x44, (byte) 0xe4, (byte) 0xff, (byte) 0x7e,
- (byte) 0xbe, (byte) 0x7c, (byte) 0x33, (byte) 0xa5, (byte) 0x10, (byte) 0xad,
- (byte) 0xd7, (byte) 0x1e, (byte) 0x13, (byte) 0x20, (byte) 0xb3, (byte) 0x1f,
- (byte) 0x41, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xf1, (byte) 0x89,
- (byte) 0x07, (byte) 0x0f, (byte) 0xe8, (byte) 0xcf, (byte) 0xab, (byte) 0x13,
- (byte) 0x2a, (byte) 0x8f, (byte) 0x88, (byte) 0x80, (byte) 0x11, (byte) 0x9a,
- (byte) 0x79, (byte) 0xb6, (byte) 0x59, (byte) 0x3a, (byte) 0x50, (byte) 0x6e,
- (byte) 0x57, (byte) 0x37, (byte) 0xab, (byte) 0x2a, (byte) 0xd2, (byte) 0xaa,
- (byte) 0xd9, (byte) 0x72, (byte) 0x73, (byte) 0xff, (byte) 0x8b, (byte) 0x47,
- (byte) 0x76, (byte) 0xdd, (byte) 0xdc, (byte) 0xf5, (byte) 0x97, (byte) 0x44,
- (byte) 0x3a, (byte) 0x78, (byte) 0xbe, (byte) 0x17, (byte) 0xb4, (byte) 0x22,
- (byte) 0x6f, (byte) 0xe5, (byte) 0x23, (byte) 0x70, (byte) 0x1d, (byte) 0x10,
- (byte) 0x5d, (byte) 0xba, (byte) 0x16, (byte) 0x81, (byte) 0xf1, (byte) 0x45,
- (byte) 0xce, (byte) 0x30, (byte) 0xb4, (byte) 0xab, (byte) 0x80, (byte) 0xe4,
- (byte) 0x98, (byte) 0x31, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xda,
- (byte) 0x82, (byte) 0x9d, (byte) 0x3f, (byte) 0xca, (byte) 0x2f, (byte) 0xe1,
- (byte) 0xd4, (byte) 0x86, (byte) 0x77, (byte) 0x48, (byte) 0xa6, (byte) 0xab,
- (byte) 0xab, (byte) 0x1c, (byte) 0x42, (byte) 0x5c, (byte) 0xd5, (byte) 0xc7,
- (byte) 0x46, (byte) 0x59, (byte) 0x91, (byte) 0x3f, (byte) 0xfc, (byte) 0xcc,
- (byte) 0xec, (byte) 0xc2, (byte) 0x40, (byte) 0x12, (byte) 0x2c, (byte) 0x8d,
- (byte) 0x1f, (byte) 0xa2, (byte) 0x18, (byte) 0x88, (byte) 0xee, (byte) 0x82,
- (byte) 0x4a, (byte) 0x5a, (byte) 0x5e, (byte) 0x88, (byte) 0x20, (byte) 0xe3,
- (byte) 0x7b, (byte) 0xe0, (byte) 0xd8, (byte) 0x3a, (byte) 0x52, (byte) 0x9a,
- (byte) 0x26, (byte) 0x6a, (byte) 0x04, (byte) 0xec, (byte) 0xe8, (byte) 0xb9,
- (byte) 0x48, (byte) 0x40, (byte) 0xe1, (byte) 0xe1, (byte) 0x83, (byte) 0xa6,
- (byte) 0x67, (byte) 0xa6, (byte) 0xfd, (byte) 0x02, (byte) 0x41, (byte) 0x00,
- (byte) 0x89, (byte) 0x72, (byte) 0x3e, (byte) 0xb0, (byte) 0x90, (byte) 0xfd,
- (byte) 0x4c, (byte) 0x0e, (byte) 0xd6, (byte) 0x13, (byte) 0x63, (byte) 0xcb,
- (byte) 0xed, (byte) 0x38, (byte) 0x88, (byte) 0xb6, (byte) 0x79, (byte) 0xc4,
- (byte) 0x33, (byte) 0x6c, (byte) 0xf6, (byte) 0xf8, (byte) 0xd8, (byte) 0xd0,
- (byte) 0xbf, (byte) 0x9d, (byte) 0x35, (byte) 0xac, (byte) 0x69, (byte) 0xd2,
- (byte) 0x2b, (byte) 0xc1, (byte) 0xf9, (byte) 0x24, (byte) 0x7b, (byte) 0xce,
- (byte) 0xcd, (byte) 0xcb, (byte) 0xa7, (byte) 0xb2, (byte) 0x7a, (byte) 0x0a,
- (byte) 0x27, (byte) 0x19, (byte) 0xc9, (byte) 0xaf, (byte) 0x0d, (byte) 0x21,
- (byte) 0x89, (byte) 0x88, (byte) 0x7c, (byte) 0xad, (byte) 0x9e, (byte) 0x8d,
- (byte) 0x47, (byte) 0x6d, (byte) 0x3f, (byte) 0xce, (byte) 0x7b, (byte) 0xa1,
- (byte) 0x74, (byte) 0xf1, (byte) 0xa0, (byte) 0xa1, (byte) 0x02, (byte) 0x41,
- (byte) 0x00, (byte) 0xd9, (byte) 0xa8, (byte) 0xf5, (byte) 0xfe, (byte) 0xce,
- (byte) 0xe6, (byte) 0x77, (byte) 0x6b, (byte) 0xfe, (byte) 0x2d, (byte) 0xe0,
- (byte) 0x1e, (byte) 0xb6, (byte) 0x2e, (byte) 0x12, (byte) 0x4e, (byte) 0x40,
- (byte) 0xaf, (byte) 0x6a, (byte) 0x7b, (byte) 0x37, (byte) 0x49, (byte) 0x2a,
- (byte) 0x96, (byte) 0x25, (byte) 0x83, (byte) 0x49, (byte) 0xd4, (byte) 0x0c,
- (byte) 0xc6, (byte) 0x78, (byte) 0x25, (byte) 0x24, (byte) 0x90, (byte) 0x90,
- (byte) 0x06, (byte) 0x15, (byte) 0x9e, (byte) 0xfe, (byte) 0xf9, (byte) 0xdf,
- (byte) 0x5b, (byte) 0xf3, (byte) 0x7e, (byte) 0x38, (byte) 0x70, (byte) 0xeb,
- (byte) 0x57, (byte) 0xd0, (byte) 0xd9, (byte) 0xa7, (byte) 0x0e, (byte) 0x14,
- (byte) 0xf7, (byte) 0x95, (byte) 0x68, (byte) 0xd5, (byte) 0xc8, (byte) 0xab,
- (byte) 0x9d, (byte) 0x3a, (byte) 0x2b, (byte) 0x51, (byte) 0xf9, (byte) 0x02,
- (byte) 0x41, (byte) 0x00, (byte) 0x96, (byte) 0xdf, (byte) 0xe9, (byte) 0x67,
- (byte) 0x6c, (byte) 0xdc, (byte) 0x90, (byte) 0x14, (byte) 0xb4, (byte) 0x1d,
- (byte) 0x22, (byte) 0x33, (byte) 0x4a, (byte) 0x31, (byte) 0xc1, (byte) 0x9d,
- (byte) 0x2e, (byte) 0xff, (byte) 0x9a, (byte) 0x2a, (byte) 0x95, (byte) 0x4b,
- (byte) 0x27, (byte) 0x74, (byte) 0xcb, (byte) 0x21, (byte) 0xc3, (byte) 0xd2,
- (byte) 0x0b, (byte) 0xb2, (byte) 0x46, (byte) 0x87, (byte) 0xf8, (byte) 0x28,
- (byte) 0x01, (byte) 0x8b, (byte) 0xd8, (byte) 0xb9, (byte) 0x4b, (byte) 0xcd,
- (byte) 0x9a, (byte) 0x96, (byte) 0x41, (byte) 0x0e, (byte) 0x36, (byte) 0x6d,
- (byte) 0x40, (byte) 0x42, (byte) 0xbc, (byte) 0xd9, (byte) 0xd3, (byte) 0x7b,
- (byte) 0xbc, (byte) 0xa7, (byte) 0x92, (byte) 0x90, (byte) 0xdd, (byte) 0xa1,
- (byte) 0x9c, (byte) 0xce, (byte) 0xa1, (byte) 0x87, (byte) 0x11, (byte) 0x51
- };
-
- /**
- * Generated from above and converted with:
- *
- * openssl x509 -outform d -in usercert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
- */
- private static final byte[] FAKE_RSA_USER_1 = new byte[] {
- (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x95, (byte) 0x30, (byte) 0x82,
- (byte) 0x01, (byte) 0xfe, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
- (byte) 0x02, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x0d,
- (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86,
- (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05,
- (byte) 0x00, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
- (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06,
- (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b,
- (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
- (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31,
- (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d, (byte) 0x6f,
- (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69, (byte) 0x6e,
- (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77, (byte) 0x31,
- (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e,
- (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20,
- (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43,
- (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x30, (byte) 0x1e,
- (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x32, (byte) 0x30, (byte) 0x38,
- (byte) 0x31, (byte) 0x34, (byte) 0x32, (byte) 0x33, (byte) 0x32, (byte) 0x35,
- (byte) 0x34, (byte) 0x38, (byte) 0x5a, (byte) 0x17, (byte) 0x0d, (byte) 0x32,
- (byte) 0x32, (byte) 0x30, (byte) 0x38, (byte) 0x31, (byte) 0x32, (byte) 0x32,
- (byte) 0x33, (byte) 0x32, (byte) 0x35, (byte) 0x34, (byte) 0x38, (byte) 0x5a,
- (byte) 0x30, (byte) 0x55, (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09,
- (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13,
- (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
- (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08,
- (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31, (byte) 0x1b,
- (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
- (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e, (byte) 0x64,
- (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20, (byte) 0x54,
- (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43, (byte) 0x61,
- (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x31, (byte) 0x1c, (byte) 0x30,
- (byte) 0x1a, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03,
- (byte) 0x13, (byte) 0x13, (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76,
- (byte) 0x65, (byte) 0x72, (byte) 0x31, (byte) 0x2e, (byte) 0x65, (byte) 0x78,
- (byte) 0x61, (byte) 0x6d, (byte) 0x70, (byte) 0x6c, (byte) 0x65, (byte) 0x2e,
- (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x30, (byte) 0x81, (byte) 0x9f,
- (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
- (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
- (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x8d,
- (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, (byte) 0x02, (byte) 0x81,
- (byte) 0x81, (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6,
- (byte) 0x5b, (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c,
- (byte) 0x66, (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86,
- (byte) 0x8a, (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3,
- (byte) 0x02, (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08,
- (byte) 0xf3, (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04,
- (byte) 0x6d, (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f,
- (byte) 0x67, (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c,
- (byte) 0xcb, (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30,
- (byte) 0xe2, (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5,
- (byte) 0x79, (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b,
- (byte) 0xce, (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb,
- (byte) 0x08, (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff,
- (byte) 0x3b, (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9,
- (byte) 0xc4, (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29,
- (byte) 0x0d, (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b,
- (byte) 0x23, (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78,
- (byte) 0x08, (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5,
- (byte) 0xf1, (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19,
- (byte) 0xb4, (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03,
- (byte) 0x16, (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce,
- (byte) 0x9e, (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03,
- (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3, (byte) 0x7b, (byte) 0x30,
- (byte) 0x79, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x02, (byte) 0x30, (byte) 0x00,
- (byte) 0x30, (byte) 0x2c, (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86,
- (byte) 0x48, (byte) 0x01, (byte) 0x86, (byte) 0xf8, (byte) 0x42, (byte) 0x01,
- (byte) 0x0d, (byte) 0x04, (byte) 0x1f, (byte) 0x16, (byte) 0x1d, (byte) 0x4f,
- (byte) 0x70, (byte) 0x65, (byte) 0x6e, (byte) 0x53, (byte) 0x53, (byte) 0x4c,
- (byte) 0x20, (byte) 0x47, (byte) 0x65, (byte) 0x6e, (byte) 0x65, (byte) 0x72,
- (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x64, (byte) 0x20, (byte) 0x43,
- (byte) 0x65, (byte) 0x72, (byte) 0x74, (byte) 0x69, (byte) 0x66, (byte) 0x69,
- (byte) 0x63, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x30, (byte) 0x1d,
- (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e, (byte) 0x04,
- (byte) 0x16, (byte) 0x04, (byte) 0x14, (byte) 0x32, (byte) 0xa1, (byte) 0x1e,
- (byte) 0x6b, (byte) 0x69, (byte) 0x04, (byte) 0xfe, (byte) 0xb3, (byte) 0xcd,
- (byte) 0xf8, (byte) 0xbb, (byte) 0x14, (byte) 0xcd, (byte) 0xff, (byte) 0xd4,
- (byte) 0x16, (byte) 0xc3, (byte) 0xab, (byte) 0x44, (byte) 0x2f, (byte) 0x30,
- (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x23,
- (byte) 0x04, (byte) 0x18, (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14,
- (byte) 0x33, (byte) 0x05, (byte) 0xee, (byte) 0xfe, (byte) 0x6f, (byte) 0x60,
- (byte) 0xc7, (byte) 0xf9, (byte) 0xa9, (byte) 0xd2, (byte) 0x73, (byte) 0x5c,
- (byte) 0x8f, (byte) 0x6d, (byte) 0xa2, (byte) 0x2f, (byte) 0x97, (byte) 0x8e,
- (byte) 0x5d, (byte) 0x51, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09,
- (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d,
- (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03,
- (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0x46, (byte) 0x42, (byte) 0xef,
- (byte) 0x56, (byte) 0x89, (byte) 0x78, (byte) 0x90, (byte) 0x38, (byte) 0x24,
- (byte) 0x9f, (byte) 0x8c, (byte) 0x7a, (byte) 0xce, (byte) 0x7a, (byte) 0xa5,
- (byte) 0xb5, (byte) 0x1e, (byte) 0x74, (byte) 0x96, (byte) 0x34, (byte) 0x49,
- (byte) 0x8b, (byte) 0xed, (byte) 0x44, (byte) 0xb3, (byte) 0xc9, (byte) 0x05,
- (byte) 0xd7, (byte) 0x48, (byte) 0x55, (byte) 0x52, (byte) 0x59, (byte) 0x15,
- (byte) 0x0b, (byte) 0xaa, (byte) 0x16, (byte) 0x86, (byte) 0xd2, (byte) 0x8e,
- (byte) 0x16, (byte) 0x99, (byte) 0xe8, (byte) 0x5f, (byte) 0x11, (byte) 0x71,
- (byte) 0x42, (byte) 0x55, (byte) 0xd1, (byte) 0xc4, (byte) 0x6f, (byte) 0x2e,
- (byte) 0xa9, (byte) 0x64, (byte) 0x6f, (byte) 0xd8, (byte) 0xfd, (byte) 0x43,
- (byte) 0x13, (byte) 0x24, (byte) 0xaa, (byte) 0x67, (byte) 0xe6, (byte) 0xf5,
- (byte) 0xca, (byte) 0x80, (byte) 0x5e, (byte) 0x3a, (byte) 0x3e, (byte) 0xcc,
- (byte) 0x4f, (byte) 0xba, (byte) 0x87, (byte) 0xe6, (byte) 0xae, (byte) 0xbf,
- (byte) 0x8f, (byte) 0xd5, (byte) 0x28, (byte) 0x38, (byte) 0x58, (byte) 0x30,
- (byte) 0x24, (byte) 0xf6, (byte) 0x53, (byte) 0x5b, (byte) 0x41, (byte) 0x53,
- (byte) 0xe6, (byte) 0x45, (byte) 0xbc, (byte) 0xbe, (byte) 0xe6, (byte) 0xbb,
- (byte) 0x5d, (byte) 0xd8, (byte) 0xa7, (byte) 0xf9, (byte) 0x64, (byte) 0x99,
- (byte) 0x04, (byte) 0x43, (byte) 0x75, (byte) 0xd7, (byte) 0x2d, (byte) 0x32,
- (byte) 0x0a, (byte) 0x94, (byte) 0xaf, (byte) 0x06, (byte) 0x34, (byte) 0xae,
- (byte) 0x46, (byte) 0xbd, (byte) 0xda, (byte) 0x00, (byte) 0x0e, (byte) 0x25,
- (byte) 0xc2, (byte) 0xf7, (byte) 0xc9, (byte) 0xc3, (byte) 0x65, (byte) 0xd2,
- (byte) 0x08, (byte) 0x41, (byte) 0x0a, (byte) 0xf3, (byte) 0x72
- };
-
- /*
- * The keys and certificates below are generated with:
- *
- * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
- * openssl ecparam -name prime256v1 -out ecparam.pem
- * openssl req -newkey ec:ecparam.pem -keyout userkey.pem -nodes -days 3650 -out userkey.req
- * mkdir -p demoCA/newcerts
- * touch demoCA/index.txt
- * echo "01" > demoCA/serial
- * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650
- */
-
- /**
- * Generated from above and converted with:
- *
- * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
- */
- private static final byte[] FAKE_EC_CA_1 = {
- (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x58, (byte) 0x30, (byte) 0x82,
- (byte) 0x01, (byte) 0xc1, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
- (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xe1, (byte) 0xb2,
- (byte) 0x8c, (byte) 0x04, (byte) 0x95, (byte) 0xeb, (byte) 0x10, (byte) 0xcb,
- (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
- (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
- (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x45, (byte) 0x31,
- (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55,
- (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53,
- (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74,
- (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30,
- (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a,
- (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65,
- (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57,
- (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73,
- (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c,
- (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d,
- (byte) 0x31, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x37,
- (byte) 0x31, (byte) 0x36, (byte) 0x32, (byte) 0x38, (byte) 0x32, (byte) 0x38,
- (byte) 0x5a, (byte) 0x17, (byte) 0x0d, (byte) 0x32, (byte) 0x33, (byte) 0x30,
- (byte) 0x38, (byte) 0x32, (byte) 0x35, (byte) 0x31, (byte) 0x36, (byte) 0x32,
- (byte) 0x38, (byte) 0x32, (byte) 0x38, (byte) 0x5a, (byte) 0x30, (byte) 0x45,
- (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41,
- (byte) 0x55, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a,
- (byte) 0x53, (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53,
- (byte) 0x74, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21,
- (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
- (byte) 0x0a, (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74,
- (byte) 0x65, (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20,
- (byte) 0x57, (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74,
- (byte) 0x73, (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20,
- (byte) 0x4c, (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x81, (byte) 0x9f,
- (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
- (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
- (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x8d,
- (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, (byte) 0x02, (byte) 0x81,
- (byte) 0x81, (byte) 0x00, (byte) 0xb5, (byte) 0xf6, (byte) 0x08, (byte) 0x0f,
- (byte) 0xc4, (byte) 0x4d, (byte) 0xe4, (byte) 0x0d, (byte) 0x34, (byte) 0x1d,
- (byte) 0xe2, (byte) 0x23, (byte) 0x18, (byte) 0x63, (byte) 0x03, (byte) 0xf7,
- (byte) 0x14, (byte) 0x0e, (byte) 0x98, (byte) 0xcd, (byte) 0x45, (byte) 0x1f,
- (byte) 0xfe, (byte) 0xfb, (byte) 0x09, (byte) 0x3f, (byte) 0x5d, (byte) 0x36,
- (byte) 0x3b, (byte) 0x0f, (byte) 0xf9, (byte) 0x5e, (byte) 0x86, (byte) 0x56,
- (byte) 0x64, (byte) 0xd7, (byte) 0x3f, (byte) 0xae, (byte) 0x33, (byte) 0x09,
- (byte) 0xd3, (byte) 0xdd, (byte) 0x06, (byte) 0x17, (byte) 0x26, (byte) 0xdc,
- (byte) 0xa2, (byte) 0x8c, (byte) 0x3c, (byte) 0x65, (byte) 0xed, (byte) 0x03,
- (byte) 0x82, (byte) 0x78, (byte) 0x9b, (byte) 0xee, (byte) 0xe3, (byte) 0x98,
- (byte) 0x58, (byte) 0xe1, (byte) 0xf1, (byte) 0xa0, (byte) 0x85, (byte) 0xae,
- (byte) 0x63, (byte) 0x84, (byte) 0x41, (byte) 0x46, (byte) 0xa7, (byte) 0x4f,
- (byte) 0xdc, (byte) 0xbb, (byte) 0x1c, (byte) 0x6e, (byte) 0xec, (byte) 0x7b,
- (byte) 0xd5, (byte) 0xab, (byte) 0x3d, (byte) 0x6a, (byte) 0x05, (byte) 0x58,
- (byte) 0x0f, (byte) 0x9b, (byte) 0x6a, (byte) 0x67, (byte) 0x4b, (byte) 0xe9,
- (byte) 0x2a, (byte) 0x6d, (byte) 0x96, (byte) 0x11, (byte) 0x53, (byte) 0x95,
- (byte) 0x78, (byte) 0xaa, (byte) 0xd1, (byte) 0x91, (byte) 0x4a, (byte) 0xf8,
- (byte) 0x54, (byte) 0x52, (byte) 0x6d, (byte) 0xb9, (byte) 0xca, (byte) 0x74,
- (byte) 0x81, (byte) 0xf8, (byte) 0x99, (byte) 0x64, (byte) 0xd1, (byte) 0x4f,
- (byte) 0x01, (byte) 0x38, (byte) 0x4f, (byte) 0x08, (byte) 0x5c, (byte) 0x31,
- (byte) 0xcb, (byte) 0x7c, (byte) 0x5c, (byte) 0x78, (byte) 0x5d, (byte) 0x47,
- (byte) 0xd9, (byte) 0xf0, (byte) 0x1a, (byte) 0xeb, (byte) 0x02, (byte) 0x03,
- (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3, (byte) 0x50, (byte) 0x30,
- (byte) 0x4e, (byte) 0x30, (byte) 0x1d, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16, (byte) 0x04, (byte) 0x14,
- (byte) 0x5f, (byte) 0x5b, (byte) 0x5e, (byte) 0xac, (byte) 0x29, (byte) 0xfa,
- (byte) 0xa1, (byte) 0x9f, (byte) 0x9e, (byte) 0xad, (byte) 0x46, (byte) 0xe1,
- (byte) 0xbc, (byte) 0x20, (byte) 0x72, (byte) 0xcf, (byte) 0x4a, (byte) 0xd4,
- (byte) 0xfa, (byte) 0xe3, (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04, (byte) 0x18, (byte) 0x30,
- (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x5f, (byte) 0x5b, (byte) 0x5e,
- (byte) 0xac, (byte) 0x29, (byte) 0xfa, (byte) 0xa1, (byte) 0x9f, (byte) 0x9e,
- (byte) 0xad, (byte) 0x46, (byte) 0xe1, (byte) 0xbc, (byte) 0x20, (byte) 0x72,
- (byte) 0xcf, (byte) 0x4a, (byte) 0xd4, (byte) 0xfa, (byte) 0xe3, (byte) 0x30,
- (byte) 0x0c, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13,
- (byte) 0x04, (byte) 0x05, (byte) 0x30, (byte) 0x03, (byte) 0x01, (byte) 0x01,
- (byte) 0xff, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
- (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
- (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81,
- (byte) 0x81, (byte) 0x00, (byte) 0xa1, (byte) 0x4a, (byte) 0xe6, (byte) 0xfc,
- (byte) 0x7f, (byte) 0x17, (byte) 0xaa, (byte) 0x65, (byte) 0x4a, (byte) 0x34,
- (byte) 0xde, (byte) 0x69, (byte) 0x67, (byte) 0x54, (byte) 0x4d, (byte) 0xa2,
- (byte) 0xc2, (byte) 0x98, (byte) 0x02, (byte) 0x43, (byte) 0x6a, (byte) 0x0e,
- (byte) 0x0b, (byte) 0x7f, (byte) 0xa4, (byte) 0x46, (byte) 0xaf, (byte) 0xa4,
- (byte) 0x65, (byte) 0xa0, (byte) 0xdb, (byte) 0xf1, (byte) 0x5b, (byte) 0xd5,
- (byte) 0x09, (byte) 0xbc, (byte) 0xee, (byte) 0x37, (byte) 0x51, (byte) 0x19,
- (byte) 0x36, (byte) 0xc0, (byte) 0x90, (byte) 0xd3, (byte) 0x5f, (byte) 0xf3,
- (byte) 0x4f, (byte) 0xb9, (byte) 0x08, (byte) 0x45, (byte) 0x0e, (byte) 0x01,
- (byte) 0x8a, (byte) 0x95, (byte) 0xef, (byte) 0x92, (byte) 0x95, (byte) 0x33,
- (byte) 0x78, (byte) 0xdd, (byte) 0x90, (byte) 0xbb, (byte) 0xf3, (byte) 0x06,
- (byte) 0x75, (byte) 0xd0, (byte) 0x66, (byte) 0xe6, (byte) 0xd0, (byte) 0x18,
- (byte) 0x6e, (byte) 0xeb, (byte) 0x1c, (byte) 0x52, (byte) 0xc3, (byte) 0x2e,
- (byte) 0x57, (byte) 0x7d, (byte) 0xa9, (byte) 0x03, (byte) 0xdb, (byte) 0xf4,
- (byte) 0x57, (byte) 0x5f, (byte) 0x6c, (byte) 0x7e, (byte) 0x00, (byte) 0x0d,
- (byte) 0x8f, (byte) 0xe8, (byte) 0x91, (byte) 0xf7, (byte) 0xae, (byte) 0x24,
- (byte) 0x35, (byte) 0x07, (byte) 0xb5, (byte) 0x48, (byte) 0x2d, (byte) 0x36,
- (byte) 0x30, (byte) 0x5d, (byte) 0xe9, (byte) 0x49, (byte) 0x2d, (byte) 0xd1,
- (byte) 0x5d, (byte) 0xc5, (byte) 0xf4, (byte) 0x33, (byte) 0x77, (byte) 0x3c,
- (byte) 0x71, (byte) 0xad, (byte) 0x90, (byte) 0x65, (byte) 0xa9, (byte) 0xc1,
- (byte) 0x0b, (byte) 0x5c, (byte) 0x62, (byte) 0x55, (byte) 0x50, (byte) 0x6f,
- (byte) 0x9b, (byte) 0xc9, (byte) 0x0d, (byte) 0xee
- };
-
- /**
- * Generated from above and converted with:
- *
- * openssl pkcs8 -topk8 -outform d -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g'
- */
- private static final byte[] FAKE_EC_KEY_1 = new byte[] {
- (byte) 0x30, (byte) 0x81, (byte) 0x87, (byte) 0x02, (byte) 0x01, (byte) 0x00,
- (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a, (byte) 0x86,
- (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06,
- (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d,
- (byte) 0x03, (byte) 0x01, (byte) 0x07, (byte) 0x04, (byte) 0x6d, (byte) 0x30,
- (byte) 0x6b, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x04, (byte) 0x20,
- (byte) 0x3a, (byte) 0x8a, (byte) 0x02, (byte) 0xdc, (byte) 0xde, (byte) 0x70,
- (byte) 0x84, (byte) 0x45, (byte) 0x34, (byte) 0xaf, (byte) 0xbd, (byte) 0xd5,
- (byte) 0x02, (byte) 0x17, (byte) 0x69, (byte) 0x90, (byte) 0x65, (byte) 0x1e,
- (byte) 0x87, (byte) 0xf1, (byte) 0x3d, (byte) 0x17, (byte) 0xb6, (byte) 0xf4,
- (byte) 0x31, (byte) 0x94, (byte) 0x86, (byte) 0x76, (byte) 0x55, (byte) 0xf7,
- (byte) 0xcc, (byte) 0xba, (byte) 0xa1, (byte) 0x44, (byte) 0x03, (byte) 0x42,
- (byte) 0x00, (byte) 0x04, (byte) 0xd9, (byte) 0xcf, (byte) 0xe7, (byte) 0x9b,
- (byte) 0x23, (byte) 0xc8, (byte) 0xa3, (byte) 0xb8, (byte) 0x33, (byte) 0x14,
- (byte) 0xa4, (byte) 0x4d, (byte) 0x75, (byte) 0x90, (byte) 0xf3, (byte) 0xcd,
- (byte) 0x43, (byte) 0xe5, (byte) 0x1b, (byte) 0x05, (byte) 0x1d, (byte) 0xf3,
- (byte) 0xd0, (byte) 0xa3, (byte) 0xb7, (byte) 0x32, (byte) 0x5f, (byte) 0x79,
- (byte) 0xdc, (byte) 0x88, (byte) 0xb8, (byte) 0x4d, (byte) 0xb3, (byte) 0xd1,
- (byte) 0x6d, (byte) 0xf7, (byte) 0x75, (byte) 0xf3, (byte) 0xbf, (byte) 0x50,
- (byte) 0xa1, (byte) 0xbc, (byte) 0x03, (byte) 0x64, (byte) 0x22, (byte) 0xe6,
- (byte) 0x1a, (byte) 0xa1, (byte) 0xe1, (byte) 0x06, (byte) 0x68, (byte) 0x3b,
- (byte) 0xbc, (byte) 0x9f, (byte) 0xd3, (byte) 0xae, (byte) 0x77, (byte) 0x5e,
- (byte) 0x88, (byte) 0x0c, (byte) 0x5e, (byte) 0x0c, (byte) 0xb2, (byte) 0x38
- };
-
- /**
- * Generated from above and converted with:
- *
- * openssl x509 -outform d -in usercert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
- */
- private static final byte[] FAKE_EC_USER_1 = new byte[] {
- (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x51, (byte) 0x30, (byte) 0x82,
- (byte) 0x01, (byte) 0xba, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
- (byte) 0x02, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x0d,
- (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86,
- (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05,
- (byte) 0x00, (byte) 0x30, (byte) 0x45, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
- (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06,
- (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, (byte) 0x31, (byte) 0x13,
- (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
- (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, (byte) 0x6f, (byte) 0x6d,
- (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, (byte) 0x61, (byte) 0x74,
- (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, (byte) 0x1f, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x0c, (byte) 0x18,
- (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x6e,
- (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, (byte) 0x69, (byte) 0x64,
- (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, (byte) 0x20, (byte) 0x50,
- (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, (byte) 0x74, (byte) 0x64,
- (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x33,
- (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x37, (byte) 0x31, (byte) 0x36,
- (byte) 0x33, (byte) 0x30, (byte) 0x30, (byte) 0x38, (byte) 0x5a, (byte) 0x17,
- (byte) 0x0d, (byte) 0x32, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32,
- (byte) 0x35, (byte) 0x31, (byte) 0x36, (byte) 0x33, (byte) 0x30, (byte) 0x30,
- (byte) 0x38, (byte) 0x5a, (byte) 0x30, (byte) 0x62, (byte) 0x31, (byte) 0x0b,
- (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
- (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, (byte) 0x31,
- (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, (byte) 0x6f,
- (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, (byte) 0x61,
- (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, (byte) 0x1f,
- (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x0c,
- (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, (byte) 0x72,
- (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, (byte) 0x69,
- (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, (byte) 0x20,
- (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, (byte) 0x74,
- (byte) 0x64, (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x0c, (byte) 0x12,
- (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76, (byte) 0x65, (byte) 0x72,
- (byte) 0x2e, (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
- (byte) 0x6c, (byte) 0x65, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, (byte) 0x6d,
- (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07,
- (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02,
- (byte) 0x01, (byte) 0x06, (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48,
- (byte) 0xce, (byte) 0x3d, (byte) 0x03, (byte) 0x01, (byte) 0x07, (byte) 0x03,
- (byte) 0x42, (byte) 0x00, (byte) 0x04, (byte) 0xd9, (byte) 0xcf, (byte) 0xe7,
- (byte) 0x9b, (byte) 0x23, (byte) 0xc8, (byte) 0xa3, (byte) 0xb8, (byte) 0x33,
- (byte) 0x14, (byte) 0xa4, (byte) 0x4d, (byte) 0x75, (byte) 0x90, (byte) 0xf3,
- (byte) 0xcd, (byte) 0x43, (byte) 0xe5, (byte) 0x1b, (byte) 0x05, (byte) 0x1d,
- (byte) 0xf3, (byte) 0xd0, (byte) 0xa3, (byte) 0xb7, (byte) 0x32, (byte) 0x5f,
- (byte) 0x79, (byte) 0xdc, (byte) 0x88, (byte) 0xb8, (byte) 0x4d, (byte) 0xb3,
- (byte) 0xd1, (byte) 0x6d, (byte) 0xf7, (byte) 0x75, (byte) 0xf3, (byte) 0xbf,
- (byte) 0x50, (byte) 0xa1, (byte) 0xbc, (byte) 0x03, (byte) 0x64, (byte) 0x22,
- (byte) 0xe6, (byte) 0x1a, (byte) 0xa1, (byte) 0xe1, (byte) 0x06, (byte) 0x68,
- (byte) 0x3b, (byte) 0xbc, (byte) 0x9f, (byte) 0xd3, (byte) 0xae, (byte) 0x77,
- (byte) 0x5e, (byte) 0x88, (byte) 0x0c, (byte) 0x5e, (byte) 0x0c, (byte) 0xb2,
- (byte) 0x38, (byte) 0xa3, (byte) 0x7b, (byte) 0x30, (byte) 0x79, (byte) 0x30,
- (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13,
- (byte) 0x04, (byte) 0x02, (byte) 0x30, (byte) 0x00, (byte) 0x30, (byte) 0x2c,
- (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01,
- (byte) 0x86, (byte) 0xf8, (byte) 0x42, (byte) 0x01, (byte) 0x0d, (byte) 0x04,
- (byte) 0x1f, (byte) 0x16, (byte) 0x1d, (byte) 0x4f, (byte) 0x70, (byte) 0x65,
- (byte) 0x6e, (byte) 0x53, (byte) 0x53, (byte) 0x4c, (byte) 0x20, (byte) 0x47,
- (byte) 0x65, (byte) 0x6e, (byte) 0x65, (byte) 0x72, (byte) 0x61, (byte) 0x74,
- (byte) 0x65, (byte) 0x64, (byte) 0x20, (byte) 0x43, (byte) 0x65, (byte) 0x72,
- (byte) 0x74, (byte) 0x69, (byte) 0x66, (byte) 0x69, (byte) 0x63, (byte) 0x61,
- (byte) 0x74, (byte) 0x65, (byte) 0x30, (byte) 0x1d, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16, (byte) 0x04,
- (byte) 0x14, (byte) 0xd5, (byte) 0xc4, (byte) 0x72, (byte) 0xbd, (byte) 0xd2,
- (byte) 0x4e, (byte) 0x90, (byte) 0x1b, (byte) 0x14, (byte) 0x32, (byte) 0xdb,
- (byte) 0x03, (byte) 0xae, (byte) 0xfa, (byte) 0x27, (byte) 0x7d, (byte) 0x8d,
- (byte) 0xe4, (byte) 0x80, (byte) 0x58, (byte) 0x30, (byte) 0x1f, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04, (byte) 0x18,
- (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x5f, (byte) 0x5b,
- (byte) 0x5e, (byte) 0xac, (byte) 0x29, (byte) 0xfa, (byte) 0xa1, (byte) 0x9f,
- (byte) 0x9e, (byte) 0xad, (byte) 0x46, (byte) 0xe1, (byte) 0xbc, (byte) 0x20,
- (byte) 0x72, (byte) 0xcf, (byte) 0x4a, (byte) 0xd4, (byte) 0xfa, (byte) 0xe3,
- (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
- (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
- (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x81,
- (byte) 0x00, (byte) 0x43, (byte) 0x99, (byte) 0x9f, (byte) 0x67, (byte) 0x08,
- (byte) 0x43, (byte) 0xd5, (byte) 0x6b, (byte) 0x6f, (byte) 0xd7, (byte) 0x05,
- (byte) 0xd6, (byte) 0x75, (byte) 0x34, (byte) 0x30, (byte) 0xca, (byte) 0x20,
- (byte) 0x47, (byte) 0x61, (byte) 0xa1, (byte) 0x89, (byte) 0xb6, (byte) 0xf1,
- (byte) 0x49, (byte) 0x7b, (byte) 0xd9, (byte) 0xb9, (byte) 0xe8, (byte) 0x1e,
- (byte) 0x29, (byte) 0x74, (byte) 0x0a, (byte) 0x67, (byte) 0xc0, (byte) 0x7d,
- (byte) 0xb8, (byte) 0xe6, (byte) 0x39, (byte) 0xa8, (byte) 0x5e, (byte) 0xc3,
- (byte) 0xb0, (byte) 0xa1, (byte) 0x30, (byte) 0x6a, (byte) 0x1f, (byte) 0x1d,
- (byte) 0xfc, (byte) 0x11, (byte) 0x59, (byte) 0x0b, (byte) 0xb9, (byte) 0xad,
- (byte) 0x3a, (byte) 0x4e, (byte) 0x50, (byte) 0x0a, (byte) 0x61, (byte) 0xdb,
- (byte) 0x75, (byte) 0x6b, (byte) 0xe5, (byte) 0x3f, (byte) 0x8d, (byte) 0xde,
- (byte) 0x28, (byte) 0x68, (byte) 0xb1, (byte) 0x29, (byte) 0x9a, (byte) 0x18,
- (byte) 0x8a, (byte) 0xfc, (byte) 0x3f, (byte) 0x13, (byte) 0x93, (byte) 0x29,
- (byte) 0xed, (byte) 0x22, (byte) 0x7c, (byte) 0xb4, (byte) 0x50, (byte) 0xd5,
- (byte) 0x4d, (byte) 0x32, (byte) 0x4d, (byte) 0x42, (byte) 0x2b, (byte) 0x29,
- (byte) 0x97, (byte) 0x86, (byte) 0xc0, (byte) 0x01, (byte) 0x00, (byte) 0x25,
- (byte) 0xf6, (byte) 0xd3, (byte) 0x2a, (byte) 0xd8, (byte) 0xda, (byte) 0x13,
- (byte) 0x94, (byte) 0x12, (byte) 0x78, (byte) 0x14, (byte) 0x0b, (byte) 0x51,
- (byte) 0xc0, (byte) 0x45, (byte) 0xb4, (byte) 0x02, (byte) 0x37, (byte) 0x98,
- (byte) 0x42, (byte) 0x3c, (byte) 0xcb, (byte) 0x2e, (byte) 0xe4, (byte) 0x38,
- (byte) 0x69, (byte) 0x1b, (byte) 0x72, (byte) 0xf0, (byte) 0xaa, (byte) 0x89,
- (byte) 0x7e, (byte) 0xde, (byte) 0xb2
- };
-
- /**
- * The amount of time to allow before and after expected time for variance
- * in timing tests.
- */
- private static final long SLOP_TIME_MILLIS = 15000L;
-
- @Override
- protected void setUp() throws Exception {
- mAndroidKeyStore = android.security.KeyStore.getInstance();
-
- assertTrue(mAndroidKeyStore.reset());
- assertFalse(mAndroidKeyStore.isUnlocked());
-
- mKeyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
- }
-
- private void setupPassword() {
- assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111"));
- assertTrue(mAndroidKeyStore.isUnlocked());
-
- assertEquals(0, mAndroidKeyStore.list("").length);
- }
-
- private void assertAliases(final String[] expectedAliases) throws KeyStoreException {
- final Enumeration<String> aliases = mKeyStore.aliases();
- int count = 0;
-
- final Set<String> expectedSet = new HashSet<String>();
- expectedSet.addAll(Arrays.asList(expectedAliases));
-
- while (aliases.hasMoreElements()) {
- count++;
- final String alias = aliases.nextElement();
- assertTrue("The alias should be in the expected set", expectedSet.contains(alias));
- expectedSet.remove(alias);
- }
- assertTrue("The expected set and actual set should be exactly equal", expectedSet.isEmpty());
- assertEquals("There should be the correct number of keystore entries",
- expectedAliases.length, count);
- }
-
- public void testKeyStore_Aliases_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertAliases(new String[] {});
-
- assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED,
- null));
-
- assertAliases(new String[] { TEST_ALIAS_1 });
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 });
- }
-
- public void testKeyStore_Aliases_NotInitialized_Encrypted_Failure() throws Exception {
- setupPassword();
-
- try {
- mKeyStore.aliases();
- fail("KeyStore should throw exception when not initialized");
- } catch (KeyStoreException success) {
- }
- }
-
- public void testKeyStore_ContainsAliases_PrivateAndCA_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertAliases(new String[] {});
-
- assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED,
- null));
-
- assertTrue("Should contain generated private key", mKeyStore.containsAlias(TEST_ALIAS_1));
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2));
-
- assertFalse("Should not contain unadded certificate alias",
- mKeyStore.containsAlias(TEST_ALIAS_3));
- }
-
- public void testKeyStore_ContainsAliases_CAOnly_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2));
- }
-
- public void testKeyStore_ContainsAliases_NonExistent_Encrypted_Failure() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertFalse("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_1));
- }
-
- public void testKeyStore_DeleteEntry_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- // TEST_ALIAS_1
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- // TEST_ALIAS_2
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- // TEST_ALIAS_3
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_3, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 });
-
- mKeyStore.deleteEntry(TEST_ALIAS_1);
-
- assertAliases(new String[] { TEST_ALIAS_2, TEST_ALIAS_3 });
-
- mKeyStore.deleteEntry(TEST_ALIAS_3);
-
- assertAliases(new String[] { TEST_ALIAS_2 });
-
- mKeyStore.deleteEntry(TEST_ALIAS_2);
-
- assertAliases(new String[] { });
- }
-
- public void testKeyStore_DeleteEntry_EmptyStore_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- // Should not throw when a non-existent entry is requested for delete.
- mKeyStore.deleteEntry(TEST_ALIAS_1);
- }
-
- public void testKeyStore_DeleteEntry_NonExistent_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- // TEST_ALIAS_1
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- // Should not throw when a non-existent entry is requested for delete.
- mKeyStore.deleteEntry(TEST_ALIAS_2);
- }
-
- public void testKeyStore_GetCertificate_Single_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertAliases(new String[] { TEST_ALIAS_1 });
-
- assertNull("Certificate should not exist in keystore",
- mKeyStore.getCertificate(TEST_ALIAS_2));
-
- Certificate retrieved = mKeyStore.getCertificate(TEST_ALIAS_1);
-
- assertNotNull("Retrieved certificate should not be null", retrieved);
-
- CertificateFactory f = CertificateFactory.getInstance("X.509");
- Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- assertEquals("Actual and retrieved certificates should be the same", actual, retrieved);
- }
-
- public void testKeyStore_GetCertificate_NonExist_Encrypted_Failure() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertNull("Certificate should not exist in keystore",
- mKeyStore.getCertificate(TEST_ALIAS_1));
- }
-
- public void testKeyStore_GetCertificateAlias_CAEntry_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- CertificateFactory f = CertificateFactory.getInstance("X.509");
- Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- assertEquals("Stored certificate alias should be found", TEST_ALIAS_1,
- mKeyStore.getCertificateAlias(actual));
- }
-
- public void testKeyStore_GetCertificateAlias_PrivateKeyEntry_Encrypted_Success()
- throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- CertificateFactory f = CertificateFactory.getInstance("X.509");
- Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-
- assertEquals("Stored certificate alias should be found", TEST_ALIAS_1,
- mKeyStore.getCertificateAlias(actual));
- }
-
- public void testKeyStore_GetCertificateAlias_CAEntry_WithPrivateKeyUsingCA_Encrypted_Success()
- throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- // Insert TrustedCertificateEntry with CA name
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- // Insert PrivateKeyEntry that uses the same CA
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- CertificateFactory f = CertificateFactory.getInstance("X.509");
- Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- assertEquals("Stored certificate alias should be found", TEST_ALIAS_2,
- mKeyStore.getCertificateAlias(actual));
- }
-
- public void testKeyStore_GetCertificateAlias_NonExist_Empty_Encrypted_Failure()
- throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- CertificateFactory f = CertificateFactory.getInstance("X.509");
- Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- assertNull("Stored certificate alias should not be found",
- mKeyStore.getCertificateAlias(actual));
- }
-
- public void testKeyStore_GetCertificateAlias_NonExist_Encrypted_Failure() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- CertificateFactory f = CertificateFactory.getInstance("X.509");
- Certificate userCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-
- assertNull("Stored certificate alias should be found",
- mKeyStore.getCertificateAlias(userCert));
- }
-
- public void testKeyStore_GetCertificateChain_SingleLength_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- CertificateFactory cf = CertificateFactory.getInstance("X.509");
- Certificate[] expected = new Certificate[2];
- expected[0] = cf.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expected[1] = cf.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- Certificate[] actual = mKeyStore.getCertificateChain(TEST_ALIAS_1);
-
- assertNotNull("Returned certificate chain should not be null", actual);
- assertEquals("Returned certificate chain should be correct size", expected.length,
- actual.length);
- assertEquals("First certificate should be user certificate", expected[0], actual[0]);
- assertEquals("Second certificate should be CA certificate", expected[1], actual[1]);
-
- // Negative test when keystore is populated.
- assertNull("Stored certificate alias should not be found",
- mKeyStore.getCertificateChain(TEST_ALIAS_2));
- }
-
- public void testKeyStore_GetCertificateChain_NonExist_Encrypted_Failure() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertNull("Stored certificate alias should not be found",
- mKeyStore.getCertificateChain(TEST_ALIAS_1));
- }
-
- public void testKeyStore_GetCreationDate_PrivateKeyEntry_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- Date now = new Date();
- Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1);
-
- Date expectedAfter = new Date(now.getTime() - SLOP_TIME_MILLIS);
- Date expectedBefore = new Date(now.getTime() + SLOP_TIME_MILLIS);
-
- assertTrue("Time should be close to current time", actual.before(expectedBefore));
- assertTrue("Time should be close to current time", actual.after(expectedAfter));
- }
-
- public void testKeyStore_GetCreationDate_PrivateKeyEntry_Unencrypted_Success() throws Exception {
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_NONE));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-
- Date now = new Date();
- Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1);
-
- Date expectedAfter = new Date(now.getTime() - SLOP_TIME_MILLIS);
- Date expectedBefore = new Date(now.getTime() + SLOP_TIME_MILLIS);
-
- assertTrue("Time should be close to current time", actual.before(expectedBefore));
- assertTrue("Time should be close to current time", actual.after(expectedAfter));
- }
-
- public void testKeyStore_GetCreationDate_CAEntry_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- Date now = new Date();
- Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1);
- assertNotNull("Certificate should be found", actual);
-
- Date expectedAfter = new Date(now.getTime() - SLOP_TIME_MILLIS);
- Date expectedBefore = new Date(now.getTime() + SLOP_TIME_MILLIS);
-
- assertTrue("Time should be close to current time", actual.before(expectedBefore));
- assertTrue("Time should be close to current time", actual.after(expectedAfter));
- }
-
- public void testKeyStore_GetEntry_NullParams_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Entry should exist", entry);
-
- assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
-
- assertPrivateKeyEntryEquals(keyEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
-
- public void testKeyStore_GetEntry_EC_NullParams_Unencrypted_Success() throws Exception {
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_EC_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
- FAKE_EC_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_EC_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-
- Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Entry should exist", entry);
-
- assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
-
- assertPrivateKeyEntryEquals(keyEntry, "EC", FAKE_EC_KEY_1, FAKE_EC_USER_1, FAKE_EC_CA_1);
- }
-
- public void testKeyStore_GetEntry_RSA_NullParams_Unencrypted_Success() throws Exception {
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
- FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-
- Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Entry should exist", entry);
-
- assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
-
- assertPrivateKeyEntryEquals(keyEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
-
- @SuppressWarnings("unchecked")
- private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, String keyType, byte[] key,
- byte[] cert, byte[] ca) throws Exception {
- KeyFactory keyFact = KeyFactory.getInstance(keyType);
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(key));
-
- CertificateFactory certFact = CertificateFactory.getInstance("X.509");
- Certificate expectedCert = certFact.generateCertificate(new ByteArrayInputStream(cert));
-
- final Collection<Certificate> expectedChain;
- if (ca != null) {
- expectedChain = (Collection<Certificate>) certFact
- .generateCertificates(new ByteArrayInputStream(ca));
- } else {
- expectedChain = null;
- }
-
- assertPrivateKeyEntryEquals(keyEntry, expectedKey, expectedCert, expectedChain);
- }
-
- private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, PrivateKey expectedKey,
- Certificate expectedCert, Collection<Certificate> expectedChain) throws Exception {
- if (expectedKey instanceof ECKey) {
- assertEquals("Returned PrivateKey should be what we inserted",
- ((ECKey) expectedKey).getParams().getCurve(),
- ((ECKey) keyEntry.getCertificate().getPublicKey()).getParams().getCurve());
- } else if (expectedKey instanceof RSAKey) {
- assertEquals("Returned PrivateKey should be what we inserted",
- ((RSAKey) expectedKey).getModulus(),
- ((RSAKey) keyEntry.getPrivateKey()).getModulus());
- }
-
- assertEquals("Returned Certificate should be what we inserted", expectedCert,
- keyEntry.getCertificate());
-
- Certificate[] actualChain = keyEntry.getCertificateChain();
-
- assertEquals("First certificate in chain should be user cert", expectedCert, actualChain[0]);
-
- if (expectedChain == null) {
- assertEquals("Certificate chain should not include CAs", 1, actualChain.length);
- } else {
- int i = 1;
- final Iterator<Certificate> it = expectedChain.iterator();
- while (it.hasNext()) {
- assertEquals("CA chain certificate should equal what we put in", it.next(),
- actualChain[i++]);
- }
- }
- }
-
- public void testKeyStore_GetEntry_Nonexistent_NullParams_Encrypted_Failure() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertNull("A non-existent entry should return null",
- mKeyStore.getEntry(TEST_ALIAS_1, null));
- }
-
- public void testKeyStore_GetEntry_Nonexistent_NullParams_Unencrypted_Failure() throws Exception {
- mKeyStore.load(null, null);
-
- assertNull("A non-existent entry should return null",
- mKeyStore.getEntry(TEST_ALIAS_1, null));
- }
-
- public void testKeyStore_GetKey_NoPassword_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
- assertNotNull("Key should exist", key);
-
- assertTrue("Should be a PrivateKey", key instanceof PrivateKey);
- assertTrue("Should be a RSAKey", key instanceof RSAKey);
-
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
- assertEquals("Inserted key should be same as retrieved key",
- ((RSAKey) expectedKey).getModulus(), ((RSAKey) key).getModulus());
- }
-
- public void testKeyStore_GetKey_NoPassword_Unencrypted_Success() throws Exception {
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_NONE));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-
- Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
- assertNotNull("Key should exist", key);
-
- assertTrue("Should be a PrivateKey", key instanceof PrivateKey);
- assertTrue("Should be a RSAKey", key instanceof RSAKey);
-
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
- assertEquals("Inserted key should be same as retrieved key",
- ((RSAKey) expectedKey).getModulus(), ((RSAKey) key).getModulus());
- }
-
- public void testKeyStore_GetKey_Certificate_Encrypted_Failure() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertNull("Certificate entries should return null", mKeyStore.getKey(TEST_ALIAS_1, null));
- }
-
- public void testKeyStore_GetKey_NonExistent_Encrypted_Failure() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertNull("A non-existent entry should return null", mKeyStore.getKey(TEST_ALIAS_1, null));
- }
-
- public void testKeyStore_GetProvider_Encrypted_Success() throws Exception {
- assertEquals(AndroidKeyStoreProvider.PROVIDER_NAME, mKeyStore.getProvider().getName());
- setupPassword();
- assertEquals(AndroidKeyStoreProvider.PROVIDER_NAME, mKeyStore.getProvider().getName());
- }
-
- public void testKeyStore_GetType_Encrypted_Success() throws Exception {
- assertEquals(AndroidKeyStoreSpi.NAME, mKeyStore.getType());
- setupPassword();
- assertEquals(AndroidKeyStoreSpi.NAME, mKeyStore.getType());
- }
-
- public void testKeyStore_IsCertificateEntry_CA_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertTrue("Should return true for CA certificate",
- mKeyStore.isCertificateEntry(TEST_ALIAS_1));
- }
-
- public void testKeyStore_IsCertificateEntry_PrivateKey_Encrypted_Failure() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertFalse("Should return false for PrivateKeyEntry",
- mKeyStore.isCertificateEntry(TEST_ALIAS_1));
- }
-
- public void testKeyStore_IsCertificateEntry_NonExist_Encrypted_Failure() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertFalse("Should return false for non-existent entry",
- mKeyStore.isCertificateEntry(TEST_ALIAS_1));
- }
-
- public void testKeyStore_IsCertificateEntry_NonExist_Unencrypted_Failure() throws Exception {
- mKeyStore.load(null, null);
-
- assertFalse("Should return false for non-existent entry",
- mKeyStore.isCertificateEntry(TEST_ALIAS_1));
- }
-
- public void testKeyStore_IsKeyEntry_PrivateKey_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertTrue("Should return true for PrivateKeyEntry", mKeyStore.isKeyEntry(TEST_ALIAS_1));
- }
-
- public void testKeyStore_IsKeyEntry_CA_Encrypted_Failure() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertFalse("Should return false for CA certificate", mKeyStore.isKeyEntry(TEST_ALIAS_1));
- }
-
- public void testKeyStore_IsKeyEntry_NonExist_Encrypted_Failure() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertFalse("Should return false for non-existent entry",
- mKeyStore.isKeyEntry(TEST_ALIAS_1));
- }
-
- public void testKeyStore_SetCertificate_CA_Encrypted_Success() throws Exception {
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
- final Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- setupPassword();
- mKeyStore.load(null, null);
-
- mKeyStore.setCertificateEntry(TEST_ALIAS_1, actual);
- assertAliases(new String[] { TEST_ALIAS_1 });
-
- Certificate retrieved = mKeyStore.getCertificate(TEST_ALIAS_1);
-
- assertEquals("Retrieved certificate should be the same as the one inserted", actual,
- retrieved);
- }
-
- public void testKeyStore_SetCertificate_CAExists_Overwrite_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertAliases(new String[] { TEST_ALIAS_1 });
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
- final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- // TODO have separate FAKE_CA for second test
- mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert);
-
- assertAliases(new String[] { TEST_ALIAS_1 });
- }
-
- public void testKeyStore_SetCertificate_PrivateKeyExists_Encrypted_Failure() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertAliases(new String[] { TEST_ALIAS_1 });
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
- final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- try {
- mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert);
- fail("Should throw when trying to overwrite a PrivateKey entry with a Certificate");
- } catch (KeyStoreException success) {
- }
- }
-
- public void testKeyStore_SetEntry_PrivateKeyEntry_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
-
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
- assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1);
- }
-
- public void testKeyStore_SetEntry_PrivateKeyEntry_EC_Unencrypted_Success() throws Exception {
- mKeyStore.load(null, null);
-
- KeyFactory keyFact = KeyFactory.getInstance("EC");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_EC_KEY_1));
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_EC_USER_1));
- expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_EC_CA_1));
-
- PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
-
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
- assertPrivateKeyEntryEquals(actual, "EC", FAKE_EC_KEY_1, FAKE_EC_USER_1, FAKE_EC_CA_1);
- }
-
- public void testKeyStore_SetEntry_PrivateKeyEntry_RSA_Unencrypted_Success() throws Exception {
- mKeyStore.load(null, null);
-
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
-
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
- assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1);
- }
-
- public void testKeyStore_SetEntry_PrivateKeyEntry_Params_Unencrypted_Failure() throws Exception {
- mKeyStore.load(null, null);
-
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- PrivateKeyEntry entry = new PrivateKeyEntry(expectedKey, expectedChain);
-
- try {
- mKeyStore.setEntry(TEST_ALIAS_1, entry,
- new KeyStoreParameter.Builder(getContext())
- .setEncryptionRequired(true)
- .build());
- fail("Shouldn't be able to insert encrypted entry when KeyStore uninitialized");
- } catch (KeyStoreException expected) {
- }
-
- assertNull(mKeyStore.getEntry(TEST_ALIAS_1, null));
- }
-
- public void
- testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_PrivateKeyEntry_Encrypted_Success()
- throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- final KeyFactory keyFact = KeyFactory.getInstance("RSA");
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- // Start with PrivateKeyEntry
- {
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
-
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
- assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
-
- // TODO make entirely new test vector for the overwrite
- // Replace with PrivateKeyEntry
- {
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
-
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
- assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
- }
-
- public void testKeyStore_SetEntry_CAEntry_Overwrites_PrivateKeyEntry_Encrypted_Success()
- throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- // Start with TrustedCertificateEntry
- {
- final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert);
- mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
- assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
- actualEntry instanceof TrustedCertificateEntry);
- TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry;
- assertEquals("Stored and retrieved certificates should be the same",
- expectedCertEntry.getTrustedCertificate(),
- actualCertEntry.getTrustedCertificate());
- }
-
- // Replace with PrivateKeyEntry
- {
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
- assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
- }
-
- public void testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_CAEntry_Encrypted_Success()
- throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- // Start with PrivateKeyEntry
- {
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expectedChain[1] = caCert;
-
- PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
- assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
-
- // Replace with TrustedCertificateEntry
- {
- TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert);
- mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
- assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
- actualEntry instanceof TrustedCertificateEntry);
- TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry;
- assertEquals("Stored and retrieved certificates should be the same",
- expectedCertEntry.getTrustedCertificate(),
- actualCertEntry.getTrustedCertificate());
- }
- }
-
- public
- void
- testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_ShortPrivateKeyEntry_Encrypted_Success()
- throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- // Start with PrivateKeyEntry
- {
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expectedChain[1] = caCert;
-
- PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
- assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
-
- // Replace with PrivateKeyEntry that has no chain
- {
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
- final Certificate[] expectedChain = new Certificate[1];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-
- PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
- assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- null);
- }
- }
-
- public void testKeyStore_SetEntry_CAEntry_Overwrites_CAEntry_Encrypted_Success()
- throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- // Insert TrustedCertificateEntry
- {
- final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert);
- mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
- assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
- actualEntry instanceof TrustedCertificateEntry);
- TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry;
- assertEquals("Stored and retrieved certificates should be the same",
- expectedCertEntry.getTrustedCertificate(),
- actualCertEntry.getTrustedCertificate());
- }
-
- // Replace with TrustedCertificateEntry of USER
- {
- final Certificate userCert = f
- .generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-
- TrustedCertificateEntry expectedUserEntry = new TrustedCertificateEntry(userCert);
- mKeyStore.setEntry(TEST_ALIAS_1, expectedUserEntry, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
- assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
- actualEntry instanceof TrustedCertificateEntry);
- TrustedCertificateEntry actualUserEntry = (TrustedCertificateEntry) actualEntry;
- assertEquals("Stored and retrieved certificates should be the same",
- expectedUserEntry.getTrustedCertificate(),
- actualUserEntry.getTrustedCertificate());
- }
- }
-
- public void testKeyStore_SetKeyEntry_ProtectedKey_Encrypted_Failure() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
- final Certificate[] chain = new Certificate[2];
- chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- chain[1] = caCert;
-
- try {
- mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, "foo".toCharArray(), chain);
- fail("Should fail when a password is specified");
- } catch (KeyStoreException success) {
- }
- }
-
- public void testKeyStore_SetKeyEntry_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
- final Certificate[] chain = new Certificate[2];
- chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- chain[1] = caCert;
-
- mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
-
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
- assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1);
- }
-
- public void testKeyStore_SetKeyEntry_Replaced_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- // Insert initial key
- {
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
- final Certificate[] chain = new Certificate[2];
- chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- chain[1] = caCert;
-
- mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
-
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
- assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
-
- // TODO make a separate key
- // Replace key
- {
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
- final Certificate[] chain = new Certificate[2];
- chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- chain[1] = caCert;
-
- mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
-
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
- assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
- }
-
- @SuppressWarnings("deprecation")
- private static X509Certificate generateCertificate(android.security.KeyStore keyStore,
- String alias, BigInteger serialNumber, X500Principal subjectDN, Date notBefore,
- Date notAfter) throws Exception {
- final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
-
- KeyPair keyPair = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
- keyStore, privateKeyAlias, KeyStore.UID_SELF);
-
- final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
- certGen.setPublicKey(keyPair.getPublic());
- certGen.setSerialNumber(serialNumber);
- certGen.setSubjectDN(subjectDN);
- certGen.setIssuerDN(subjectDN);
- certGen.setNotBefore(notBefore);
- certGen.setNotAfter(notAfter);
- certGen.setSignatureAlgorithm("sha1WithRSA");
-
- final X509Certificate cert = certGen.generate(keyPair.getPrivate());
-
- return cert;
- }
-
- public void testKeyStore_SetKeyEntry_ReplacedChain_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- // Create key #1
- {
- final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
- assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF,
- NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null));
-
- Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
-
- assertTrue(key instanceof PrivateKey);
-
- PrivateKey expectedKey = (PrivateKey) key;
-
- X509Certificate expectedCert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1,
- TEST_SERIAL_1, TEST_DN_1, NOW, NOW_PLUS_10_YEARS);
-
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
- expectedCert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-
- assertTrue(entry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
-
- assertPrivateKeyEntryEquals(keyEntry, expectedKey, expectedCert, null);
- }
-
- // Replace key #1 with new chain
- {
- Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
-
- assertTrue(key instanceof PrivateKey);
-
- PrivateKey expectedKey = (PrivateKey) key;
-
- X509Certificate expectedCert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1,
- TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS);
-
- mKeyStore.setKeyEntry(TEST_ALIAS_1, expectedKey, null,
- new Certificate[] { expectedCert });
-
- Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-
- assertTrue(entry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
-
- assertPrivateKeyEntryEquals(keyEntry, expectedKey, expectedCert, null);
- }
- }
-
- public void testKeyStore_SetKeyEntry_ReplacedChain_DifferentPrivateKey_Encrypted_Failure()
- throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- // Create key #1
- {
- final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
- assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF,
- NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null));
-
- X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1,
- TEST_SERIAL_1, TEST_DN_1, NOW, NOW_PLUS_10_YEARS);
-
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
- cert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- }
-
- // Create key #2
- {
- final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_2;
- assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF,
- NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null));
-
- X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_2,
- TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS);
-
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_2,
- cert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- }
-
- // Replace key #1 with key #2
- {
- Key key1 = mKeyStore.getKey(TEST_ALIAS_2, null);
-
- X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_2,
- TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS);
-
- try {
- mKeyStore.setKeyEntry(TEST_ALIAS_1, key1, null, new Certificate[] { cert });
- fail("Should not allow setting of KeyEntry with wrong PrivaetKey");
- } catch (KeyStoreException success) {
- }
- }
- }
-
- public void testKeyStore_SetKeyEntry_ReplacedChain_UnencryptedToEncrypted_Failure()
- throws Exception {
- mKeyStore.load(null, null);
-
- // Create key #1
- {
- final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
- assertTrue(mAndroidKeyStore.generate(privateKeyAlias,
- android.security.KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024,
- android.security.KeyStore.FLAG_NONE, null));
-
- X509Certificate cert =
- generateCertificate(mAndroidKeyStore, TEST_ALIAS_1, TEST_SERIAL_1, TEST_DN_1,
- NOW, NOW_PLUS_10_YEARS);
-
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
- cert.getEncoded(), android.security.KeyStore.UID_SELF,
- android.security.KeyStore.FLAG_NONE));
- }
-
- // Replace with one that requires encryption
- {
- Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-
- try {
- mKeyStore.setEntry(TEST_ALIAS_1, entry,
- new KeyStoreParameter.Builder(getContext())
- .setEncryptionRequired(true)
- .build());
- fail("Should not allow setting of Entry without unlocked keystore");
- } catch (KeyStoreException success) {
- }
-
- assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111"));
- assertTrue(mAndroidKeyStore.isUnlocked());
-
- mKeyStore.setEntry(TEST_ALIAS_1, entry,
- new KeyStoreParameter.Builder(getContext())
- .setEncryptionRequired(true)
- .build());
- }
- }
-
- public void testKeyStore_Size_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertEquals("The keystore size should match expected", 1, mKeyStore.size());
- assertAliases(new String[] { TEST_ALIAS_1 });
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertEquals("The keystore size should match expected", 2, mKeyStore.size());
- assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 });
-
- assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3,
- KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED,
- null));
-
- assertEquals("The keystore size should match expected", 3, mKeyStore.size());
- assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 });
-
- assertTrue(mAndroidKeyStore.delete(Credentials.CA_CERTIFICATE + TEST_ALIAS_1));
-
- assertEquals("The keystore size should match expected", 2, mKeyStore.size());
- assertAliases(new String[] { TEST_ALIAS_2, TEST_ALIAS_3 });
-
- assertTrue(mAndroidKeyStore.delete(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3));
-
- assertEquals("The keystore size should match expected", 1, mKeyStore.size());
- assertAliases(new String[] { TEST_ALIAS_2 });
- }
-
- public void testKeyStore_Store_LoadStoreParam_Encrypted_Failure() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- try {
- mKeyStore.store(null);
- fail("Should throw UnsupportedOperationException when trying to store");
- } catch (UnsupportedOperationException success) {
- }
- }
-
- public void testKeyStore_Load_InputStreamSupplied_Encrypted_Failure() throws Exception {
- byte[] buf = "FAKE KEYSTORE".getBytes();
- ByteArrayInputStream is = new ByteArrayInputStream(buf);
-
- try {
- mKeyStore.load(is, null);
- fail("Should throw IllegalArgumentException when InputStream is supplied");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testKeyStore_Load_PasswordSupplied_Encrypted_Failure() throws Exception {
- try {
- mKeyStore.load(null, "password".toCharArray());
- fail("Should throw IllegalArgumentException when password is supplied");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testKeyStore_Store_OutputStream_Encrypted_Failure() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- OutputStream sink = new ByteArrayOutputStream();
- try {
- mKeyStore.store(sink, null);
- fail("Should throw UnsupportedOperationException when trying to store");
- } catch (UnsupportedOperationException success) {
- }
-
- try {
- mKeyStore.store(sink, "blah".toCharArray());
- fail("Should throw UnsupportedOperationException when trying to store");
- } catch (UnsupportedOperationException success) {
- }
- }
-
- private void setupKey() throws Exception {
- final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
- assertTrue(mAndroidKeyStore
- .generate(privateKeyAlias, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024,
- KeyStore.FLAG_ENCRYPTED, null));
-
- X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1, TEST_SERIAL_1,
- TEST_DN_1, NOW, NOW_PLUS_10_YEARS);
-
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
- cert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- }
-
- public void testKeyStore_KeyOperations_Wrap_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- setupKey();
-
- // Test key usage
- Entry e = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull(e);
- assertTrue(e instanceof PrivateKeyEntry);
-
- PrivateKeyEntry privEntry = (PrivateKeyEntry) e;
- PrivateKey privKey = privEntry.getPrivateKey();
- assertNotNull(privKey);
-
- PublicKey pubKey = privEntry.getCertificate().getPublicKey();
-
- Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
- c.init(Cipher.WRAP_MODE, pubKey);
-
- byte[] expectedKey = new byte[] {
- 0x00, 0x05, (byte) 0xAA, (byte) 0x0A5, (byte) 0xFF, 0x55, 0x0A
- };
-
- SecretKey expectedSecret = new SecretKeySpec(expectedKey, "AES");
-
- byte[] wrappedExpected = c.wrap(expectedSecret);
-
- c.init(Cipher.UNWRAP_MODE, privKey);
- SecretKey actualSecret = (SecretKey) c.unwrap(wrappedExpected, "AES", Cipher.SECRET_KEY);
-
- assertEquals(Arrays.toString(expectedSecret.getEncoded()),
- Arrays.toString(actualSecret.getEncoded()));
- }
-}
diff --git a/legacy-test/Android.mk b/legacy-test/Android.mk
index 05fec5e..8efda2a 100644
--- a/legacy-test/Android.mk
+++ b/legacy-test/Android.mk
@@ -19,10 +19,12 @@
# Build the legacy-test library
# =============================
# This contains the junit.framework and android.test classes that were in
-# Android API level 25.
+# Android API level 25 excluding those from android.test.runner.
+# Also contains the com.android.internal.util.Predicate[s] classes.
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
LOCAL_MODULE := legacy-test
LOCAL_NO_STANDARD_LIBRARIES := true
LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
@@ -30,17 +32,35 @@
include $(BUILD_JAVA_LIBRARY)
# Build the legacy-android-test library
-# =============================
-# This contains the android.test classes that were in Android API level 25.
+# =====================================
+# This contains the android.test classes that were in Android API level 25,
+# including those from android.test.runner.
+# Also contains the com.android.internal.util.Predicate[s] classes.
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-java-files-under, src/android)
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src/android) \
+ $(call all-java-files-under, ../test-runner/src/android) \
+ $(call all-java-files-under, src/com)
LOCAL_MODULE := legacy-android-test
LOCAL_NO_STANDARD_LIBRARIES := true
LOCAL_JAVA_LIBRARIES := core-oj core-libart framework junit
include $(BUILD_STATIC_JAVA_LIBRARY)
+# Build the legacy-android-tests library
+# ======================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, tests)
+LOCAL_MODULE := legacy-android-tests
+LOCAL_NO_STANDARD_LIBRARIES := true
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework junit
+LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
ifeq ($(HOST_OS),linux)
# Build the legacy-performance-test-hostdex library
# =================================================
diff --git a/core/java/com/android/internal/util/Predicate.java b/legacy-test/src/com/android/internal/util/Predicate.java
similarity index 100%
rename from core/java/com/android/internal/util/Predicate.java
rename to legacy-test/src/com/android/internal/util/Predicate.java
diff --git a/legacy-test/src/com/android/internal/util/Predicates.java b/legacy-test/src/com/android/internal/util/Predicates.java
new file mode 100644
index 0000000..fe1ff15
--- /dev/null
+++ b/legacy-test/src/com/android/internal/util/Predicates.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import java.util.Arrays;
+
+/**
+ * Predicates contains static methods for creating the standard set of
+ * {@code Predicate} objects.
+ *
+ * @hide
+ */
+public class Predicates {
+
+ private Predicates() {
+ }
+
+ /**
+ * Returns a Predicate that evaluates to true iff each of its components
+ * evaluates to true. The components are evaluated in order, and evaluation
+ * will be "short-circuited" as soon as the answer is determined.
+ */
+ public static <T> Predicate<T> and(Predicate<? super T>... components) {
+ return Predicates.<T>and(Arrays.asList(components));
+ }
+
+ /**
+ * Returns a Predicate that evaluates to true iff each of its components
+ * evaluates to true. The components are evaluated in order, and evaluation
+ * will be "short-circuited" as soon as the answer is determined. Does not
+ * defensively copy the iterable passed in, so future changes to it will alter
+ * the behavior of this Predicate. If components is empty, the returned
+ * Predicate will always evaluate to true.
+ */
+ public static <T> Predicate<T> and(Iterable<? extends Predicate<? super T>> components) {
+ return new AndPredicate(components);
+ }
+
+ /**
+ * Returns a Predicate that evaluates to true iff any one of its components
+ * evaluates to true. The components are evaluated in order, and evaluation
+ * will be "short-circuited" as soon as the answer is determined.
+ */
+ public static <T> Predicate<T> or(Predicate<? super T>... components) {
+ return Predicates.<T>or(Arrays.asList(components));
+ }
+
+ /**
+ * Returns a Predicate that evaluates to true iff any one of its components
+ * evaluates to true. The components are evaluated in order, and evaluation
+ * will be "short-circuited" as soon as the answer is determined. Does not
+ * defensively copy the iterable passed in, so future changes to it will alter
+ * the behavior of this Predicate. If components is empty, the returned
+ * Predicate will always evaluate to false.
+ */
+ public static <T> Predicate<T> or(Iterable<? extends Predicate<? super T>> components) {
+ return new OrPredicate(components);
+ }
+
+ /**
+ * Returns a Predicate that evaluates to true iff the given Predicate
+ * evaluates to false.
+ */
+ public static <T> Predicate<T> not(Predicate<? super T> predicate) {
+ return new NotPredicate<T>(predicate);
+ }
+
+ private static class AndPredicate<T> implements Predicate<T> {
+ private final Iterable<? extends Predicate<? super T>> components;
+
+ private AndPredicate(Iterable<? extends Predicate<? super T>> components) {
+ this.components = components;
+ }
+
+ public boolean apply(T t) {
+ for (Predicate<? super T> predicate : components) {
+ if (!predicate.apply(t)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private static class OrPredicate<T> implements Predicate<T> {
+ private final Iterable<? extends Predicate<? super T>> components;
+
+ private OrPredicate(Iterable<? extends Predicate<? super T>> components) {
+ this.components = components;
+ }
+
+ public boolean apply(T t) {
+ for (Predicate<? super T> predicate : components) {
+ if (predicate.apply(t)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private static class NotPredicate<T> implements Predicate<T> {
+ private final Predicate<? super T> predicate;
+
+ private NotPredicate(Predicate<? super T> predicate) {
+ this.predicate = predicate;
+ }
+
+ public boolean apply(T t) {
+ return !predicate.apply(t);
+ }
+ }
+}
diff --git a/core/tests/utiltests/src/com/android/internal/util/PredicatesTest.java b/legacy-test/tests/com/android/internal/util/PredicatesTest.java
similarity index 100%
rename from core/tests/utiltests/src/com/android/internal/util/PredicatesTest.java
rename to legacy-test/tests/com/android/internal/util/PredicatesTest.java
diff --git a/libs/androidfw/DisplayEventDispatcher.cpp b/libs/androidfw/DisplayEventDispatcher.cpp
index b8ef9ea..7708e43 100644
--- a/libs/androidfw/DisplayEventDispatcher.cpp
+++ b/libs/androidfw/DisplayEventDispatcher.cpp
@@ -33,8 +33,9 @@
// using just a few large reads.
static const size_t EVENT_BUFFER_SIZE = 100;
-DisplayEventDispatcher::DisplayEventDispatcher(const sp<Looper>& looper) :
- mLooper(looper), mWaitingForVsync(false) {
+DisplayEventDispatcher::DisplayEventDispatcher(const sp<Looper>& looper,
+ ISurfaceComposer::VsyncSource vsyncSource) :
+ mLooper(looper), mReceiver(vsyncSource), mWaitingForVsync(false) {
ALOGV("dispatcher %p ~ Initializing display event dispatcher.", this);
}
diff --git a/libs/androidfw/include/androidfw/DisplayEventDispatcher.h b/libs/androidfw/include/androidfw/DisplayEventDispatcher.h
index 3ade215..e1dfb94 100644
--- a/libs/androidfw/include/androidfw/DisplayEventDispatcher.h
+++ b/libs/androidfw/include/androidfw/DisplayEventDispatcher.h
@@ -22,7 +22,8 @@
class DisplayEventDispatcher : public LooperCallback {
public:
- DisplayEventDispatcher(const sp<Looper>& looper);
+ DisplayEventDispatcher(const sp<Looper>& looper,
+ ISurfaceComposer::VsyncSource vsyncSource = ISurfaceComposer::eVsyncSourceApp);
status_t initialize();
void dispose();
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
new file mode 100644
index 0000000..303d05f
--- /dev/null
+++ b/libs/hwui/Android.bp
@@ -0,0 +1,390 @@
+cc_defaults {
+ name: "hwui_defaults",
+ defaults: [
+ "hwui_static_deps",
+
+ //"hwui_bugreport_font_cache_usage",
+ //"hwui_compile_for_perf",
+ ],
+
+ cflags: [
+ "-DEGL_EGLEXT_PROTOTYPES",
+ "-DGL_GLEXT_PROTOTYPES",
+ "-DATRACE_TAG=ATRACE_TAG_VIEW",
+ "-DLOG_TAG=\"OpenGLRenderer\"",
+ "-Wall",
+ "-Wno-unused-parameter",
+ "-Wunreachable-code",
+ "-Werror",
+ "-fvisibility=hidden",
+
+ // GCC false-positives on this warning, and since we -Werror that's
+ // a problem
+ "-Wno-free-nonheap-object",
+
+ // clang's warning is broken, see: https://llvm.org/bugs/show_bug.cgi?id=21629
+ "-Wno-missing-braces",
+
+ // TODO: Linear blending should be enabled by default, but we are
+ // TODO: making it an opt-in while it's a work in progress
+ //"-DANDROID_ENABLE_LINEAR_BLENDING",
+ ],
+
+ include_dirs: [
+ "external/skia/include/private",
+ "external/skia/src/core",
+ "external/skia/src/effects",
+ "external/skia/src/image",
+ "external/skia/src/utils",
+ ],
+
+ product_variables: {
+ device_uses_hwc2: {
+ cflags: ["-DUSE_HWC2"],
+ },
+ },
+}
+
+cc_defaults {
+ name: "hwui_static_deps",
+ shared_libs: [
+ "liblog",
+ "libcutils",
+ "libutils",
+ "libEGL",
+ "libGLESv2",
+ "libvulkan",
+ "libskia",
+ "libui",
+ "libgui",
+ "libprotobuf-cpp-lite",
+ "libharfbuzz_ng",
+ "libft2",
+ "libminikin",
+ "libandroidfw",
+ "libRScpp",
+ ],
+ static_libs: [
+ "libplatformprotos",
+ ],
+}
+
+cc_defaults {
+ name: "hwui_bugreport_font_cache_usage",
+ srcs: ["font/FontCacheHistoryTracker.cpp"],
+ cflags: ["-DBUGREPORT_FONT_CACHE_USAGE"],
+}
+
+cc_defaults {
+ name: "hwui_compile_for_perf",
+ // TODO: Non-arm?
+ cflags: [
+ "-fno-omit-frame-pointer",
+ "-marm",
+ "-mapcs",
+ ],
+}
+
+cc_defaults {
+ name: "hwui_debug",
+ cflags: ["-include debug/wrap_gles.h"],
+ srcs: [
+ "debug/wrap_gles.cpp",
+ "debug/DefaultGlesDriver.cpp",
+ "debug/GlesErrorCheckWrapper.cpp",
+ "debug/GlesDriver.cpp",
+ "debug/FatalBaseDriver.cpp",
+ "debug/NullGlesDriver.cpp",
+ ],
+ include_dirs: ["frameworks/native/opengl/libs/GLES2"],
+}
+
+cc_defaults {
+ name: "hwui_enable_opengl_validation",
+ defaults: ["hwui_debug"],
+ cflags: ["-DDEBUG_OPENGL=3"],
+ include_dirs: ["frameworks/native/opengl/libs/GLES2"],
+}
+
+// ------------------------
+// library
+// ------------------------
+
+cc_defaults {
+ name: "libhwui_defaults",
+ defaults: ["hwui_defaults"],
+ srcs: [
+ "hwui/Bitmap.cpp",
+ "font/CacheTexture.cpp",
+ "font/Font.cpp",
+ "hwui/Canvas.cpp",
+ "hwui/MinikinSkia.cpp",
+ "hwui/MinikinUtils.cpp",
+ "hwui/PaintImpl.cpp",
+ "hwui/Typeface.cpp",
+ "pipeline/skia/GLFunctorDrawable.cpp",
+ "pipeline/skia/LayerDrawable.cpp",
+ "pipeline/skia/RenderNodeDrawable.cpp",
+ "pipeline/skia/ReorderBarrierDrawables.cpp",
+ "pipeline/skia/SkiaDisplayList.cpp",
+ "pipeline/skia/SkiaOpenGLPipeline.cpp",
+ "pipeline/skia/SkiaOpenGLReadback.cpp",
+ "pipeline/skia/SkiaPipeline.cpp",
+ "pipeline/skia/SkiaProfileRenderer.cpp",
+ "pipeline/skia/SkiaRecordingCanvas.cpp",
+ "pipeline/skia/SkiaVulkanPipeline.cpp",
+ "renderstate/Blend.cpp",
+ "renderstate/MeshState.cpp",
+ "renderstate/OffscreenBufferPool.cpp",
+ "renderstate/PixelBufferState.cpp",
+ "renderstate/RenderState.cpp",
+ "renderstate/Scissor.cpp",
+ "renderstate/Stencil.cpp",
+ "renderstate/TextureState.cpp",
+ "renderthread/CacheManager.cpp",
+ "renderthread/CanvasContext.cpp",
+ "renderthread/OpenGLPipeline.cpp",
+ "renderthread/DrawFrameTask.cpp",
+ "renderthread/EglManager.cpp",
+ "renderthread/VulkanManager.cpp",
+ "renderthread/RenderProxy.cpp",
+ "renderthread/RenderTask.cpp",
+ "renderthread/RenderThread.cpp",
+ "renderthread/TimeLord.cpp",
+ "renderthread/Frame.cpp",
+ "service/GraphicsStatsService.cpp",
+ "thread/TaskManager.cpp",
+ "utils/Blur.cpp",
+ "utils/Color.cpp",
+ "utils/GLUtils.cpp",
+ "utils/LinearAllocator.cpp",
+ "utils/StringUtils.cpp",
+ "utils/TestWindowContext.cpp",
+ "utils/VectorDrawableUtils.cpp",
+ "AmbientShadow.cpp",
+ "AnimationContext.cpp",
+ "Animator.cpp",
+ "AnimatorManager.cpp",
+ "BakedOpDispatcher.cpp",
+ "BakedOpRenderer.cpp",
+ "BakedOpState.cpp",
+ "Caches.cpp",
+ "CanvasState.cpp",
+ "ClipArea.cpp",
+ "DamageAccumulator.cpp",
+ "DeferredLayerUpdater.cpp",
+ "DeviceInfo.cpp",
+ "DisplayList.cpp",
+ "Extensions.cpp",
+ "FboCache.cpp",
+ "FontRenderer.cpp",
+ "FrameBuilder.cpp",
+ "FrameInfo.cpp",
+ "FrameInfoVisualizer.cpp",
+ "GammaFontRenderer.cpp",
+ "GlLayer.cpp",
+ "GlopBuilder.cpp",
+ "GpuMemoryTracker.cpp",
+ "GradientCache.cpp",
+ "Image.cpp",
+ "Interpolator.cpp",
+ "JankTracker.cpp",
+ "Layer.cpp",
+ "LayerBuilder.cpp",
+ "LayerUpdateQueue.cpp",
+ "Matrix.cpp",
+ "OpDumper.cpp",
+ "OpenGLReadback.cpp",
+ "Patch.cpp",
+ "PatchCache.cpp",
+ "PathCache.cpp",
+ "PathParser.cpp",
+ "PathTessellator.cpp",
+ "PixelBuffer.cpp",
+ "ProfileRenderer.cpp",
+ "Program.cpp",
+ "ProgramCache.cpp",
+ "Properties.cpp",
+ "PropertyValuesAnimatorSet.cpp",
+ "PropertyValuesHolder.cpp",
+ "RecordingCanvas.cpp",
+ "RenderBufferCache.cpp",
+ "RenderNode.cpp",
+ "RenderProperties.cpp",
+ "ResourceCache.cpp",
+ "ShadowTessellator.cpp",
+ "SkiaCanvas.cpp",
+ "SkiaCanvasProxy.cpp",
+ "SkiaShader.cpp",
+ "Snapshot.cpp",
+ "SpotShadow.cpp",
+ "TessellationCache.cpp",
+ "TextDropShadowCache.cpp",
+ "Texture.cpp",
+ "TextureCache.cpp",
+ "VectorDrawable.cpp",
+ "VkLayer.cpp",
+ "protos/hwui.proto",
+ ],
+
+ proto: {
+ export_proto_headers: true,
+ },
+
+ export_include_dirs: ["."],
+ export_shared_lib_headers: ["libRScpp"],
+}
+
+cc_library {
+ name: "libhwui",
+ defaults: [
+ "libhwui_defaults",
+
+ // Enables fine-grained GLES error checking
+ // If enabled, every GLES call is wrapped & error checked
+ // Has moderate overhead
+ "hwui_enable_opengl_validation",
+],
+}
+
+// ------------------------
+// static library null gpu
+// ------------------------
+
+cc_library_static {
+ name: "libhwui_static_debug",
+ defaults: [
+ "libhwui_defaults",
+ "hwui_debug",
+ ],
+ cflags: ["-DHWUI_NULL_GPU"],
+ srcs: [
+ "debug/nullegl.cpp",
+ ],
+}
+
+cc_defaults {
+ name: "hwui_test_defaults",
+ defaults: ["hwui_defaults"],
+ test_suites: ["device-tests"],
+ srcs: [
+ "tests/common/scenes/*.cpp",
+ "tests/common/LeakChecker.cpp",
+ "tests/common/TestListViewSceneBase.cpp",
+ "tests/common/TestContext.cpp",
+ "tests/common/TestScene.cpp",
+ "tests/common/TestUtils.cpp",
+ ],
+}
+
+// ------------------------
+// unit tests
+// ------------------------
+
+cc_test {
+ name: "hwui_unit_tests",
+ defaults: ["hwui_test_defaults"],
+
+ static_libs: [
+ "libgmock",
+ "libhwui_static_debug",
+ ],
+ shared_libs: ["libmemunreachable"],
+ cflags: [
+ "-include debug/wrap_gles.h",
+ "-DHWUI_NULL_GPU",
+ ],
+
+ srcs: [
+ "tests/unit/main.cpp",
+ "tests/unit/BakedOpDispatcherTests.cpp",
+ "tests/unit/BakedOpRendererTests.cpp",
+ "tests/unit/BakedOpStateTests.cpp",
+ "tests/unit/BitmapTests.cpp",
+ "tests/unit/CacheManagerTests.cpp",
+ "tests/unit/CanvasContextTests.cpp",
+ "tests/unit/CanvasStateTests.cpp",
+ "tests/unit/ClipAreaTests.cpp",
+ "tests/unit/DamageAccumulatorTests.cpp",
+ "tests/unit/DeferredLayerUpdaterTests.cpp",
+ "tests/unit/DeviceInfoTests.cpp",
+ "tests/unit/FatVectorTests.cpp",
+ "tests/unit/FontRendererTests.cpp",
+ "tests/unit/FrameBuilderTests.cpp",
+ "tests/unit/GlopBuilderTests.cpp",
+ "tests/unit/GpuMemoryTrackerTests.cpp",
+ "tests/unit/GradientCacheTests.cpp",
+ "tests/unit/GraphicsStatsServiceTests.cpp",
+ "tests/unit/LayerUpdateQueueTests.cpp",
+ "tests/unit/LeakCheckTests.cpp",
+ "tests/unit/LinearAllocatorTests.cpp",
+ "tests/unit/MatrixTests.cpp",
+ "tests/unit/MeshStateTests.cpp",
+ "tests/unit/OffscreenBufferPoolTests.cpp",
+ "tests/unit/OpDumperTests.cpp",
+ "tests/unit/PathInterpolatorTests.cpp",
+ "tests/unit/RenderNodeDrawableTests.cpp",
+ "tests/unit/RecordingCanvasTests.cpp",
+ "tests/unit/RenderNodeTests.cpp",
+ "tests/unit/RenderPropertiesTests.cpp",
+ "tests/unit/SkiaBehaviorTests.cpp",
+ "tests/unit/SkiaDisplayListTests.cpp",
+ "tests/unit/SkiaPipelineTests.cpp",
+ "tests/unit/SkiaRenderPropertiesTests.cpp",
+ "tests/unit/SkiaCanvasTests.cpp",
+ "tests/unit/SnapshotTests.cpp",
+ "tests/unit/StringUtilsTests.cpp",
+ "tests/unit/TestUtilsTests.cpp",
+ "tests/unit/TextDropShadowCacheTests.cpp",
+ "tests/unit/TextureCacheTests.cpp",
+ "tests/unit/TypefaceTests.cpp",
+ "tests/unit/VectorDrawableTests.cpp",
+ ],
+}
+
+// ------------------------
+// Macro-bench app
+// ------------------------
+
+cc_benchmark {
+ name: "hwuimacro",
+ defaults: ["hwui_test_defaults"],
+
+ // set to libhwui_static_debug to skip actual GL commands
+ whole_static_libs: ["libhwui"],
+ shared_libs: ["libmemunreachable"],
+
+ srcs: [
+ "tests/macrobench/TestSceneRunner.cpp",
+ "tests/macrobench/main.cpp",
+ ],
+}
+
+// ------------------------
+// Micro-bench app
+// ---------------------
+
+cc_benchmark {
+ name: "hwuimicro",
+ defaults: ["hwui_test_defaults"],
+
+ cflags: [
+ "-include debug/wrap_gles.h",
+ "-DHWUI_NULL_GPU",
+ ],
+
+ whole_static_libs: ["libhwui_static_debug"],
+ shared_libs: ["libmemunreachable"],
+
+ srcs: [
+ "tests/microbench/main.cpp",
+ "tests/microbench/DisplayListCanvasBench.cpp",
+ "tests/microbench/FontBench.cpp",
+ "tests/microbench/FrameBuilderBench.cpp",
+ "tests/microbench/LinearAllocatorBench.cpp",
+ "tests/microbench/PathParserBench.cpp",
+ "tests/microbench/RenderNodeBench.cpp",
+ "tests/microbench/ShadowBench.cpp",
+ "tests/microbench/TaskManagerBench.cpp",
+ ],
+}
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
deleted file mode 100644
index 030e845..0000000
--- a/libs/hwui/Android.mk
+++ /dev/null
@@ -1,384 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-
-BUGREPORT_FONT_CACHE_USAGE := false
-
-# Enables fine-grained GLES error checking
-# If set to true, every GLES call is wrapped & error checked
-# Has moderate overhead
-HWUI_ENABLE_OPENGL_VALIDATION := false
-
-hwui_src_files := \
- hwui/Bitmap.cpp \
- font/CacheTexture.cpp \
- font/Font.cpp \
- hwui/Canvas.cpp \
- hwui/MinikinSkia.cpp \
- hwui/MinikinUtils.cpp \
- hwui/PaintImpl.cpp \
- hwui/Typeface.cpp \
- pipeline/skia/GLFunctorDrawable.cpp \
- pipeline/skia/LayerDrawable.cpp \
- pipeline/skia/RenderNodeDrawable.cpp \
- pipeline/skia/ReorderBarrierDrawables.cpp \
- pipeline/skia/SkiaDisplayList.cpp \
- pipeline/skia/SkiaOpenGLPipeline.cpp \
- pipeline/skia/SkiaOpenGLReadback.cpp \
- pipeline/skia/SkiaPipeline.cpp \
- pipeline/skia/SkiaProfileRenderer.cpp \
- pipeline/skia/SkiaRecordingCanvas.cpp \
- pipeline/skia/SkiaVulkanPipeline.cpp \
- renderstate/Blend.cpp \
- renderstate/MeshState.cpp \
- renderstate/OffscreenBufferPool.cpp \
- renderstate/PixelBufferState.cpp \
- renderstate/RenderState.cpp \
- renderstate/Scissor.cpp \
- renderstate/Stencil.cpp \
- renderstate/TextureState.cpp \
- renderthread/CanvasContext.cpp \
- renderthread/OpenGLPipeline.cpp \
- renderthread/DrawFrameTask.cpp \
- renderthread/EglManager.cpp \
- renderthread/VulkanManager.cpp \
- renderthread/RenderProxy.cpp \
- renderthread/RenderTask.cpp \
- renderthread/RenderThread.cpp \
- renderthread/TimeLord.cpp \
- renderthread/Frame.cpp \
- service/GraphicsStatsService.cpp \
- thread/TaskManager.cpp \
- utils/Blur.cpp \
- utils/Color.cpp \
- utils/GLUtils.cpp \
- utils/LinearAllocator.cpp \
- utils/StringUtils.cpp \
- utils/TestWindowContext.cpp \
- utils/VectorDrawableUtils.cpp \
- AmbientShadow.cpp \
- AnimationContext.cpp \
- Animator.cpp \
- AnimatorManager.cpp \
- BakedOpDispatcher.cpp \
- BakedOpRenderer.cpp \
- BakedOpState.cpp \
- Caches.cpp \
- CanvasState.cpp \
- ClipArea.cpp \
- DamageAccumulator.cpp \
- DeferredLayerUpdater.cpp \
- DeviceInfo.cpp \
- DisplayList.cpp \
- Extensions.cpp \
- FboCache.cpp \
- FontRenderer.cpp \
- FrameBuilder.cpp \
- FrameInfo.cpp \
- FrameInfoVisualizer.cpp \
- GammaFontRenderer.cpp \
- GlLayer.cpp \
- GlopBuilder.cpp \
- GpuMemoryTracker.cpp \
- GradientCache.cpp \
- Image.cpp \
- Interpolator.cpp \
- JankTracker.cpp \
- Layer.cpp \
- LayerBuilder.cpp \
- LayerUpdateQueue.cpp \
- Matrix.cpp \
- OpDumper.cpp \
- OpenGLReadback.cpp \
- Patch.cpp \
- PatchCache.cpp \
- PathCache.cpp \
- PathParser.cpp \
- PathTessellator.cpp \
- PixelBuffer.cpp \
- ProfileRenderer.cpp \
- Program.cpp \
- ProgramCache.cpp \
- Properties.cpp \
- PropertyValuesAnimatorSet.cpp \
- PropertyValuesHolder.cpp \
- RecordingCanvas.cpp \
- RenderBufferCache.cpp \
- RenderNode.cpp \
- RenderProperties.cpp \
- ResourceCache.cpp \
- ShadowTessellator.cpp \
- SkiaCanvas.cpp \
- SkiaCanvasProxy.cpp \
- SkiaShader.cpp \
- Snapshot.cpp \
- SpotShadow.cpp \
- TessellationCache.cpp \
- TextDropShadowCache.cpp \
- Texture.cpp \
- TextureCache.cpp \
- VectorDrawable.cpp \
- VkLayer.cpp \
- protos/hwui.proto
-
-hwui_test_common_src_files := \
- $(call all-cpp-files-under, tests/common/scenes) \
- tests/common/LeakChecker.cpp \
- tests/common/TestListViewSceneBase.cpp \
- tests/common/TestContext.cpp \
- tests/common/TestScene.cpp \
- tests/common/TestUtils.cpp
-
-hwui_debug_common_src_files := \
- debug/wrap_gles.cpp \
- debug/DefaultGlesDriver.cpp \
- debug/GlesErrorCheckWrapper.cpp \
- debug/GlesDriver.cpp \
- debug/FatalBaseDriver.cpp \
- debug/NullGlesDriver.cpp
-
-hwui_cflags := \
- -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES \
- -DATRACE_TAG=ATRACE_TAG_VIEW -DLOG_TAG=\"OpenGLRenderer\" \
- -Wall -Wno-unused-parameter -Wunreachable-code -Werror
-
-ifeq ($(TARGET_USES_HWC2),true)
- hwui_cflags += -DUSE_HWC2
-endif
-
-# TODO: Linear blending should be enabled by default, but we are
-# TODO: making it an opt-in while it's a work in progress
-# TODO: The final test should be:
-# TODO: ifneq ($(TARGET_ENABLE_LINEAR_BLENDING),false)
-ifeq ($(TARGET_ENABLE_LINEAR_BLENDING),true)
- hwui_cflags += -DANDROID_ENABLE_LINEAR_BLENDING
-endif
-
-# GCC false-positives on this warning, and since we -Werror that's
-# a problem
-hwui_cflags += -Wno-free-nonheap-object
-
-# clang's warning is broken, see: https://llvm.org/bugs/show_bug.cgi?id=21629
-hwui_cflags += -Wno-missing-braces
-
-ifeq (true, $(BUGREPORT_FONT_CACHE_USAGE))
- hwui_src_files += \
- font/FontCacheHistoryTracker.cpp
- hwui_cflags += -DBUGREPORT_FONT_CACHE_USAGE
-endif
-
-ifndef HWUI_COMPILE_SYMBOLS
- hwui_cflags += -fvisibility=hidden
-endif
-
-ifdef HWUI_COMPILE_FOR_PERF
- # TODO: Non-arm?
- hwui_cflags += -fno-omit-frame-pointer -marm -mapcs
-endif
-
-# This has to be lazy-resolved because it depends on the LOCAL_MODULE_CLASS
-# which varies depending on what is being built
-define hwui_proto_include
-$(call local-generated-sources-dir)/proto/$(LOCAL_PATH)
-endef
-
-hwui_c_includes += \
- external/skia/include/private \
- external/skia/src/core \
- external/skia/src/effects \
- external/skia/src/image \
- external/skia/src/utils \
- external/icu/icu4c/source/common \
- external/harfbuzz_ng/src \
- external/freetype/include
-
-# enable RENDERSCRIPT
-hwui_c_includes += \
- $(call intermediates-dir-for,STATIC_LIBRARIES,TARGET,) \
- frameworks/rs/cpp \
- frameworks/rs
-
-# ------------------------
-# static library
-# ------------------------
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_CLASS := STATIC_LIBRARIES
-LOCAL_MODULE := libhwui_static
-LOCAL_CFLAGS := $(hwui_cflags)
-LOCAL_SRC_FILES := $(hwui_src_files)
-
-ifeq (true, $(HWUI_ENABLE_OPENGL_VALIDATION))
- LOCAL_CFLAGS += -include debug/wrap_gles.h
- LOCAL_CFLAGS += -DDEBUG_OPENGL=3
- LOCAL_SRC_FILES += $(hwui_debug_common_src_files)
-endif
-
-LOCAL_C_INCLUDES := $(hwui_c_includes) $(call hwui_proto_include)
-LOCAL_EXPORT_C_INCLUDE_DIRS := \
- $(LOCAL_PATH) \
- $(call hwui_proto_include)
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_STATIC_LIBRARY)
-
-# ------------------------
-# static library null gpu
-# ------------------------
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_CLASS := STATIC_LIBRARIES
-LOCAL_MODULE := libhwui_static_debug
-LOCAL_CFLAGS := \
- $(hwui_cflags) \
- -include debug/wrap_gles.h \
- -DHWUI_NULL_GPU
-LOCAL_SRC_FILES := \
- $(hwui_src_files) \
- $(hwui_debug_common_src_files) \
- debug/nullegl.cpp
-LOCAL_C_INCLUDES := $(hwui_c_includes) $(call hwui_proto_include)
-LOCAL_EXPORT_C_INCLUDE_DIRS := \
- $(LOCAL_PATH) \
- $(call hwui_proto_include)
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_STATIC_LIBRARY)
-
-# ------------------------
-# shared library
-# ------------------------
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_CLASS := SHARED_LIBRARIES
-LOCAL_MODULE := libhwui
-LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_SHARED_LIBRARY)
-
-# ------------------------
-# unit tests
-# ------------------------
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := hwui_unit_tests
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_LIBRARIES := libgmock libhwui_static_debug
-LOCAL_SHARED_LIBRARIES := libmemunreachable
-LOCAL_CFLAGS := \
- $(hwui_cflags) \
- -include debug/wrap_gles.h \
- -DHWUI_NULL_GPU
-LOCAL_C_INCLUDES := $(hwui_c_includes)
-
-LOCAL_SRC_FILES += \
- $(hwui_test_common_src_files) \
- tests/unit/main.cpp \
- tests/unit/BakedOpDispatcherTests.cpp \
- tests/unit/BakedOpRendererTests.cpp \
- tests/unit/BakedOpStateTests.cpp \
- tests/unit/BitmapTests.cpp \
- tests/unit/CanvasContextTests.cpp \
- tests/unit/CanvasStateTests.cpp \
- tests/unit/ClipAreaTests.cpp \
- tests/unit/DamageAccumulatorTests.cpp \
- tests/unit/DeferredLayerUpdaterTests.cpp \
- tests/unit/DeviceInfoTests.cpp \
- tests/unit/FatVectorTests.cpp \
- tests/unit/FontRendererTests.cpp \
- tests/unit/FrameBuilderTests.cpp \
- tests/unit/GlopBuilderTests.cpp \
- tests/unit/GpuMemoryTrackerTests.cpp \
- tests/unit/GradientCacheTests.cpp \
- tests/unit/GraphicsStatsServiceTests.cpp \
- tests/unit/LayerUpdateQueueTests.cpp \
- tests/unit/LeakCheckTests.cpp \
- tests/unit/LinearAllocatorTests.cpp \
- tests/unit/MatrixTests.cpp \
- tests/unit/MeshStateTests.cpp \
- tests/unit/OffscreenBufferPoolTests.cpp \
- tests/unit/OpDumperTests.cpp \
- tests/unit/PathInterpolatorTests.cpp \
- tests/unit/RenderNodeDrawableTests.cpp \
- tests/unit/RecordingCanvasTests.cpp \
- tests/unit/RenderNodeTests.cpp \
- tests/unit/RenderPropertiesTests.cpp \
- tests/unit/SkiaBehaviorTests.cpp \
- tests/unit/SkiaDisplayListTests.cpp \
- tests/unit/SkiaPipelineTests.cpp \
- tests/unit/SkiaRenderPropertiesTests.cpp \
- tests/unit/SkiaCanvasTests.cpp \
- tests/unit/SnapshotTests.cpp \
- tests/unit/StringUtilsTests.cpp \
- tests/unit/TestUtilsTests.cpp \
- tests/unit/TextDropShadowCacheTests.cpp \
- tests/unit/TextureCacheTests.cpp \
- tests/unit/TypefaceTests.cpp \
- tests/unit/VectorDrawableTests.cpp \
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_NATIVE_TEST)
-
-# ------------------------
-# Macro-bench app
-# ------------------------
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local/tmp
-LOCAL_MODULE:= hwuimacro
-LOCAL_MODULE_TAGS := tests
-LOCAL_MULTILIB := both
-LOCAL_CFLAGS := $(hwui_cflags)
-LOCAL_C_INCLUDES := $(hwui_c_includes)
-
-# set to libhwui_static_debug to skip actual GL commands
-LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static
-LOCAL_SHARED_LIBRARIES := libmemunreachable
-
-LOCAL_SRC_FILES += \
- $(hwui_test_common_src_files) \
- tests/macrobench/TestSceneRunner.cpp \
- tests/macrobench/main.cpp
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_NATIVE_BENCHMARK)
-
-# ------------------------
-# Micro-bench app
-# ---------------------
-include $(CLEAR_VARS)
-
-LOCAL_MODULE:= hwuimicro
-LOCAL_MODULE_TAGS := tests
-LOCAL_CFLAGS := \
- $(hwui_cflags) \
- -include debug/wrap_gles.h \
- -DHWUI_NULL_GPU
-
-LOCAL_C_INCLUDES := $(hwui_c_includes)
-
-LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static_debug
-LOCAL_SHARED_LIBRARIES := libmemunreachable
-
-LOCAL_SRC_FILES += \
- $(hwui_test_common_src_files) \
- tests/microbench/main.cpp \
- tests/microbench/DisplayListCanvasBench.cpp \
- tests/microbench/FontBench.cpp \
- tests/microbench/FrameBuilderBench.cpp \
- tests/microbench/LinearAllocatorBench.cpp \
- tests/microbench/PathParserBench.cpp \
- tests/microbench/RenderNodeBench.cpp \
- tests/microbench/ShadowBench.cpp \
- tests/microbench/TaskManagerBench.cpp
-
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_NATIVE_BENCHMARK)
diff --git a/libs/hwui/AndroidTest.xml b/libs/hwui/AndroidTest.xml
new file mode 100644
index 0000000..eab32c5
--- /dev/null
+++ b/libs/hwui/AndroidTest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for hwuimicro">
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="hwui_unit_tests->/data/nativetest/hwui_unit_tests" />
+ <option name="push" value="hwuimicro->/data/benchmarktest/hwuimicro" />
+ <option name="push" value="hwuimacro->/data/benchmarktest/hwuimacro" />
+ </target_preparer>
+ <option name="test-suite-tag" value="apct" />
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/nativetest" />
+ <option name="module-name" value="hwui_unit_tests" />
+ </test>
+ <test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
+ <option name="native-benchmark-device-path" value="/data/benchmarktest" />
+ <option name="benchmark-module-name" value="hwuimicro" />
+ </test>
+ <test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
+ <option name="native-benchmark-device-path" value="/data/benchmarktest" />
+ <option name="benchmark-module-name" value="hwuimacro" />
+ </test>
+</configuration>
diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h
index 34c7934..e91c08d 100644
--- a/libs/hwui/Glop.h
+++ b/libs/hwui/Glop.h
@@ -110,6 +110,7 @@
} vertices;
int elementCount;
+ int vertexCount; // only used for meshes (for glDrawRangeElements)
TextureVertex mappedVertices[4];
} mesh;
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index 931a55a..2e9a6e8 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -199,6 +199,7 @@
alphaVertex ? kAlphaVertexStride : kVertexStride };
mOutGlop->mesh.elementCount = indices
? vertexBuffer.getIndexCount() : vertexBuffer.getVertexCount();
+ mOutGlop->mesh.vertexCount = vertexBuffer.getVertexCount(); // used for glDrawRangeElements()
return *this;
}
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 8126d57..028d9f7 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -291,7 +291,6 @@
/ kSlowFrameBucketIntervalMs;
framebucket = std::min(framebucket,
static_cast<uint32_t>(mData->slowFrameCounts.size() - 1));
- framebucket = std::max(framebucket, 0u);
mData->slowFrameCounts[framebucket]++;
}
diff --git a/libs/hwui/OpenGLReadback.cpp b/libs/hwui/OpenGLReadback.cpp
index c460c0d..4ef0f4b 100644
--- a/libs/hwui/OpenGLReadback.cpp
+++ b/libs/hwui/OpenGLReadback.cpp
@@ -27,6 +27,7 @@
#include <GLES2/gl2.h>
#include <ui/Fence.h>
#include <ui/GraphicBuffer.h>
+#include <gui/Surface.h>
namespace android {
namespace uirenderer {
@@ -144,8 +145,6 @@
return CopyResult::UnknownError;
}
- SkAutoLockPixels alp(*bitmap);
-
GLuint texture;
GLenum format;
diff --git a/libs/hwui/OpenGLReadback.h b/libs/hwui/OpenGLReadback.h
index c9222cf..403f2e3 100644
--- a/libs/hwui/OpenGLReadback.h
+++ b/libs/hwui/OpenGLReadback.h
@@ -18,6 +18,9 @@
#include "Readback.h"
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index d0f0949..8cc0aa7 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -58,7 +58,9 @@
"uniform mat4 textureTransform;\n"
"uniform mediump vec2 textureDimension;\n";
const char* gVS_Header_Uniforms_HasRoundRectClip =
- "uniform mat4 roundRectInvTransform;\n";
+ "uniform mat4 roundRectInvTransform;\n"
+ "uniform mediump vec4 roundRectInnerRectLTWH;\n"
+ "uniform mediump float roundRectRadius;\n";
const char* gVS_Header_Varyings_HasTexture =
"varying vec2 outTexCoords;\n";
const char* gVS_Header_Varyings_HasColors =
@@ -81,7 +83,7 @@
"varying highp vec2 sweep;\n",
};
const char* gVS_Header_Varyings_HasRoundRectClip =
- "varying highp vec2 roundRectPos;\n";
+ "varying mediump vec2 roundRectPos;\n";
const char* gVS_Main =
"\nvoid main(void) {\n";
const char* gVS_Main_OutTexCoords =
@@ -113,7 +115,7 @@
" alpha = vtxAlpha;\n";
const char* gVS_Main_HasRoundRectClip =
- " roundRectPos = (roundRectInvTransform * transformedPosition).xy;\n";
+ " roundRectPos = ((roundRectInvTransform * transformedPosition).xy / roundRectRadius) - roundRectInnerRectLTWH.xy;\n";
const char* gVS_Footer =
"}\n\n";
@@ -158,8 +160,8 @@
};
const char* gFS_Uniforms_HasRoundRectClip =
- "uniform vec4 roundRectInnerRectLTRB;\n"
- "uniform float roundRectRadius;\n";
+ "uniform mediump vec4 roundRectInnerRectLTWH;\n"
+ "uniform mediump float roundRectRadius;\n";
const char* gFS_Uniforms_ColorSpaceConversion =
// TODO: Should we use a 3D LUT to combine the matrix and transfer functions?
@@ -431,15 +433,18 @@
" fragColor = blendColors(colorBlend, fragColor);\n"
};
-// Note: LTRB -> xyzw
+// Note: LTWH (left top width height) -> xyzw
+// roundRectPos is now divided by roundRectRadius in vertex shader
+// after we also subtract roundRectInnerRectLTWH.xy from roundRectPos
const char* gFS_Main_FragColor_HasRoundRectClip =
- " mediump vec2 fragToLT = roundRectInnerRectLTRB.xy - roundRectPos;\n"
- " mediump vec2 fragFromRB = roundRectPos - roundRectInnerRectLTRB.zw;\n"
+ " mediump vec2 fragToLT = -roundRectPos;\n"
+ " mediump vec2 fragFromRB = roundRectPos - roundRectInnerRectLTWH.zw;\n"
- // divide + multiply by 128 to avoid falling out of range in length() function
- " mediump vec2 dist = max(max(fragToLT, fragFromRB), vec2(0.0, 0.0)) / 128.0;\n"
- " mediump float linearDist = roundRectRadius - (length(dist) * 128.0);\n"
- " gl_FragColor *= clamp(linearDist, 0.0, 1.0);\n";
+ // since distance is divided by radius, it's in [0;1] so precision is not an issue
+ // this also lets us clamp(0.0, 1.0) instead of max() which is cheaper on GPUs
+ " mediump vec2 dist = clamp(max(fragToLT, fragFromRB), 0.0, 1.0);\n"
+ " mediump float linearDist = clamp(roundRectRadius - (length(dist) * roundRectRadius), 0.0, 1.0);\n"
+ " gl_FragColor *= linearDist;\n";
const char* gFS_Main_DebugHighlight =
" gl_FragColor.rgb = vec3(0.0, gl_FragColor.a, 0.0);\n";
diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h
index b763953..7cf4262 100644
--- a/libs/hwui/Readback.h
+++ b/libs/hwui/Readback.h
@@ -20,10 +20,10 @@
#include "Rect.h"
#include <SkBitmap.h>
-#include <gui/Surface.h>
namespace android {
class GraphicBuffer;
+class Surface;
namespace uirenderer {
// Keep in sync with PixelCopy.java codes
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 374c1b1..d966372 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -540,12 +540,13 @@
}
// Text
-void RecordingCanvas::drawGlyphs(const uint16_t* glyphs, const float* positions, int glyphCount,
- const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
- float boundsRight, float boundsBottom, float totalAdvance) {
- if (!glyphs || !positions || glyphCount <= 0 || paint.nothingToDraw()) return;
- glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
- positions = refBuffer<float>(positions, glyphCount * 2);
+void RecordingCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int glyphCount, const SkPaint& paint,
+ float x, float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
+ float totalAdvance) {
+ if (glyphCount <= 0 || paint.nothingToDraw()) return;
+ uint16_t* glyphs = (glyph_t*)alloc().alloc<glyph_t>(glyphCount * sizeof(glyph_t));
+ float* positions = (float*)alloc().alloc<float>(2 * glyphCount * sizeof(float));
+ glyphFunc(glyphs, positions);
// TODO: either must account for text shadow in bounds, or record separate ops for text shadows
addOp(alloc().create_trivial<TextOp>(
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 44181bd..ccdb4b0 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -168,9 +168,7 @@
virtual void drawArc(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) override;
virtual void drawPath(const SkPath& path, const SkPaint& paint) override;
- virtual void drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount,
- const float* verts, const float* tex, const int* colors,
- const uint16_t* indices, int indexCount, const SkPaint& paint) override
+ virtual void drawVertices(const SkVertices*, SkBlendMode, const SkPaint& paint) override
{ /* RecordingCanvas does not support drawVertices(); ignore */ }
virtual void drawVectorDrawable(VectorDrawableRoot* tree) override;
@@ -191,9 +189,8 @@
virtual bool drawTextAbsolutePos() const override { return false; }
protected:
- virtual void drawGlyphs(const uint16_t* text, const float* positions, int count,
- const SkPaint& paint, float x, float y,
- float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
+ virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const SkPaint& paint, float x,
+ float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) override;
virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
const SkPaint& paint, const SkPath& path, size_t start, size_t end) override;
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 3e5e3bf..623b496 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -482,7 +482,7 @@
void SkiaCanvas::drawRect(float left, float top, float right, float bottom,
const SkPaint& paint) {
if (CC_UNLIKELY(paint.nothingToDraw())) return;
- mCanvas->drawRectCoords(left, top, right, bottom, paint);
+ mCanvas->drawRect({left, top, right, bottom}, paint);
}
@@ -521,17 +521,8 @@
mCanvas->drawPath(path, paint);
}
-void SkiaCanvas::drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount,
- const float* verts, const float* texs, const int* colors,
- const uint16_t* indices, int indexCount, const SkPaint& paint) {
-#ifndef SK_SCALAR_IS_FLOAT
- SkDEBUGFAIL("SkScalar must be a float for these conversions to be valid");
-#endif
- const int ptCount = vertexCount >> 1;
- mCanvas->drawVertices(SkVertices::MakeCopy(vertexMode, ptCount, (SkPoint*)verts,
- (SkPoint*)texs, (SkColor*)colors,
- indexCount, indices),
- SkBlendMode::kModulate, paint);
+void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint) {
+ mCanvas->drawVertices(vertices, mode, paint);
}
// ----------------------------------------------------------------------------
@@ -572,7 +563,7 @@
if (colors) {
flags |= SkVertices::kHasColors_BuilderFlag;
}
- SkVertices::Builder builder(SkCanvas::kTriangles_VertexMode, ptCount, indexCount, flags);
+ SkVertices::Builder builder(SkVertices::kTriangles_VertexMode, ptCount, indexCount, flags);
memcpy(builder.positions(), vertices, ptCount * sizeof(SkPoint));
if (colors) {
memcpy(builder.colors(), colors, ptCount * sizeof(SkColor));
@@ -685,11 +676,10 @@
// Canvas draw operations: Text
// ----------------------------------------------------------------------------
-void SkiaCanvas::drawGlyphs(const uint16_t* text, const float* positions, int count,
- const SkPaint& paint, float x, float y,
- float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
+void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const SkPaint& paint, float x,
+ float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) {
- if (!text || !positions || count <= 0 || paint.nothingToDraw()) return;
+ if (count <= 0 || paint.nothingToDraw()) return;
// Set align to left for drawing, as we don't want individual
// glyphs centered or right-aligned; the offset above takes
// care of all alignment.
@@ -701,10 +691,7 @@
SkTextBlobBuilder builder;
const SkTextBlobBuilder::RunBuffer& buffer = builder.allocRunPos(paintCopy, count, &bounds);
- // TODO: we could reduce the number of memcpy's if the this were exposed further up
- // in the architecture.
- memcpy(buffer.glyphs, text, count * sizeof(uint16_t));
- memcpy(buffer.pos, positions, (count << 1) * sizeof(float));
+ glyphFunc(buffer.glyphs, buffer.pos);
sk_sp<SkTextBlob> textBlob(builder.make());
mCanvas->drawTextBlob(textBlob, 0, 0, paintCopy);
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index aeecafa..af2c23e 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -123,9 +123,7 @@
virtual void drawArc(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) override;
virtual void drawPath(const SkPath& path, const SkPaint& paint) override;
- virtual void drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount,
- const float* verts, const float* tex, const int* colors,
- const uint16_t* indices, int indexCount, const SkPaint& paint) override;
+ virtual void drawVertices(const SkVertices*, SkBlendMode, const SkPaint& paint) override;
virtual void drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) override;
virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const SkPaint* paint) override;
@@ -159,9 +157,8 @@
void reset(SkCanvas* skiaCanvas);
void drawDrawable(SkDrawable* drawable) { mCanvas->drawDrawable(drawable); }
- virtual void drawGlyphs(const uint16_t* text, const float* positions, int count,
- const SkPaint& paint, float x, float y,
- float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
+ virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const SkPaint& paint, float x,
+ float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) override;
virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
const SkPaint& paint, const SkPath& path, size_t start, size_t end) override;
diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp
index f6e92dc..34dddd1 100644
--- a/libs/hwui/SkiaCanvasProxy.cpp
+++ b/libs/hwui/SkiaCanvasProxy.cpp
@@ -183,18 +183,10 @@
void SkiaCanvasProxy::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode bmode,
const SkPaint& paint) {
- // TODO: should we pass through blendmode
if (mFilterHwuiCalls) {
return;
}
- // convert the SkPoints into floats
- static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
- const int floatCount = vertices->vertexCount() << 1;
- const float* vArray = (const float*)vertices->positions();
- const float* tArray = (const float*)vertices->texCoords();
- const int* cArray = (const int*)vertices->colors();
- mCanvas->drawVertices(vertices->mode(), floatCount, vArray, tArray, cArray,
- vertices->indices(), vertices->indexCount(), paint);
+ mCanvas->drawVertices(vertices, bmode, paint);
}
sk_sp<SkSurface> SkiaCanvasProxy::onNewSurface(const SkImageInfo&, const SkSurfaceProps&) {
@@ -286,7 +278,6 @@
GlyphIDConverter glyphs(text, byteLength, origPaint);
// compute the glyph positions
- std::unique_ptr<SkPoint[]> pointStorage(new SkPoint[glyphs.count]);
std::unique_ptr<SkScalar[]> glyphWidths(new SkScalar[glyphs.count]);
glyphs.paint.getTextWidths(glyphs.glyphIDs, glyphs.count << 1, glyphWidths.get());
@@ -320,22 +311,33 @@
xBaseline = x;
yBaseline = y;
}
- pointStorage[0].set(xBaseline, yBaseline);
-
- // setup the remaining glyph positions
- if (glyphs.paint.isVerticalText()) {
- for (int i = 1; i < glyphs.count; i++) {
- pointStorage[i].set(xBaseline, glyphWidths[i-1] + pointStorage[i-1].fY);
- }
- } else {
- for (int i = 1; i < glyphs.count; i++) {
- pointStorage[i].set(glyphWidths[i-1] + pointStorage[i-1].fX, yBaseline);
- }
- }
static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
- mCanvas->drawGlyphs(glyphs.glyphIDs, &pointStorage[0].fX, glyphs.count, glyphs.paint,
- x, y, bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, 0);
+ auto glyphFunc = [&] (uint16_t* text, float* positions) {
+ memcpy(text, glyphs.glyphIDs, glyphs.count*sizeof(uint16_t));
+ size_t posIndex = 0;
+ // setup the first glyph position
+ positions[posIndex++] = xBaseline;
+ positions[posIndex++] = yBaseline;
+ // setup the remaining glyph positions
+ if (glyphs.paint.isVerticalText()) {
+ float yPosition = yBaseline;
+ for (int i = 1; i < glyphs.count; i++) {
+ positions[posIndex++] = xBaseline;
+ yPosition += glyphWidths[i-1];
+ positions[posIndex++] = yPosition;
+ }
+ } else {
+ float xPosition = xBaseline;
+ for (int i = 1; i < glyphs.count; i++) {
+ xPosition += glyphWidths[i-1];
+ positions[posIndex++] = xPosition;
+ positions[posIndex++] = yBaseline;
+ }
+ }
+ };
+ mCanvas->drawGlyphs(glyphFunc, glyphs.count, glyphs.paint, x, y, bounds.fLeft, bounds.fTop,
+ bounds.fRight, bounds.fBottom, 0);
}
void SkiaCanvasProxy::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
@@ -345,21 +347,12 @@
// convert to relative positions if necessary
int x, y;
- const SkPoint* posArray;
- std::unique_ptr<SkPoint[]> pointStorage;
if (mCanvas->drawTextAbsolutePos()) {
x = 0;
y = 0;
- posArray = pos;
} else {
x = pos[0].fX;
y = pos[0].fY;
- pointStorage.reset(new SkPoint[glyphs.count]);
- for (int i = 0; i < glyphs.count; i++) {
- pointStorage[i].fX = pos[i].fX - x;
- pointStorage[i].fY = pos[i].fY - y;
- }
- posArray = pointStorage.get();
}
// Compute conservative bounds. If the content has already been processed
@@ -375,8 +368,19 @@
}
static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
- mCanvas->drawGlyphs(glyphs.glyphIDs, &posArray[0].fX, glyphs.count, glyphs.paint, x, y,
- bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, 0);
+ auto glyphFunc = [&] (uint16_t* text, float* positions) {
+ memcpy(text, glyphs.glyphIDs, glyphs.count*sizeof(uint16_t));
+ if (mCanvas->drawTextAbsolutePos()) {
+ memcpy(positions, pos, 2*glyphs.count*sizeof(float));
+ } else {
+ for (int i = 0, posIndex = 0; i < glyphs.count; i++) {
+ positions[posIndex++] = pos[i].fX - x;
+ positions[posIndex++] = pos[i].fY - y;
+ }
+ }
+ };
+ mCanvas->drawGlyphs(glyphFunc, glyphs.count, glyphs.paint, x, y, bounds.fLeft, bounds.fTop,
+ bounds.fRight, bounds.fBottom, 0);
}
void SkiaCanvasProxy::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
@@ -456,19 +460,13 @@
if (mFilterHwuiCalls) {
return;
}
- SkPatchUtils::VertexData data;
-
SkMatrix matrix;
mCanvas->getMatrix(&matrix);
SkISize lod = SkPatchUtils::GetLevelOfDetail(cubics, &matrix);
- // It automatically adjusts lodX and lodY in case it exceeds the number of indices.
- // If it fails to generate the vertices, then we do not draw.
- if (SkPatchUtils::getVertexData(&data, cubics, colors, texCoords, lod.width(), lod.height())) {
- this->drawVertices(SkCanvas::kTriangles_VertexMode, data.fVertexCount, data.fPoints,
- data.fTexCoords, data.fColors, bmode, data.fIndices, data.fIndexCount,
- paint);
- }
+ mCanvas->drawVertices(SkPatchUtils::MakeVertices(cubics, colors, texCoords,
+ lod.width(), lod.height()).get(),
+ bmode, paint);
}
void SkiaCanvasProxy::onClipRect(const SkRect& rect, SkClipOp op, ClipEdgeStyle) {
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 8823a92..f6b2912 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -491,6 +491,36 @@
return *mCache.bitmap;
}
+void Tree::updateCache(sk_sp<SkSurface> surface) {
+ if (surface.get()) {
+ mCache.surface = surface;
+ }
+ if (surface.get() || mCache.dirty) {
+ SkSurface* vdSurface = mCache.surface.get();
+ SkCanvas* canvas = vdSurface->getCanvas();
+ float scaleX = vdSurface->width() / mProperties.getViewportWidth();
+ float scaleY = vdSurface->height() / mProperties.getViewportHeight();
+ SkAutoCanvasRestore acr(canvas, true);
+ canvas->clear(SK_ColorTRANSPARENT);
+ canvas->scale(scaleX, scaleY);
+ mRootNode->draw(canvas, false);
+ mCache.dirty = false;
+ canvas->flush();
+ }
+}
+
+void Tree::draw(SkCanvas* canvas) {
+ /*
+ * TODO address the following...
+ *
+ * 1) figure out how to set path's as volatile during animation
+ * 2) if mRoot->getPaint() != null either promote to layer (during
+ * animation) or cache in SkSurface (for static content)
+ */
+ canvas->drawImageRect(mCache.surface->makeImageSnapshot().get(),
+ mutateProperties()->getBounds(), getPaint());
+}
+
void Tree::updateBitmapCache(Bitmap& bitmap, bool useStagingData) {
SkBitmap outCache;
bitmap.getSkBitmap(&outCache);
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index 729a4dd..22cfe29 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -31,6 +31,7 @@
#include <SkPathMeasure.h>
#include <SkRect.h>
#include <SkShader.h>
+#include <SkSurface.h>
#include <cutils/compiler.h>
#include <stddef.h>
@@ -677,15 +678,37 @@
// This should only be called from animations on RT
TreeProperties* mutateProperties() { return &mProperties; }
+ // called from RT only
+ const TreeProperties& properties() const { return mProperties; }
+
// This should always be called from RT.
void markDirty() { mCache.dirty = true; }
bool isDirty() const { return mCache.dirty; }
bool getPropertyChangeWillBeConsumed() const { return mWillBeConsumed; }
void setPropertyChangeWillBeConsumed(bool willBeConsumed) { mWillBeConsumed = willBeConsumed; }
+ // Returns true if VD cache surface is big enough. This should always be called from RT and it
+ // works with Skia pipelines only.
+ bool canReuseSurface() {
+ SkSurface* surface = mCache.surface.get();
+ return surface && surface->width() >= mProperties.getScaledWidth()
+ && surface->height() >= mProperties.getScaledHeight();
+ }
+
+ // Draws VD cache into a canvas. This should always be called from RT and it works with Skia
+ // pipelines only.
+ void draw(SkCanvas* canvas);
+
+ // Draws VD into a GPU backed surface. If canReuseSurface returns false, then "surface" must
+ // contain a new surface. This should always be called from RT and it works with Skia pipelines
+ // only.
+ void updateCache(sk_sp<SkSurface> surface);
+
private:
struct Cache {
- sk_sp<Bitmap> bitmap;
+ sk_sp<Bitmap> bitmap; //used by HWUI pipeline and software
+ //TODO: use surface instead of bitmap when drawing in software canvas
+ sk_sp<SkSurface> surface; //used only by Skia pipelines
bool dirty = true;
};
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index bb1e674..6dde005 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -26,16 +26,12 @@
#include <log/log.h>
#include <cutils/ashmem.h>
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-
#include <private/gui/ComposerService.h>
#include <binder/IServiceManager.h>
#include <ui/PixelFormat.h>
#include <SkCanvas.h>
+#include <SkImagePriv.h>
namespace android {
@@ -51,9 +47,9 @@
}
typedef sk_sp<Bitmap> (*AllocPixeRef)(size_t allocSize, const SkImageInfo& info, size_t rowBytes,
- SkColorTable* ctable);
+ sk_sp<SkColorTable> ctable);
-static sk_sp<Bitmap> allocateBitmap(SkBitmap* bitmap, SkColorTable* ctable, AllocPixeRef alloc) {
+static sk_sp<Bitmap> allocateBitmap(SkBitmap* bitmap, sk_sp<SkColorTable> ctable, AllocPixeRef alloc) {
const SkImageInfo& info = bitmap->info();
if (info.colorType() == kUnknown_SkColorType) {
LOG_ALWAYS_FATAL("unknown bitmap configuration");
@@ -69,201 +65,32 @@
return nullptr;
}
- auto wrapper = alloc(size, info, rowBytes, ctable);
+ auto wrapper = alloc(size, info, rowBytes, std::move(ctable));
if (wrapper) {
wrapper->getSkBitmap(bitmap);
- // since we're already allocated, we lockPixels right away
- // HeapAllocator behaves this way too
- bitmap->lockPixels();
}
return wrapper;
}
-sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(SkBitmap* bitmap, SkColorTable* ctable) {
- return allocateBitmap(bitmap, ctable, &Bitmap::allocateAshmemBitmap);
+sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(SkBitmap* bitmap, sk_sp<SkColorTable> ctable) {
+ return allocateBitmap(bitmap, std::move(ctable), &Bitmap::allocateAshmemBitmap);
}
static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes,
- SkColorTable* ctable) {
+ sk_sp<SkColorTable> ctable) {
void* addr = calloc(size, 1);
if (!addr) {
return nullptr;
}
- return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes, ctable));
-}
-
-#define FENCE_TIMEOUT 2000000000
-
-// TODO: handle SRGB sanely
-static PixelFormat internalFormatToPixelFormat(GLint internalFormat) {
- switch (internalFormat) {
- case GL_LUMINANCE:
- return PIXEL_FORMAT_RGBA_8888;
- case GL_SRGB8_ALPHA8:
- return PIXEL_FORMAT_RGBA_8888;
- case GL_RGBA:
- return PIXEL_FORMAT_RGBA_8888;
- case GL_RGB:
- return PIXEL_FORMAT_RGB_565;
- case GL_RGBA16F:
- return PIXEL_FORMAT_RGBA_FP16;
- default:
- LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", internalFormat);
- return PIXEL_FORMAT_UNKNOWN;
- }
-}
-
-class AutoEglFence {
-public:
- AutoEglFence(EGLDisplay display)
- : mDisplay(display) {
- fence = eglCreateSyncKHR(mDisplay, EGL_SYNC_FENCE_KHR, NULL);
- }
-
- ~AutoEglFence() {
- if (fence != EGL_NO_SYNC_KHR) {
- eglDestroySyncKHR(mDisplay, fence);
- }
- }
-
- EGLSyncKHR fence = EGL_NO_SYNC_KHR;
-private:
- EGLDisplay mDisplay = EGL_NO_DISPLAY;
-};
-
-class AutoEglImage {
-public:
- AutoEglImage(EGLDisplay display, EGLClientBuffer clientBuffer)
- : mDisplay(display) {
- EGLint imageAttrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE };
- image = eglCreateImageKHR(display, EGL_NO_CONTEXT,
- EGL_NATIVE_BUFFER_ANDROID, clientBuffer, imageAttrs);
- }
-
- ~AutoEglImage() {
- if (image != EGL_NO_IMAGE_KHR) {
- eglDestroyImageKHR(mDisplay, image);
- }
- }
-
- EGLImageKHR image = EGL_NO_IMAGE_KHR;
-private:
- EGLDisplay mDisplay = EGL_NO_DISPLAY;
-};
-
-class AutoGlTexture {
-public:
- AutoGlTexture(uirenderer::Caches& caches)
- : mCaches(caches) {
- glGenTextures(1, &mTexture);
- caches.textureState().bindTexture(mTexture);
- }
-
- ~AutoGlTexture() {
- mCaches.textureState().deleteTexture(mTexture);
- }
-
-private:
- uirenderer::Caches& mCaches;
- GLuint mTexture = 0;
-};
-
-static bool uploadBitmapToGraphicBuffer(uirenderer::Caches& caches, SkBitmap& bitmap,
- GraphicBuffer& buffer, GLint format, GLint type) {
- SkAutoLockPixels alp(bitmap);
- EGLDisplay display = eglGetCurrentDisplay();
- LOG_ALWAYS_FATAL_IF(display == EGL_NO_DISPLAY,
- "Failed to get EGL_DEFAULT_DISPLAY! err=%s",
- uirenderer::renderthread::EglManager::eglErrorString());
- // We use an EGLImage to access the content of the GraphicBuffer
- // The EGL image is later bound to a 2D texture
- EGLClientBuffer clientBuffer = (EGLClientBuffer) buffer.getNativeBuffer();
- AutoEglImage autoImage(display, clientBuffer);
- if (autoImage.image == EGL_NO_IMAGE_KHR) {
- ALOGW("Could not create EGL image, err =%s",
- uirenderer::renderthread::EglManager::eglErrorString());
- return false;
- }
- AutoGlTexture glTexture(caches);
- glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, autoImage.image);
-
- GL_CHECKPOINT(MODERATE);
-
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(),
- format, type, bitmap.getPixels());
-
- GL_CHECKPOINT(MODERATE);
-
- // The fence is used to wait for the texture upload to finish
- // properly. We cannot rely on glFlush() and glFinish() as
- // some drivers completely ignore these API calls
- AutoEglFence autoFence(display);
- if (autoFence.fence == EGL_NO_SYNC_KHR) {
- LOG_ALWAYS_FATAL("Could not create sync fence %#x", eglGetError());
- return false;
- }
- // The flag EGL_SYNC_FLUSH_COMMANDS_BIT_KHR will trigger a
- // pipeline flush (similar to what a glFlush() would do.)
- EGLint waitStatus = eglClientWaitSyncKHR(display, autoFence.fence,
- EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, FENCE_TIMEOUT);
- if (waitStatus != EGL_CONDITION_SATISFIED_KHR) {
- LOG_ALWAYS_FATAL("Failed to wait for the fence %#x", eglGetError());
- return false;
- }
- return true;
-}
-
-sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(uirenderer::renderthread::RenderThread& renderThread,
- SkBitmap& skBitmap) {
- renderThread.eglManager().initialize();
- uirenderer::Caches& caches = uirenderer::Caches::getInstance();
-
- const SkImageInfo& info = skBitmap.info();
- if (info.colorType() == kUnknown_SkColorType || info.colorType() == kAlpha_8_SkColorType) {
- ALOGW("unable to create hardware bitmap of colortype: %d", info.colorType());
- return nullptr;
- }
-
- bool needSRGB = uirenderer::transferFunctionCloseToSRGB(skBitmap.info().colorSpace());
- bool hasLinearBlending = caches.extensions().hasLinearBlending();
- GLint format, type, internalFormat;
- uirenderer::Texture::colorTypeToGlFormatAndType(caches, skBitmap.colorType(),
- needSRGB && hasLinearBlending, &internalFormat, &format, &type);
-
- PixelFormat pixelFormat = internalFormatToPixelFormat(internalFormat);
- sp<GraphicBuffer> buffer = new GraphicBuffer(info.width(), info.height(), pixelFormat,
- GraphicBuffer::USAGE_HW_TEXTURE |
- GraphicBuffer::USAGE_SW_WRITE_NEVER |
- GraphicBuffer::USAGE_SW_READ_NEVER,
- std::string("Bitmap::allocateHardwareBitmap pid [") + std::to_string(getpid()) + "]");
-
- status_t error = buffer->initCheck();
- if (error < 0) {
- ALOGW("createGraphicBuffer() failed in GraphicBuffer.create()");
- return nullptr;
- }
-
- SkBitmap bitmap;
- if (CC_UNLIKELY(uirenderer::Texture::hasUnsupportedColorType(skBitmap.info(),
- hasLinearBlending))) {
- sk_sp<SkColorSpace> sRGB = SkColorSpace::MakeSRGB();
- bitmap = uirenderer::Texture::uploadToN32(skBitmap, hasLinearBlending, std::move(sRGB));
- } else {
- bitmap = skBitmap;
- }
-
- if (!uploadBitmapToGraphicBuffer(caches, bitmap, *buffer, format, type)) {
- return nullptr;
- }
- return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info()));
+ return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes, std::move(ctable)));
}
sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(SkBitmap& bitmap) {
return uirenderer::renderthread::RenderProxy::allocateHardwareBitmap(bitmap);
}
-sk_sp<Bitmap> Bitmap::allocateHeapBitmap(SkBitmap* bitmap, SkColorTable* ctable) {
- return allocateBitmap(bitmap, ctable, &android::allocateHeapBitmap);
+sk_sp<Bitmap> Bitmap::allocateHeapBitmap(SkBitmap* bitmap, sk_sp<SkColorTable> ctable) {
+ return allocateBitmap(bitmap, std::move(ctable), &android::allocateHeapBitmap);
}
sk_sp<Bitmap> Bitmap::allocateHeapBitmap(const SkImageInfo& info) {
@@ -276,7 +103,7 @@
}
sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(size_t size, const SkImageInfo& info,
- size_t rowBytes, SkColorTable* ctable) {
+ size_t rowBytes, sk_sp<SkColorTable> ctable) {
// Create new ashmem region with read/write priv
int fd = ashmem_create_region("bitmap", size);
if (fd < 0) {
@@ -294,20 +121,18 @@
close(fd);
return nullptr;
}
- return sk_sp<Bitmap>(new Bitmap(addr, fd, size, info, rowBytes, ctable));
+ return sk_sp<Bitmap>(new Bitmap(addr, fd, size, info, rowBytes, std::move(ctable)));
}
void FreePixelRef(void* addr, void* context) {
auto pixelRef = (SkPixelRef*) context;
- pixelRef->unlockPixels();
pixelRef->unref();
}
sk_sp<Bitmap> Bitmap::createFrom(const SkImageInfo& info, SkPixelRef& pixelRef) {
pixelRef.ref();
- pixelRef.lockPixels();
return sk_sp<Bitmap>(new Bitmap((void*) pixelRef.pixels(), (void*) &pixelRef, FreePixelRef,
- info, pixelRef.rowBytes(), pixelRef.colorTable()));
+ info, pixelRef.rowBytes(), sk_ref_sp(pixelRef.colorTable())));
}
sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer) {
@@ -323,76 +148,87 @@
}
void Bitmap::setColorSpace(sk_sp<SkColorSpace> colorSpace) {
- // TODO: See todo in reconfigure() below
- SkImageInfo* myInfo = const_cast<SkImageInfo*>(&this->info());
- *myInfo = info().makeColorSpace(std::move(colorSpace));
+ mInfo = mInfo.makeColorSpace(std::move(colorSpace));
}
-void Bitmap::reconfigure(const SkImageInfo& newInfo, size_t rowBytes, SkColorTable* ctable) {
- if (kIndex_8_SkColorType != newInfo.colorType()) {
- ctable = nullptr;
- }
- mRowBytes = rowBytes;
- if (mColorTable.get() != ctable) {
- mColorTable.reset(SkSafeRef(ctable));
- }
-
+static SkImageInfo validateAlpha(const SkImageInfo& info) {
// Need to validate the alpha type to filter against the color type
// to prevent things like a non-opaque RGB565 bitmap
SkAlphaType alphaType;
LOG_ALWAYS_FATAL_IF(!SkColorTypeValidateAlphaType(
- newInfo.colorType(), newInfo.alphaType(), &alphaType),
+ info.colorType(), info.alphaType(), &alphaType),
"Failed to validate alpha type!");
+ return info.makeAlphaType(alphaType);
+}
+
+void Bitmap::reconfigure(const SkImageInfo& newInfo, size_t rowBytes, sk_sp<SkColorTable> ctable) {
+ if (kIndex_8_SkColorType != newInfo.colorType()) {
+ ctable = nullptr;
+ }
+
+ mInfo = validateAlpha(newInfo);
// Dirty hack is dirty
// TODO: Figure something out here, Skia's current design makes this
// really hard to work with. Skia really, really wants immutable objects,
// but with the nested-ref-count hackery going on that's just not
// feasible without going insane trying to figure it out
- SkImageInfo* myInfo = const_cast<SkImageInfo*>(&this->info());
- *myInfo = newInfo;
- changeAlphaType(alphaType);
-
- // Docs say to only call this in the ctor, but we're going to call
- // it anyway even if this isn't always the ctor.
- // TODO: Fix this too as part of the above TODO
- setPreLocked(getStorage(), mRowBytes, mColorTable.get());
+ this->android_only_reset(mInfo.width(), mInfo.height(), rowBytes, std::move(ctable));
}
-Bitmap::Bitmap(void* address, size_t size, const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
- : SkPixelRef(info)
+static sk_sp<SkColorTable> sanitize(const SkImageInfo& info, sk_sp<SkColorTable> ctable) {
+ if (info.colorType() == kIndex_8_SkColorType) {
+ SkASSERT(ctable);
+ return ctable;
+ }
+ return nullptr; // drop the ctable if we're not indexed
+}
+Bitmap::Bitmap(void* address, size_t size, const SkImageInfo& info, size_t rowBytes,
+ sk_sp<SkColorTable> ctable)
+ : SkPixelRef(info.width(), info.height(), address, rowBytes,
+ sanitize(info, std::move(ctable)))
+ , mInfo(validateAlpha(info))
, mPixelStorageType(PixelStorageType::Heap) {
mPixelStorage.heap.address = address;
mPixelStorage.heap.size = size;
- reconfigure(info, rowBytes, ctable);
}
Bitmap::Bitmap(void* address, void* context, FreeFunc freeFunc,
- const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
- : SkPixelRef(info)
+ const SkImageInfo& info, size_t rowBytes, sk_sp<SkColorTable> ctable)
+ : SkPixelRef(info.width(), info.height(), address, rowBytes,
+ sanitize(info, std::move(ctable)))
+ , mInfo(validateAlpha(info))
, mPixelStorageType(PixelStorageType::External) {
mPixelStorage.external.address = address;
mPixelStorage.external.context = context;
mPixelStorage.external.freeFunc = freeFunc;
- reconfigure(info, rowBytes, ctable);
}
Bitmap::Bitmap(void* address, int fd, size_t mappedSize,
- const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
- : SkPixelRef(info)
+ const SkImageInfo& info, size_t rowBytes, sk_sp<SkColorTable> ctable)
+ : SkPixelRef(info.width(), info.height(), address, rowBytes,
+ sanitize(info, std::move(ctable)))
+ , mInfo(validateAlpha(info))
, mPixelStorageType(PixelStorageType::Ashmem) {
mPixelStorage.ashmem.address = address;
mPixelStorage.ashmem.fd = fd;
mPixelStorage.ashmem.size = mappedSize;
- reconfigure(info, rowBytes, ctable);
}
Bitmap::Bitmap(GraphicBuffer* buffer, const SkImageInfo& info)
- : SkPixelRef(info)
+ : SkPixelRef(info.width(), info.height(), nullptr,
+ bytesPerPixel(buffer->getPixelFormat()) * buffer->getStride(),
+ nullptr)
+ , mInfo(validateAlpha(info))
, mPixelStorageType(PixelStorageType::Hardware) {
mPixelStorage.hardware.buffer = buffer;
buffer->incStrong(buffer);
- mRowBytes = bytesPerPixel(buffer->getPixelFormat()) * buffer->getStride();
+ setImmutable(); // HW bitmaps are always immutable
+ if (uirenderer::Properties::isSkiaEnabled()) {
+ // TODO: add color correctness for Skia pipeline - pass null color space for now
+ mImage = SkImage::MakeFromAHardwareBuffer(reinterpret_cast<AHardwareBuffer*>(buffer),
+ mInfo.alphaType(), nullptr);
+ }
}
Bitmap::~Bitmap() {
@@ -440,17 +276,6 @@
}
}
-bool Bitmap::onNewLockPixels(LockRec* rec) {
- rec->fPixels = getStorage();
- rec->fRowBytes = mRowBytes;
- rec->fColorTable = mColorTable.get();
- return true;
-}
-
-size_t Bitmap::getAllocatedSizeInBytes() const {
- return info().getSafeSize(mRowBytes);
-}
-
int Bitmap::getAshmemFd() const {
switch (mPixelStorageType) {
case PixelStorageType::Ashmem:
@@ -478,7 +303,7 @@
return;
}
- changeAlphaType(alphaType);
+ mInfo = mInfo.makeAlphaType(alphaType);
}
void Bitmap::getSkBitmap(SkBitmap* outBitmap) {
@@ -494,23 +319,13 @@
uirenderer::renderthread::RenderProxy::copyGraphicBufferInto(graphicBuffer(), outBitmap);
return;
}
- outBitmap->setInfo(info(), rowBytes());
- outBitmap->setPixelRef(this);
-}
-
-void Bitmap::getSkBitmapForShaders(SkBitmap* outBitmap) {
- if (isHardware() && uirenderer::Properties::isSkiaEnabled()) {
- getSkBitmap(outBitmap);
- } else {
- outBitmap->setInfo(info(), rowBytes());
- outBitmap->setPixelRef(this);
- outBitmap->setHasHardwareMipMap(mHasHardwareMipMap);
- }
+ outBitmap->setInfo(mInfo, rowBytes());
+ outBitmap->setPixelRef(sk_ref_sp(this), 0, 0);
}
void Bitmap::getBounds(SkRect* bounds) const {
SkASSERT(bounds);
- bounds->set(0, 0, SkIntToScalar(info().width()), SkIntToScalar(info().height()));
+ bounds->set(0, 0, SkIntToScalar(width()), SkIntToScalar(height()));
}
GraphicBuffer* Bitmap::graphicBuffer() {
@@ -520,4 +335,20 @@
return nullptr;
}
+sk_sp<SkImage> Bitmap::makeImage() {
+ sk_sp<SkImage> image = mImage;
+ if (!image) {
+ SkASSERT(!(isHardware() && uirenderer::Properties::isSkiaEnabled()));
+ SkBitmap skiaBitmap;
+ skiaBitmap.setInfo(info(), rowBytes());
+ skiaBitmap.setPixelRef(sk_ref_sp(this), 0, 0);
+ skiaBitmap.setHasHardwareMipMap(mHasHardwareMipMap);
+ // Note we don't cache in this case, because the raster image holds a pointer to this Bitmap
+ // internally and ~Bitmap won't be invoked.
+ // TODO: refactor Bitmap to not derive from SkPixelRef, which would allow caching here.
+ image = SkMakeImageFromRasterBitmap(skiaBitmap, kNever_SkCopyPixelsMode);
+ }
+ return image;
+}
+
} // namespace android
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index da45f76..364240670 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -18,10 +18,12 @@
#include <SkBitmap.h>
#include <SkColorSpace.h>
#include <SkColorTable.h>
+#include <SkImage.h>
#include <SkImageInfo.h>
#include <SkPixelRef.h>
#include <cutils/compiler.h>
#include <ui/GraphicBuffer.h>
+#include <SkImage.h>
namespace android {
@@ -44,66 +46,54 @@
class ANDROID_API Bitmap : public SkPixelRef {
public:
- static sk_sp<Bitmap> allocateHeapBitmap(SkBitmap* bitmap, SkColorTable* ctable);
+ static sk_sp<Bitmap> allocateHeapBitmap(SkBitmap* bitmap, sk_sp<SkColorTable> ctable);
static sk_sp<Bitmap> allocateHeapBitmap(const SkImageInfo& info);
static sk_sp<Bitmap> allocateHardwareBitmap(SkBitmap& bitmap);
- static sk_sp<Bitmap> allocateAshmemBitmap(SkBitmap* bitmap, SkColorTable* ctable);
+ static sk_sp<Bitmap> allocateAshmemBitmap(SkBitmap* bitmap, sk_sp<SkColorTable> ctable);
static sk_sp<Bitmap> allocateAshmemBitmap(size_t allocSize, const SkImageInfo& info,
- size_t rowBytes, SkColorTable* ctable);
+ size_t rowBytes, sk_sp<SkColorTable> ctable);
static sk_sp<Bitmap> createFrom(sp<GraphicBuffer> graphicBuffer);
static sk_sp<Bitmap> createFrom(const SkImageInfo&, SkPixelRef&);
- static sk_sp<Bitmap> allocateHardwareBitmap(uirenderer::renderthread::RenderThread&,
- SkBitmap& bitmap);
-
Bitmap(void* address, size_t allocSize, const SkImageInfo& info, size_t rowBytes,
- SkColorTable* ctable);
+ sk_sp<SkColorTable> ctable);
Bitmap(void* address, void* context, FreeFunc freeFunc,
- const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable);
+ const SkImageInfo& info, size_t rowBytes, sk_sp<SkColorTable> ctable);
Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info,
- size_t rowBytes, SkColorTable* ctable);
-
- int width() const { return info().width(); }
- int height() const { return info().height(); }
-
- // Can't mark as override since SkPixelRef::rowBytes isn't virtual
- // but that's OK since we just want Bitmap to be able to rely
- // on calling rowBytes() on an unlocked pixelref, which it will be
- // doing on a Bitmap type, not a SkPixelRef, so static
- // dispatching will do what we want.
- size_t rowBytes() const { return mRowBytes; }
+ size_t rowBytes, sk_sp<SkColorTable> ctable);
+ Bitmap(GraphicBuffer* buffer, const SkImageInfo& info);
int rowBytesAsPixels() const {
- return mRowBytes >> info().shiftPerPixel();
+ return rowBytes() >> SkColorTypeShiftPerPixel(mInfo.colorType());
}
- void reconfigure(const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable);
+ void reconfigure(const SkImageInfo& info, size_t rowBytes, sk_sp<SkColorTable> ctable);
void reconfigure(const SkImageInfo& info);
void setColorSpace(sk_sp<SkColorSpace> colorSpace);
void setAlphaType(SkAlphaType alphaType);
void getSkBitmap(SkBitmap* outBitmap);
- // Ugly hack: in case of hardware bitmaps, it sets nullptr as pixels pointer
- // so it would crash if anyone tries to render this bitmap.
- void getSkBitmapForShaders(SkBitmap* outBitmap);
-
int getAshmemFd() const;
size_t getAllocationByteCount() const;
void setHasHardwareMipMap(bool hasMipMap);
bool hasHardwareMipMap() const;
- bool isOpaque() const {return info().isOpaque(); }
- SkColorType colorType() const { return info().colorType(); }
+ bool isOpaque() const { return mInfo.isOpaque(); }
+ SkColorType colorType() const { return mInfo.colorType(); }
+ const SkImageInfo& info() const {
+ return mInfo;
+ }
+
void getBounds(SkRect* bounds) const;
bool readyToDraw() const {
- return this->colorType() != kIndex_8_SkColorType || mColorTable;
+ return this->colorType() != kIndex_8_SkColorType || this->colorTable();
}
bool isHardware() const {
@@ -111,19 +101,18 @@
}
GraphicBuffer* graphicBuffer();
-protected:
- virtual bool onNewLockPixels(LockRec* rec) override;
- virtual void onUnlockPixels() override { };
- virtual size_t getAllocatedSizeInBytes() const override;
+
+ // makeImage creates or returns a cached SkImage. Can be invoked from UI or render thread.
+ // Caching is supported only for HW Bitmaps with skia pipeline.
+ sk_sp<SkImage> makeImage();
private:
- Bitmap(GraphicBuffer* buffer, const SkImageInfo& info);
virtual ~Bitmap();
void* getStorage() const;
- PixelStorageType mPixelStorageType;
+ SkImageInfo mInfo;
- size_t mRowBytes = 0;
- sk_sp<SkColorTable> mColorTable;
+ const PixelStorageType mPixelStorageType;
+
bool mHasHardwareMipMap = false;
union {
@@ -145,6 +134,8 @@
GraphicBuffer* buffer;
} hardware;
} mPixelStorage;
+
+ sk_sp<SkImage> mImage; // Cache is used only for HW Bitmaps with Skia pipeline.
};
-} //namespace android
\ No newline at end of file
+} //namespace android
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index c64a89d..679cb50 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -80,13 +80,11 @@
class DrawTextFunctor {
public:
- DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, uint16_t* glyphs, float* pos,
+ DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas,
const SkPaint& paint, float x, float y, minikin::MinikinRect& bounds,
float totalAdvance)
: layout(layout)
, canvas(canvas)
- , glyphs(glyphs)
- , pos(pos)
, paint(paint)
, x(x)
, y(y)
@@ -95,19 +93,21 @@
}
void operator()(size_t start, size_t end) {
- if (canvas->drawTextAbsolutePos()) {
- for (size_t i = start; i < end; i++) {
- glyphs[i] = layout.getGlyphId(i);
- pos[2 * i] = x + layout.getX(i);
- pos[2 * i + 1] = y + layout.getY(i);
+ auto glyphFunc = [&] (uint16_t* text, float* positions) {
+ if (canvas->drawTextAbsolutePos()) {
+ for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
+ text[textIndex++] = layout.getGlyphId(i);
+ positions[posIndex++] = x + layout.getX(i);
+ positions[posIndex++] = y + layout.getY(i);
+ }
+ } else {
+ for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
+ text[textIndex++] = layout.getGlyphId(i);
+ positions[posIndex++] = layout.getX(i);
+ positions[posIndex++] = layout.getY(i);
+ }
}
- } else {
- for (size_t i = start; i < end; i++) {
- glyphs[i] = layout.getGlyphId(i);
- pos[2 * i] = layout.getX(i);
- pos[2 * i + 1] = layout.getY(i);
- }
- }
+ };
size_t glyphCount = end - start;
@@ -121,26 +121,24 @@
SkPaint outlinePaint(paint);
simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
- canvas->drawGlyphs(glyphs + start, pos + (2 * start), glyphCount, outlinePaint, x, y,
- bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance);
+ canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, bounds.mLeft, bounds.mTop,
+ bounds.mRight, bounds.mBottom, totalAdvance);
// inner
SkPaint innerPaint(paint);
simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
innerPaint.setStyle(SkPaint::kFill_Style);
- canvas->drawGlyphs(glyphs + start, pos + (2 * start), glyphCount, innerPaint, x, y,
- bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance);
+ canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, bounds.mLeft, bounds.mTop,
+ bounds.mRight, bounds.mBottom, totalAdvance);
} else {
// standard draw path
- canvas->drawGlyphs(glyphs + start, pos + (2 * start), glyphCount, paint, x, y,
- bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance);
+ canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, bounds.mLeft, bounds.mTop,
+ bounds.mRight, bounds.mBottom, totalAdvance);
}
}
private:
const minikin::Layout& layout;
Canvas* canvas;
- uint16_t* glyphs;
- float* pos;
const SkPaint& paint;
float x;
float y;
@@ -156,10 +154,6 @@
minikin::Layout layout = MinikinUtils::doLayout(
&paint, bidiFlags, typeface, text, start, count, contextCount);
- size_t nGlyphs = layout.nGlyphs();
- std::unique_ptr<uint16_t[]> glyphs(new uint16_t[nGlyphs]);
- std::unique_ptr<float[]> pos(new float[nGlyphs * 2]);
-
x += MinikinUtils::xOffsetForTextAlign(&paint, layout);
minikin::MinikinRect bounds;
@@ -173,8 +167,7 @@
// care of all alignment.
paint.setTextAlign(Paint::kLeft_Align);
- DrawTextFunctor f(layout, this, glyphs.get(), pos.get(),
- paint, x, y, bounds, layout.getAdvance());
+ DrawTextFunctor f(layout, this, paint, x, y, bounds, layout.getAdvance());
MinikinUtils::forFontRun(layout, &paint, f);
}
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 86af678..ac8a081 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -28,6 +28,7 @@
#include <SkMatrix.h>
class SkCanvasState;
+class SkVertices;
namespace minikin {
class Layout;
@@ -67,6 +68,8 @@
};
typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot;
+typedef std::function<void(uint16_t* text, float* positions)> ReadGlyphFunc;
+
class Bitmap;
class Paint;
struct Typeface;
@@ -228,9 +231,7 @@
virtual void drawArc(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) = 0;
virtual void drawPath(const SkPath& path, const SkPaint& paint) = 0;
- virtual void drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount,
- const float* verts, const float* tex, const int* colors,
- const uint16_t* indices, int indexCount, const SkPaint& paint) = 0;
+ virtual void drawVertices(const SkVertices*, SkBlendMode, const SkPaint& paint) = 0;
// Bitmap-based
virtual void drawBitmap(Bitmap& bitmap, float left, float top,
@@ -274,12 +275,12 @@
void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
/**
+ * glyphFunc: valid only for the duration of the call and should not be cached.
* drawText: count is of glyphs
* totalAdvance: used to define width of text decorations (underlines, strikethroughs).
*/
- virtual void drawGlyphs(const uint16_t* glyphs, const float* positions, int count,
- const SkPaint& paint, float x, float y,
- float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
+ virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const SkPaint& paint, float x,
+ float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) = 0;
virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
const SkPaint& paint, const SkPath& path, size_t start, size_t end) = 0;
diff --git a/libs/hwui/hwui_static_deps.mk b/libs/hwui/hwui_static_deps.mk
deleted file mode 100644
index 8826cfc..0000000
--- a/libs/hwui/hwui_static_deps.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-###############################################################################
-#
-#
-# This file contains the shared and static dependencies needed by any target
-# that attempts to statically link HWUI (i.e. libhwui_static build target). This
-# file should be included by any target that lists libhwui_static as a
-# dependency.
-#
-# This is a workaround for the fact that the build system does not add these
-# transitive dependencies when it attempts to link libhwui_static into another
-# library.
-#
-###############################################################################
-
-LOCAL_SHARED_LIBRARIES += \
- liblog \
- libcutils \
- libutils \
- libEGL \
- libGLESv2 \
- libvulkan \
- libskia \
- libui \
- libgui \
- libprotobuf-cpp-full \
- libharfbuzz_ng \
- libft2 \
- libminikin \
- libandroidfw \
- libRScpp
-
-LOCAL_STATIC_LIBRARIES += \
- libplatformprotos
diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
index 68a0869..975f849 100644
--- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
+++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
@@ -21,7 +21,6 @@
#include <SkBlurMask.h>
#include <SkBlurMaskFilter.h>
-#include <SkGaussianEdgeShader.h>
#include <SkPathOps.h>
#include <SkRRectsGaussianEdgeMaskFilter.h>
#include <SkShadowUtils.h>
@@ -137,7 +136,6 @@
float ambientAlpha = (SkiaPipeline::getAmbientShadowAlpha()/255.f)*casterAlpha;
float spotAlpha = (SkiaPipeline::getSpotShadowAlpha()/255.f)*casterAlpha;
- const float casterZValue = casterProperties.getZ();
const RevealClip& revealClip = casterProperties.getRevealClip();
const SkPath* revealClipPath = revealClip.getPath();
@@ -178,6 +176,7 @@
// intersect the shadow-casting path with the reveal, if present
if (revealClipPath) {
Op(*casterPath, *revealClipPath, kIntersect_SkPathOp, &tmpPath);
+ tmpPath.setIsVolatile(true);
casterPath = &tmpPath;
}
@@ -186,36 +185,23 @@
SkPath clipBoundsPath;
clipBoundsPath.addRect(casterClipRect);
Op(*casterPath, clipBoundsPath, kIntersect_SkPathOp, &tmpPath);
+ tmpPath.setIsVolatile(true);
casterPath = &tmpPath;
}
const Vector3 lightPos = SkiaPipeline::getLightCenter();
SkPoint3 skiaLightPos = SkPoint3::Make(lightPos.x, lightPos.y, lightPos.z);
- if (shadowMatrix.hasPerspective() || revealClipPath || clippedToBounds) {
- std::function<SkScalar(SkScalar, SkScalar)> casterHeightFunc;
- if (shadowMatrix.hasPerspective()) {
- // get the matrix with the full 3D transform
- mat4 zMatrix;
- caster->getRenderNode()->applyViewPropertyTransforms(zMatrix, true);
- SkScalar A = zMatrix[2];
- SkScalar B = zMatrix[6];
- SkScalar C = zMatrix[mat4::kTranslateZ];
- casterHeightFunc = [A, B, C](SkScalar x, SkScalar y) {
- return A*x + B*y + C; // casterZValue already baked into C
- };
- } else {
- casterHeightFunc = [casterZValue] (SkScalar, SkScalar) {
- return casterZValue;
- };
- }
-
- SkShadowUtils::DrawUncachedShadow(canvas, *casterPath, casterHeightFunc, skiaLightPos,
- SkiaPipeline::getLightRadius(), ambientAlpha, spotAlpha, SK_ColorBLACK,
- casterAlpha < 1.0f ? SkShadowFlags::kTransparentOccluder_ShadowFlag : 0);
+ SkPoint3 zParams;
+ if (shadowMatrix.hasPerspective()) {
+ // get the matrix with the full 3D transform
+ mat4 zMatrix;
+ caster->getRenderNode()->applyViewPropertyTransforms(zMatrix, true);
+ zParams = SkPoint3::Make(zMatrix[2], zMatrix[6], zMatrix[mat4::kTranslateZ]);
} else {
- SkShadowUtils::DrawShadow(canvas, *casterPath, casterZValue, skiaLightPos,
- SkiaPipeline::getLightRadius(), ambientAlpha, spotAlpha, SK_ColorBLACK,
- casterAlpha < 1.0f ? SkShadowFlags::kTransparentOccluder_ShadowFlag : 0);
+ zParams = SkPoint3::Make(0, 0, casterProperties.getZ());
}
+ SkShadowUtils::DrawShadow(canvas, *casterPath, zParams, skiaLightPos,
+ SkiaPipeline::getLightRadius(), ambientAlpha, spotAlpha, SK_ColorBLACK,
+ casterAlpha < 1.0f ? SkShadowFlags::kTransparentOccluder_ShadowFlag : 0);
}
}; // namespace skiapipeline
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index 496f7ba..3ddc09f 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -19,6 +19,7 @@
#include "renderthread/CanvasContext.h"
#include "VectorDrawable.h"
#include "DumpOpsCanvas.h"
+#include "SkiaPipeline.h"
#include <SkImagePriv.h>
@@ -92,6 +93,8 @@
// If any vector drawable in the display list needs update, damage the node.
if (vectorDrawable->isDirty()) {
isDirty = true;
+ static_cast<SkiaPipeline*>(info.canvasContext.getRenderPipeline())
+ ->getVectorDrawables()->push_back(vectorDrawable);
}
vectorDrawable->setPropertyChangeWillBeConsumed(true);
}
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 6ee5922..66375d1 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -60,7 +60,7 @@
* Use the linear allocator to create any SkDrawables needed by the display
* list. This could be dangerous as these objects are ref-counted, so we
* need to monitor that they don't extend beyond the lifetime of the class
- * that creates them.
+ * that creates them. Allocator dtor invokes all SkDrawable dtors.
*/
template<class T, typename... Params>
SkDrawable* allocateDrawable(Params&&... params) {
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index ae13131..095d32e 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -16,6 +16,7 @@
#include "SkiaOpenGLPipeline.h"
+#include "hwui/Bitmap.h"
#include "DeferredLayerUpdater.h"
#include "GlLayer.h"
#include "LayerDrawable.h"
@@ -155,7 +156,8 @@
}
}
-bool SkiaOpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior) {
+bool SkiaOpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior,
+ ColorMode colorMode) {
if (mEglSurface != EGL_NO_SURFACE) {
mEglManager.destroySurface(mEglSurface);
@@ -163,7 +165,8 @@
}
if (surface) {
- mEglSurface = mEglManager.createSurface(surface);
+ const bool wideColorGamut = colorMode == ColorMode::WideColorGamut;
+ mEglSurface = mEglManager.createSurface(surface, wideColorGamut);
}
if (mEglSurface != EGL_NO_SURFACE) {
@@ -197,6 +200,186 @@
}
}
+#define FENCE_TIMEOUT 2000000000
+
+class AutoEglFence {
+public:
+ AutoEglFence(EGLDisplay display)
+ : mDisplay(display) {
+ fence = eglCreateSyncKHR(mDisplay, EGL_SYNC_FENCE_KHR, NULL);
+ }
+
+ ~AutoEglFence() {
+ if (fence != EGL_NO_SYNC_KHR) {
+ eglDestroySyncKHR(mDisplay, fence);
+ }
+ }
+
+ EGLSyncKHR fence = EGL_NO_SYNC_KHR;
+private:
+ EGLDisplay mDisplay = EGL_NO_DISPLAY;
+};
+
+class AutoEglImage {
+public:
+ AutoEglImage(EGLDisplay display, EGLClientBuffer clientBuffer)
+ : mDisplay(display) {
+ EGLint imageAttrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE };
+ image = eglCreateImageKHR(display, EGL_NO_CONTEXT,
+ EGL_NATIVE_BUFFER_ANDROID, clientBuffer, imageAttrs);
+ }
+
+ ~AutoEglImage() {
+ if (image != EGL_NO_IMAGE_KHR) {
+ eglDestroyImageKHR(mDisplay, image);
+ }
+ }
+
+ EGLImageKHR image = EGL_NO_IMAGE_KHR;
+private:
+ EGLDisplay mDisplay = EGL_NO_DISPLAY;
+};
+
+class AutoSkiaGlTexture {
+public:
+ AutoSkiaGlTexture() {
+ glGenTextures(1, &mTexture);
+ glBindTexture(GL_TEXTURE_2D, mTexture);
+ }
+
+ ~AutoSkiaGlTexture() {
+ glDeleteTextures(1, &mTexture);
+ }
+
+private:
+ GLuint mTexture = 0;
+};
+
+sk_sp<Bitmap> SkiaOpenGLPipeline::allocateHardwareBitmap(renderthread::RenderThread& renderThread,
+ SkBitmap& skBitmap) {
+ renderThread.eglManager().initialize();
+
+ sk_sp<GrContext> grContext = sk_ref_sp(renderThread.getGrContext());
+ const SkImageInfo& info = skBitmap.info();
+ PixelFormat pixelFormat;
+ GLint format, type;
+ bool isSupported = false;
+
+ //TODO: add support for linear blending (when ANDROID_ENABLE_LINEAR_BLENDING is defined)
+ switch (info.colorType()) {
+ case kRGBA_8888_SkColorType:
+ isSupported = true;
+ // ARGB_4444 and Index_8 are both upconverted to RGBA_8888
+ case kIndex_8_SkColorType:
+ case kARGB_4444_SkColorType:
+ pixelFormat = PIXEL_FORMAT_RGBA_8888;
+ format = GL_RGBA;
+ type = GL_UNSIGNED_BYTE;
+ break;
+ case kRGBA_F16_SkColorType:
+ isSupported = grContext->caps()->isConfigTexturable(kRGBA_half_GrPixelConfig);
+ if (isSupported) {
+ type = GL_HALF_FLOAT;
+ pixelFormat = PIXEL_FORMAT_RGBA_FP16;
+ } else {
+ type = GL_UNSIGNED_BYTE;
+ pixelFormat = PIXEL_FORMAT_RGBA_8888;
+ }
+ format = GL_RGBA;
+ break;
+ case kRGB_565_SkColorType:
+ isSupported = true;
+ pixelFormat = PIXEL_FORMAT_RGB_565;
+ format = GL_RGB;
+ type = GL_UNSIGNED_SHORT_5_6_5;
+ break;
+ case kGray_8_SkColorType:
+ isSupported = true;
+ pixelFormat = PIXEL_FORMAT_RGBA_8888;
+ format = GL_LUMINANCE;
+ type = GL_UNSIGNED_BYTE;
+ break;
+ default:
+ ALOGW("unable to create hardware bitmap of colortype: %d", info.colorType());
+ return nullptr;
+ }
+
+ SkBitmap bitmap;
+ if (isSupported) {
+ bitmap = skBitmap;
+ } else {
+ bitmap.allocPixels(SkImageInfo::MakeN32(info.width(), info.height(), info.alphaType(),
+ nullptr));
+ bitmap.eraseColor(0);
+ if (info.colorType() == kRGBA_F16_SkColorType) {
+ // Drawing RGBA_F16 onto ARGB_8888 is not supported
+ skBitmap.readPixels(bitmap.info().makeColorSpace(SkColorSpace::MakeSRGB()),
+ bitmap.getPixels(), bitmap.rowBytes(), 0, 0);
+ } else {
+ SkCanvas canvas(bitmap);
+ canvas.drawBitmap(skBitmap, 0.0f, 0.0f, nullptr);
+ }
+ }
+
+ sp<GraphicBuffer> buffer = new GraphicBuffer(info.width(), info.height(), pixelFormat,
+ GraphicBuffer::USAGE_HW_TEXTURE |
+ GraphicBuffer::USAGE_SW_WRITE_NEVER |
+ GraphicBuffer::USAGE_SW_READ_NEVER,
+ std::string("Bitmap::allocateSkiaHardwareBitmap pid [") + std::to_string(getpid()) + "]");
+
+ status_t error = buffer->initCheck();
+ if (error < 0) {
+ ALOGW("createGraphicBuffer() failed in GraphicBuffer.create()");
+ return nullptr;
+ }
+
+ //upload the bitmap into a texture
+ EGLDisplay display = eglGetCurrentDisplay();
+ LOG_ALWAYS_FATAL_IF(display == EGL_NO_DISPLAY,
+ "Failed to get EGL_DEFAULT_DISPLAY! err=%s",
+ uirenderer::renderthread::EglManager::eglErrorString());
+ // We use an EGLImage to access the content of the GraphicBuffer
+ // The EGL image is later bound to a 2D texture
+ EGLClientBuffer clientBuffer = (EGLClientBuffer) buffer->getNativeBuffer();
+ AutoEglImage autoImage(display, clientBuffer);
+ if (autoImage.image == EGL_NO_IMAGE_KHR) {
+ ALOGW("Could not create EGL image, err =%s",
+ uirenderer::renderthread::EglManager::eglErrorString());
+ return nullptr;
+ }
+ AutoSkiaGlTexture glTexture;
+ glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, autoImage.image);
+ GL_CHECKPOINT(MODERATE);
+
+ // glTexSubImage2D is synchronous in sense that it memcpy() from pointer that we provide.
+ // But asynchronous in sense that driver may upload texture onto hardware buffer when we first
+ // use it in drawing
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, info.width(), info.height(), format, type,
+ bitmap.getPixels());
+ GL_CHECKPOINT(MODERATE);
+
+ // The fence is used to wait for the texture upload to finish
+ // properly. We cannot rely on glFlush() and glFinish() as
+ // some drivers completely ignore these API calls
+ AutoEglFence autoFence(display);
+ if (autoFence.fence == EGL_NO_SYNC_KHR) {
+ LOG_ALWAYS_FATAL("Could not create sync fence %#x", eglGetError());
+ return nullptr;
+ }
+ // The flag EGL_SYNC_FLUSH_COMMANDS_BIT_KHR will trigger a
+ // pipeline flush (similar to what a glFlush() would do.)
+ EGLint waitStatus = eglClientWaitSyncKHR(display, autoFence.fence,
+ EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, FENCE_TIMEOUT);
+ if (waitStatus != EGL_CONDITION_SATISFIED_KHR) {
+ LOG_ALWAYS_FATAL("Failed to wait for the fence %#x", eglGetError());
+ return nullptr;
+ }
+
+ grContext->resetContext(kTextureBinding_GrGLBackendState);
+
+ return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info()));
+}
+
} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 36685dd..d5471de 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -19,6 +19,9 @@
#include "SkiaPipeline.h"
namespace android {
+
+class Bitmap;
+
namespace uirenderer {
namespace skiapipeline {
@@ -40,12 +43,15 @@
FrameInfo* currentFrameInfo, bool* requireSwap) override;
bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) override;
DeferredLayerUpdater* createTextureLayer() override;
- bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior) override;
+ bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior,
+ renderthread::ColorMode colorMode) override;
void onStop() override;
bool isSurfaceReady() override;
bool isContextReady() override;
static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor);
+ static sk_sp<Bitmap> allocateHardwareBitmap(renderthread::RenderThread& thread,
+ SkBitmap& skBitmap);
private:
renderthread::EglManager& mEglManager;
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
index a18d264..89697d7 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
@@ -63,8 +63,6 @@
CopyResult copyResult = CopyResult::UnknownError;
sk_sp<SkImage> image(SkImage::MakeFromAdoptedTexture(grContext.get(), textureDescription));
if (image) {
- SkAutoLockPixels alp(*bitmap);
-
// convert to Skia data structures
const SkRect bufferRect = SkRect::MakeIWH(imgWidth, imgHeight);
SkRect skiaSrcRect = srcRect.toSkRect();
@@ -88,23 +86,17 @@
textureMatrix.mapRect(&skiaSrcRect);
if (skiaSrcRect.intersect(bufferRect)) {
- SkPoint srcOrigin = SkPoint::Make(skiaSrcRect.fLeft, skiaSrcRect.fTop);
+ // we render in an offscreen buffer to scale and to avoid an issue b/62262733
+ // with reading incorrect data from EGLImage backed SkImage (likely a driver bug)
+ sk_sp<SkSurface> scaledSurface = SkSurface::MakeRenderTarget(
+ grContext.get(), SkBudgeted::kYes, bitmap->info());
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc);
+ scaledSurface->getCanvas()->drawImageRect(image, skiaSrcRect,
+ SkRect::MakeWH(bitmap->width(), bitmap->height()), &paint);
+ image = scaledSurface->makeImageSnapshot();
- // if we need to scale the result we must render to an offscreen buffer
- if (bitmap->width() != skiaSrcRect.width()
- || bitmap->height() != skiaSrcRect.height()) {
- sk_sp<SkSurface> scaledSurface = SkSurface::MakeRenderTarget(
- grContext.get(), SkBudgeted::kYes, bitmap->info());
- SkPaint paint;
- paint.setBlendMode(SkBlendMode::kSrc);
- scaledSurface->getCanvas()->drawImageRect(image, skiaSrcRect,
- SkRect::MakeWH(bitmap->width(), bitmap->height()), &paint);
- image = scaledSurface->makeImageSnapshot();
- srcOrigin.set(0,0);
- }
-
- if (image->readPixels(bitmap->info(), bitmap->getPixels(), bitmap->rowBytes(),
- srcOrigin.fX, srcOrigin.fY)) {
+ if (image->readPixels(bitmap->info(), bitmap->getPixels(), bitmap->rowBytes(), 0, 0)) {
copyResult = CopyResult::Success;
}
}
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 10c1865..bbbbd5c 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -25,6 +25,7 @@
#include <SkPictureRecorder.h>
#include <SkPixelSerializer.h>
#include <SkStream.h>
+#include "VectorDrawable.h"
#include <unistd.h>
@@ -40,15 +41,16 @@
Vector3 SkiaPipeline::mLightCenter = {FLT_MIN, FLT_MIN, FLT_MIN};
-SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) { }
+SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) {
+ mVectorDrawables.reserve(30);
+}
TaskManager* SkiaPipeline::getTaskManager() {
return &mTaskManager;
}
void SkiaPipeline::onDestroyHardwareResources() {
- // No need to flush the caches here. There is a timer
- // which will flush temporary resources over time.
+ mRenderThread.cacheManager().trimStaleResources();
}
bool SkiaPipeline::pinImages(std::vector<SkImage*>& mutableImages) {
@@ -74,6 +76,7 @@
const BakedOpRenderer::LightInfo& lightInfo) {
updateLighting(lightGeometry, lightInfo);
ATRACE_NAME("draw layers");
+ renderVectorDrawableCache();
renderLayersImpl(*layerUpdateQueue, opaque);
layerUpdateQueue->clear();
}
@@ -155,11 +158,11 @@
GrContext* context = thread.getGrContext();
if (context) {
ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
- SkBitmap skiaBitmap;
- bitmap->getSkBitmap(&skiaBitmap);
- sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(skiaBitmap, kNever_SkCopyPixelsMode);
- SkImage_pinAsTexture(image.get(), context);
- SkImage_unpinAsTexture(image.get(), context);
+ auto image = bitmap->makeImage();
+ if (image.get() && !bitmap->isHardware()) {
+ SkImage_pinAsTexture(image.get(), context);
+ SkImage_unpinAsTexture(image.get(), context);
+ }
}
}
@@ -176,10 +179,35 @@
}
};
+void SkiaPipeline::renderVectorDrawableCache() {
+ //render VectorDrawables into offscreen buffers
+ for (auto vd : mVectorDrawables) {
+ sk_sp<SkSurface> surface;
+ if (!vd->canReuseSurface()) {
+#ifndef ANDROID_ENABLE_LINEAR_BLENDING
+ sk_sp<SkColorSpace> colorSpace = nullptr;
+#else
+ sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB();
+#endif
+ int scaledWidth = SkScalarCeilToInt(vd->properties().getScaledWidth());
+ int scaledHeight = SkScalarCeilToInt(vd->properties().getScaledHeight());
+ SkImageInfo info = SkImageInfo::MakeN32(scaledWidth, scaledHeight,
+ kPremul_SkAlphaType, colorSpace);
+ SkASSERT(mRenderThread.getGrContext() != nullptr);
+ surface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes,
+ info);
+ }
+ vd->updateCache(surface);
+ }
+ mVectorDrawables.clear();
+}
+
void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect &contentDrawBounds,
sk_sp<SkSurface> surface) {
+ renderVectorDrawableCache();
+
// draw all layers up front
renderLayersImpl(layers, opaque);
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index c58fedf..6f5e719 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -49,6 +49,8 @@
const std::vector< sp<RenderNode> >& nodes, bool opaque, const Rect &contentDrawBounds,
sk_sp<SkSurface> surface);
+ std::vector<VectorDrawableRoot*>* getVectorDrawables() { return &mVectorDrawables; }
+
static void destroyLayer(RenderNode* node);
static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
@@ -119,8 +121,18 @@
const std::vector< sp<RenderNode> >& nodes, const Rect &contentDrawBounds,
sk_sp<SkSurface>);
+ /**
+ * Render mVectorDrawables into offscreen buffers.
+ */
+ void renderVectorDrawableCache();
+
TaskManager mTaskManager;
std::vector<sk_sp<SkImage>> mPinnedImages;
+
+ /**
+ * populated by prepareTree with dirty VDs
+ */
+ std::vector<VectorDrawableRoot*> mVectorDrawables;
static float mLightRadius;
static uint8_t mAmbientShadowAlpha;
static uint8_t mSpotShadowAlpha;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 559d268..a0cce98 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -62,6 +62,7 @@
uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right,
uirenderer::CanvasPropertyPrimitive* bottom, uirenderer::CanvasPropertyPrimitive* rx,
uirenderer::CanvasPropertyPrimitive* ry, uirenderer::CanvasPropertyPaint* paint) {
+ // Destructor of drawables created with allocateDrawable, will be invoked by ~LinearAllocator.
drawDrawable(mDisplayList->allocateDrawable<AnimatedRoundRect>(left, top, right, bottom,
rx, ry, paint));
}
@@ -92,13 +93,14 @@
void SkiaRecordingCanvas::drawLayer(uirenderer::DeferredLayerUpdater* layerUpdater) {
if (layerUpdater != nullptr && layerUpdater->backingLayer() != nullptr) {
uirenderer::Layer* layer = layerUpdater->backingLayer();
+ // Create a ref-counted drawable, which is kept alive by sk_sp in SkLiteDL.
sk_sp<SkDrawable> drawable(new LayerDrawable(layer));
drawDrawable(drawable.get());
}
}
void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) {
- // record the child node
+ // Record the child node. Drawable dtor will be invoked when mChildNodes deque is cleared.
mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas(), true, mCurrentBarrier);
auto& renderNodeDrawable = mDisplayList->mChildNodes.back();
drawDrawable(&renderNodeDrawable);
@@ -113,6 +115,7 @@
void SkiaRecordingCanvas::callDrawGLFunction(Functor* functor,
uirenderer::GlFunctorLifecycleListener* listener) {
+ // Drawable dtor will be invoked when mChildFunctors deque is cleared.
mDisplayList->mChildFunctors.emplace_back(functor, listener, asSkCanvas());
drawDrawable(&mDisplayList->mChildFunctors.back());
}
@@ -126,22 +129,7 @@
return SkRect::MakeLargest();
}
virtual void onDraw(SkCanvas* canvas) override {
- Bitmap& hwuiBitmap = mRoot->getBitmapUpdateIfDirty();
- SkBitmap bitmap;
- hwuiBitmap.getSkBitmap(&bitmap);
- SkPaint* paint = mRoot->getPaint();
- canvas->drawBitmapRect(bitmap, mRoot->mutateProperties()->getBounds(), paint);
- /*
- * TODO we can draw this directly but need to address the following...
- *
- * 1) Add drawDirect(SkCanvas*) to VectorDrawableRoot
- * 2) fix VectorDrawable.cpp's Path::draw to not make a temporary path
- * so that we don't break caching
- * 3) figure out how to set path's as volatile during animation
- * 4) if mRoot->getPaint() != null either promote to layer (during
- * animation) or cache in SkSurface (for static content)
- *
- */
+ mRoot->draw(canvas);
}
private:
@@ -168,53 +156,47 @@
}
void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) {
- SkBitmap skBitmap;
- bitmap.getSkBitmap(&skBitmap);
-
- sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(skBitmap, kNever_SkCopyPixelsMode);
- if (!skBitmap.isImmutable()) {
- mDisplayList->mMutableImages.push_back(image.get());
- }
+ sk_sp<SkImage> image = bitmap.makeImage();
SkPaint tmpPaint;
mRecorder.drawImage(image, left, top, nonAAPaint(paint, &tmpPaint));
+ // if image->unique() is true, then mRecorder.drawImage failed for some reason. It also means
+ // it is not safe to store a raw SkImage pointer, because the image object will be destroyed
+ // when this function ends.
+ if (!bitmap.isImmutable() && image.get() && !image->unique()) {
+ mDisplayList->mMutableImages.push_back(image.get());
+ }
}
void SkiaRecordingCanvas::drawBitmap(Bitmap& hwuiBitmap, const SkMatrix& matrix,
const SkPaint* paint) {
- SkBitmap bitmap;
- hwuiBitmap.getSkBitmap(&bitmap);
SkAutoCanvasRestore acr(&mRecorder, true);
concat(matrix);
- sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
- if (!bitmap.isImmutable()) {
- mDisplayList->mMutableImages.push_back(image.get());
- }
+ sk_sp<SkImage> image = hwuiBitmap.makeImage();
SkPaint tmpPaint;
mRecorder.drawImage(image, 0, 0, nonAAPaint(paint, &tmpPaint));
+ if (!hwuiBitmap.isImmutable() && image.get() && !image->unique()) {
+ mDisplayList->mMutableImages.push_back(image.get());
+ }
}
void SkiaRecordingCanvas::drawBitmap(Bitmap& hwuiBitmap, float srcLeft, float srcTop,
float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight,
float dstBottom, const SkPaint* paint) {
- SkBitmap bitmap;
- hwuiBitmap.getSkBitmap(&bitmap);
SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
- sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
- if (!bitmap.isImmutable()) {
- mDisplayList->mMutableImages.push_back(image.get());
- }
+ sk_sp<SkImage> image = hwuiBitmap.makeImage();
SkPaint tmpPaint;
mRecorder.drawImageRect(image, srcRect, dstRect, nonAAPaint(paint, &tmpPaint));
+ if (!hwuiBitmap.isImmutable() && image.get() && !image->unique() && !srcRect.isEmpty()
+ && !dstRect.isEmpty()) {
+ mDisplayList->mMutableImages.push_back(image.get());
+ }
}
void SkiaRecordingCanvas::drawNinePatch(Bitmap& hwuiBitmap, const Res_png_9patch& chunk,
float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint) {
- SkBitmap bitmap;
- hwuiBitmap.getSkBitmap(&bitmap);
-
SkCanvas::Lattice lattice;
- NinePatchUtils::SetLatticeDivs(&lattice, chunk, bitmap.width(), bitmap.height());
+ NinePatchUtils::SetLatticeDivs(&lattice, chunk, hwuiBitmap.width(), hwuiBitmap.height());
lattice.fFlags = nullptr;
int numFlags = 0;
@@ -231,13 +213,13 @@
lattice.fBounds = nullptr;
SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
- sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
- if (!bitmap.isImmutable()) {
- mDisplayList->mMutableImages.push_back(image.get());
- }
+ sk_sp<SkImage> image = hwuiBitmap.makeImage();
SkPaint tmpPaint;
mRecorder.drawImageLattice(image.get(), lattice, dst, nonAAPaint(paint, &tmpPaint));
+ if (!hwuiBitmap.isImmutable() && image.get() && !image->unique() && !dst.isEmpty()) {
+ mDisplayList->mMutableImages.push_back(image.get());
+ }
}
}; // namespace skiapipeline
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index d28e605..f1ef9e6 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -131,13 +131,15 @@
void SkiaVulkanPipeline::onStop() {
}
-bool SkiaVulkanPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior) {
+bool SkiaVulkanPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior,
+ ColorMode colorMode) {
if (mVkSurface) {
mVkManager.destroySurface(mVkSurface);
mVkSurface = nullptr;
}
if (surface) {
+ // TODO: handle color mode
mVkSurface = mVkManager.createSurface(surface);
}
@@ -158,6 +160,25 @@
(*functor)(mode, nullptr);
}
+sk_sp<Bitmap> SkiaVulkanPipeline::allocateHardwareBitmap(renderthread::RenderThread& renderThread,
+ SkBitmap& skBitmap) {
+ //TODO: implement this function for Vulkan pipeline
+ //code below is a hack to avoid crashing because of missing HW Bitmap support
+ sp<GraphicBuffer> buffer = new GraphicBuffer(skBitmap.info().width(), skBitmap.info().height(),
+ PIXEL_FORMAT_RGBA_8888,
+ GraphicBuffer::USAGE_HW_TEXTURE |
+ GraphicBuffer::USAGE_SW_WRITE_NEVER |
+ GraphicBuffer::USAGE_SW_READ_NEVER,
+ std::string("SkiaVulkanPipeline::allocateHardwareBitmap pid [")
+ + std::to_string(getpid()) + "]");
+ status_t error = buffer->initCheck();
+ if (error < 0) {
+ ALOGW("SkiaVulkanPipeline::allocateHardwareBitmap() failed in GraphicBuffer.create()");
+ return nullptr;
+ }
+ return sk_sp<Bitmap>(new Bitmap(buffer.get(), skBitmap.info()));
+}
+
} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index aab1d7a..3481312 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -41,12 +41,15 @@
FrameInfo* currentFrameInfo, bool* requireSwap) override;
bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) override;
DeferredLayerUpdater* createTextureLayer() override;
- bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior) override;
+ bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior,
+ renderthread::ColorMode colorMode) override;
void onStop() override;
bool isSurfaceReady() override;
bool isContextReady() override;
static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor);
+ static sk_sp<Bitmap> allocateHardwareBitmap(renderthread::RenderThread& thread,
+ SkBitmap& skBitmap);
private:
renderthread::VulkanManager& mVkManager;
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index ed96d49..2c92924 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -296,14 +296,20 @@
// TODO: avoid query, and cache values (or RRCS ptr) in program
const RoundRectClipState* state = glop.roundRectClipState;
const Rect& innerRect = state->innerRect;
- glUniform4f(fill.program->getUniform("roundRectInnerRectLTRB"),
- innerRect.left, innerRect.top,
- innerRect.right, innerRect.bottom);
- glUniformMatrix4fv(fill.program->getUniform("roundRectInvTransform"),
- 1, GL_FALSE, &state->matrix.data[0]);
// add half pixel to round out integer rect space to cover pixel centers
float roundedOutRadius = state->radius + 0.5f;
+
+ // Divide by the radius to simplify the calculations in the fragment shader
+ // roundRectPos is also passed from vertex shader relative to top/left & radius
+ glUniform4f(fill.program->getUniform("roundRectInnerRectLTWH"),
+ innerRect.left / roundedOutRadius, innerRect.top / roundedOutRadius,
+ (innerRect.right - innerRect.left) / roundedOutRadius,
+ (innerRect.bottom - innerRect.top) / roundedOutRadius);
+
+ glUniformMatrix4fv(fill.program->getUniform("roundRectInvTransform"),
+ 1, GL_FALSE, &state->matrix.data[0]);
+
glUniform1f(fill.program->getUniform("roundRectRadius"),
roundedOutRadius);
}
@@ -420,18 +426,28 @@
const GLbyte* vertexData = static_cast<const GLbyte*>(vertices.position);
while (elementsCount > 0) {
GLsizei drawCount = std::min(elementsCount, (GLsizei) kMaxNumberOfQuads * 6);
+ GLsizei vertexCount = (drawCount / 6) * 4;
meshState().bindPositionVertexPointer(vertexData, vertices.stride);
if (vertices.attribFlags & VertexAttribFlags::TextureCoord) {
meshState().bindTexCoordsVertexPointer(
vertexData + kMeshTextureOffset, vertices.stride);
}
- glDrawElements(mesh.primitiveMode, drawCount, GL_UNSIGNED_SHORT, nullptr);
+ if (mCaches->extensions().getMajorGlVersion() >= 3) {
+ glDrawRangeElements(mesh.primitiveMode, 0, vertexCount-1, drawCount, GL_UNSIGNED_SHORT, nullptr);
+ } else {
+ glDrawElements(mesh.primitiveMode, drawCount, GL_UNSIGNED_SHORT, nullptr);
+ }
elementsCount -= drawCount;
- vertexData += (drawCount / 6) * 4 * vertices.stride;
+ vertexData += vertexCount * vertices.stride;
}
} else if (indices.bufferObject || indices.indices) {
- glDrawElements(mesh.primitiveMode, mesh.elementCount, GL_UNSIGNED_SHORT, indices.indices);
+ if (mCaches->extensions().getMajorGlVersion() >= 3) {
+ // use glDrawRangeElements to reduce CPU overhead (otherwise the driver has to determine the min/max index values)
+ glDrawRangeElements(mesh.primitiveMode, 0, mesh.vertexCount-1, mesh.elementCount, GL_UNSIGNED_SHORT, indices.indices);
+ } else {
+ glDrawElements(mesh.primitiveMode, mesh.elementCount, GL_UNSIGNED_SHORT, indices.indices);
+ }
} else {
glDrawArrays(mesh.primitiveMode, 0, mesh.elementCount);
}
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index 787946f..4b7a865 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -45,6 +45,7 @@
class DeferredLayerUpdater;
namespace renderthread {
+class CacheManager;
class CanvasContext;
class RenderThread;
}
@@ -55,6 +56,7 @@
PREVENT_COPY_AND_ASSIGN(RenderState);
friend class renderthread::RenderThread;
friend class Caches;
+ friend class renderthread::CacheManager;
public:
void onGLContextCreated();
void onGLContextDestroyed();
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
new file mode 100644
index 0000000..f0d6b38
--- /dev/null
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "CacheManager.h"
+
+#include "Layer.h"
+#include "RenderThread.h"
+#include "renderstate/RenderState.h"
+
+#include <gui/Surface.h>
+#include <GrContextOptions.h>
+#include <math.h>
+#include <set>
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+// This multiplier was selected based on historical review of cache sizes relative
+// to the screen resolution. This is meant to be a conservative default based on
+// that analysis. The 4.0f is used because the default pixel format is assumed to
+// be ARGB_8888.
+#define SURFACE_SIZE_MULTIPLIER (12.0f * 4.0f)
+#define BACKGROUND_RETENTION_PERCENTAGE (0.5f)
+
+// for super large fonts we will draw them as paths so no need to keep linearly
+// increasing the font cache size.
+#define FONT_CACHE_MIN_MB (0.5f)
+#define FONT_CACHE_MAX_MB (4.0f)
+
+CacheManager::CacheManager(const DisplayInfo& display)
+ : mMaxSurfaceArea(display.w * display.h) {
+ mVectorDrawableAtlas.reset(new VectorDrawableAtlas);
+}
+
+void CacheManager::reset(GrContext* context) {
+ if (context != mGrContext.get()) {
+ destroy();
+ }
+
+ if (context) {
+ mGrContext = sk_ref_sp(context);
+ mGrContext->getResourceCacheLimits(&mMaxResources, nullptr);
+ updateContextCacheSizes();
+ }
+}
+
+void CacheManager::destroy() {
+ // cleanup any caches here as the GrContext is about to go away...
+ mGrContext.reset(nullptr);
+ mVectorDrawableAtlas.reset(new VectorDrawableAtlas);
+}
+
+void CacheManager::updateContextCacheSizes() {
+ mMaxResourceBytes = mMaxSurfaceArea * SURFACE_SIZE_MULTIPLIER;
+ mBackgroundResourceBytes = mMaxResourceBytes * BACKGROUND_RETENTION_PERCENTAGE;
+
+ mGrContext->setResourceCacheLimits(mMaxResources, mMaxResourceBytes);
+}
+
+void CacheManager::configureContext(GrContextOptions* contextOptions) {
+ contextOptions->fAllowPathMaskCaching = true;
+
+ float screenMP = mMaxSurfaceArea / 1024.0f / 1024.0f;
+ float fontCacheMB = 0;
+ float decimalVal = std::modf(screenMP, &fontCacheMB);
+
+ // This is a basic heuristic to size the cache to a multiple of 512 KB
+ if (decimalVal > 0.8f) {
+ fontCacheMB += 1.0f;
+ } else if (decimalVal > 0.5f) {
+ fontCacheMB += 0.5f;
+ }
+
+ // set limits on min/max size of the cache
+ fontCacheMB = std::max(FONT_CACHE_MIN_MB, std::min(FONT_CACHE_MAX_MB, fontCacheMB));
+
+ // We must currently set the size of the text cache based on the size of the
+ // display even though we like to be dynamicallysizing it to the size of the window.
+ // Skia's implementation doesn't provide a mechanism to resize the font cache due to
+ // the potential cost of recreating the glyphs.
+ contextOptions->fGlyphCacheTextureMaximumBytes = fontCacheMB * 1024 * 1024;
+}
+
+void CacheManager::trimMemory(TrimMemoryMode mode) {
+ if (!mGrContext) {
+ return;
+ }
+
+ mGrContext->flush();
+
+ switch (mode) {
+ case TrimMemoryMode::Complete:
+ mVectorDrawableAtlas.reset(new VectorDrawableAtlas);
+ mGrContext->freeGpuResources();
+ break;
+ case TrimMemoryMode::UiHidden:
+ mGrContext->purgeUnlockedResources(mMaxResourceBytes - mBackgroundResourceBytes, true);
+ break;
+ }
+}
+
+void CacheManager::trimStaleResources() {
+ if (!mGrContext) {
+ return;
+ }
+ mGrContext->flush();
+ mGrContext->purgeResourcesNotUsedInMs(std::chrono::seconds(30));
+}
+
+VectorDrawableAtlas* CacheManager::acquireVectorDrawableAtlas() {
+ LOG_ALWAYS_FATAL_IF(mVectorDrawableAtlas.get() == nullptr);
+ LOG_ALWAYS_FATAL_IF(mGrContext == nullptr);
+
+ /**
+ * TODO LIST:
+ * 1) compute the atlas based on the surfaceArea surface
+ * 2) identify a way to reuse cache entries
+ * 3) add ability to repack the cache?
+ * 4) define memory conditions where we clear the cache (e.g. surface->reset())
+ */
+
+ return mVectorDrawableAtlas.release();
+}
+void CacheManager::releaseVectorDrawableAtlas(VectorDrawableAtlas* atlas) {
+ LOG_ALWAYS_FATAL_IF(mVectorDrawableAtlas.get() != nullptr);
+ mVectorDrawableAtlas.reset(atlas);
+ mVectorDrawableAtlas->isNewAtlas = false;
+}
+
+void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) {
+ if (!mGrContext) {
+ log.appendFormat("No valid cache instance.\n");
+ return;
+ }
+
+ size_t bytesCached;
+ mGrContext->getResourceCacheUsage(nullptr, &bytesCached);
+
+ log.appendFormat("Caches:\n");
+ log.appendFormat(" Current / Maximum\n");
+ log.appendFormat(" VectorDrawableAtlas %6.2f kB / %6.2f kB (entries = %zu)\n",
+ 0.0f, 0.0f, (size_t)0);
+
+ if (renderState) {
+ if (renderState->mActiveLayers.size() > 0) {
+ log.appendFormat(" Layer Info:\n");
+ }
+
+ size_t layerMemoryTotal = 0;
+ for (std::set<Layer*>::iterator it = renderState->mActiveLayers.begin();
+ it != renderState->mActiveLayers.end(); it++) {
+ const Layer* layer = *it;
+ const char* layerType = layer->getApi() == Layer::Api::OpenGL ? "GlLayer" : "VkLayer";
+ log.appendFormat(" %s size %dx%d\n", layerType,
+ layer->getWidth(), layer->getHeight());
+ layerMemoryTotal += layer->getWidth() * layer->getHeight() * 4;
+ }
+ log.appendFormat(" Layers Total %6.2f kB (numLayers = %zu)\n",
+ layerMemoryTotal / 1024.0f, renderState->mActiveLayers.size());
+ }
+
+
+ log.appendFormat("Total memory usage:\n");
+ log.appendFormat(" %zu bytes, %.2f MB (%.2f MB is purgeable)\n",
+ bytesCached, bytesCached / 1024.0f / 1024.0f,
+ mGrContext->getResourceCachePurgeableBytes() / 1024.0f / 1024.0f);
+
+
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
new file mode 100644
index 0000000..43d58f2
--- /dev/null
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CACHEMANAGER_H
+#define CACHEMANAGER_H
+
+#include <GrContext.h>
+#include <SkSurface.h>
+#include <ui/DisplayInfo.h>
+#include <utils/String8.h>
+#include <vector>
+
+namespace android {
+
+class Surface;
+
+namespace uirenderer {
+
+class RenderState;
+
+namespace renderthread {
+
+class IRenderPipeline;
+class RenderThread;
+
+struct VectorDrawableAtlas {
+ sk_sp<SkSurface> surface;
+ bool isNewAtlas = true;
+};
+
+class CacheManager {
+public:
+ enum class TrimMemoryMode {
+ Complete,
+ UiHidden
+ };
+
+ void configureContext(GrContextOptions* context);
+ void trimMemory(TrimMemoryMode mode);
+ void trimStaleResources();
+ void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr);
+
+ VectorDrawableAtlas* acquireVectorDrawableAtlas();
+ void releaseVectorDrawableAtlas(VectorDrawableAtlas*);
+
+ size_t getCacheSize() const { return mMaxResourceBytes; }
+ size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; }
+
+private:
+ friend class RenderThread;
+
+ CacheManager(const DisplayInfo& display);
+
+
+ void reset(GrContext* grContext);
+ void destroy();
+ void updateContextCacheSizes();
+
+ const size_t mMaxSurfaceArea;
+ sk_sp<GrContext> mGrContext;
+
+ int mMaxResources = 0;
+ size_t mMaxResourceBytes = 0;
+ size_t mBackgroundResourceBytes = 0;
+
+ struct PipelineProps {
+ const void* pipelineKey = nullptr;
+ size_t surfaceArea = 0;
+ };
+
+ std::unique_ptr<VectorDrawableAtlas> mVectorDrawableAtlas;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* CACHEMANAGER_H */
+
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 9c80ab3..a79bf35 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -186,7 +186,8 @@
mNativeSurface = surface;
- bool hasSurface = mRenderPipeline->setSurface(surface, mSwapBehavior);
+ ColorMode colorMode = mWideColorGamut ? ColorMode::WideColorGamut : ColorMode::Srgb;
+ bool hasSurface = mRenderPipeline->setSurface(surface, mSwapBehavior, colorMode);
mFrameNumber = -1;
@@ -241,6 +242,10 @@
mOpaque = opaque;
}
+void CanvasContext::setWideGamut(bool wideGamut) {
+ mWideColorGamut = wideGamut;
+}
+
bool CanvasContext::makeCurrent() {
if (mStopped) return false;
@@ -580,15 +585,37 @@
}
void CanvasContext::trimMemory(RenderThread& thread, int level) {
- // No context means nothing to free
- if (!thread.eglManager().hasEglContext()) return;
-
- ATRACE_CALL();
- if (level >= TRIM_MEMORY_COMPLETE) {
- thread.renderState().flush(Caches::FlushMode::Full);
- thread.eglManager().destroy();
- } else if (level >= TRIM_MEMORY_UI_HIDDEN) {
- thread.renderState().flush(Caches::FlushMode::Moderate);
+ auto renderType = Properties::getRenderPipelineType();
+ switch (renderType) {
+ case RenderPipelineType::OpenGL: {
+ // No context means nothing to free
+ if (!thread.eglManager().hasEglContext()) return;
+ ATRACE_CALL();
+ if (level >= TRIM_MEMORY_COMPLETE) {
+ thread.renderState().flush(Caches::FlushMode::Full);
+ thread.eglManager().destroy();
+ } else if (level >= TRIM_MEMORY_UI_HIDDEN) {
+ thread.renderState().flush(Caches::FlushMode::Moderate);
+ }
+ break;
+ }
+ case RenderPipelineType::SkiaGL:
+ case RenderPipelineType::SkiaVulkan: {
+ // No context means nothing to free
+ if (!thread.getGrContext()) return;
+ ATRACE_CALL();
+ if (level >= TRIM_MEMORY_COMPLETE) {
+ thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
+ thread.eglManager().destroy();
+ thread.vulkanManager().destroy();
+ } else if (level >= TRIM_MEMORY_UI_HIDDEN) {
+ thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden);
+ }
+ break;
+ }
+ default:
+ LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType);
+ break;
}
}
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 738c091..76623f9 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -128,6 +128,7 @@
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
void setLightCenter(const Vector3& lightCenter);
void setOpaque(bool opaque);
+ void setWideGamut(bool wideGamut);
bool makeCurrent();
void prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
int64_t syncQueued, RenderNode* target);
@@ -194,6 +195,8 @@
void waitOnFences();
+ IRenderPipeline* getRenderPipeline() { return mRenderPipeline.get(); }
+
private:
CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline);
@@ -238,6 +241,7 @@
nsecs_t mLastDropVsync = 0;
bool mOpaque;
+ bool mWideColorGamut = false;
BakedOpRenderer::LightInfo mLightInfo;
FrameBuilder::LightGeometry mLightGeometry = { {0, 0, 0}, 0 };
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 44af5fd..ecf686c 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -76,12 +76,16 @@
static struct {
bool bufferAge = false;
bool setDamage = false;
+ bool noConfigContext = false;
+ bool pixelFormatFloat = false;
+ bool glColorSpace = false;
} EglExtensions;
EglManager::EglManager(RenderThread& thread)
: mRenderThread(thread)
, mEglDisplay(EGL_NO_DISPLAY)
, mEglConfig(nullptr)
+ , mEglConfigWideGamut(nullptr)
, mEglContext(EGL_NO_CONTEXT)
, mPBufferSurface(EGL_NO_SURFACE)
, mCurrentSurface(EGL_NO_SURFACE) {
@@ -116,7 +120,7 @@
}
}
- loadConfig();
+ loadConfigs();
createContext();
createPBufferSurface();
makeCurrent(mPBufferSurface);
@@ -134,7 +138,7 @@
GrContextOptions options;
options.fGpuPathRenderers &= ~GrContextOptions::GpuPathRenderers::kDistanceField;
- options.fAllowPathMaskCaching = true;
+ mRenderThread.cacheManager().configureContext(&options);
mRenderThread.setGrContext(GrContext::Create(GrBackend::kOpenGL_GrBackend,
(GrBackendContext)glInterface.get(), options));
}
@@ -143,6 +147,7 @@
void EglManager::initExtensions() {
auto extensions = StringUtils::split(
eglQueryString(mEglDisplay, EGL_EXTENSIONS));
+
// For our purposes we don't care if EGL_BUFFER_AGE is a result of
// EGL_EXT_buffer_age or EGL_KHR_partial_update as our usage is covered
// under EGL_KHR_partial_update and we don't need the expanded scope
@@ -152,13 +157,17 @@
EglExtensions.setDamage = extensions.has("EGL_KHR_partial_update");
LOG_ALWAYS_FATAL_IF(!extensions.has("EGL_KHR_swap_buffers_with_damage"),
"Missing required extension EGL_KHR_swap_buffers_with_damage");
+
+ EglExtensions.glColorSpace = extensions.has("EGL_KHR_gl_colorspace");
+ EglExtensions.noConfigContext = extensions.has("EGL_KHR_no_config_context");
+ EglExtensions.pixelFormatFloat = extensions.has("EGL_EXT_pixel_format_float");
}
bool EglManager::hasEglContext() {
return mEglDisplay != EGL_NO_DISPLAY;
}
-void EglManager::loadConfig() {
+void EglManager::loadConfigs() {
ALOGD("Swap behavior %d", static_cast<int>(mSwapBehavior));
EGLint swapBehavior = (mSwapBehavior == SwapBehavior::Preserved)
? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
@@ -175,19 +184,44 @@
EGL_NONE
};
- EGLint num_configs = 1;
- if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, num_configs, &num_configs)
- || num_configs != 1) {
+ EGLint numConfigs = 1;
+ if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, numConfigs, &numConfigs)
+ || numConfigs != 1) {
if (mSwapBehavior == SwapBehavior::Preserved) {
// Try again without dirty regions enabled
ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...");
mSwapBehavior = SwapBehavior::Discard;
- loadConfig();
+ loadConfigs();
+ return; // the call to loadConfigs() we just made picks the wide gamut config
} else {
// Failed to get a valid config
LOG_ALWAYS_FATAL("Failed to choose config, error = %s", eglErrorString());
}
}
+
+ if (EglExtensions.pixelFormatFloat) {
+ // If we reached this point, we have a valid swap behavior
+ EGLint attribs16F[] = {
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_COLOR_COMPONENT_TYPE_EXT, EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT,
+ EGL_RED_SIZE, 16,
+ EGL_GREEN_SIZE, 16,
+ EGL_BLUE_SIZE, 16,
+ EGL_ALPHA_SIZE, 16,
+ EGL_DEPTH_SIZE, 0,
+ EGL_STENCIL_SIZE, Stencil::getStencilSize(),
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior,
+ EGL_NONE
+ };
+
+ numConfigs = 1;
+ if (!eglChooseConfig(mEglDisplay, attribs16F, &mEglConfigWideGamut, numConfigs, &numConfigs)
+ || numConfigs != 1) {
+ LOG_ALWAYS_FATAL(
+ "Device claims wide gamut support, cannot find matching config, error = %s",
+ eglErrorString());
+ }
+ }
}
void EglManager::createContext() {
@@ -195,7 +229,9 @@
EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION,
EGL_NONE
};
- mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attribs);
+ mEglContext = eglCreateContext(mEglDisplay,
+ EglExtensions.noConfigContext ? ((EGLConfig) nullptr) : mEglConfig,
+ EGL_NO_CONTEXT, attribs);
LOG_ALWAYS_FATAL_IF(mEglContext == EGL_NO_CONTEXT,
"Failed to create context, error = %s", eglErrorString());
}
@@ -210,18 +246,60 @@
}
}
-EGLSurface EglManager::createSurface(EGLNativeWindowType window) {
+EGLSurface EglManager::createSurface(EGLNativeWindowType window, bool wideColorGamut) {
initialize();
+ wideColorGamut = wideColorGamut && EglExtensions.glColorSpace
+ && EglExtensions.pixelFormatFloat && EglExtensions.noConfigContext;
+
+ // The color space we want to use depends on whether linear blending is turned
+ // on and whether the app has requested wide color gamut rendering. When wide
+ // color gamut rendering is off, the app simply renders in the display's native
+ // color gamut.
+ //
+ // When wide gamut rendering is off:
+ // - Blending is done by default in gamma space, which requires using a
+ // linear EGL color space (the GPU uses the color values as is)
+ // - If linear blending is on, we must use the sRGB EGL color space (the
+ // GPU will perform sRGB to linear and linear to SRGB conversions before
+ // and after blending)
+ //
+ // When wide gamut rendering is on we cannot rely on the GPU performing
+ // linear blending for us. We use two different color spaces to tag the
+ // surface appropriately for SurfaceFlinger:
+ // - Gamma blending (default) requires the use of the scRGB-nl color space
+ // - Linear blending requires the use of the scRGB color space
+
+ // Not all Android targets support the EGL_GL_COLOR_SPACE_KHR extension
+ // We insert to placeholders to set EGL_GL_COLORSPACE_KHR and its value.
+ // According to section 3.4.1 of the EGL specification, the attributes
+ // list is considered empty if the first entry is EGL_NONE
EGLint attribs[] = {
-#ifdef ANDROID_ENABLE_LINEAR_BLENDING
- EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR,
- EGL_COLORSPACE, EGL_COLORSPACE_sRGB,
-#endif
+ EGL_NONE, EGL_NONE,
EGL_NONE
};
- EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, attribs);
+ if (EglExtensions.glColorSpace) {
+ attribs[0] = EGL_GL_COLORSPACE_KHR;
+#ifdef ANDROID_ENABLE_LINEAR_BLENDING
+ if (wideColorGamut) {
+ attribs[1] = EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT;
+ } else {
+ attribs[1] = EGL_GL_COLORSPACE_SRGB_KHR;
+ }
+#else
+ if (wideColorGamut) {
+ // TODO: this should be using scRGB-nl, not scRGB, we need an extension for this
+ // TODO: in the meantime SurfaceFlinger just assumes that scRGB is scRGB-nl
+ attribs[1] = EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT;
+ } else {
+ attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
+ }
+#endif
+ }
+
+ EGLSurface surface = eglCreateWindowSurface(mEglDisplay,
+ wideColorGamut ? mEglConfigWideGamut : mEglConfig, window, attribs);
LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
"Failed to create EGLSurface for window %p, eglErr = %s",
(void*) window, eglErrorString());
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index 0251925..2982c23 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -39,7 +39,7 @@
bool hasEglContext();
- EGLSurface createSurface(EGLNativeWindowType window);
+ EGLSurface createSurface(EGLNativeWindowType window, bool wideColorGamut);
void destroySurface(EGLSurface surface);
void destroy();
@@ -68,7 +68,7 @@
void initExtensions();
void createPBufferSurface();
- void loadConfig();
+ void loadConfigs();
void createContext();
EGLint queryBufferAge(EGLSurface surface);
@@ -76,6 +76,7 @@
EGLDisplay mEglDisplay;
EGLConfig mEglConfig;
+ EGLConfig mEglConfigWideGamut;
EGLContext mEglContext;
EGLSurface mPBufferSurface;
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 45f6718..46ac0d2 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -44,6 +44,12 @@
Succeeded
};
+enum class ColorMode {
+ Srgb,
+ WideColorGamut,
+ // Hdr
+};
+
class Frame;
class IRenderPipeline {
@@ -61,7 +67,7 @@
FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
virtual bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) = 0;
virtual DeferredLayerUpdater* createTextureLayer() = 0;
- virtual bool setSurface(Surface* window, SwapBehavior swapBehavior) = 0;
+ virtual bool setSurface(Surface* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0;
virtual void onStop() = 0;
virtual bool isSurfaceReady() = 0;
virtual bool isContextReady() = 0;
diff --git a/libs/hwui/renderthread/OpenGLPipeline.cpp b/libs/hwui/renderthread/OpenGLPipeline.cpp
index e1ae585..e15d0eb 100644
--- a/libs/hwui/renderthread/OpenGLPipeline.cpp
+++ b/libs/hwui/renderthread/OpenGLPipeline.cpp
@@ -146,7 +146,7 @@
}
}
-bool OpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior) {
+bool OpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior, ColorMode colorMode) {
if (mEglSurface != EGL_NO_SURFACE) {
mEglManager.destroySurface(mEglSurface);
@@ -154,7 +154,8 @@
}
if (surface) {
- mEglSurface = mEglManager.createSurface(surface);
+ const bool wideColorGamut = colorMode == ColorMode::WideColorGamut;
+ mEglSurface = mEglManager.createSurface(surface, wideColorGamut);
}
if (mEglSurface != EGL_NO_SURFACE) {
@@ -267,6 +268,171 @@
thread.renderState().invokeFunctor(functor, mode, nullptr);
}
+#define FENCE_TIMEOUT 2000000000
+
+class AutoEglFence {
+public:
+ AutoEglFence(EGLDisplay display)
+ : mDisplay(display) {
+ fence = eglCreateSyncKHR(mDisplay, EGL_SYNC_FENCE_KHR, NULL);
+ }
+
+ ~AutoEglFence() {
+ if (fence != EGL_NO_SYNC_KHR) {
+ eglDestroySyncKHR(mDisplay, fence);
+ }
+ }
+
+ EGLSyncKHR fence = EGL_NO_SYNC_KHR;
+private:
+ EGLDisplay mDisplay = EGL_NO_DISPLAY;
+};
+
+class AutoEglImage {
+public:
+ AutoEglImage(EGLDisplay display, EGLClientBuffer clientBuffer)
+ : mDisplay(display) {
+ EGLint imageAttrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE };
+ image = eglCreateImageKHR(display, EGL_NO_CONTEXT,
+ EGL_NATIVE_BUFFER_ANDROID, clientBuffer, imageAttrs);
+ }
+
+ ~AutoEglImage() {
+ if (image != EGL_NO_IMAGE_KHR) {
+ eglDestroyImageKHR(mDisplay, image);
+ }
+ }
+
+ EGLImageKHR image = EGL_NO_IMAGE_KHR;
+private:
+ EGLDisplay mDisplay = EGL_NO_DISPLAY;
+};
+
+class AutoGlTexture {
+public:
+ AutoGlTexture(uirenderer::Caches& caches)
+ : mCaches(caches) {
+ glGenTextures(1, &mTexture);
+ caches.textureState().bindTexture(mTexture);
+ }
+
+ ~AutoGlTexture() {
+ mCaches.textureState().deleteTexture(mTexture);
+ }
+
+private:
+ uirenderer::Caches& mCaches;
+ GLuint mTexture = 0;
+};
+
+static bool uploadBitmapToGraphicBuffer(uirenderer::Caches& caches, SkBitmap& bitmap,
+ GraphicBuffer& buffer, GLint format, GLint type) {
+ EGLDisplay display = eglGetCurrentDisplay();
+ LOG_ALWAYS_FATAL_IF(display == EGL_NO_DISPLAY,
+ "Failed to get EGL_DEFAULT_DISPLAY! err=%s",
+ uirenderer::renderthread::EglManager::eglErrorString());
+ // We use an EGLImage to access the content of the GraphicBuffer
+ // The EGL image is later bound to a 2D texture
+ EGLClientBuffer clientBuffer = (EGLClientBuffer) buffer.getNativeBuffer();
+ AutoEglImage autoImage(display, clientBuffer);
+ if (autoImage.image == EGL_NO_IMAGE_KHR) {
+ ALOGW("Could not create EGL image, err =%s",
+ uirenderer::renderthread::EglManager::eglErrorString());
+ return false;
+ }
+ AutoGlTexture glTexture(caches);
+ glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, autoImage.image);
+
+ GL_CHECKPOINT(MODERATE);
+
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(),
+ format, type, bitmap.getPixels());
+
+ GL_CHECKPOINT(MODERATE);
+
+ // The fence is used to wait for the texture upload to finish
+ // properly. We cannot rely on glFlush() and glFinish() as
+ // some drivers completely ignore these API calls
+ AutoEglFence autoFence(display);
+ if (autoFence.fence == EGL_NO_SYNC_KHR) {
+ LOG_ALWAYS_FATAL("Could not create sync fence %#x", eglGetError());
+ return false;
+ }
+ // The flag EGL_SYNC_FLUSH_COMMANDS_BIT_KHR will trigger a
+ // pipeline flush (similar to what a glFlush() would do.)
+ EGLint waitStatus = eglClientWaitSyncKHR(display, autoFence.fence,
+ EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, FENCE_TIMEOUT);
+ if (waitStatus != EGL_CONDITION_SATISFIED_KHR) {
+ LOG_ALWAYS_FATAL("Failed to wait for the fence %#x", eglGetError());
+ return false;
+ }
+ return true;
+}
+
+// TODO: handle SRGB sanely
+static PixelFormat internalFormatToPixelFormat(GLint internalFormat) {
+ switch (internalFormat) {
+ case GL_LUMINANCE:
+ return PIXEL_FORMAT_RGBA_8888;
+ case GL_SRGB8_ALPHA8:
+ return PIXEL_FORMAT_RGBA_8888;
+ case GL_RGBA:
+ return PIXEL_FORMAT_RGBA_8888;
+ case GL_RGB:
+ return PIXEL_FORMAT_RGB_565;
+ case GL_RGBA16F:
+ return PIXEL_FORMAT_RGBA_FP16;
+ default:
+ LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", internalFormat);
+ return PIXEL_FORMAT_UNKNOWN;
+ }
+}
+
+sk_sp<Bitmap> OpenGLPipeline::allocateHardwareBitmap(RenderThread& renderThread,
+ SkBitmap& skBitmap) {
+ renderThread.eglManager().initialize();
+ uirenderer::Caches& caches = uirenderer::Caches::getInstance();
+
+ const SkImageInfo& info = skBitmap.info();
+ if (info.colorType() == kUnknown_SkColorType || info.colorType() == kAlpha_8_SkColorType) {
+ ALOGW("unable to create hardware bitmap of colortype: %d", info.colorType());
+ return nullptr;
+ }
+
+ bool needSRGB = uirenderer::transferFunctionCloseToSRGB(skBitmap.info().colorSpace());
+ bool hasLinearBlending = caches.extensions().hasLinearBlending();
+ GLint format, type, internalFormat;
+ uirenderer::Texture::colorTypeToGlFormatAndType(caches, skBitmap.colorType(),
+ needSRGB && hasLinearBlending, &internalFormat, &format, &type);
+
+ PixelFormat pixelFormat = internalFormatToPixelFormat(internalFormat);
+ sp<GraphicBuffer> buffer = new GraphicBuffer(info.width(), info.height(), pixelFormat,
+ GraphicBuffer::USAGE_HW_TEXTURE |
+ GraphicBuffer::USAGE_SW_WRITE_NEVER |
+ GraphicBuffer::USAGE_SW_READ_NEVER,
+ std::string("Bitmap::allocateHardwareBitmap pid [") + std::to_string(getpid()) + "]");
+
+ status_t error = buffer->initCheck();
+ if (error < 0) {
+ ALOGW("createGraphicBuffer() failed in GraphicBuffer.create()");
+ return nullptr;
+ }
+
+ SkBitmap bitmap;
+ if (CC_UNLIKELY(uirenderer::Texture::hasUnsupportedColorType(skBitmap.info(),
+ hasLinearBlending))) {
+ sk_sp<SkColorSpace> sRGB = SkColorSpace::MakeSRGB();
+ bitmap = uirenderer::Texture::uploadToN32(skBitmap, hasLinearBlending, std::move(sRGB));
+ } else {
+ bitmap = skBitmap;
+ }
+
+ if (!uploadBitmapToGraphicBuffer(caches, bitmap, *buffer, format, type)) {
+ return nullptr;
+ }
+ return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info()));
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/OpenGLPipeline.h b/libs/hwui/renderthread/OpenGLPipeline.h
index 6df8be4..0e8c3f5 100644
--- a/libs/hwui/renderthread/OpenGLPipeline.h
+++ b/libs/hwui/renderthread/OpenGLPipeline.h
@@ -44,7 +44,7 @@
FrameInfo* currentFrameInfo, bool* requireSwap) override;
bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) override;
DeferredLayerUpdater* createTextureLayer() override;
- bool setSurface(Surface* window, SwapBehavior swapBehavior) override;
+ bool setSurface(Surface* window, SwapBehavior swapBehavior, ColorMode colorMode) override;
void onStop() override;
bool isSurfaceReady() override;
bool isContextReady() override;
@@ -61,6 +61,8 @@
static void destroyLayer(RenderNode* node);
static void prepareToDraw(const RenderThread& thread, Bitmap* bitmap);
static void invokeFunctor(const RenderThread& thread, Functor* functor);
+ static sk_sp<Bitmap> allocateHardwareBitmap(RenderThread& thread,
+ SkBitmap& skBitmap);
private:
EglManager& mEglManager;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index eed5238..ec56c31 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -227,6 +227,18 @@
post(task);
}
+CREATE_BRIDGE2(setWideGamut, CanvasContext* context, bool wideGamut) {
+ args->context->setWideGamut(args->wideGamut);
+ return nullptr;
+}
+
+void RenderProxy::setWideGamut(bool wideGamut) {
+ SETUP_TASK(setWideGamut);
+ args->context = mContext;
+ args->wideGamut = wideGamut;
+ post(task);
+}
+
int64_t* RenderProxy::frameInfo() {
return mDrawFrameTask.frameInfo();
}
@@ -457,18 +469,7 @@
}
CREATE_BRIDGE2(dumpGraphicsMemory, int fd, RenderThread* thread) {
- args->thread->jankTracker().dump(args->fd);
-
- FILE *file = fdopen(args->fd, "a");
- if (Caches::hasInstance()) {
- String8 cachesLog;
- Caches::getInstance().dumpMemoryUsage(cachesLog);
- fprintf(file, "\nCaches:\n%s\n", cachesLog.string());
- } else {
- fprintf(file, "\nNo caches instance.\n");
- }
- fprintf(file, "\nPipeline=FrameBuilder\n");
- fflush(file);
+ args->thread->dumpGraphicsMemory(args->fd);
return nullptr;
}
@@ -664,7 +665,7 @@
}
CREATE_BRIDGE2(allocateHardwareBitmap, RenderThread* thread, SkBitmap* bitmap) {
- sk_sp<Bitmap> hardwareBitmap = Bitmap::allocateHardwareBitmap(*args->thread, *args->bitmap);
+ sk_sp<Bitmap> hardwareBitmap = args->thread->allocateHardwareBitmap(*args->bitmap);
return hardwareBitmap.release();
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index b21772c..e1e2808 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -69,7 +69,7 @@
*/
class ANDROID_API RenderProxy {
public:
- ANDROID_API RenderProxy(bool translucent, RenderNode* rootNode, IContextFactory* contextFactory);
+ ANDROID_API RenderProxy(bool opaque, RenderNode* rootNode, IContextFactory* contextFactory);
ANDROID_API virtual ~RenderProxy();
// Won't take effect until next EGLSurface creation
@@ -85,6 +85,7 @@
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
ANDROID_API void setLightCenter(const Vector3& lightCenter);
ANDROID_API void setOpaque(bool opaque);
+ ANDROID_API void setWideGamut(bool wideGamut);
ANDROID_API int64_t* frameInfo();
ANDROID_API int syncAndDrawFrame();
ANDROID_API void destroy();
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 1450ec9..13af2c4 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -16,8 +16,12 @@
#include "RenderThread.h"
-#include "../renderstate/RenderState.h"
-#include "../pipeline/skia/SkiaOpenGLReadback.h"
+#include "hwui/Bitmap.h"
+#include "renderstate/RenderState.h"
+#include "renderthread/OpenGLPipeline.h"
+#include "pipeline/skia/SkiaOpenGLReadback.h"
+#include "pipeline/skia/SkiaOpenGLPipeline.h"
+#include "pipeline/skia/SkiaVulkanPipeline.h"
#include "CanvasContext.h"
#include "EglManager.h"
#include "OpenGLReadback.h"
@@ -198,6 +202,45 @@
mRenderState = new RenderState(*this);
mJankTracker = new JankTracker(mDisplayInfo);
mVkManager = new VulkanManager(*this);
+ mCacheManager = new CacheManager(mDisplayInfo);
+}
+
+void RenderThread::dumpGraphicsMemory(int fd) {
+ jankTracker().dump(fd);
+
+ String8 cachesOutput;
+ String8 pipeline;
+ auto renderType = Properties::getRenderPipelineType();
+ switch (renderType) {
+ case RenderPipelineType::OpenGL: {
+ if (Caches::hasInstance()) {
+ cachesOutput.appendFormat("Caches:\n");
+ Caches::getInstance().dumpMemoryUsage(cachesOutput);
+ } else {
+ cachesOutput.appendFormat("No caches instance.");
+ }
+ pipeline.appendFormat("FrameBuilder");
+ break;
+ }
+ case RenderPipelineType::SkiaGL: {
+ mCacheManager->dumpMemoryUsage(cachesOutput, mRenderState);
+ pipeline.appendFormat("Skia (OpenGL)");
+ break;
+ }
+ case RenderPipelineType::SkiaVulkan: {
+ mCacheManager->dumpMemoryUsage(cachesOutput, mRenderState);
+ pipeline.appendFormat("Skia (Vulkan)");
+ break;
+ }
+ default:
+ LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType);
+ break;
+ }
+
+ FILE *file = fdopen(fd, "a");
+ fprintf(file, "\n%s\n", cachesOutput.string());
+ fprintf(file, "\nPipeline=%s\n", pipeline.string());
+ fflush(file);
}
Readback& RenderThread::readback() {
@@ -224,6 +267,14 @@
return *mReadback;
}
+void RenderThread::setGrContext(GrContext* context) {
+ mCacheManager->reset(context);
+ if (mGrContext.get()) {
+ mGrContext->releaseResourcesAndAbandonContext();
+ }
+ mGrContext.reset(context);
+}
+
int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) {
if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
ALOGE("Display event receiver pipe was closed or an error occurred. "
@@ -433,6 +484,22 @@
return next;
}
+sk_sp<Bitmap> RenderThread::allocateHardwareBitmap(SkBitmap& skBitmap) {
+ auto renderType = Properties::getRenderPipelineType();
+ switch (renderType) {
+ case RenderPipelineType::OpenGL:
+ return OpenGLPipeline::allocateHardwareBitmap(*this, skBitmap);
+ case RenderPipelineType::SkiaGL:
+ return skiapipeline::SkiaOpenGLPipeline::allocateHardwareBitmap(*this, skBitmap);
+ case RenderPipelineType::SkiaVulkan:
+ return skiapipeline::SkiaVulkanPipeline::allocateHardwareBitmap(*this, skBitmap);
+ default:
+ LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType);
+ break;
+ }
+ return nullptr;
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 9bc5985..d984257 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -20,10 +20,12 @@
#include "RenderTask.h"
#include "../JankTracker.h"
+#include "CacheManager.h"
#include "TimeLord.h"
#include <GrContext.h>
#include <cutils/compiler.h>
+#include <SkBitmap.h>
#include <ui/DisplayInfo.h>
#include <utils/Looper.h>
#include <utils/Thread.h>
@@ -33,6 +35,7 @@
namespace android {
+class Bitmap;
class DisplayEventReceiver;
namespace uirenderer {
@@ -100,10 +103,14 @@
const DisplayInfo& mainDisplayInfo() { return mDisplayInfo; }
GrContext* getGrContext() const { return mGrContext.get(); }
- void setGrContext(GrContext* cxt) { mGrContext.reset(cxt); }
+ void setGrContext(GrContext* cxt);
+ CacheManager& cacheManager() { return *mCacheManager; }
VulkanManager& vulkanManager() { return *mVkManager; }
+ sk_sp<Bitmap> allocateHardwareBitmap(SkBitmap& skBitmap);
+ void dumpGraphicsMemory(int fd);
+
protected:
virtual bool threadLoop() override;
@@ -157,6 +164,7 @@
Readback* mReadback = nullptr;
sk_sp<GrContext> mGrContext;
+ CacheManager* mCacheManager;
VulkanManager* mVkManager;
};
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index c2c2f22..a745320 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -54,7 +54,8 @@
auto canPresent = [](VkInstance, VkPhysicalDevice, uint32_t) { return true; };
- mBackendContext.reset(GrVkBackendContext::Create(&mPresentQueueIndex, canPresent));
+ mBackendContext.reset(GrVkBackendContext::Create(vkGetInstanceProcAddr, vkGetDeviceProcAddr,
+ &mPresentQueueIndex, canPresent));
// Get all the addresses of needed vulkan functions
VkInstance instance = mBackendContext->fInstance;
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index ab6420e..87eaa6a 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -19,7 +19,7 @@
#include "JankTracker.h"
#include <frameworks/base/core/proto/android/service/graphicsstats.pb.h>
-#include <google/protobuf/io/zero_copy_stream_impl.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <log/log.h>
#include <inttypes.h>
@@ -27,6 +27,7 @@
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
+#include <sys/mman.h>
namespace android {
namespace uirenderer {
@@ -46,10 +47,74 @@
const ProfileData* data);
static void dumpAsTextToFd(service::GraphicsStatsProto* proto, int outFd);
+class FileDescriptor {
+public:
+ FileDescriptor(int fd) : mFd(fd) {}
+ ~FileDescriptor() {
+ if (mFd != -1) {
+ close(mFd);
+ mFd = -1;
+ }
+ }
+ bool valid() { return mFd != -1; }
+ operator int() { return mFd; }
+private:
+ int mFd;
+};
+
+class FileOutputStreamLite : public io::ZeroCopyOutputStream {
+public:
+ FileOutputStreamLite(int fd) : mCopyAdapter(fd), mImpl(&mCopyAdapter) {}
+ virtual ~FileOutputStreamLite() {}
+
+ int GetErrno() { return mCopyAdapter.mErrno; }
+
+ virtual bool Next(void** data, int* size) override {
+ return mImpl.Next(data, size);
+ }
+
+ virtual void BackUp(int count) override {
+ mImpl.BackUp(count);
+ }
+
+ virtual int64 ByteCount() const override {
+ return mImpl.ByteCount();
+ }
+
+ bool Flush() {
+ return mImpl.Flush();
+ }
+
+private:
+ struct FDAdapter : public io::CopyingOutputStream {
+ int mFd;
+ int mErrno = 0;
+
+ FDAdapter(int fd) : mFd(fd) {}
+ virtual ~FDAdapter() {}
+
+ virtual bool Write(const void* buffer, int size) override {
+ int ret;
+ while (size) {
+ ret = TEMP_FAILURE_RETRY( write(mFd, buffer, size) );
+ if (ret <= 0) {
+ mErrno = errno;
+ return false;
+ }
+ size -= ret;
+ }
+ return true;
+ }
+ };
+
+ FileOutputStreamLite::FDAdapter mCopyAdapter;
+ io::CopyingOutputStreamAdaptor mImpl;
+};
+
bool GraphicsStatsService::parseFromFile(const std::string& path, service::GraphicsStatsProto* output) {
- int fd = open(path.c_str(), O_RDONLY);
- if (fd == -1) {
+ FileDescriptor fd{open(path.c_str(), O_RDONLY)};
+ if (!fd.valid()) {
int err = errno;
// The file not existing is normal for addToDump(), so only log if
// we get an unexpected error
@@ -58,26 +123,41 @@
}
return false;
}
- uint32_t file_version;
- ssize_t bytesRead = read(fd, &file_version, sHeaderSize);
- if (bytesRead != sHeaderSize || file_version != sCurrentFileVersion) {
- ALOGW("Failed to read '%s', bytesRead=%zd file_version=%d", path.c_str(), bytesRead,
- file_version);
- close(fd);
+ struct stat sb;
+ if (fstat(fd, &sb) || sb.st_size < sHeaderSize) {
+ int err = errno;
+ // The file not existing is normal for addToDump(), so only log if
+ // we get an unexpected error
+ if (err != ENOENT) {
+ ALOGW("Failed to fstat '%s', errno=%d (%s) (st_size %d)", path.c_str(), err,
+ strerror(err), (int) sb.st_size);
+ }
+ return false;
+ }
+ void* addr = mmap(nullptr, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (!addr) {
+ int err = errno;
+ // The file not existing is normal for addToDump(), so only log if
+ // we get an unexpected error
+ if (err != ENOENT) {
+ ALOGW("Failed to mmap '%s', errno=%d (%s)", path.c_str(), err, strerror(err));
+ }
+ return false;
+ }
+ uint32_t file_version = *reinterpret_cast<uint32_t*>(addr);
+ if (file_version != sCurrentFileVersion) {
+ ALOGW("file_version mismatch! expected %d got %d", sCurrentFileVersion, file_version);
return false;
}
- io::FileInputStream input(fd);
+ void* data = reinterpret_cast<uint8_t*>(addr) + sHeaderSize;
+ int dataSize = sb.st_size - sHeaderSize;
+ io::ArrayInputStream input{data, dataSize};
bool success = output->ParseFromZeroCopyStream(&input);
- if (input.GetErrno() != 0) {
- ALOGW("Error reading from fd=%d, path='%s' err=%d (%s)",
- fd, path.c_str(), input.GetErrno(), strerror(input.GetErrno()));
- success = false;
- } else if (!success) {
+ if (!success) {
ALOGW("Parse failed on '%s' error='%s'",
path.c_str(), output->InitializationErrorString().c_str());
}
- close(fd);
return success;
}
@@ -212,7 +292,7 @@
return;
}
{
- io::FileOutputStream output(outFd);
+ FileOutputStreamLite output(outFd);
bool success = statsProto.SerializeToZeroCopyStream(&output) && output.Flush();
if (output.GetErrno() != 0) {
ALOGW("Error writing to fd=%d, path='%s' err=%d (%s)",
@@ -277,7 +357,7 @@
void GraphicsStatsService::finishDump(Dump* dump) {
if (dump->type() == DumpType::Protobuf) {
- io::FileOutputStream stream(dump->fd());
+ FileOutputStreamLite stream(dump->fd());
dump->proto().SerializeToZeroCopyStream(&stream);
}
delete dump;
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index dd45786..98d5fb3 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -365,8 +365,15 @@
}
auto displayList = node->getDisplayList();
if (displayList) {
- for (auto&& childOp : displayList->getChildren()) {
- syncHierarchyPropertiesAndDisplayListImpl(childOp->renderNode);
+ if (displayList->isSkiaDL()) {
+ for (auto&& childDr : static_cast<skiapipeline::SkiaDisplayList*>(
+ const_cast<DisplayList*>(displayList))->mChildNodes) {
+ syncHierarchyPropertiesAndDisplayListImpl(childDr.getRenderNode());
+ }
+ } else {
+ for (auto&& childOp : displayList->getChildren()) {
+ syncHierarchyPropertiesAndDisplayListImpl(childOp->renderNode);
+ }
}
}
}
diff --git a/libs/hwui/tests/common/scenes/BitmapShaders.cpp b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
index a7ebb68..4797dec 100644
--- a/libs/hwui/tests/common/scenes/BitmapShaders.cpp
+++ b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
@@ -45,10 +45,8 @@
skCanvas.drawRect(SkRect::MakeXYWH(100, 100, 100, 100), skPaint);
});
- SkBitmap bitmap;
SkPaint paint;
- hwuiBitmap->getSkBitmapForShaders(&bitmap);
- sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
+ sk_sp<SkImage> image = hwuiBitmap->makeImage();
sk_sp<SkShader> repeatShader = image->makeShader(
SkShader::TileMode::kRepeat_TileMode,
SkShader::TileMode::kRepeat_TileMode,
diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
index a461426..c246eba 100644
--- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
+++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
@@ -75,9 +75,7 @@
void doFrame(int frameNr) override { }
sk_sp<SkShader> createBitmapShader(Bitmap& bitmap) {
- SkBitmap skBitmap;
- bitmap.getSkBitmapForShaders(&skBitmap);
- sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(skBitmap, kNever_SkCopyPixelsMode);
+ sk_sp<SkImage> image = bitmap.makeImage();
return image->makeShader(SkShader::TileMode::kClamp_TileMode,
SkShader::TileMode::kClamp_TileMode);
}
diff --git a/libs/hwui/tests/scripts/process_systrace.py b/libs/hwui/tests/scripts/process_systrace.py
new file mode 100755
index 0000000..f497bf5
--- /dev/null
+++ b/libs/hwui/tests/scripts/process_systrace.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+
+import codecs, httplib, json, os, urllib, shutil, subprocess, sys, argparse
+
+upstream_git = 'https://github.com/catapult-project/catapult.git'
+
+script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
+catapult_src_dir = os.path.join(script_dir, 'catapult-upstream')
+
+parser = argparse.ArgumentParser()
+parser.add_argument('trace_file_or_dir',
+ help='Path to trace file or directory of trace files.')
+parser.add_argument('--output_file', dest='outfile', default=os.path.join(os.getcwd(), 'mapper_output.json'),
+ help='Path to output file to store results.')
+parser.add_argument('--mapper_func', dest='func', default='AvgDrawFrame',
+ help='Name of javascript mapper function in systrace_parser.html.')
+args = parser.parse_args()
+
+# Update the source if needed.
+if not os.path.exists(catapult_src_dir):
+ # Pull the latest source from the upstream git.
+ git_args = ['git', 'clone', upstream_git, catapult_src_dir]
+ p = subprocess.Popen(git_args, stdout=subprocess.PIPE, cwd=script_dir)
+ p.communicate()
+ if p.wait() != 0:
+ print 'Failed to checkout source from upstream git.'
+ sys.exit(1)
+
+mapper_func_file = os.path.join(script_dir, 'systrace_parser.html')
+path_to_process_traces = os.path.join(catapult_src_dir, 'trace_processor/bin/process_traces')
+run_command = path_to_process_traces + " --mapper_handle " + mapper_func_file + ":" + args.func + " --output_file " + args.outfile + " " + args.trace_file_or_dir
+print run_command
+sys.exit(os.system(run_command))
+
diff --git a/libs/hwui/tests/scripts/systrace_parser.html b/libs/hwui/tests/scripts/systrace_parser.html
new file mode 100644
index 0000000..4c66ae2
--- /dev/null
+++ b/libs/hwui/tests/scripts/systrace_parser.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+
+<script>
+'use strict';
+
+const RENDER_THREAD_NAME = "RenderThread";
+const UI_THREAD_NAME = "UI Thread";
+const DRAW_FRAME_SLICE_TITLE = "DrawFrame";
+const BINDER_SLICE_TITLE = "binder transaction";
+const RECORD_SLICE_TITLE = "Record View#draw()";
+const DEQUEUE_BUFFER_SLICE_TITLE = "dequeueBuffer";
+
+function getTimeInBinder(slice) {
+ if (slice.title === BINDER_SLICE_TITLE) {
+ return slice.duration;
+ }
+ let result = 0.0;
+ for (let subslice of slice.subSlices) {
+ result += getTimeInBinder(subslice);
+ }
+ return result;
+}
+
+function getTimeInDequeueBuffer(slice) {
+ if (slice.title === DEQUEUE_BUFFER_SLICE_TITLE) {
+ return slice.duration;
+ }
+ let result = 0.0;
+ for (let subslice of slice.subSlices) {
+ result += getTimeInDequeueBuffer(subslice);
+ }
+ return result;
+}
+
+tr.mre.FunctionRegistry.register(
+ function AvgDrawFrame(result, model) {
+ let canonicalUrl = model.canonicalUrl;
+
+ for (let p of model.getAllProcesses()) {
+ //calc stats for processes that have UI and render threads and at least 10 frames
+ let renderThread = p.findAtMostOneThreadNamed(RENDER_THREAD_NAME);
+ let UIThread = p.findAtMostOneThreadNamed(UI_THREAD_NAME);
+ if (renderThread && UIThread)
+ {
+ let numDrawFrames = 0;
+ let drawFramesWallDuration = 0.0;
+ let binderDuration = 0.0;
+ let dequeueBufferDuration = 0.0;
+
+ let numRecordViewDraw = 0;
+ let recordViewDrawWallDuration = 0.0;
+
+ renderThread.sliceGroup.slices.forEach(function(slice) {
+ if (slice.title === DRAW_FRAME_SLICE_TITLE) {
+ drawFramesWallDuration += slice.duration;
+ numDrawFrames++;
+ binderDuration += getTimeInBinder(slice);
+ dequeueBufferDuration += getTimeInDequeueBuffer(slice);
+ }
+ });
+ if (numDrawFrames < 10) continue;
+ UIThread.sliceGroup.slices.forEach(function(slice) {
+ if (slice.title === RECORD_SLICE_TITLE) {
+ recordViewDrawWallDuration += slice.duration;
+ numRecordViewDraw++;
+ }
+ });
+
+ let avgDrawFrameDuration = undefined;
+ if (numDrawFrames > 0) {
+ avgDrawFrameDuration = (drawFramesWallDuration-dequeueBufferDuration)/numDrawFrames;
+ }
+ let avgRecordViewDrawDuration = undefined;
+ if (numRecordViewDraw > 0) {
+ avgRecordViewDrawDuration = recordViewDrawWallDuration/numRecordViewDraw;
+ }
+
+ result.addPair('result', {
+ canonicalUrl: canonicalUrl,
+ processName: p.name,
+ avgDrawFrameDuration: Number(avgDrawFrameDuration).toFixed(3),
+ avgRecordViewDrawDuration: Number(avgRecordViewDrawDuration).toFixed(3),
+ avgRecordAndPlay: Number(avgDrawFrameDuration+avgRecordViewDrawDuration).toFixed(3)
+ });
+ }
+ }
+ });
+
+</script>
diff --git a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
index 9a3b81c..c46592c 100644
--- a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
+++ b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
@@ -118,7 +118,7 @@
layerPaint.setAlpha(128);
OffscreenBuffer* buffer = nullptr; // no providing a buffer, should hit rect fallback case
LayerOp op(Rect(10, 10), Matrix4::identity(), nullptr, &layerPaint, &buffer);
- testUnmergedGlopDispatch(renderThread, &op, [&renderThread] (const Glop& glop) {
+ testUnmergedGlopDispatch(renderThread, &op, [] (const Glop& glop) {
ADD_FAILURE() << "Nothing should happen";
}, 0);
}
diff --git a/libs/hwui/tests/unit/BitmapTests.cpp b/libs/hwui/tests/unit/BitmapTests.cpp
index 8c7e081..ed689bd 100644
--- a/libs/hwui/tests/unit/BitmapTests.cpp
+++ b/libs/hwui/tests/unit/BitmapTests.cpp
@@ -29,16 +29,15 @@
TEST(Bitmap, colorTableRefCounting) {
const SkPMColor c[] = { SkPackARGB32(0x80, 0x80, 0, 0) };
- SkColorTable* ctable = new SkColorTable(c, SK_ARRAY_COUNT(c));
+ sk_sp<SkColorTable> ctable = SkColorTable::Make(c, SK_ARRAY_COUNT(c));
SkBitmap* bm = new SkBitmap();
bm->allocPixels(SkImageInfo::Make(1, 1, kIndex_8_SkColorType, kPremul_SkAlphaType),
- nullptr, ctable);
+ ctable);
sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(bm, ctable);
EXPECT_FALSE(ctable->unique());
delete bm;
bitmap.reset();
EXPECT_TRUE(ctable->unique());
- ctable->unref();
}
diff --git a/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp
new file mode 100644
index 0000000..6115162
--- /dev/null
+++ b/libs/hwui/tests/unit/CacheManagerTests.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "renderthread/CacheManager.h"
+#include "renderthread/EglManager.h"
+#include "tests/common/TestUtils.h"
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+
+static size_t getCacheUsage(GrContext* grContext) {
+ size_t cacheUsage;
+ grContext->getResourceCacheUsage(nullptr, &cacheUsage);
+ return cacheUsage;
+}
+
+RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, trimMemory) {
+ DisplayInfo displayInfo = renderThread.mainDisplayInfo();
+ GrContext* grContext = renderThread.getGrContext();
+ ASSERT_TRUE(grContext != nullptr);
+
+ // create pairs of offscreen render targets and images until we exceed the backgroundCacheSizeLimit
+ std::vector<sk_sp<SkSurface>> surfaces;
+
+ while (getCacheUsage(grContext) <= renderThread.cacheManager().getBackgroundCacheSize()) {
+ SkImageInfo info = SkImageInfo::MakeA8(displayInfo.w, displayInfo.h);
+ sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(grContext, SkBudgeted::kYes, info);
+ surface->getCanvas()->drawColor(SK_AlphaTRANSPARENT);
+
+ grContext->flush();
+
+ surfaces.push_back(surface);
+ }
+
+ ASSERT_TRUE(1 < surfaces.size());
+
+ // attempt to trim all memory while we still hold strong refs
+ renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
+ ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
+
+ // free the surfaces
+ for (size_t i = 0; i < surfaces.size(); i++) {
+ ASSERT_TRUE(surfaces[i]->unique());
+ surfaces[i].reset();
+ }
+
+ // verify that we have enough purgeable bytes
+ const size_t purgeableBytes = grContext->getResourceCachePurgeableBytes();
+ ASSERT_TRUE(renderThread.cacheManager().getBackgroundCacheSize() < purgeableBytes);
+
+ // UI hidden and make sure only some got purged
+ renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden);
+ ASSERT_TRUE(0 < grContext->getResourceCachePurgeableBytes());
+ ASSERT_TRUE(renderThread.cacheManager().getBackgroundCacheSize() > getCacheUsage(grContext));
+
+ // complete and make sure all get purged
+ renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
+ ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
+}
diff --git a/libs/hwui/tests/unit/FontRendererTests.cpp b/libs/hwui/tests/unit/FontRendererTests.cpp
index 773a7ea..ee20236 100644
--- a/libs/hwui/tests/unit/FontRendererTests.cpp
+++ b/libs/hwui/tests/unit/FontRendererTests.cpp
@@ -28,7 +28,7 @@
return true;
}
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FontRenderer, DISABLED_renderDropShadow) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(FontRenderer, renderDropShadow) {
SkPaint paint;
paint.setTextSize(10);
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
diff --git a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
index cfe1134..f6f7337 100644
--- a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
+++ b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
@@ -50,7 +50,11 @@
// No code left untested
TEST(GraphicsStats, findRootPath) {
+#ifdef __LP64__
+ std::string expected = "/data/nativetest64/hwui_unit_tests";
+#else
std::string expected = "/data/nativetest/hwui_unit_tests";
+#endif
EXPECT_EQ(expected, findRootPath());
}
@@ -156,4 +160,4 @@
EXPECT_EQ(expectedCount, loadedProto.histogram().Get(i).frame_count());
EXPECT_EQ(expectedBucket, loadedProto.histogram().Get(i).render_millis());
}
-}
\ No newline at end of file
+}
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
index dafa074..a3d5079 100644
--- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -17,6 +17,7 @@
#include "tests/common/TestUtils.h"
#include <gtest/gtest.h>
+#include <SkBlurDrawLooper.h>
#include <SkColorMatrixFilter.h>
#include <SkColorSpace.h>
#include <SkImagePriv.h>
@@ -106,3 +107,16 @@
sk_sp<SkColorSpace> sRGB2 = SkColorSpace::MakeSRGB();
ASSERT_EQ(sRGB1.get(), sRGB2.get());
}
+
+TEST(SkiaBehavior, blurDrawLooper) {
+ sk_sp<SkDrawLooper> looper = SkBlurDrawLooper::Make(SK_ColorRED, 5.0f, 3.0f, 4.0f);
+
+ SkDrawLooper::BlurShadowRec blur;
+ bool success = looper->asABlurShadow(&blur);
+ ASSERT_TRUE(success);
+
+ ASSERT_EQ(SK_ColorRED, blur.fColor);
+ ASSERT_EQ(5.0f, blur.fSigma);
+ ASSERT_EQ(3.0f, blur.fOffset.fX);
+ ASSERT_EQ(4.0f, blur.fOffset.fY);
+}
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index 0aecb85..c048dda 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -89,7 +89,6 @@
sk_sp<Bitmap> adobeBitmap = Bitmap::allocateHeapBitmap(adobeInfo);
SkBitmap adobeSkBitmap;
adobeBitmap->getSkBitmap(&adobeSkBitmap);
- adobeSkBitmap.lockPixels();
*adobeSkBitmap.getAddr32(0, 0) = 0xFF0000F0; // Opaque, almost fully-red
SkImageInfo info = adobeInfo.makeColorSpace(nullptr);
@@ -101,7 +100,6 @@
SkiaCanvas canvas(skBitmap);
canvas.drawBitmap(*adobeBitmap, 0, 0, nullptr);
// The result should be fully red, since we convert to sRGB at draw time.
- skBitmap.lockPixels();
ASSERT_EQ(0xFF0000FF, *skBitmap.getAddr32(0, 0));
// Create a software canvas with an Adobe color space.
@@ -116,7 +114,6 @@
SkiaCanvas deferCanvas(&skCanvas, Canvas::XformToSRGB::kDefer);
deferCanvas.drawBitmap(*adobeBitmap, 0, 0, nullptr);
// The result should be as before, since we deferred the conversion to sRGB.
- skBitmap.lockPixels();
ASSERT_EQ(0xFF0000DC, *skBitmap.getAddr32(0, 0));
// Test picture recording. We will kDefer the xform at recording time, but handle it when
diff --git a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
index 5383e57..8312bda 100644
--- a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
+++ b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
@@ -26,7 +26,7 @@
using namespace android;
using namespace android::uirenderer;
-RENDERTHREAD_OPENGL_PIPELINE_TEST(TextDropShadowCache, DISABLED_addRemove) {
+RENDERTHREAD_OPENGL_PIPELINE_TEST(TextDropShadowCache, addRemove) {
SkPaint paint;
paint.setTextSize(20);
diff --git a/libs/hwui/tests/unit/TextureCacheTests.cpp b/libs/hwui/tests/unit/TextureCacheTests.cpp
index 72384bf..ab740dd 100644
--- a/libs/hwui/tests/unit/TextureCacheTests.cpp
+++ b/libs/hwui/tests/unit/TextureCacheTests.cpp
@@ -31,7 +31,7 @@
SkBitmap skBitmap;
SkImageInfo info = SkImageInfo::Make(100, 100, kN32_SkColorType, kPremul_SkAlphaType);
skBitmap.setInfo(info);
- sk_sp<Bitmap> hwBitmap(Bitmap::allocateHardwareBitmap(renderThread, skBitmap));
+ sk_sp<Bitmap> hwBitmap(renderThread.allocateHardwareBitmap(skBitmap));
cache.get(hwBitmap.get());
ASSERT_EQ(GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture), initialCount + 1);
cache.clear();
diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp
index 6f264e1..1c21567 100644
--- a/libs/hwui/tests/unit/VectorDrawableTests.cpp
+++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp
@@ -382,47 +382,24 @@
}
-static SkShader* createShader(bool* isDestroyed) {
- class TestShader : public SkShader {
- public:
- TestShader(bool* isDestroyed) : SkShader(), mDestroyed(isDestroyed) {
- }
- ~TestShader() {
- *mDestroyed = true;
- }
-
- Factory getFactory() const override { return nullptr; }
- private:
- bool* mDestroyed;
- };
- return new TestShader(isDestroyed);
-}
-
TEST(VectorDrawable, drawPathWithoutIncrementingShaderRefCount) {
VectorDrawable::FullPath path("m1 1", 4);
SkBitmap bitmap;
- SkImageInfo info = SkImageInfo::Make(5, 5, kN32_SkColorType, kPremul_SkAlphaType);
- bitmap.setInfo(info);
- bitmap.allocPixels(info);
+ bitmap.allocN32Pixels(5, 5, false);
SkCanvas canvas(bitmap);
- bool shaderIsDestroyed = false;
-
+ sk_sp<SkShader> shader = SkShader::MakeColorShader(SK_ColorBLACK);
// Initial ref count is 1
- SkShader* shader = createShader(&shaderIsDestroyed);
+ EXPECT_TRUE(shader->unique());
// Setting the fill gradient increments the ref count of the shader by 1
- path.mutateStagingProperties()->setFillGradient(shader);
+ path.mutateStagingProperties()->setFillGradient(shader.get());
+ EXPECT_FALSE(shader->unique());
path.draw(&canvas, true);
// Resetting the fill gradient decrements the ref count of the shader by 1
path.mutateStagingProperties()->setFillGradient(nullptr);
- // Expect ref count to be 1 again, i.e. nothing else to have a ref to the shader now. Unref()
- // again should bring the ref count to zero and consequently trigger detor.
- shader->unref();
-
- // Verify that detor is called.
- EXPECT_TRUE(shaderIsDestroyed);
+ EXPECT_TRUE(shader->unique());
}
}; // namespace uirenderer
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
new file mode 100644
index 0000000..4310706
--- /dev/null
+++ b/libs/input/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2010 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: "libinputservice",
+
+ srcs: [
+ "PointerController.cpp",
+ "SpriteController.cpp",
+ ],
+
+ shared_libs: [
+ "libcutils",
+ "liblog",
+ "libutils",
+ "libskia",
+ "libgui",
+ "libui",
+ "libinput",
+ "libinputflinger",
+ "libnativewindow",
+ ],
+
+ include_dirs: ["frameworks/native/services"],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wunused",
+ "-Wunreachable-code",
+ ],
+
+}
diff --git a/libs/input/Android.mk b/libs/input/Android.mk
deleted file mode 100644
index e824275..0000000
--- a/libs/input/Android.mk
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright (C) 2010 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:= \
- PointerController.cpp \
- SpriteController.cpp
-
-LOCAL_SHARED_LIBRARIES := \
- libcutils \
- liblog \
- libutils \
- libskia \
- libgui \
- libui \
- libinput \
- libinputflinger \
- libnativewindow
-
-LOCAL_C_INCLUDES := \
- frameworks/native/services
-
-
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
-
-LOCAL_MODULE:= libinputservice
-
-LOCAL_MODULE_TAGS := optional
-
-include $(BUILD_SHARED_LIBRARY)
-
-
-# Include subdirectory makefiles
-# ============================================================
-
-# If we're building with ONE_SHOT_MAKEFILE (mm, mmm), then what the framework
-# team really wants is to build the stuff defined by this makefile.
-ifeq (,$(ONE_SHOT_MAKEFILE))
-include $(call first-makefiles-under,$(LOCAL_PATH))
-endif
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index 4991f04..ed31b12 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -220,13 +220,13 @@
if (outBuffer.width > update.state.icon.bitmap.width()) {
paint.setColor(0); // transparent fill color
- surfaceCanvas.drawRectCoords(update.state.icon.bitmap.width(), 0,
- outBuffer.width, update.state.icon.bitmap.height(), paint);
+ surfaceCanvas.drawRect(SkRect::MakeLTRB(update.state.icon.bitmap.width(), 0,
+ outBuffer.width, update.state.icon.bitmap.height()), paint);
}
if (outBuffer.height > update.state.icon.bitmap.height()) {
paint.setColor(0); // transparent fill color
- surfaceCanvas.drawRectCoords(0, update.state.icon.bitmap.height(),
- outBuffer.width, outBuffer.height, paint);
+ surfaceCanvas.drawRect(SkRect::MakeLTRB(0, update.state.icon.bitmap.height(),
+ outBuffer.width, outBuffer.height), paint);
}
status = surface->unlockAndPost();
@@ -405,7 +405,11 @@
uint32_t dirty;
if (icon.isValid()) {
- icon.bitmap.copyTo(&mLocked.state.icon.bitmap, kN32_SkColorType);
+ SkBitmap* bitmapCopy = &mLocked.state.icon.bitmap;
+ if (bitmapCopy->tryAllocPixels(icon.bitmap.info().makeColorType(kN32_SkColorType))) {
+ icon.bitmap.readPixels(bitmapCopy->info(), bitmapCopy->getPixels(),
+ bitmapCopy->rowBytes(), 0, 0);
+ }
if (!mLocked.state.icon.isValid()
|| mLocked.state.icon.hotSpotX != icon.hotSpotX
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 7fc8d6f..31e43e9 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -65,7 +65,10 @@
inline SpriteIcon copy() const {
SkBitmap bitmapCopy;
- bitmap.copyTo(&bitmapCopy, kN32_SkColorType);
+ if (bitmapCopy.tryAllocPixels(bitmap.info().makeColorType(kN32_SkColorType))) {
+ bitmap.readPixels(bitmapCopy.info(), bitmapCopy.getPixels(), bitmapCopy.rowBytes(),
+ 0, 0);
+ }
return SpriteIcon(bitmapCopy, hotSpotX, hotSpotY);
}
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index de85c16..d24a477 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -436,8 +436,8 @@
* E1BC code lock : [ 0 4ms ] : STATE_GAL_E1BC_CODE_LOCK is set
* E1C 2nd code lock : [ 0 100ms ] : STATE_GAL_E1C_2ND_CODE_LOCK is set
* E1B page : [ 0 2s ] : STATE_GAL_E1B_PAGE_SYNC is set
- * Time of week decoded : [ 0 1week ] : STATE_GAL_TOW_DECODED is set
- * Time of week known : [ 0 1week ] : STATE_GAL_TOW_KNOWN set</pre>
+ * Time of week decoded : [ 0 1week ] : STATE_TOW_DECODED is set
+ * Time of week known : [ 0 1week ] : STATE_TOW_KNOWN set</pre>
*
* Note: TOW Known refers to the case where TOW is possibly not decoded over the air but has
* been determined from other sources. If TOW decoded is set then TOW Known must also be set.
diff --git a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
new file mode 100644
index 0000000..f5f1024
--- /dev/null
+++ b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.location.gnssmetrics;
+
+import android.os.SystemClock;
+
+import android.util.Base64;
+import android.util.TimeUtils;
+
+import com.android.internal.location.nano.GnssLogsProto.GnssLog;
+
+/**
+ * GnssMetrics: Is used for logging GNSS metrics
+ * @hide
+ */
+public class GnssMetrics {
+
+ /** Default time between location fixes (in millisecs) */
+ private static final int DEFAULT_TIME_BETWEEN_FIXES_MILLISECS = 1000;
+
+ /* The time since boot when logging started */
+ private String logStartInElapsedRealTime;
+
+ /** Constructor */
+ public GnssMetrics() {
+ locationFailureStatistics = new Statistics();
+ timeToFirstFixSecStatistics = new Statistics();
+ positionAccuracyMeterStatistics = new Statistics();
+ reset();
+ }
+
+ /**
+ * Logs the status of a location report received from the HAL
+ *
+ * @param isSuccessful
+ */
+ public void logReceivedLocationStatus(boolean isSuccessful) {
+ if (!isSuccessful) {
+ locationFailureStatistics.addItem(1.0);
+ return;
+ }
+ locationFailureStatistics.addItem(0.0);
+ return;
+ }
+
+ /**
+ * Logs missed reports
+ *
+ * @param desiredTimeBetweenFixesMilliSeconds
+ * @param actualTimeBetweenFixesMilliSeconds
+ */
+ public void logMissedReports(int desiredTimeBetweenFixesMilliSeconds,
+ int actualTimeBetweenFixesMilliSeconds) {
+ int numReportMissed = (actualTimeBetweenFixesMilliSeconds /
+ Math.max(DEFAULT_TIME_BETWEEN_FIXES_MILLISECS, desiredTimeBetweenFixesMilliSeconds)) - 1;
+ if (numReportMissed > 0) {
+ for (int i = 0; i < numReportMissed; i++) {
+ locationFailureStatistics.addItem(1.0);
+ }
+ }
+ return;
+ }
+
+ /**
+ * Logs time to first fix
+ *
+ * @param timeToFirstFixMilliSeconds
+ */
+ public void logTimeToFirstFixMilliSecs(int timeToFirstFixMilliSeconds) {
+ timeToFirstFixSecStatistics.addItem((double) (timeToFirstFixMilliSeconds/1000));
+ return;
+ }
+
+ /**
+ * Logs position accuracy
+ *
+ * @param positionAccuracyMeters
+ */
+ public void logPositionAccuracyMeters(float positionAccuracyMeters) {
+ positionAccuracyMeterStatistics.addItem((double) positionAccuracyMeters);
+ return;
+ }
+
+ /**
+ * Dumps GNSS metrics as a proto string
+ * @return
+ */
+ public String dumpGnssMetricsAsProtoString() {
+ GnssLog msg = new GnssLog();
+ if (locationFailureStatistics.getCount() > 0) {
+ msg.numLocationReportProcessed = locationFailureStatistics.getCount();
+ msg.percentageLocationFailure = (int) (100.0 * locationFailureStatistics.getMean());
+ }
+ if (timeToFirstFixSecStatistics.getCount() > 0) {
+ msg.numTimeToFirstFixProcessed = timeToFirstFixSecStatistics.getCount();
+ msg.meanTimeToFirstFixSecs = (int) timeToFirstFixSecStatistics.getMean();
+ msg.standardDeviationTimeToFirstFixSecs
+ = (int) timeToFirstFixSecStatistics.getStandardDeviation();
+ }
+ if (positionAccuracyMeterStatistics.getCount() > 0) {
+ msg.numPositionAccuracyProcessed = positionAccuracyMeterStatistics.getCount();
+ msg.meanPositionAccuracyMeters = (int) positionAccuracyMeterStatistics.getMean();
+ msg.standardDeviationPositionAccuracyMeters
+ = (int) positionAccuracyMeterStatistics.getStandardDeviation();
+ }
+ String s = Base64.encodeToString(GnssLog.toByteArray(msg), Base64.DEFAULT);
+ reset();
+ return s;
+ }
+
+ /**
+ * Dumps GNSS Metrics as text
+ *
+ * @return GNSS Metrics
+ */
+ public String dumpGnssMetricsAsText() {
+ StringBuilder s = new StringBuilder();
+ s.append("GNSS_KPI_START").append('\n');
+ s.append(" KPI logging start time: ").append(logStartInElapsedRealTime).append("\n");
+ s.append(" KPI logging end time: ");
+ TimeUtils.formatDuration(SystemClock.elapsedRealtimeNanos() / 1000000L, s);
+ s.append("\n");
+ s.append(" Number of location reports: ").append(
+ locationFailureStatistics.getCount()).append("\n");
+ if (locationFailureStatistics.getCount() > 0) {
+ s.append(" Percentage location failure: ").append(
+ 100.0 * locationFailureStatistics.getMean()).append("\n");
+ }
+ s.append(" Number of TTFF reports: ").append(
+ timeToFirstFixSecStatistics.getCount()).append("\n");
+ if (timeToFirstFixSecStatistics.getCount() > 0) {
+ s.append(" TTFF mean (sec): ").append(timeToFirstFixSecStatistics.getMean()).append("\n");
+ s.append(" TTFF standard deviation (sec): ").append(
+ timeToFirstFixSecStatistics.getStandardDeviation()).append("\n");
+ }
+ s.append(" Number of position accuracy reports: ").append(
+ positionAccuracyMeterStatistics.getCount()).append("\n");
+ if (positionAccuracyMeterStatistics.getCount() > 0) {
+ s.append(" Position accuracy mean (m): ").append(
+ positionAccuracyMeterStatistics.getMean()).append("\n");
+ s.append(" Position accuracy standard deviation (m): ").append(
+ positionAccuracyMeterStatistics.getStandardDeviation()).append("\n");
+ }
+ s.append("GNSS_KPI_END").append("\n");
+ return s.toString();
+ }
+
+ /** Class for storing statistics */
+ private class Statistics {
+
+ /** Resets statistics */
+ public void reset() {
+ count = 0;
+ sum = 0.0;
+ sumSquare = 0.0;
+ }
+
+ /** Adds an item */
+ public void addItem(double item) {
+ count++;
+ sum += item;
+ sumSquare += item * item;
+ }
+
+ /** Returns number of items added */
+ public int getCount() {
+ return count;
+ }
+
+ /** Returns mean */
+ public double getMean() {
+ return sum/count;
+ }
+
+ /** Returns standard deviation */
+ public double getStandardDeviation() {
+ double m = sum/count;
+ m = m * m;
+ double v = sumSquare/count;
+ if (v > m) {
+ return Math.sqrt(v - m);
+ }
+ return 0;
+ }
+
+ private int count;
+ private double sum;
+ private double sumSquare;
+ }
+
+ /** Location failure statistics */
+ private Statistics locationFailureStatistics;
+
+ /** Time to first fix statistics */
+ private Statistics timeToFirstFixSecStatistics;
+
+ /** Position accuracy statistics */
+ private Statistics positionAccuracyMeterStatistics;
+
+ /**
+ * Resets GNSS metrics
+ */
+ private void reset() {
+ StringBuilder s = new StringBuilder();
+ TimeUtils.formatDuration(SystemClock.elapsedRealtimeNanos() / 1000000L, s);
+ logStartInElapsedRealTime = s.toString();
+ locationFailureStatistics.reset();
+ timeToFirstFixSecStatistics.reset();
+ positionAccuracyMeterStatistics.reset();
+ return;
+ }
+}
\ No newline at end of file
diff --git a/lowpan/Android.mk b/lowpan/Android.mk
new file mode 100644
index 0000000..9e9164f
--- /dev/null
+++ b/lowpan/Android.mk
@@ -0,0 +1,31 @@
+#
+# 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)
+
+ifneq (,$(findstring lowpan/java,$(FRAMEWORKS_BASE_SUBDIRS)))
+include $(CLEAR_VARS)
+LOCAL_MODULE := libandroid_net_lowpan
+LOCAL_MODULE_TAGS := optional
+LOCAL_SHARED_LIBRARIES += libbase
+LOCAL_SHARED_LIBRARIES += libbinder
+LOCAL_SHARED_LIBRARIES += libutils
+LOCAL_AIDL_INCLUDES += frameworks/native/aidl/binder
+LOCAL_AIDL_INCLUDES += frameworks/base/lowpan/java
+LOCAL_AIDL_INCLUDES += frameworks/base/core/java
+LOCAL_SRC_FILES += $(call all-Iaidl-files-under, java/android/net/lowpan)
+include $(BUILD_SHARED_LIBRARY)
+endif
diff --git a/lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl b/lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl
new file mode 100644
index 0000000..f09dbe3
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+/** {@hide} */
+interface ILowpanEnergyScanCallback {
+ oneway void onEnergyScanResult(int channel, int rssi);
+ oneway void onEnergyScanFinished();
+}
diff --git a/lowpan/java/android/net/lowpan/ILowpanInterface.aidl b/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
new file mode 100644
index 0000000..647fcc1
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+import android.net.lowpan.ILowpanInterfaceListener;
+import android.net.lowpan.ILowpanNetScanCallback;
+import android.net.lowpan.ILowpanEnergyScanCallback;
+import android.os.PersistableBundle;
+import android.net.IpPrefix;
+
+/** {@hide} */
+interface ILowpanInterface {
+
+ //////////////////////////////////////////////////////////////////////////
+ // Permission String Constants
+
+ /* These are here for the sake of C++ interface implementations. */
+
+ const String PERM_ACCESS_LOWPAN_STATE = "android.permission.ACCESS_LOWPAN_STATE";
+ const String PERM_CHANGE_LOWPAN_STATE = "android.permission.CHANGE_LOWPAN_STATE";
+ const String PERM_READ_LOWPAN_CREDENTIAL = "android.permission.READ_LOWPAN_CREDENTIAL";
+
+ //////////////////////////////////////////////////////////////////////////
+ // Property Key Constants
+
+ const String KEY_INTERFACE_ENABLED = "android.net.lowpan.property.INTERFACE_ENABLED";
+ const String KEY_INTERFACE_UP = "android.net.lowpan.property.INTERFACE_UP";
+ const String KEY_INTERFACE_COMMISSIONED = "android.net.lowpan.property.INTERFACE_COMMISSIONED";
+ const String KEY_INTERFACE_CONNECTED = "android.net.lowpan.property.INTERFACE_CONNECTED";
+ const String KEY_INTERFACE_STATE = "android.net.lowpan.property.INTERFACE_STATE";
+
+ const String KEY_NETWORK_NAME = "android.net.lowpan.property.NETWORK_NAME";
+ const String KEY_NETWORK_TYPE = "android.net.lowpan.property.NETWORK_TYPE";
+ const String KEY_NETWORK_PANID = "android.net.lowpan.property.NETWORK_PANID";
+ const String KEY_NETWORK_XPANID = "android.net.lowpan.property.NETWORK_XPANID";
+ const String KEY_NETWORK_ROLE = "android.net.lowpan.property.NETWORK_ROLE";
+ const String KEY_NETWORK_MASTER_KEY = "android.net.lowpan.property.NETWORK_MASTER_KEY";
+ const String KEY_NETWORK_MASTER_KEY_INDEX
+ = "android.net.lowpan.property.NETWORK_MASTER_KEY_INDEX";
+
+ const String KEY_SUPPORTED_CHANNELS = "android.net.lowpan.property.SUPPORTED_CHANNELS";
+ const String KEY_CHANNEL = "android.net.lowpan.property.CHANNEL";
+ const String KEY_CHANNEL_MASK = "android.net.lowpan.property.CHANNEL_MASK";
+ const String KEY_MAX_TX_POWER = "android.net.lowpan.property.MAX_TX_POWER";
+ const String KEY_RSSI = "android.net.lowpan.property.RSSI";
+ const String KEY_LQI = "android.net.lowpan.property.LQI";
+
+ const String KEY_LINK_ADDRESS_ARRAY = "android.net.lowpan.property.LINK_ADDRESS_ARRAY";
+ const String KEY_ROUTE_INFO_ARRAY = "android.net.lowpan.property.ROUTE_INFO_ARRAY";
+
+ const String KEY_BEACON_ADDRESS = "android.net.lowpan.property.BEACON_ORIGIN_ADDRESS";
+ const String KEY_BEACON_CAN_ASSIST = "android.net.lowpan.property.BEACON_CAN_ASSIST";
+
+ const String DRIVER_VERSION = "android.net.lowpan.property.DRIVER_VERSION";
+ const String NCP_VERSION = "android.net.lowpan.property.NCP_VERSION";
+
+ /** @hide */
+ const String KEY_EXTENDED_ADDRESS = "android.net.lowpan.property.EXTENDED_ADDRESS";
+
+ /** @hide */
+ const String KEY_MAC_ADDRESS = "android.net.lowpan.property.MAC_ADDRESS";
+
+ //////////////////////////////////////////////////////////////////////////
+ // Interface States
+
+ const String STATE_OFFLINE = "offline";
+ const String STATE_COMMISSIONING = "commissioning";
+ const String STATE_ATTACHING = "attaching";
+ const String STATE_ATTACHED = "attached";
+ const String STATE_FAULT = "fault";
+
+ //////////////////////////////////////////////////////////////////////////
+ // Device Roles
+
+ const String ROLE_END_DEVICE = "end-device";
+ const String ROLE_ROUTER = "router";
+ const String ROLE_SLEEPY_END_DEVICE = "sleepy-end-device";
+ const String ROLE_SLEEPY_ROUTER = "sleepy-router";
+ const String ROLE_UNKNOWN = "unknown";
+
+ //////////////////////////////////////////////////////////////////////////
+ // Service-Specific Error Code Constants
+
+ const int ERROR_UNSPECIFIED = 1;
+ const int ERROR_INVALID_ARGUMENT = 2;
+ const int ERROR_DISABLED = 3;
+ const int ERROR_WRONG_STATE = 4;
+ const int ERROR_INVALID_TYPE = 5;
+ const int ERROR_INVALID_VALUE = 6;
+ const int ERROR_TIMEOUT = 7;
+ const int ERROR_IO_FAILURE = 8;
+ const int ERROR_BUSY = 9;
+ const int ERROR_ALREADY = 10;
+ const int ERROR_CANCELED = 11;
+ const int ERROR_CREDENTIAL_NEEDED = 12;
+ const int ERROR_FEATURE_NOT_SUPPORTED = 14;
+ const int ERROR_PROPERTY_NOT_FOUND = 16;
+ const int ERROR_JOIN_FAILED_UNKNOWN = 18;
+ const int ERROR_JOIN_FAILED_AT_SCAN = 19;
+ const int ERROR_JOIN_FAILED_AT_AUTH = 20;
+ const int ERROR_FORM_FAILED_AT_SCAN = 21;
+ const int ERROR_NCP_PROBLEM = 27;
+ const int ERROR_PERMISSION_DENIED = 28;
+
+ //////////////////////////////////////////////////////////////////////////
+ // Methods
+
+ @utf8InCpp String getName();
+
+ void join(in Map parameters);
+ void form(in Map parameters);
+ void leave();
+ void reset();
+
+ void beginLowPower();
+ void pollForData();
+
+ oneway void onHostWake();
+
+ @utf8InCpp String[] getPropertyKeys();
+ Map getProperties(in @utf8InCpp String[] keys);
+ void setProperties(in Map properties);
+
+ void addListener(ILowpanInterfaceListener listener);
+ oneway void removeListener(ILowpanInterfaceListener listener);
+
+ void startNetScan(in Map properties, ILowpanNetScanCallback listener);
+ oneway void stopNetScan();
+
+ void startEnergyScan(in Map properties, ILowpanEnergyScanCallback listener);
+ oneway void stopEnergyScan();
+
+ void addOnMeshPrefix(in IpPrefix prefix, int flags);
+ oneway void removeOnMeshPrefix(in IpPrefix prefix);
+
+ void addExternalRoute(in IpPrefix prefix, int flags);
+ oneway void removeExternalRoute(in IpPrefix prefix);
+
+ @utf8InCpp String getPropertyAsString(@utf8InCpp String key);
+}
diff --git a/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl b/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
new file mode 100644
index 0000000..c99d732
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+/** {@hide} */
+interface ILowpanInterfaceListener {
+ oneway void onPropertiesChanged(in Map properties);
+}
diff --git a/lowpan/java/android/net/lowpan/ILowpanManager.aidl b/lowpan/java/android/net/lowpan/ILowpanManager.aidl
new file mode 100644
index 0000000..5a8d7dc
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/ILowpanManager.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+import android.net.lowpan.ILowpanInterface;
+import android.net.lowpan.ILowpanManagerListener;
+
+/** {@hide} */
+interface ILowpanManager {
+
+ const String LOWPAN_SERVICE_NAME = "lowpan";
+
+ ILowpanInterface getInterface(@utf8InCpp String name);
+
+ @utf8InCpp String[] getInterfaceList();
+
+ void addListener(ILowpanManagerListener listener);
+ void removeListener(ILowpanManagerListener listener);
+
+ void addInterface(ILowpanInterface lowpan_interface);
+ void removeInterface(ILowpanInterface lowpan_interface);
+}
diff --git a/lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl b/lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl
new file mode 100644
index 0000000..d4846f6
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+import android.net.lowpan.ILowpanInterface;
+
+/** {@hide} */
+interface ILowpanManagerListener {
+ oneway void onInterfaceAdded(ILowpanInterface lowpanInterface);
+ oneway void onInterfaceRemoved(ILowpanInterface lowpanInterface);
+}
diff --git a/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl b/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl
new file mode 100644
index 0000000..c20a6f8
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+/** {@hide} */
+interface ILowpanNetScanCallback {
+ oneway void onNetScanBeacon(in Map parameters);
+ oneway void onNetScanFinished();
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java b/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java
new file mode 100644
index 0000000..b344527
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+import com.android.internal.util.HexDump;
+import java.util.Collection;
+import java.util.Map;
+import java.util.TreeSet;
+
+/**
+ * Describes a LoWPAN Beacon
+ *
+ * @hide
+ */
+//@SystemApi
+public class LowpanBeaconInfo extends LowpanIdentity {
+
+ private int mRssi = UNKNOWN;
+ private int mLqi = UNKNOWN;
+ private byte[] mBeaconAddress = null;
+ private final TreeSet<Integer> mFlags = new TreeSet<>();
+
+ public static final int FLAG_CAN_ASSIST = 1;
+
+ static class Builder extends LowpanIdentity.Builder {
+ private final LowpanBeaconInfo identity = new LowpanBeaconInfo();
+
+ public Builder setRssi(int x) {
+ identity.mRssi = x;
+ return this;
+ }
+
+ public Builder setLqi(int x) {
+ identity.mLqi = x;
+ return this;
+ }
+
+ public Builder setBeaconAddress(byte x[]) {
+ identity.mBeaconAddress = x.clone();
+ return this;
+ }
+
+ public Builder setFlag(int x) {
+ identity.mFlags.add(x);
+ return this;
+ }
+
+ public Builder setFlags(Collection<Integer> x) {
+ identity.mFlags.addAll(x);
+ return this;
+ }
+
+ /** @hide */
+ Builder updateFromMap(Map map) {
+ if (map.containsKey(LowpanProperties.KEY_RSSI.getName())) {
+ setRssi(LowpanProperties.KEY_RSSI.getFromMap(map));
+ }
+ if (map.containsKey(LowpanProperties.KEY_LQI.getName())) {
+ setLqi(LowpanProperties.KEY_LQI.getFromMap(map));
+ }
+ if (map.containsKey(LowpanProperties.KEY_BEACON_ADDRESS.getName())) {
+ setBeaconAddress(LowpanProperties.KEY_BEACON_ADDRESS.getFromMap(map));
+ }
+ identity.mFlags.clear();
+ if (map.containsKey(LowpanProperties.KEY_BEACON_CAN_ASSIST.getName())
+ && LowpanProperties.KEY_BEACON_CAN_ASSIST.getFromMap(map).booleanValue()) {
+ setFlag(FLAG_CAN_ASSIST);
+ }
+ super.updateFromMap(map);
+ return this;
+ }
+
+ public LowpanBeaconInfo build() {
+ return identity;
+ }
+ }
+
+ private LowpanBeaconInfo() {}
+
+ public int getRssi() {
+ return mRssi;
+ }
+
+ public int getLqi() {
+ return mLqi;
+ }
+
+ public byte[] getBeaconAddress() {
+ return mBeaconAddress.clone();
+ }
+
+ public Collection<Integer> getFlags() {
+ return mFlags.clone();
+ }
+
+ public boolean isFlagSet(int flag) {
+ return mFlags.contains(flag);
+ }
+
+ @Override
+ void addToMap(Map<String, Object> parameters) {
+ super.addToMap(parameters);
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append(super.toString());
+
+ if (mRssi != UNKNOWN) {
+ sb.append(", RSSI: ").append(mRssi);
+ }
+
+ if (mLqi != UNKNOWN) {
+ sb.append(", LQI: ").append(mLqi);
+ }
+
+ if (mBeaconAddress != null) {
+ sb.append(", BeaconAddress: ").append(HexDump.toHexString(mBeaconAddress));
+ }
+
+ for (Integer flag : mFlags) {
+ switch (flag.intValue()) {
+ case FLAG_CAN_ASSIST:
+ sb.append(", CAN_ASSIST");
+ break;
+ default:
+ sb.append(", FLAG_").append(Integer.toHexString(flag));
+ break;
+ }
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanChannelInfo.java b/lowpan/java/android/net/lowpan/LowpanChannelInfo.java
new file mode 100644
index 0000000..50afe6d
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanChannelInfo.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+
+/** Provides detailed information about a given channel. */
+//@SystemApi
+public class LowpanChannelInfo {
+
+ public static final int UNKNOWN_POWER = Integer.MAX_VALUE;
+
+ //////////////////////////////////////////////////////////////////////////
+ // Instance Variables
+
+ private String mName = null;
+ private int mIndex = 0;
+ private boolean mIsMaskedByRegulatoryDomain = false;
+ private float mSpectrumCenterFrequency = 0.0f;
+ private float mSpectrumBandwidth = 0.0f;
+ private int mMaxTransmitPower = UNKNOWN_POWER;
+
+ //////////////////////////////////////////////////////////////////////////
+ // Public Getters and Setters
+
+ public String getName() {
+ return mName;
+ }
+
+ public int getIndex() {
+ return mIndex;
+ }
+
+ public int getMaxTransmitPower() {
+ return mMaxTransmitPower;
+ }
+
+ public boolean isMaskedByRegulatoryDomain() {
+ return mIsMaskedByRegulatoryDomain;
+ }
+
+ public float getSpectrumCenterFrequency() {
+ return mSpectrumCenterFrequency;
+ }
+
+ public float getSpectrumBandwidth() {
+ return mSpectrumBandwidth;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("Channel ").append(mIndex);
+
+ if (mName != null) {
+ sb.append(" (").append(mName).append(")");
+ }
+
+ if (mSpectrumCenterFrequency > 0.0f) {
+ sb.append(", SpectrumCenterFrequency: ").append(mSpectrumCenterFrequency).append("Hz");
+ }
+
+ if (mSpectrumBandwidth > 0.0f) {
+ sb.append(", SpectrumBandwidth: ").append(mSpectrumBandwidth).append("Hz");
+ }
+
+ if (mMaxTransmitPower != UNKNOWN_POWER) {
+ sb.append(", MaxTransmitPower: ").append(mMaxTransmitPower);
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java b/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java
new file mode 100644
index 0000000..9cad00c
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Network;
+import android.os.Handler;
+import java.net.InetSocketAddress;
+
+/**
+ * Commissioning Session.
+ *
+ * <p>This class enables a device to learn the credential needed to join a network using a technique
+ * called "in-band commissioning".
+ *
+ * @hide
+ */
+//@SystemApi
+public abstract class LowpanCommissioningSession {
+ public LowpanCommissioningSession() {}
+
+ /**
+ * Callback base class for {@link LowpanCommissioningSession}
+ *
+ * @hide
+ */
+ //@SystemApi
+ public class Callback {
+ public void onReceiveFromCommissioner(@NonNull byte[] packet) {};
+
+ public void onClosed() {};
+ }
+
+ /** TODO: doc */
+ @NonNull
+ public abstract LowpanBeaconInfo getBeaconInfo();
+
+ /** TODO: doc */
+ public abstract void sendToCommissioner(@NonNull byte[] packet);
+
+ /** TODO: doc */
+ public abstract void setCallback(@Nullable Callback cb, @Nullable Handler handler);
+
+ /** TODO: doc */
+ public abstract void close();
+
+ /**
+ * This method is largely for Nest Weave, as an alternative to {@link #sendToCommissioner()}
+ * and @{link Callback#onReceiveFromCommissioner()}.
+ *
+ * <p>When used with the Network instance obtained from getNetwork(), the caller can use the
+ * given InetSocketAddress to communicate with the commissioner using a UDP (or, under certain
+ * circumstances, TCP) socket.
+ */
+ public abstract @Nullable InetSocketAddress getInetSocketAddress();
+
+ public abstract @Nullable Network getNetwork();
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanCredential.java b/lowpan/java/android/net/lowpan/LowpanCredential.java
new file mode 100644
index 0000000..dea4d78
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanCredential.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+
+import java.util.Map;
+
+/**
+ * Describes a credential for a LoWPAN network.
+ *
+ * @hide
+ */
+//@SystemApi
+public class LowpanCredential {
+
+ public static final int UNSPECIFIED_KEY_INDEX = 0;
+
+ private byte[] mMasterKey = null;
+ private int mMasterKeyIndex = UNSPECIFIED_KEY_INDEX;
+
+ LowpanCredential() {}
+
+ private LowpanCredential(byte[] masterKey, int keyIndex) {
+ setMasterKey(masterKey, keyIndex);
+ }
+
+ private LowpanCredential(byte[] masterKey) {
+ setMasterKey(masterKey);
+ }
+
+ public static LowpanCredential createMasterKey(byte[] masterKey) {
+ return new LowpanCredential(masterKey);
+ }
+
+ public static LowpanCredential createMasterKey(byte[] masterKey, int keyIndex) {
+ return new LowpanCredential(masterKey, keyIndex);
+ }
+
+ public void setMasterKey(byte[] masterKey) {
+ if (masterKey != null) {
+ masterKey = masterKey.clone();
+ }
+ mMasterKey = masterKey;
+ }
+
+ public void setMasterKeyIndex(int keyIndex) {
+ mMasterKeyIndex = keyIndex;
+ }
+
+ public void setMasterKey(byte[] masterKey, int keyIndex) {
+ setMasterKey(masterKey);
+ setMasterKeyIndex(keyIndex);
+ }
+
+ public byte[] getMasterKey() {
+ if (mMasterKey != null) {
+ return mMasterKey.clone();
+ }
+ return null;
+ }
+
+ public int getMasterKeyIndex() {
+ return mMasterKeyIndex;
+ }
+
+ public boolean isMasterKey() {
+ return mMasterKey != null;
+ }
+
+ void addToMap(Map<String, Object> parameters) throws LowpanException {
+ if (isMasterKey()) {
+ LowpanProperties.KEY_NETWORK_MASTER_KEY.putInMap(parameters, getMasterKey());
+ LowpanProperties.KEY_NETWORK_MASTER_KEY_INDEX.putInMap(
+ parameters, getMasterKeyIndex());
+ } else {
+ throw new LowpanException("Unsupported Network Credential");
+ }
+ }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java b/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java
new file mode 100644
index 0000000..c680687
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+
+/**
+ * Describes the result from one channel of an energy scan.
+ *
+ * @hide
+ */
+//@SystemApi
+public class LowpanEnergyScanResult {
+ public static final int UNKNOWN = Integer.MAX_VALUE;
+
+ private int mChannel = UNKNOWN;
+ private int mMaxRssi = UNKNOWN;
+
+ public LowpanEnergyScanResult() {}
+
+ public int getChannel() {
+ return mChannel;
+ }
+
+ public int getMaxRssi() {
+ return mMaxRssi;
+ }
+
+ public void setChannel(int x) {
+ mChannel = x;
+ }
+
+ public void setMaxRssi(int x) {
+ mMaxRssi = x;
+ }
+
+ @Override
+ public String toString() {
+ return "LowpanEnergyScanResult(channel: " + mChannel + ", maxRssi:" + mMaxRssi + ")";
+ }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanException.java b/lowpan/java/android/net/lowpan/LowpanException.java
new file mode 100644
index 0000000..8ff37f9
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanException.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+import android.os.DeadObjectException;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.AndroidException;
+
+/**
+ * <code>LowpanException</code> is thrown if an action to a LoWPAN interface could not be performed
+ * or a LoWPAN interface property could not be fetched or changed.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+//@SystemApi
+public class LowpanException extends AndroidException {
+ // Make the eclipse warning about serializable exceptions go away
+ private static final long serialVersionUID = 0x31863cbe562b0e11l; // randomly generated
+
+ public static final int LOWPAN_ERROR = 1;
+ public static final int LOWPAN_CREDENTIAL_NEEDED = 2;
+ public static final int LOWPAN_DEAD = 3;
+ public static final int LOWPAN_DISABLED = 4;
+ public static final int LOWPAN_WRONG_STATE = 5;
+ public static final int LOWPAN_BUSY = 7;
+ public static final int LOWPAN_NCP_PROBLEM = 8;
+ public static final int LOWPAN_ALREADY = 9;
+ public static final int LOWPAN_CANCELED = 10;
+ public static final int LOWPAN_FEATURE_NOT_SUPPORTED = 12;
+ public static final int LOWPAN_PROPERTY_NOT_FOUND = 13;
+ public static final int LOWPAN_JOIN_FAILED_UNKNOWN = 14;
+ public static final int LOWPAN_JOIN_FAILED_AT_SCAN = 15;
+ public static final int LOWPAN_JOIN_FAILED_AT_AUTH = 16;
+ public static final int LOWPAN_FORM_FAILED_AT_SCAN = 17;
+
+ /**
+ * Convert ServiceSpecificExceptions and Binder RemoteExceptions from LoWPAN binder interfaces
+ * into the correct public exceptions.
+ *
+ * @hide
+ */
+ public static void throwAsPublicException(Throwable t) throws LowpanException {
+ if (t instanceof ServiceSpecificException) {
+ ServiceSpecificException e = (ServiceSpecificException) t;
+ int reason;
+ switch (e.errorCode) {
+ case ILowpanInterface.ERROR_INVALID_ARGUMENT:
+ case ILowpanInterface.ERROR_INVALID_TYPE:
+ case ILowpanInterface.ERROR_INVALID_VALUE:
+ throw new IllegalArgumentException(e.getMessage(), e);
+
+ case ILowpanInterface.ERROR_PERMISSION_DENIED:
+ throw new SecurityException(e.getMessage(), e);
+
+ case ILowpanInterface.ERROR_DISABLED:
+ reason = LowpanException.LOWPAN_DISABLED;
+ break;
+
+ case ILowpanInterface.ERROR_WRONG_STATE:
+ reason = LowpanException.LOWPAN_WRONG_STATE;
+ break;
+
+ case ILowpanInterface.ERROR_BUSY:
+ reason = LowpanException.LOWPAN_BUSY;
+ break;
+
+ case ILowpanInterface.ERROR_ALREADY:
+ reason = LowpanException.LOWPAN_ALREADY;
+ break;
+
+ case ILowpanInterface.ERROR_CANCELED:
+ reason = LowpanException.LOWPAN_CANCELED;
+ break;
+
+ case ILowpanInterface.ERROR_CREDENTIAL_NEEDED:
+ reason = LowpanException.LOWPAN_CREDENTIAL_NEEDED;
+ break;
+
+ case ILowpanInterface.ERROR_FEATURE_NOT_SUPPORTED:
+ reason = LowpanException.LOWPAN_FEATURE_NOT_SUPPORTED;
+ break;
+
+ case ILowpanInterface.ERROR_PROPERTY_NOT_FOUND:
+ reason = LowpanException.LOWPAN_PROPERTY_NOT_FOUND;
+ break;
+
+ case ILowpanInterface.ERROR_JOIN_FAILED_UNKNOWN:
+ reason = LowpanException.LOWPAN_JOIN_FAILED_UNKNOWN;
+ break;
+
+ case ILowpanInterface.ERROR_JOIN_FAILED_AT_SCAN:
+ reason = LowpanException.LOWPAN_JOIN_FAILED_AT_SCAN;
+ break;
+
+ case ILowpanInterface.ERROR_JOIN_FAILED_AT_AUTH:
+ reason = LowpanException.LOWPAN_JOIN_FAILED_AT_AUTH;
+ break;
+
+ case ILowpanInterface.ERROR_FORM_FAILED_AT_SCAN:
+ reason = LowpanException.LOWPAN_FORM_FAILED_AT_SCAN;
+ break;
+
+ case ILowpanInterface.ERROR_TIMEOUT:
+ case ILowpanInterface.ERROR_NCP_PROBLEM:
+ reason = LowpanException.LOWPAN_NCP_PROBLEM;
+ break;
+ case ILowpanInterface.ERROR_UNSPECIFIED:
+ default:
+ reason = LOWPAN_ERROR;
+ break;
+ }
+ throw new LowpanException(reason, e.getMessage(), e);
+ } else if (t instanceof DeadObjectException) {
+ throw new LowpanException(LOWPAN_DEAD, t);
+ } else if (t instanceof RemoteException) {
+ throw new UnsupportedOperationException(
+ "An unknown RemoteException was thrown" + " which should never happen.", t);
+ } else if (t instanceof RuntimeException) {
+ RuntimeException e = (RuntimeException) t;
+ throw e;
+ }
+ }
+
+ private final int mReason;
+
+ public final int getReason() {
+ return mReason;
+ }
+
+ public LowpanException(int problem) {
+ super(getDefaultMessage(problem));
+ mReason = problem;
+ }
+
+ public LowpanException(String message) {
+ super(getCombinedMessage(LOWPAN_ERROR, message));
+ mReason = LOWPAN_ERROR;
+ }
+
+ public LowpanException(int problem, String message, Throwable cause) {
+ super(getCombinedMessage(problem, message), cause);
+ mReason = problem;
+ }
+
+ public LowpanException(int problem, Throwable cause) {
+ super(getDefaultMessage(problem), cause);
+ mReason = problem;
+ }
+
+ /** @hide */
+ public static String getDefaultMessage(int problem) {
+ String problemString;
+
+ // TODO: Does this need localization?
+
+ switch (problem) {
+ case LOWPAN_DEAD:
+ problemString = "LoWPAN interface is no longer alive";
+ break;
+ case LOWPAN_DISABLED:
+ problemString = "LoWPAN interface is disabled";
+ break;
+ case LOWPAN_WRONG_STATE:
+ problemString = "LoWPAN interface in wrong state to perfom requested action";
+ break;
+ case LOWPAN_BUSY:
+ problemString =
+ "LoWPAN interface was unable to perform the requestion action because it was busy";
+ break;
+ case LOWPAN_NCP_PROBLEM:
+ problemString =
+ "The Network Co-Processor associated with this interface has experienced a problem";
+ break;
+ case LOWPAN_ALREADY:
+ problemString = "The LoWPAN interface is already in the given state";
+ break;
+ case LOWPAN_CANCELED:
+ problemString = "This operation was canceled";
+ break;
+ case LOWPAN_CREDENTIAL_NEEDED:
+ problemString = "Additional credentials are required to complete this operation";
+ break;
+ case LOWPAN_FEATURE_NOT_SUPPORTED:
+ problemString =
+ "A dependent feature required to perform the given action is not currently supported";
+ break;
+ case LOWPAN_PROPERTY_NOT_FOUND:
+ problemString = "The given property was not found";
+ break;
+ case LOWPAN_JOIN_FAILED_UNKNOWN:
+ problemString = "The join operation failed for an unspecified reason";
+ break;
+ case LOWPAN_JOIN_FAILED_AT_SCAN:
+ problemString =
+ "The join operation failed because it could not communicate with any peers";
+ break;
+ case LOWPAN_JOIN_FAILED_AT_AUTH:
+ problemString =
+ "The join operation failed because the credentials were not accepted by any peers";
+ break;
+ case LOWPAN_FORM_FAILED_AT_SCAN:
+ problemString = "Network form failed";
+ break;
+ case LOWPAN_ERROR:
+ default:
+ problemString = "The requested LoWPAN operation failed";
+ break;
+ }
+
+ return problemString;
+ }
+
+ private static String getCombinedMessage(int problem, String message) {
+ String problemString = getProblemString(problem);
+ return String.format("%s (%d): %s", problemString, problem, message);
+ }
+
+ private static String getProblemString(int problem) {
+ String problemString;
+
+ switch (problem) {
+ case LOWPAN_ERROR:
+ problemString = "LOWPAN_ERROR";
+ break;
+ case LOWPAN_DEAD:
+ problemString = "LOWPAN_DEAD";
+ break;
+ case LOWPAN_DISABLED:
+ problemString = "LOWPAN_DISABLED";
+ break;
+ case LOWPAN_WRONG_STATE:
+ problemString = "LOWPAN_WRONG_STATE";
+ break;
+ case LOWPAN_BUSY:
+ problemString = "LOWPAN_BUSY";
+ break;
+ case LOWPAN_NCP_PROBLEM:
+ problemString = "LOWPAN_NCP_PROBLEM";
+ break;
+ case LOWPAN_ALREADY:
+ problemString = "LOWPAN_ALREADY";
+ break;
+ case LOWPAN_CANCELED:
+ problemString = "LOWPAN_CANCELED";
+ break;
+ case LOWPAN_CREDENTIAL_NEEDED:
+ problemString = "LOWPAN_CREDENTIAL_NEEDED";
+ break;
+ case LOWPAN_FEATURE_NOT_SUPPORTED:
+ problemString = "LOWPAN_FEATURE_NOT_SUPPORTED";
+ break;
+ case LOWPAN_PROPERTY_NOT_FOUND:
+ problemString = "LOWPAN_PROPERTY_NOT_FOUND";
+ break;
+ case LOWPAN_JOIN_FAILED_UNKNOWN:
+ problemString = "LOWPAN_JOIN_FAILED_UNKNOWN";
+ break;
+ case LOWPAN_JOIN_FAILED_AT_SCAN:
+ problemString = "LOWPAN_JOIN_FAILED_AT_SCAN";
+ break;
+ case LOWPAN_JOIN_FAILED_AT_AUTH:
+ problemString = "LOWPAN_JOIN_FAILED_AT_AUTH";
+ break;
+ case LOWPAN_FORM_FAILED_AT_SCAN:
+ problemString = "LOWPAN_FORM_FAILED_AT_SCAN";
+ break;
+ default:
+ problemString = "LOWPAN_ERROR_CODE_" + problem;
+ break;
+ }
+
+ return problemString;
+ }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanIdentity.java b/lowpan/java/android/net/lowpan/LowpanIdentity.java
new file mode 100644
index 0000000..2e7b560
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanIdentity.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+import com.android.internal.util.HexDump;
+import java.util.Map;
+
+/**
+ * Describes an instance of a LoWPAN network.
+ *
+ * @hide
+ */
+//@SystemApi
+public class LowpanIdentity {
+
+ //////////////////////////////////////////////////////////////////////////
+ // Constants
+
+ /** @hide */
+ public static final int TYPE_ZIGBEE = 1;
+
+ /** @hide */
+ public static final int TYPE_ZIGBEE_IP = 2;
+
+ /** @hide */
+ public static final int TYPE_THREAD = 3;
+
+ public static final int UNKNOWN = Integer.MAX_VALUE;
+
+ //////////////////////////////////////////////////////////////////////////
+ // Builder
+
+ /** @hide */
+ //@SystemApi
+ public static class Builder {
+ private final LowpanIdentity identity = new LowpanIdentity();
+
+ public Builder setName(String x) {
+ identity.mName = x;
+ return this;
+ }
+
+ public Builder setXpanid(byte x[]) {
+ identity.mXpanid = x.clone();
+ return this;
+ }
+
+ public Builder setPanid(int x) {
+ identity.mPanid = x;
+ return this;
+ }
+
+ /** @hide */
+ public Builder setType(int x) {
+ identity.mType = x;
+ return this;
+ }
+
+ public Builder setChannel(int x) {
+ identity.mChannel = x;
+ return this;
+ }
+
+ /** @hide */
+ Builder updateFromMap(Map map) {
+ if (map.containsKey(ILowpanInterface.KEY_NETWORK_NAME)) {
+ setName(LowpanProperties.KEY_NETWORK_NAME.getFromMap(map));
+ }
+ if (map.containsKey(ILowpanInterface.KEY_NETWORK_PANID)) {
+ setPanid(LowpanProperties.KEY_NETWORK_PANID.getFromMap(map));
+ }
+ if (map.containsKey(ILowpanInterface.KEY_NETWORK_XPANID)) {
+ setXpanid(LowpanProperties.KEY_NETWORK_XPANID.getFromMap(map));
+ }
+ if (map.containsKey(ILowpanInterface.KEY_CHANNEL)) {
+ setChannel(LowpanProperties.KEY_CHANNEL.getFromMap(map));
+ }
+ if (map.containsKey(ILowpanInterface.KEY_NETWORK_TYPE)) {
+ setType(LowpanProperties.KEY_NETWORK_TYPE.getFromMap(map));
+ }
+ return this;
+ }
+
+ public LowpanIdentity build() {
+ return identity;
+ }
+ }
+
+ LowpanIdentity() {}
+
+ //////////////////////////////////////////////////////////////////////////
+ // Instance Variables
+
+ private String mName = null;
+ private byte[] mXpanid = null;
+ private int mType = UNKNOWN;
+ private int mPanid = UNKNOWN;
+ private int mChannel = UNKNOWN;
+
+ //////////////////////////////////////////////////////////////////////////
+ // Public Getters and Setters
+
+ public String getName() {
+ return mName;
+ }
+
+ public byte[] getXpanid() {
+ return mXpanid.clone();
+ }
+
+ public int getPanid() {
+ return mPanid;
+ }
+
+ /** @hide */
+ public int getType() {
+ return mType;
+ }
+
+ public int getChannel() {
+ return mChannel;
+ }
+
+ static void addToMap(Map<String, Object> parameters, LowpanIdentity networkInfo) {
+ if (networkInfo.getName() != null) {
+ LowpanProperties.KEY_NETWORK_NAME.putInMap(parameters, networkInfo.getName());
+ }
+ if (networkInfo.getPanid() != LowpanIdentity.UNKNOWN) {
+ LowpanProperties.KEY_NETWORK_PANID.putInMap(
+ parameters, networkInfo.getPanid());
+ }
+ if (networkInfo.getChannel() != LowpanIdentity.UNKNOWN) {
+ LowpanProperties.KEY_CHANNEL.putInMap(
+ parameters, networkInfo.getChannel());
+ }
+ if (networkInfo.getXpanid() != null) {
+ LowpanProperties.KEY_NETWORK_XPANID.putInMap(parameters, networkInfo.getXpanid());
+ }
+ }
+
+ void addToMap(Map<String, Object> parameters) {
+ addToMap(parameters, this);
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("Name: ").append(mName == null ? "<none>" : mName);
+
+ if (mXpanid != null) {
+ sb.append(", XPANID: ").append(HexDump.toHexString(mXpanid));
+ }
+
+ if (mPanid != UNKNOWN) {
+ sb.append(", PANID: ").append(String.format("0x%04X", mPanid));
+ }
+
+ if (mChannel != UNKNOWN) {
+ sb.append(", Channel: ").append(mChannel);
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanInterface.java b/lowpan/java/android/net/lowpan/LowpanInterface.java
new file mode 100644
index 0000000..cd54819
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanInterface.java
@@ -0,0 +1,824 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.IpPrefix;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Class for managing a specific Low-power Wireless Personal Area Network (LoWPAN) interface.
+ *
+ * @hide
+ */
+//@SystemApi
+public class LowpanInterface {
+ private static final String TAG = LowpanInterface.class.getSimpleName();
+
+ /** Detached role. The interface is not currently attached to a network. */
+ public static final String ROLE_DETACHED = "detached";
+
+ /** End-device role. End devices do not route traffic for other nodes. */
+ public static final String ROLE_END_DEVICE = "end-device";
+
+ /** Router role. Routers help route traffic around the mesh network. */
+ public static final String ROLE_ROUTER = "router";
+
+ /**
+ * Sleepy End-Device role.
+ *
+ * <p>End devices with this role are nominally asleep, waking up periodically to check in with
+ * their parent to see if there are packets destined for them. Such devices are capable of
+ * extraordinarilly low power consumption, but packet latency can be on the order of dozens of
+ * seconds(depending on how the node is configured).
+ */
+ public static final String ROLE_SLEEPY_END_DEVICE = "sleepy-end-device";
+
+ /**
+ * Sleepy-router role.
+ *
+ * <p>Routers with this role are nominally asleep, waking up periodically to check in with other
+ * routers and their children.
+ */
+ public static final String ROLE_SLEEPY_ROUTER = "sleepy-router";
+
+ /** TODO: doc */
+ public static final String ROLE_LEADER = "leader";
+
+ /** TODO: doc */
+ public static final String ROLE_COORDINATOR = "coordinator";
+
+ /**
+ * Offline state.
+ *
+ * <p>This is the initial state of the LoWPAN interface when the underlying driver starts. In
+ * this state the NCP is idle and not connected to any network.
+ *
+ * <p>This state can be explicitly entered by calling {@link #reset()}, {@link #leave()}, or
+ * <code>setUp(false)</code>, with the later two only working if we were not previously in the
+ * {@link #STATE_FAULT} state.
+ *
+ * @see #getState()
+ * @see #STATE_FAULT
+ */
+ public static final String STATE_OFFLINE = "offline";
+
+ /**
+ * Commissioning state.
+ *
+ * <p>The interface enters this state after a call to {@link #startCommissioningSession()}. This
+ * state may only be entered directly from the {@link #STATE_OFFLINE} state.
+ *
+ * @see #startCommissioningSession()
+ * @see #getState()
+ * @hide
+ */
+ public static final String STATE_COMMISSIONING = "commissioning";
+
+ /**
+ * Attaching state.
+ *
+ * <p>The interface enters this state when it starts the process of trying to find other nodes
+ * so that it can attach to any pre-existing network fragment, or when it is in the process of
+ * calculating the optimal values for unspecified parameters when forming a new network.
+ *
+ * <p>The interface may stay in this state for a prolonged period of time (or may spontaneously
+ * enter this state from {@link #STATE_ATTACHED}) if the underlying network technology is
+ * heirarchical (like ZigBeeIP) or if the device role is that of an "end-device" ({@link
+ * #ROLE_END_DEVICE} or {@link #ROLE_SLEEPY_END_DEVICE}). This is because such roles cannot
+ * create their own network fragments.
+ *
+ * @see #STATE_ATTACHED
+ * @see #getState()
+ */
+ public static final String STATE_ATTACHING = "attaching";
+
+ /**
+ * Attached state.
+ *
+ * <p>The interface enters this state from {@link #STATE_ATTACHING} once it is actively
+ * participating on a network fragment.
+ *
+ * @see #STATE_ATTACHING
+ * @see #getState()
+ */
+ public static final String STATE_ATTACHED = "attached";
+
+ /**
+ * Fault state.
+ *
+ * <p>The interface will enter this state when the driver has detected some sort of problem from
+ * which it was not immediately able to recover.
+ *
+ * <p>This state can be entered spontaneously from any other state. Calling {@link #reset} will
+ * cause the device to return to the {@link #STATE_OFFLINE} state.
+ *
+ * @see #getState
+ * @see #STATE_OFFLINE
+ */
+ public static final String STATE_FAULT = "fault";
+
+ /**
+ * Network type for Thread 1.x networks.
+ *
+ * @see android.net.lowpan.LowpanIdentity#getType
+ * @see #getLowpanIdentity
+ * @hide
+ */
+ public static final String NETWORK_TYPE_THREAD = "org.threadgroup.thread.v1";
+
+ /**
+ * Network type for ZigBeeIP 1.x networks.
+ *
+ * @see android.net.lowpan.LowpanIdentity#getType
+ * @see #getLowpanIdentity
+ * @hide
+ */
+ public static final String NETWORK_TYPE_ZIGBEE_IP = "org.zigbee.zigbeeip.v1";
+
+ private static final String NETWORK_PROPERTY_KEYS[] = {
+ LowpanProperties.KEY_NETWORK_NAME.getName(),
+ LowpanProperties.KEY_NETWORK_PANID.getName(),
+ LowpanProperties.KEY_NETWORK_XPANID.getName(),
+ LowpanProperties.KEY_CHANNEL.getName()
+ };
+
+ /**
+ * Callback base class for LowpanInterface
+ *
+ * @hide
+ */
+ //@SystemApi
+ public abstract static class Callback {
+ public void onConnectedChanged(boolean value) {}
+
+ public void onEnabledChanged(boolean value) {}
+
+ public void onUpChanged(boolean value) {}
+
+ public void onRoleChanged(@NonNull String value) {}
+
+ public void onStateChanged(@NonNull String state) {}
+
+ public void onLowpanIdentityChanged(@NonNull LowpanIdentity value) {}
+
+ /** @hide */
+ public void onPropertiesChanged(@NonNull Map properties) {}
+ }
+
+ private ILowpanInterface mBinder;
+ private final HashMap<Integer, ILowpanInterfaceListener> mListenerMap = new HashMap<>();
+
+ /** Map between IBinder identity hashes and LowpanInstance objects. */
+ private static final HashMap<Integer, LowpanInterface> sInstanceMap = new HashMap<>();
+
+ private LowpanInterface(IBinder binder) {
+ mBinder = ILowpanInterface.Stub.asInterface(binder);
+ }
+
+ /**
+ * Get the LowpanInterface object associated with this IBinder. Returns null if this IBinder
+ * does not implement the appropriate interface.
+ *
+ * @hide
+ */
+ @NonNull
+ public static final LowpanInterface from(IBinder binder) {
+ Integer hashCode = Integer.valueOf(System.identityHashCode(binder));
+ LowpanInterface instance;
+
+ synchronized (sInstanceMap) {
+ instance = sInstanceMap.get(hashCode);
+
+ if (instance == null) {
+ instance = new LowpanInterface(binder);
+ sInstanceMap.put(hashCode, instance);
+ }
+ }
+
+ return instance;
+ }
+
+ /** {@hide} */
+ public static final LowpanInterface from(ILowpanInterface iface) {
+ return from(iface.asBinder());
+ }
+
+ /** {@hide} */
+ public static final LowpanInterface getInterfaceFromBinder(IBinder binder) {
+ return from(binder);
+ }
+
+ /**
+ * Returns the IBinder object associated with this interface.
+ *
+ * @hide
+ */
+ public IBinder getBinder() {
+ return mBinder.asBinder();
+ }
+
+ private static void throwAsPublicException(Throwable t) throws LowpanException {
+ LowpanException.throwAsPublicException(t);
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // Private Property Helpers
+
+ void setProperties(Map properties) throws LowpanException {
+ try {
+ mBinder.setProperties(properties);
+
+ } catch (RemoteException x) {
+ // Catch and ignore all binder exceptions
+ Log.e(TAG, x.toString());
+
+ } catch (ServiceSpecificException x) {
+ throwAsPublicException(x);
+ }
+ }
+
+ @NonNull
+ Map<String, Object> getProperties(String keys[]) throws LowpanException {
+ try {
+ return mBinder.getProperties(keys);
+ } catch (RemoteException x) {
+ // Catch and ignore all binder exceptions
+ Log.e(TAG, x.toString());
+ } catch (ServiceSpecificException x) {
+ throwAsPublicException(x);
+ }
+ return new HashMap();
+ }
+
+ /** @hide */
+ public <T> void setProperty(LowpanProperty<T> key, T value) throws LowpanException {
+ HashMap<String, T> prop = new HashMap<>();
+ prop.put(key.getName(), value);
+ setProperties(prop);
+ }
+
+ /** @hide */
+ @Nullable
+ public <T> T getProperty(LowpanProperty<T> key) throws LowpanException {
+ Map<String, Object> map = getProperties(new String[] {key.getName()});
+ if (map != null && !map.isEmpty()) {
+ // We know there is only one value.
+ return (T) map.values().iterator().next();
+ }
+ return null;
+ }
+
+ @Nullable
+ <T> String getPropertyAsString(LowpanProperty<T> key) throws LowpanException {
+ try {
+ return mBinder.getPropertyAsString(key.getName());
+ } catch (RemoteException x) {
+ // Catch and ignore all binder exceptions
+ Log.e(TAG, x.toString());
+ } catch (ServiceSpecificException x) {
+ throwAsPublicException(x);
+ }
+ return null;
+ }
+
+ int getPropertyAsInt(LowpanProperty<Integer> key) throws LowpanException {
+ Integer value = getProperty(key);
+ return (value != null) ? value : 0;
+ }
+
+ boolean getPropertyAsBoolean(LowpanProperty<Boolean> key) throws LowpanException {
+ Boolean value = getProperty(key);
+ return (value != null) ? value : 0;
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // Public Actions
+
+ /**
+ * Form a new network with the given network information optional credential. Unspecified fields
+ * in the network information will be filled in with reasonable values. If the network
+ * credential is unspecified, one will be generated automatically.
+ *
+ * <p>This method will block until either the network was successfully formed or an error
+ * prevents the network form being formed.
+ *
+ * <p>Upon success, the interface will be up and attached to the newly formed network.
+ *
+ * @see #join(LowpanProvision)
+ */
+ public void form(@NonNull LowpanProvision provision) throws LowpanException {
+ try {
+ Map<String, Object> parameters = new HashMap();
+ provision.addToMap(parameters);
+ mBinder.form(parameters);
+ } catch (RemoteException x) {
+ throwAsPublicException(x);
+ } catch (ServiceSpecificException x) {
+ throwAsPublicException(x);
+ }
+ }
+
+ /**
+ * Attempts to join a new network with the given network information. This method will block
+ * until either the network was successfully joined or an error prevented the network from being
+ * formed. Upon success, the interface will be up and attached to the newly joined network.
+ *
+ * <p>Note that “joining” is distinct from “attaching”: Joining requires at least one other peer
+ * device to be present in order for the operation to complete successfully.
+ */
+ public void join(@NonNull LowpanProvision provision) throws LowpanException {
+ try {
+ Map<String, Object> parameters = new HashMap();
+ provision.addToMap(parameters);
+ mBinder.join(parameters);
+ } catch (RemoteException x) {
+ throwAsPublicException(x);
+ } catch (ServiceSpecificException x) {
+ throwAsPublicException(x);
+ }
+ }
+
+ /**
+ * Attaches to the network described by identity and credential. This is similar to {@link
+ * #join}, except that (assuming the identity and credential are valid) it will always succeed
+ * and provision the interface, even if there are no peers nearby.
+ *
+ * <p>This method will block execution until the operation has completed.
+ */
+ public void attach(@NonNull LowpanProvision provision) throws LowpanException {
+ if (ROLE_DETACHED.equals(getRole())) {
+ Map<String, Object> parameters = new HashMap();
+ provision.addToMap(parameters);
+ setProperties(parameters);
+ setUp(true);
+ } else {
+ throw new LowpanException(LowpanException.LOWPAN_ALREADY);
+ }
+ }
+
+ /**
+ * Bring down the network interface and forget all non-volatile details about the current
+ * network.
+ *
+ * <p>This method will block execution until the operation has completed.
+ */
+ public void leave() throws LowpanException {
+ try {
+ mBinder.leave();
+ } catch (RemoteException x) {
+ throwAsPublicException(x);
+ } catch (ServiceSpecificException x) {
+ throwAsPublicException(x);
+ }
+ }
+
+ /**
+ * Start a new commissioning session. Will fail if the interface is attached to a network or if
+ * the interface is disabled.
+ */
+ public @NonNull LowpanCommissioningSession startCommissioningSession(
+ @NonNull LowpanBeaconInfo beaconInfo) throws LowpanException {
+
+ /* TODO: Implement startCommissioningSession */
+ throw new LowpanException(LowpanException.LOWPAN_FEATURE_NOT_SUPPORTED);
+ }
+
+ /**
+ * Reset this network interface as if it has been power cycled. Will bring the network interface
+ * down if it was previously up. Will not erase any non-volatile settings.
+ *
+ * <p>This method will block execution until the operation has completed.
+ *
+ * @hide
+ */
+ public void reset() throws LowpanException {
+ try {
+ mBinder.reset();
+ } catch (RemoteException x) {
+ throwAsPublicException(x);
+ } catch (ServiceSpecificException x) {
+ throwAsPublicException(x);
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // Public Getters and Setters
+
+ /**
+ * Returns the name of this network interface.
+ *
+ * <p>Will return empty string if this interface is no longer viable.
+ */
+ @NonNull
+ public String getName() {
+ try {
+ return mBinder.getName();
+ } catch (RemoteException x) {
+ // Catch and ignore all binder exceptions
+ // when fetching the name.
+ Log.e(TAG, x.toString());
+ } catch (ServiceSpecificException x) {
+ // Catch and ignore all service-specific exceptions
+ // when fetching the name.
+ Log.e(TAG, x.toString());
+ }
+ return "";
+ }
+
+ /**
+ * Indicates if the interface is enabled or disabled.
+ *
+ * @see #setEnabled
+ * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
+ */
+ public boolean isEnabled() {
+ try {
+ return getPropertyAsBoolean(LowpanProperties.KEY_INTERFACE_ENABLED);
+ } catch (LowpanException x) {
+ return false;
+ }
+ }
+
+ /**
+ * Enables or disables the LoWPAN interface. When disabled, the interface is put into a low-power
+ * state and all commands that require the NCP to be queried will fail with {@link
+ * android.net.lowpan.LowpanException#LOWPAN_DISABLED}.
+ *
+ * @see #isEnabled
+ * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
+ * @hide
+ */
+ public void setEnabled(boolean enabled) throws LowpanException {
+ setProperty(LowpanProperties.KEY_INTERFACE_ENABLED, enabled);
+ }
+
+ /**
+ * Indicates if the network interface is up or down.
+ *
+ * @hide
+ */
+ public boolean isUp() {
+ try {
+ return getPropertyAsBoolean(LowpanProperties.KEY_INTERFACE_UP);
+ } catch (LowpanException x) {
+ return false;
+ }
+ }
+
+ /**
+ * Bring up or shut down the network interface.
+ *
+ * <p>This method brings up or shuts down the network interface, attaching or (gracefully)
+ * detaching from the currently configured LoWPAN network as appropriate.
+ *
+ * @hide
+ */
+ public void setUp(boolean interfaceUp) throws LowpanException {
+ setProperty(LowpanProperties.KEY_INTERFACE_UP, interfaceUp);
+ }
+
+ /**
+ * Indicates if there is at least one peer in range.
+ *
+ * @return <code>true</code> if we have at least one other peer in range, <code>false</code>
+ * otherwise.
+ */
+ public boolean isConnected() {
+ try {
+ return getPropertyAsBoolean(LowpanProperties.KEY_INTERFACE_CONNECTED);
+ } catch (LowpanException x) {
+ return false;
+ }
+ }
+
+ /**
+ * Indicates if this interface is currently commissioned onto an existing network. If the
+ * interface is commissioned, the interface may be brought up using setUp().
+ */
+ public boolean isCommissioned() {
+ try {
+ return getPropertyAsBoolean(LowpanProperties.KEY_INTERFACE_COMMISSIONED);
+ } catch (LowpanException x) {
+ return false;
+ }
+ }
+
+ /**
+ * Get interface state
+ *
+ * <h3>State Diagram</h3>
+ *
+ * <img src="LowpanInterface-1.png" />
+ *
+ * @return The current state of the interface.
+ * @see #STATE_OFFLINE
+ * @see #STATE_COMMISSIONING
+ * @see #STATE_ATTACHING
+ * @see #STATE_ATTACHED
+ * @see #STATE_FAULT
+ */
+ public String getState() {
+ try {
+ return getProperty(LowpanProperties.KEY_INTERFACE_STATE);
+ } catch (LowpanException x) {
+ Log.e(TAG, x.toString());
+ return STATE_FAULT;
+ }
+ }
+
+ /** TODO: doc */
+ public LowpanIdentity getLowpanIdentity() {
+ LowpanIdentity.Builder builder = new LowpanIdentity.Builder();
+ try {
+ builder.updateFromMap(getProperties(NETWORK_PROPERTY_KEYS));
+ } catch (LowpanException x) {
+ // We ignore all LoWPAN-specitic exceptions here.
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * TODO: doc
+ *
+ * @hide
+ */
+ public void setLowpanIdentity(LowpanIdentity network) throws LowpanException {
+ Map<String, Object> map = new HashMap();
+ LowpanIdentity.addToMap(map, network);
+ setProperties(map);
+ }
+
+ /** TODO: doc */
+ @NonNull
+ public String getRole() {
+ String role = null;
+
+ try {
+ role = getProperty(LowpanProperties.KEY_NETWORK_ROLE);
+ } catch (LowpanException x) {
+ // We ignore all LoWPAN-specitic exceptions here.
+ Log.e(TAG, x.toString());
+ }
+
+ if (role == null) {
+ role = ROLE_DETACHED;
+ }
+
+ return role;
+ }
+
+ /** TODO: doc */
+ @Nullable
+ public LowpanCredential getLowpanCredential() {
+ LowpanCredential credential = null;
+
+ try {
+ Integer keyIndex = getProperty(LowpanProperties.KEY_NETWORK_MASTER_KEY_INDEX);
+
+ if (keyIndex == null) {
+ credential =
+ LowpanCredential.createMasterKey(
+ getProperty(LowpanProperties.KEY_NETWORK_MASTER_KEY));
+ } else {
+ credential =
+ LowpanCredential.createMasterKey(
+ getProperty(LowpanProperties.KEY_NETWORK_MASTER_KEY),
+ keyIndex.intValue());
+ }
+ } catch (LowpanException x) {
+ // We ignore all LoWPAN-specitic exceptions here.
+ Log.e(TAG, x.toString());
+ }
+
+ return credential;
+ }
+
+ /**
+ * TODO: doc
+ *
+ * @hide
+ */
+ public void setLowpanCredential(LowpanCredential networkCredential) throws LowpanException {
+ Map<String, Object> map = new HashMap();
+ networkCredential.addToMap(map);
+ setProperties(map);
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // Listener Support
+
+ /**
+ * Registers a subclass of {@link LowpanInterface.Callback} to receive events.
+ *
+ * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
+ * @param handler If not <code>null</code>, events will be dispatched via the given handler
+ * object. If <code>null</code>, the thread upon which events will be dispatched is
+ * unspecified.
+ * @see #registerCallback(Callback)
+ * @see #unregisterCallback(Callback)
+ */
+ public void registerCallback(@NonNull Callback cb, @Nullable Handler handler) {
+ ILowpanInterfaceListener.Stub listenerBinder =
+ new ILowpanInterfaceListener.Stub() {
+ public void onPropertiesChanged(Map<String, Object> properties) {
+ Runnable runnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ for (String key : (Set<String>) properties.keySet()) {
+ Object value = properties.get(key);
+ switch (key) {
+ case ILowpanInterface.KEY_INTERFACE_ENABLED:
+ cb.onEnabledChanged(
+ ((Boolean) value).booleanValue());
+ break;
+ case ILowpanInterface.KEY_INTERFACE_UP:
+ cb.onUpChanged(
+ ((Boolean) value).booleanValue());
+ break;
+ case ILowpanInterface.KEY_INTERFACE_CONNECTED:
+ cb.onConnectedChanged(
+ ((Boolean) value).booleanValue());
+ break;
+ case ILowpanInterface.KEY_INTERFACE_STATE:
+ cb.onStateChanged((String) value);
+ break;
+ case ILowpanInterface.KEY_NETWORK_NAME:
+ case ILowpanInterface.KEY_NETWORK_PANID:
+ case ILowpanInterface.KEY_NETWORK_XPANID:
+ case ILowpanInterface.KEY_CHANNEL:
+ cb.onLowpanIdentityChanged(getLowpanIdentity());
+ break;
+ case ILowpanInterface.KEY_NETWORK_ROLE:
+ cb.onRoleChanged(value.toString());
+ break;
+ }
+ }
+ cb.onPropertiesChanged(properties);
+ }
+ };
+
+ if (handler != null) {
+ handler.post(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+ };
+ try {
+ mBinder.addListener(listenerBinder);
+ } catch (RemoteException x) {
+ // Log and ignore. If this happens, this interface
+ // is likely dead anyway.
+ Log.e(TAG, x.toString());
+ }
+ synchronized (mListenerMap) {
+ mListenerMap.put(System.identityHashCode(cb), listenerBinder);
+ }
+ }
+
+ /**
+ * Registers a subclass of {@link LowpanInterface.Callback} to receive events.
+ *
+ * <p>The thread upon which events will be dispatched is unspecified.
+ *
+ * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
+ * @see #registerCallback(Callback, Handler)
+ * @see #unregisterCallback(Callback)
+ */
+ public void registerCallback(Callback cb) {
+ registerCallback(cb, null);
+ }
+
+ /**
+ * Unregisters a previously registered callback class.
+ *
+ * @param cb Subclass of {@link LowpanInterface.Callback} which was previously registered to
+ * receive events.
+ * @see #registerCallback(Callback, Handler)
+ * @see #registerCallback(Callback)
+ */
+ public void unregisterCallback(Callback cb) {
+ int hashCode = System.identityHashCode(cb);
+ ILowpanInterfaceListener listenerBinder = mListenerMap.get(hashCode);
+
+ if (listenerBinder != null) {
+ synchronized (mListenerMap) {
+ mListenerMap.remove(hashCode);
+ }
+ try {
+ mBinder.removeListener(listenerBinder);
+ } catch (RemoteException x) {
+ // Catch and ignore all binder exceptions
+ Log.e(TAG, x.toString());
+ }
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // Active and Passive Scanning
+
+ /**
+ * Creates a new {@link android.net.lowpan.LowpanScanner} object for this interface.
+ *
+ * <p>This method allocates a new unique object for each call.
+ *
+ * @see android.net.lowpan.LowpanScanner
+ */
+ public @NonNull LowpanScanner createScanner() {
+ return new LowpanScanner(mBinder);
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // Route Management
+
+ /**
+ * Advertise the given IP prefix as an on-mesh prefix.
+ *
+ * @hide
+ */
+ public void addOnMeshPrefix(IpPrefix prefix, int flags) throws LowpanException {
+ try {
+ mBinder.addOnMeshPrefix(prefix, flags);
+ } catch (RemoteException x) {
+ throwAsPublicException(x);
+ } catch (ServiceSpecificException x) {
+ throwAsPublicException(x);
+ }
+ }
+
+ /**
+ * Remove an IP prefix previously advertised by this device from the list of advertised on-mesh
+ * prefixes.
+ *
+ * @hide
+ */
+ public void removeOnMeshPrefix(IpPrefix prefix) {
+ try {
+ mBinder.removeOnMeshPrefix(prefix);
+ } catch (RemoteException x) {
+ // Catch and ignore all binder exceptions
+ Log.e(TAG, x.toString());
+ } catch (ServiceSpecificException x) {
+ // Catch and ignore all service exceptions
+ Log.e(TAG, x.toString());
+ }
+ }
+
+ /**
+ * Advertise this device to other devices on the mesh network as having a specific route to the
+ * given network. This device will then receive forwarded traffic for that network.
+ *
+ * @hide
+ */
+ public void addExternalRoute(IpPrefix prefix, int flags) throws LowpanException {
+ try {
+ mBinder.addExternalRoute(prefix, flags);
+ } catch (RemoteException x) {
+ throwAsPublicException(x);
+ } catch (ServiceSpecificException x) {
+ throwAsPublicException(x);
+ }
+ }
+
+ /**
+ * Revoke a previously advertised specific route to the given network.
+ *
+ * @hide
+ */
+ public void removeExternalRoute(IpPrefix prefix) {
+ try {
+ mBinder.removeExternalRoute(prefix);
+ } catch (RemoteException x) {
+ // Catch and ignore all binder exceptions
+ Log.e(TAG, x.toString());
+ } catch (ServiceSpecificException x) {
+ // Catch and ignore all service exceptions
+ Log.e(TAG, x.toString());
+ }
+ }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanManager.java b/lowpan/java/android/net/lowpan/LowpanManager.java
new file mode 100644
index 0000000..b58608d
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanManager.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.AndroidException;
+import android.util.Log;
+import java.util.HashMap;
+
+/**
+ * Manager object for looking up LoWPAN interfaces.
+ *
+ * @hide
+ */
+//@SystemApi
+public class LowpanManager {
+ private static final String TAG = LowpanManager.class.getSimpleName();
+
+ //////////////////////////////////////////////////////////////////////////
+ // Public Classes
+
+ /** @hide */
+ //@SystemApi
+ public abstract static class Callback {
+ public void onInterfaceAdded(LowpanInterface lowpan_interface) {}
+
+ public void onInterfaceRemoved(LowpanInterface lowpan_interface) {}
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // Instance Variables
+
+ private ILowpanManager mManager;
+ private HashMap<Integer, ILowpanManagerListener> mListenerMap = new HashMap<>();
+
+ //////////////////////////////////////////////////////////////////////////
+
+ private static LowpanManager sSingletonInstance;
+
+ //////////////////////////////////////////////////////////////////////////
+ // Static Methods
+
+ /** Returns a reference to the LowpanManager object, allocating it if necessary. */
+ public static LowpanManager getManager() {
+ return from(null);
+ }
+
+ public static LowpanManager from(Context context) {
+ // TODO: Actually get this from the context!
+
+ if (sSingletonInstance == null) {
+ sSingletonInstance = new LowpanManager();
+ }
+ return sSingletonInstance;
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // Constructors
+
+ /**
+ * Private LowpanManager constructor. Since we are a singleton, we do not allow external
+ * construction.
+ */
+ private LowpanManager() {}
+
+ //////////////////////////////////////////////////////////////////////////
+ // Private Methods
+
+ /**
+ * Returns a reference to the ILowpanManager interface, provided by the LoWPAN Manager Service.
+ */
+ @Nullable
+ private ILowpanManager getILowpanManager() {
+ ILowpanManager manager = mManager;
+ if (manager == null) {
+ IBinder serviceBinder =
+ new ServiceManager().getService(ILowpanManager.LOWPAN_SERVICE_NAME);
+ mManager = manager = ILowpanManager.Stub.asInterface(serviceBinder);
+
+ // Add any listeners
+ synchronized (mListenerMap) {
+ for (Integer hashObj : mListenerMap.keySet()) {
+ try {
+ manager.addListener(mListenerMap.get(hashObj));
+ } catch (RemoteException x) {
+ // Consider any failure here as implying the manager is defunct
+ mManager = manager = null;
+ }
+ }
+ }
+ }
+ return manager;
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // Public Methods
+
+ /**
+ * Returns a reference to the requested LowpanInterface object. If the given interface doesn't
+ * exist, or it is not a LoWPAN interface, returns null.
+ */
+ @Nullable
+ public LowpanInterface getInterface(@NonNull String name) {
+ LowpanInterface ret = null;
+ ILowpanManager manager = getILowpanManager();
+
+ // Maximum number of tries is two. We should only try
+ // more than once if our manager has died or there
+ // was some sort of AIDL buffer full event.
+ for (int i = 0; i < 2 && manager != null; i++) {
+ try {
+ ILowpanInterface iface = manager.getInterface(name);
+ if (iface != null) {
+ ret = LowpanInterface.getInterfaceFromBinder(iface.asBinder());
+ }
+ break;
+ } catch (RemoteException x) {
+ // In all of the cases when we get this exception, we reconnect and try again
+ mManager = null;
+ manager = getILowpanManager();
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Returns a reference to the first registered LowpanInterface object. If there are no LoWPAN
+ * interfaces registered, returns null.
+ */
+ @Nullable
+ public LowpanInterface getInterface() {
+ String[] ifaceList = getInterfaceList();
+ if (ifaceList != null && ifaceList.length > 0) {
+ return getInterface(ifaceList[0]);
+ }
+ return null;
+ }
+
+ /**
+ * Returns a string array containing the names of LoWPAN interfaces. This list may contain fewer
+ * interfaces if the calling process does not have permissions to see individual interfaces.
+ */
+ @NonNull
+ public String[] getInterfaceList() {
+ ILowpanManager manager = getILowpanManager();
+
+ if (manager != null) {
+ try {
+ return manager.getInterfaceList();
+
+ } catch (RemoteException x) {
+ // In all of the cases when we get this exception, we reconnect and try again
+ mManager = null;
+ try {
+ manager = getILowpanManager();
+ if (manager != null) {
+ return manager.getInterfaceList();
+ }
+ } catch (RemoteException ex) {
+ // Something weird is going on, so we log it
+ // and fall back thru to returning an empty array.
+ Log.e(TAG, ex.toString());
+ mManager = null;
+ }
+ }
+ }
+
+ // Return empty list if we have no service.
+ return new String[0];
+ }
+
+ /**
+ * Registers a callback object to receive notifications when LoWPAN interfaces are added or
+ * removed.
+ *
+ * @hide
+ */
+ public void registerCallback(@NonNull Callback cb, @Nullable Handler handler)
+ throws LowpanException {
+ ILowpanManagerListener.Stub listenerBinder =
+ new ILowpanManagerListener.Stub() {
+ public void onInterfaceAdded(ILowpanInterface lowpan_interface) {
+ Runnable runnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ cb.onInterfaceAdded(
+ LowpanInterface.getInterfaceFromBinder(
+ lowpan_interface.asBinder()));
+ }
+ };
+
+ if (handler != null) {
+ handler.post(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+
+ public void onInterfaceRemoved(ILowpanInterface lowpan_interface) {
+ Runnable runnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ cb.onInterfaceRemoved(
+ LowpanInterface.getInterfaceFromBinder(
+ lowpan_interface.asBinder()));
+ }
+ };
+
+ if (handler != null) {
+ handler.post(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+ };
+ ILowpanManager manager = getILowpanManager();
+ if (manager != null) {
+ try {
+ manager.addListener(listenerBinder);
+ } catch (DeadObjectException x) {
+ mManager = null;
+ // Tickle the ILowpanManager instance, which might
+ // get us added back.
+ getILowpanManager();
+ } catch (Throwable x) {
+ LowpanException.throwAsPublicException(x);
+ }
+ }
+ synchronized (mListenerMap) {
+ mListenerMap.put(Integer.valueOf(System.identityHashCode(cb)), listenerBinder);
+ }
+ }
+
+ /** @hide */
+ public void registerCallback(@NonNull Callback cb) throws LowpanException {
+ registerCallback(cb, null);
+ }
+
+ /**
+ * Unregisters a previously registered {@link LowpanManager.Callback} object.
+ *
+ * @hide
+ */
+ public void unregisterCallback(@NonNull Callback cb) throws AndroidException {
+ Integer hashCode = Integer.valueOf(System.identityHashCode(cb));
+ ILowpanManagerListener listenerBinder = mListenerMap.get(hashCode);
+ if (listenerBinder != null) {
+ synchronized (mListenerMap) {
+ mListenerMap.remove(hashCode);
+ }
+ if (getILowpanManager() != null) {
+ try {
+ mManager.removeListener(listenerBinder);
+ } catch (DeadObjectException x) {
+ mManager = null;
+ }
+ }
+ }
+ }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanProperties.java b/lowpan/java/android/net/lowpan/LowpanProperties.java
new file mode 100644
index 0000000..0d5acc2
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanProperties.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+import android.net.LinkAddress;
+import android.net.RouteInfo;
+import java.util.List;
+
+/** {@hide} */
+public final class LowpanProperties {
+
+ public static final LowpanProperty<Boolean> KEY_INTERFACE_ENABLED =
+ new LowpanStandardProperty(
+ "android.net.lowpan.property.INTERFACE_ENABLED", Boolean.class);
+ public static final LowpanProperty<Boolean> KEY_INTERFACE_COMMISSIONED =
+ new LowpanStandardProperty(
+ "android.net.lowpan.property.INTERFACE_COMMISSIONED", Boolean.class);
+ public static final LowpanProperty<Boolean> KEY_INTERFACE_CONNECTED =
+ new LowpanStandardProperty(
+ "android.net.lowpan.property.INTERFACE_CONNECTED", Boolean.class);
+ public static final LowpanProperty<Boolean> KEY_INTERFACE_UP =
+ new LowpanStandardProperty("android.net.lowpan.property.INTERFACE_UP", Boolean.class);
+ public static final LowpanProperty<String> KEY_INTERFACE_STATE =
+ new LowpanStandardProperty("android.net.lowpan.property.INTERFACE_STATE", String.class);
+
+ public static final LowpanProperty<String> KEY_NETWORK_NAME =
+ new LowpanStandardProperty("android.net.lowpan.property.NETWORK_NAME", Boolean.class);
+ public static final LowpanProperty<Integer> KEY_NETWORK_PANID =
+ new LowpanStandardProperty("android.net.lowpan.property.NETWORK_PANID", Integer.class);
+ public static final LowpanProperty<byte[]> KEY_NETWORK_XPANID =
+ new LowpanStandardProperty("android.net.lowpan.property.NETWORK_XPANID", byte[].class);
+ public static final LowpanProperty<byte[]> KEY_NETWORK_MASTER_KEY =
+ new LowpanStandardProperty(
+ "android.net.lowpan.property.NETWORK_MASTER_KEY", byte[].class);
+ public static final LowpanProperty<Integer> KEY_NETWORK_MASTER_KEY_INDEX =
+ new LowpanStandardProperty(
+ "android.net.lowpan.property.NETWORK_MASTER_KEY_INDEX", Integer.class);
+ public static final LowpanProperty<Integer> KEY_NETWORK_TYPE =
+ new LowpanStandardProperty("android.net.lowpan.property.NETWORK_TYPE", Integer.class);
+ public static final LowpanProperty<String> KEY_NETWORK_ROLE =
+ new LowpanStandardProperty("android.net.lowpan.property.NETWORK_ROLE", String.class);
+
+ public static final LowpanProperty<Integer> KEY_CHANNEL =
+ new LowpanStandardProperty("android.net.lowpan.property.CHANNEL", Integer.class);
+ public static final LowpanProperty<int[]> KEY_CHANNEL_MASK =
+ new LowpanStandardProperty("android.net.lowpan.property.CHANNEL_MASK", int[].class);
+ public static final LowpanProperty<Integer> KEY_MAX_TX_POWER =
+ new LowpanStandardProperty("android.net.lowpan.property.MAX_TX_POWER", Integer.class);
+ public static final LowpanProperty<Integer> KEY_RSSI =
+ new LowpanStandardProperty("android.net.lowpan.property.RSSI", Integer.class);
+
+ public static final LowpanProperty<Integer> KEY_LQI =
+ new LowpanStandardProperty("android.net.lowpan.property.LQI", Integer.class);
+ public static final LowpanProperty<byte[]> KEY_BEACON_ADDRESS =
+ new LowpanStandardProperty("android.net.lowpan.property.BEACON_ADDRESS", byte[].class);
+ public static final LowpanProperty<Boolean> KEY_BEACON_CAN_ASSIST =
+ new LowpanStandardProperty(
+ "android.net.lowpan.property.BEACON_CAN_ASSIST", Boolean.class);
+
+ public static final LowpanProperty<String> KEY_DRIVER_VERSION =
+ new LowpanStandardProperty("android.net.lowpan.property.DRIVER_VERSION", String.class);
+
+ public static final LowpanProperty<String> KEY_NCP_VERSION =
+ new LowpanStandardProperty("android.net.lowpan.property.NCP_VERSION", String.class);
+
+ public static final LowpanProperty<List<LinkAddress>> KEY_LINK_ADDRESS_ARRAY =
+ new LowpanStandardProperty(
+ "android.net.lowpan.property.LINK_ADDRESS_ARRAY", LinkAddress[].class);
+
+ public static final LowpanProperty<List<RouteInfo>> KEY_ROUTE_INFO_ARRAY =
+ new LowpanStandardProperty(
+ "android.net.lowpan.property.ROUTE_INFO_ARRAY", RouteInfo[].class);
+
+ /** @hide */
+ public static final LowpanProperty<byte[]> KEY_EXTENDED_ADDRESS =
+ new LowpanStandardProperty(
+ "android.net.lowpan.property.EXTENDED_ADDRESS", byte[].class);
+
+ /** @hide */
+ public static final LowpanProperty<byte[]> KEY_MAC_ADDRESS =
+ new LowpanStandardProperty("android.net.lowpan.property.MAC_ADDRESS", byte[].class);
+
+ /** @hide */
+ private LowpanProperties() {}
+
+ /** @hide */
+ static final class LowpanStandardProperty<T> extends LowpanProperty<T> {
+ private final String mName;
+ private final Class<T> mType;
+
+ LowpanStandardProperty(String name, Class<T> type) {
+ mName = name;
+ mType = type;
+ }
+
+ @Override
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public Class<T> getType() {
+ return mType;
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+ }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanProperty.java b/lowpan/java/android/net/lowpan/LowpanProperty.java
new file mode 100644
index 0000000..7f26986
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanProperty.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+import java.util.Map;
+
+/** {@hide} */
+public abstract class LowpanProperty<T> {
+ public abstract String getName();
+
+ public abstract Class<T> getType();
+
+ public void putInMap(Map map, T value) {
+ map.put(getName(), value);
+ }
+
+ public T getFromMap(Map map) {
+ return (T) map.get(getName());
+ }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanProvision.java b/lowpan/java/android/net/lowpan/LowpanProvision.java
new file mode 100644
index 0000000..ace1f9c
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanProvision.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import java.util.Map;
+
+/**
+ * Describes the information needed to describe a network
+ *
+ * @hide
+ */
+//@SystemApi
+public class LowpanProvision {
+
+ //////////////////////////////////////////////////////////////////////////
+ // Builder
+
+ /** @hide */
+ //@SystemApi
+ public static class Builder {
+ private final LowpanProvision provision = new LowpanProvision();
+
+ public Builder setLowpanIdentity(@NonNull LowpanIdentity identity) {
+ provision.mIdentity = identity;
+ return this;
+ }
+
+ public Builder setLowpanCredential(@NonNull LowpanCredential credential) {
+ provision.mCredential = credential;
+ return this;
+ }
+
+ public LowpanProvision build() {
+ return provision;
+ }
+ }
+
+ private LowpanProvision() {}
+
+ //////////////////////////////////////////////////////////////////////////
+ // Instance Variables
+
+ private LowpanIdentity mIdentity = new LowpanIdentity();
+ private LowpanCredential mCredential = null;
+
+ //////////////////////////////////////////////////////////////////////////
+ // Public Getters and Setters
+
+ @NonNull
+ public LowpanIdentity getLowpanIdentity() {
+ return mIdentity;
+ }
+
+ @Nullable
+ public LowpanCredential getLowpanCredential() {
+ return mCredential;
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // LoWPAN-Internal Methods
+
+ static void addToMap(Map<String, Object> parameters, LowpanProvision provision)
+ throws LowpanException {
+ provision.mIdentity.addToMap(parameters);
+ if (provision.mCredential != null) {
+ provision.mCredential.addToMap(parameters);
+ }
+ }
+
+ void addToMap(Map<String, Object> parameters) throws LowpanException {
+ addToMap(parameters, this);
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("LowpanProvision { identity => ").append(mIdentity.toString());
+
+ if (mCredential != null) {
+ sb.append(", credential: ").append(mCredential.toString());
+ }
+
+ sb.append("}");
+
+ return sb.toString();
+ }
+};
diff --git a/lowpan/java/android/net/lowpan/LowpanScanner.java b/lowpan/java/android/net/lowpan/LowpanScanner.java
new file mode 100644
index 0000000..754f72e3
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanScanner.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.lowpan;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * LoWPAN Scanner
+ *
+ * <p>This class allows performing network (active) scans and energy (passive) scans.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+//@SystemApi
+public class LowpanScanner {
+ private static final String TAG = LowpanInterface.class.getSimpleName();
+
+ //////////////////////////////////////////////////////////////////////////
+ // Public Classes
+
+ /**
+ * Callback base class for LowpanScanner
+ *
+ * @hide
+ */
+ //@SystemApi
+ public abstract static class Callback {
+ public void onNetScanBeacon(LowpanBeaconInfo beacon) {}
+
+ public void onEnergyScanResult(LowpanEnergyScanResult result) {}
+
+ public void onScanFinished() {}
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // Instance Variables
+
+ private ILowpanInterface mBinder;
+ private Callback mCallback = null;
+ private Handler mHandler = null;
+ private List<Integer> mChannelMask = null;
+ private int mTxPower = Integer.MAX_VALUE;
+
+ //////////////////////////////////////////////////////////////////////////
+ // Constructors/Accessors and Exception Glue
+
+ LowpanScanner(@NonNull ILowpanInterface binder) {
+ mBinder = binder;
+ }
+
+ /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
+ public void setCallback(@Nullable Callback cb, @Nullable Handler handler) {
+ mCallback = cb;
+ mHandler = handler;
+ }
+
+ /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
+ public void setCallback(@Nullable Callback cb) {
+ setCallback(cb, null);
+ }
+
+ /**
+ * Sets the channel mask to use when scanning.
+ *
+ * @param mask The channel mask to use when scanning. If <code>null</code>, any previously set
+ * channel mask will be cleared and all channels not masked by the current regulatory zone
+ * will be scanned.
+ */
+ public void setChannelMask(@Nullable Collection<Integer> mask) {
+ if (mask == null) {
+ mChannelMask = null;
+ } else {
+ if (mChannelMask == null) {
+ mChannelMask = new ArrayList<>();
+ } else {
+ mChannelMask.clear();
+ }
+ mChannelMask.addAll(mask);
+ }
+ }
+
+ /**
+ * Gets the current channel mask.
+ *
+ * @return the current channel mask, or <code>null</code> if no channel mask is currently set.
+ */
+ public @Nullable Collection<Integer> getChannelMask() {
+ return mChannelMask.clone();
+ }
+
+ /**
+ * Adds a channel to the channel mask used for scanning.
+ *
+ * <p>If a channel mask was previously <code>null</code>, a new one is created containing only
+ * this channel. May be called multiple times to add additional channels ot the channel mask.
+ *
+ * @see #setChannelMask
+ * @see #getChannelMask
+ * @see #getTxPower
+ */
+ public void addChannel(int channel) {
+ if (mChannelMask == null) {
+ mChannelMask = new ArrayList<>();
+ }
+ mChannelMask.add(Integer.valueOf(channel));
+ }
+
+ /**
+ * Sets the maximum transmit power to be used for active scanning.
+ *
+ * <p>The actual transmit power used is the lesser of this value and the currently configured
+ * maximum transmit power for the interface.
+ *
+ * @see #getTxPower
+ */
+ public void setTxPower(int txPower) {
+ mTxPower = txPower;
+ }
+
+ /**
+ * Gets the maximum transmit power used for active scanning.
+ *
+ * @see #setTxPower
+ */
+ public int getTxPower() {
+ return mTxPower;
+ }
+
+ private Map<String, Object> createScanOptionMap() {
+ Map<String, Object> map = new HashMap();
+
+ if (mChannelMask != null) {
+ LowpanProperties.KEY_CHANNEL_MASK.putInMap(
+ map, mChannelMask.stream().mapToInt(i -> i).toArray());
+ }
+
+ if (mTxPower != Integer.MAX_VALUE) {
+ LowpanProperties.KEY_MAX_TX_POWER.putInMap(map, Integer.valueOf(mTxPower));
+ }
+
+ return map;
+ }
+
+ /**
+ * Start a network scan.
+ *
+ * <p>This method will return once the scan has started.
+ *
+ * @see #stopNetScan
+ */
+ public void startNetScan() throws LowpanException {
+ Map<String, Object> map = createScanOptionMap();
+
+ ILowpanNetScanCallback binderListener =
+ new ILowpanNetScanCallback.Stub() {
+ public void onNetScanBeacon(Map parameters) {
+ Callback callback = mCallback;
+ Handler handler = mHandler;
+
+ if (callback == null) {
+ return;
+ }
+
+ Runnable runnable = () -> callback.onNetScanBeacon(
+ new LowpanBeaconInfo.Builder()
+ .updateFromMap(parameters)
+ .build());
+
+ if (handler != null) {
+ handler.post(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+
+ public void onNetScanFinished() {
+ Callback callback = mCallback;
+ Handler handler = mHandler;
+
+ if (callback == null) {
+ return;
+ }
+
+ Runnable runnable = () -> callback.onScanFinished();
+
+ if (handler != null) {
+ handler.post(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+ };
+
+ try {
+ mBinder.startNetScan(map, binderListener);
+ } catch (ServiceSpecificException|RemoteException x) {
+ LowpanException.throwAsPublicException(x);
+ }
+ }
+
+ /**
+ * Stop a network scan currently in progress.
+ *
+ * @see #startNetScan
+ */
+ public void stopNetScan() {
+ try {
+ mBinder.stopNetScan();
+ } catch (RemoteException x) {
+ // Catch and ignore all binder exceptions
+ Log.e(TAG, x.toString());
+ }
+ }
+
+ /**
+ * Start an energy scan.
+ *
+ * <p>This method will return once the scan has started.
+ *
+ * @see #stopEnergyScan
+ */
+ public void startEnergyScan() throws LowpanException {
+ Map<String, Object> map = createScanOptionMap();
+
+ ILowpanEnergyScanCallback binderListener =
+ new ILowpanEnergyScanCallback.Stub() {
+ public void onEnergyScanResult(int channel, int rssi) {
+ Callback callback = mCallback;
+ Handler handler = mHandler;
+
+ if (callback == null) {
+ return;
+ }
+
+ Runnable runnable = () -> {
+ if (callback != null) {
+ LowpanEnergyScanResult result =
+ new LowpanEnergyScanResult();
+ result.setChannel(channel);
+ result.setMaxRssi(rssi);
+ callback.onEnergyScanResult(result);
+ }
+ };
+
+ if (handler != null) {
+ handler.post(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+
+ public void onEnergyScanFinished() {
+ Callback callback = mCallback;
+ Handler handler = mHandler;
+
+ if (callback == null) {
+ return;
+ }
+
+ Runnable runnable = () -> callback.onScanFinished();
+
+ if (handler != null) {
+ handler.post(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+ };
+
+ try {
+ mBinder.startEnergyScan(map, binderListener);
+ } catch (RemoteException x) {
+ LowpanException.throwAsPublicException(x);
+ } catch (ServiceSpecificException x) {
+ LowpanException.throwAsPublicException(x);
+ }
+ }
+
+ /**
+ * Stop an energy scan currently in progress.
+ *
+ * @see #startEnergyScan
+ */
+ public void stopEnergyScan() {
+ try {
+ mBinder.stopEnergyScan();
+ } catch (RemoteException x) {
+ // Catch and ignore all binder exceptions
+ Log.e(TAG, x.toString());
+ }
+ }
+}
diff --git a/lowpan/java/android/net/lowpan/package.html b/lowpan/java/android/net/lowpan/package.html
new file mode 100644
index 0000000..342e32ee
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/package.html
@@ -0,0 +1,29 @@
+<HTML>
+<BODY>
+<p>@SystemApi</p>
+<!-- @hide -->
+<p>Provides classes to manage Low-power Wireless Personal Area Network (LoWPAN) functionality on the device.
+Examples of such network technologies include <a href="http://threadgroup.org/">Thread</a> and
+<a href="http://www.zigbee.org/zigbee-for-developers/network-specifications/zigbeeip/">ZigBee IP</a>.</p>
+<p>The LoWPAN APIs provide a means by which applications can communicate
+with the lower-level wireless stack that provides LoWPAN network access.</p>
+
+<p>Some APIs may require the following user permissions:</p>
+<ul>
+ <li>{@link android.Manifest.permission#ACCESS_LOWPAN_STATE}</li>
+ <li>{@link android.Manifest.permission#CHANGE_LOWPAN_STATE}</li>
+ <li>TBD</li>
+</ul>
+
+<p class="note"><strong>Note:</strong> Not all Android-powered devices provide LoWPAN functionality.
+If your application uses these APIs, declare so with a <a
+href="{@docRoot}guide/topics/manifest/uses-feature-element.html">{@code <uses-feature>}</a>
+element in the manifest file:</p>
+<pre>
+<manifest ...>
+ <uses-feature android:name="android.hardware.lowpan" />
+ ...
+</manifest>
+</pre>
+</BODY>
+</HTML>
diff --git a/media/java/Android.bp b/media/java/Android.bp
new file mode 100644
index 0000000..0810699
--- /dev/null
+++ b/media/java/Android.bp
@@ -0,0 +1,4 @@
+filegroup {
+ name: "IMidiDeviceServer.aidl",
+ srcs: ["android/media/midi/IMidiDeviceServer.aidl"],
+}
diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java
index abf6b20..2a0e04e 100644
--- a/media/java/android/media/ImageUtils.java
+++ b/media/java/android/media/ImageUtils.java
@@ -62,6 +62,7 @@
case ImageFormat.RAW12:
case ImageFormat.DEPTH16:
case ImageFormat.DEPTH_POINT_CLOUD:
+ case ImageFormat.RAW_DEPTH:
return 1;
case ImageFormat.PRIVATE:
return 0;
@@ -103,6 +104,10 @@
throw new IllegalArgumentException(
"Copy of RAW_OPAQUE format has not been implemented");
}
+ if (src.getFormat() == ImageFormat.RAW_DEPTH) {
+ throw new IllegalArgumentException(
+ "Copy of RAW_DEPTH format has not been implemented");
+ }
if (!(dst.getOwner() instanceof ImageWriter)) {
throw new IllegalArgumentException("Destination image is not from ImageWriter. Only"
+ " the images from ImageWriter are writable");
@@ -206,6 +211,7 @@
case PixelFormat.RGB_565:
case ImageFormat.YUY2:
case ImageFormat.Y16:
+ case ImageFormat.RAW_DEPTH:
case ImageFormat.RAW_SENSOR:
case ImageFormat.RAW_PRIVATE: // round estimate, real size is unknown
case ImageFormat.DEPTH16:
@@ -253,6 +259,7 @@
case ImageFormat.RAW_SENSOR:
case ImageFormat.RAW10:
case ImageFormat.RAW12:
+ case ImageFormat.RAW_DEPTH:
return new Size(image.getWidth(), image.getHeight());
case ImageFormat.PRIVATE:
return new Size(0, 0);
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 2de69e7..9b74656 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -339,6 +339,12 @@
(unsigned 64-bit {@linkplain ByteOrder#nativeOrder native-order} integer.)</td>
</tr>
<tr>
+ <td>FLAC</td>
+ <td>Metadata blocks</td>
+ <td class=NA>Not Used</td>
+ <td class=NA>Not Used</td>
+ </tr>
+ <tr>
<td>MPEG-4</td>
<td>Decoder-specific information from ESDS<sup>*</sup></td>
<td class=NA>Not Used</td>
@@ -2440,6 +2446,8 @@
}
};
+ private final Pattern zeroPattern = new Pattern(0, 0);
+
/**
* The pattern applicable to the protected data in each subsample.
*/
@@ -2462,7 +2470,7 @@
key = newKey;
iv = newIV;
mode = newMode;
- pattern = new Pattern(0, 0);
+ pattern = zeroPattern;
}
/**
diff --git a/media/java/android/media/MediaHTTPConnection.java b/media/java/android/media/MediaHTTPConnection.java
index 228a6de..dd731df 100644
--- a/media/java/android/media/MediaHTTPConnection.java
+++ b/media/java/android/media/MediaHTTPConnection.java
@@ -136,7 +136,13 @@
private void teardownConnection() {
if (mConnection != null) {
- mInputStream = null;
+ if (mInputStream != null) {
+ try {
+ mInputStream.close();
+ } catch (IOException e) {
+ }
+ mInputStream = null;
+ }
mConnection.disconnect();
mConnection = null;
@@ -298,8 +304,7 @@
mCurrentOffset = offset;
} catch (IOException e) {
mTotalSize = -1;
- mInputStream = null;
- mConnection = null;
+ teardownConnection();
mCurrentOffset = -1;
throw e;
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 5cbccea..849bd01 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1332,7 +1332,7 @@
private native int _getAudioStreamType() throws IllegalStateException;
/**
- * Stops playback after playback has been stopped or paused.
+ * Stops playback after playback has been started or paused.
*
* @throws IllegalStateException if the internal player engine has not been
* initialized.
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 91dada7..d5509c1 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -417,8 +417,6 @@
}
final ArrayList<MediaRouterClientState.RouteInfo> globalRoutes =
mClientState != null ? mClientState.routes : null;
- final String globallySelectedRouteId = mClientState != null ?
- mClientState.globallySelectedRouteId : null;
// Add or update routes.
final int globalRouteCount = globalRoutes != null ? globalRoutes.size() : 0;
@@ -433,25 +431,6 @@
}
}
- // Synchronize state with the globally selected route.
- if (globallySelectedRouteId != null) {
- final RouteInfo route = findGlobalRoute(globallySelectedRouteId);
- if (route == null) {
- Log.w(TAG, "Could not find new globally selected route: "
- + globallySelectedRouteId);
- } else if (route != mSelectedRoute) {
- if (DEBUG) {
- Log.d(TAG, "Selecting new globally selected route: " + route);
- }
- selectRouteStatic(route.mSupportedTypes, route, false);
- }
- } else if (mSelectedRoute != null && mSelectedRoute.mGlobalRouteId != null) {
- if (DEBUG) {
- Log.d(TAG, "Unselecting previous globally selected route: " + mSelectedRoute);
- }
- selectDefaultRouteStatic();
- }
-
// Remove defunct routes.
outer: for (int i = mRoutes.size(); i-- > 0; ) {
final RouteInfo route = mRoutes.get(i);
diff --git a/media/java/android/media/MediaRouterClientState.java b/media/java/android/media/MediaRouterClientState.java
index 34e18f6..7643924 100644
--- a/media/java/android/media/MediaRouterClientState.java
+++ b/media/java/android/media/MediaRouterClientState.java
@@ -34,20 +34,12 @@
*/
public final ArrayList<RouteInfo> routes;
- /**
- * The id of the current globally selected route, or null if none.
- * Globally selected routes override any other route selections that applications
- * may have made. Used for remote displays.
- */
- public String globallySelectedRouteId;
-
public MediaRouterClientState() {
routes = new ArrayList<RouteInfo>();
}
MediaRouterClientState(Parcel src) {
routes = src.createTypedArrayList(RouteInfo.CREATOR);
- globallySelectedRouteId = src.readString();
}
public RouteInfo getRoute(String id) {
@@ -69,13 +61,11 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeTypedList(routes);
- dest.writeString(globallySelectedRouteId);
}
@Override
public String toString() {
- return "MediaRouterClientState{ globallySelectedRouteId="
- + globallySelectedRouteId + ", routes=" + routes.toString() + " }";
+ return "MediaRouterClientState{ routes=" + routes.toString() + " }";
}
public static final Parcelable.Creator<MediaRouterClientState> CREATOR =
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index ddfa025..cb4e46f 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -1958,7 +1958,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mCloseGuard.warnIfOpen();
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
close();
} finally {
super.finalize();
diff --git a/media/java/android/media/midi/IMidiDeviceServer.aidl b/media/java/android/media/midi/IMidiDeviceServer.aidl
index fbd3510..59f0f23 100644
--- a/media/java/android/media/midi/IMidiDeviceServer.aidl
+++ b/media/java/android/media/midi/IMidiDeviceServer.aidl
@@ -24,12 +24,14 @@
FileDescriptor openInputPort(IBinder token, int portNumber);
FileDescriptor openOutputPort(IBinder token, int portNumber);
void closePort(IBinder token);
- void closeDevice();
+ oneway void closeDevice();
// connects the input port pfd to the specified output port
// Returns the PID of the called process.
int connectPorts(IBinder token, in FileDescriptor fd, int outputPortNumber);
MidiDeviceInfo getDeviceInfo();
- void setDeviceInfo(in MidiDeviceInfo deviceInfo);
+
+ // For use by MidiService.
+ oneway void setDeviceInfo(in MidiDeviceInfo deviceInfo);
}
diff --git a/media/java/android/media/midi/MidiDevice.java b/media/java/android/media/midi/MidiDevice.java
index f7dd0b3..a995736 100644
--- a/media/java/android/media/midi/MidiDevice.java
+++ b/media/java/android/media/midi/MidiDevice.java
@@ -103,7 +103,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mGuard.warnIfOpen();
+ if (mGuard != null) {
+ mGuard.warnIfOpen();
+ }
+
close();
} finally {
super.finalize();
@@ -285,7 +288,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mGuard.warnIfOpen();
+ if (mGuard != null) {
+ mGuard.warnIfOpen();
+ }
+
close();
} finally {
super.finalize();
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index eaa8654..51d5520 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -429,7 +429,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mGuard.warnIfOpen();
+ if (mGuard != null) {
+ mGuard.warnIfOpen();
+ }
+
close();
} finally {
super.finalize();
diff --git a/media/java/android/media/midi/MidiInputPort.java b/media/java/android/media/midi/MidiInputPort.java
index 98ec779..a300886 100644
--- a/media/java/android/media/midi/MidiInputPort.java
+++ b/media/java/android/media/midi/MidiInputPort.java
@@ -159,7 +159,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mGuard.warnIfOpen();
+ if (mGuard != null) {
+ mGuard.warnIfOpen();
+ }
+
// not safe to make binder calls from finalize()
mDeviceServer = null;
close();
diff --git a/media/java/android/media/midi/MidiOutputPort.java b/media/java/android/media/midi/MidiOutputPort.java
index ce0402c..511f6cd 100644
--- a/media/java/android/media/midi/MidiOutputPort.java
+++ b/media/java/android/media/midi/MidiOutputPort.java
@@ -145,7 +145,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mGuard.warnIfOpen();
+ if (mGuard != null) {
+ mGuard.warnIfOpen();
+ }
+
// not safe to make binder calls from finalize()
mDeviceServer = null;
close();
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index a48abff..7969ee7 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -138,7 +138,7 @@
}
/**
- * Gets the raw audio that triggered the keyphrase.
+ * Gets the raw audio that triggered the detector.
* This may be null if the trigger audio isn't available.
* If non-null, the format of the audio can be obtained by calling
* {@link #getCaptureAudioFormat()}.
@@ -155,6 +155,24 @@
}
/**
+ * Gets the opaque data passed from the detection engine for the event.
+ * This may be null if it was not populated by the engine, or if the data is known to
+ * contain the trigger audio.
+ *
+ * @see #getTriggerAudio
+ *
+ * @hide
+ */
+ @Nullable
+ public byte[] getData() {
+ if (!mTriggerAvailable) {
+ return mData;
+ } else {
+ return null;
+ }
+ }
+
+ /**
* Gets the session ID to start a capture from the DSP.
* This may be null if streaming capture isn't possible.
* If non-null, the format of the audio that can be captured can be
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index e7da20b..102e02d 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -601,10 +601,12 @@
* given time frame.
*
* @param channelId The ID of the channel to return programs for.
- * @param startTime The start time used to filter programs. The returned programs should have
- * {@link Programs#COLUMN_END_TIME_UTC_MILLIS} that is greater than this time.
- * @param endTime The end time used to filter programs. The returned programs should have
- * {@link Programs#COLUMN_START_TIME_UTC_MILLIS} that is less than this time.
+ * @param startTime The start time used to filter programs. The returned programs will have a
+ * {@link Programs#COLUMN_END_TIME_UTC_MILLIS} that is greater than or equal to
+ {@code startTime}.
+ * @param endTime The end time used to filter programs. The returned programs will have
+ * {@link Programs#COLUMN_START_TIME_UTC_MILLIS} that is less than or equal to
+ * {@code endTime}.
*/
public static Uri buildProgramsUriForChannel(long channelId, long startTime,
long endTime) {
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 7b5f778..e24124d 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -974,7 +974,7 @@
* seek to a position earlier than the start position.
*
* <p>For playback of a recorded program initiated by {@link #onTimeShiftPlay(Uri)}, the
- * start position is the time when playback starts. It does not change.
+ * start position should be 0 and does not change.
*
* @see #onTimeShiftPlay(Uri)
* @see #onTimeShiftResume()
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 56a5737..698c9c9 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -233,7 +233,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mCloseGuard.warnIfOpen();
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
close();
} finally {
super.finalize();
diff --git a/media/java/android/mtp/MtpDevice.java b/media/java/android/mtp/MtpDevice.java
index d6958b3..e8b04ed 100644
--- a/media/java/android/mtp/MtpDevice.java
+++ b/media/java/android/mtp/MtpDevice.java
@@ -123,7 +123,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mCloseGuard.warnIfOpen();
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
close();
} finally {
super.finalize();
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
new file mode 100644
index 0000000..9686ab5
--- /dev/null
+++ b/media/jni/Android.bp
@@ -0,0 +1,83 @@
+cc_library_shared {
+ name: "libmedia_jni",
+
+ srcs: [
+ "android_media_ImageWriter.cpp",
+ "android_media_ImageReader.cpp",
+ "android_media_MediaCrypto.cpp",
+ "android_media_MediaCodec.cpp",
+ "android_media_MediaCodecList.cpp",
+ "android_media_MediaDataSource.cpp",
+ "android_media_MediaDescrambler.cpp",
+ "android_media_MediaDrm.cpp",
+ "android_media_MediaExtractor.cpp",
+ "android_media_MediaHTTPConnection.cpp",
+ "android_media_MediaMetricsJNI.cpp",
+ "android_media_MediaMetadataRetriever.cpp",
+ "android_media_MediaMuxer.cpp",
+ "android_media_MediaPlayer.cpp",
+ "android_media_MediaProfiles.cpp",
+ "android_media_MediaRecorder.cpp",
+ "android_media_MediaScanner.cpp",
+ "android_media_MediaSync.cpp",
+ "android_media_ResampleInputStream.cpp",
+ "android_media_SyncParams.cpp",
+ "android_media_Utils.cpp",
+ "android_mtp_MtpDatabase.cpp",
+ "android_mtp_MtpDevice.cpp",
+ "android_mtp_MtpServer.cpp",
+ "midi/android_media_midi_MidiDevice.cpp",
+ ],
+
+ shared_libs: [
+ "libandroid_runtime",
+ "libnativehelper",
+ "libutils",
+ "libbinder",
+ "libmedia",
+ "libmediametrics",
+ "libmediadrm",
+ "libmidi",
+ "libskia",
+ "libui",
+ "liblog",
+ "libcutils",
+ "libgui",
+ "libstagefright",
+ "libstagefright_foundation",
+ "libcamera_client",
+ "libmtp",
+ "libexif",
+ "libpiex",
+ "libandroidfw",
+ ],
+
+ header_libs: ["libhardware_headers"],
+
+ static_libs: ["libgrallocusage"],
+
+ include_dirs: [
+ "frameworks/base/core/jni",
+ "frameworks/native/include/media/openmax",
+ "system/media/camera/include",
+ ],
+
+ export_include_dirs: ["."],
+
+ export_shared_lib_headers: [
+ "libpiex",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-error=deprecated-declarations",
+ "-Wunused",
+ "-Wunreachable-code",
+ ],
+}
+
+subdirs = [
+ "audioeffect",
+ "soundpool",
+]
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
deleted file mode 100644
index c2ed8cf..0000000
--- a/media/jni/Android.mk
+++ /dev/null
@@ -1,79 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
- android_media_ImageWriter.cpp \
- android_media_ImageReader.cpp \
- android_media_MediaCrypto.cpp \
- android_media_MediaCodec.cpp \
- android_media_MediaCodecList.cpp \
- android_media_MediaDataSource.cpp \
- android_media_MediaDescrambler.cpp \
- android_media_MediaDrm.cpp \
- android_media_MediaExtractor.cpp \
- android_media_MediaHTTPConnection.cpp \
- android_media_MediaMetricsJNI.cpp \
- android_media_MediaMetadataRetriever.cpp \
- android_media_MediaMuxer.cpp \
- android_media_MediaPlayer.cpp \
- android_media_MediaProfiles.cpp \
- android_media_MediaRecorder.cpp \
- android_media_MediaScanner.cpp \
- android_media_MediaSync.cpp \
- android_media_ResampleInputStream.cpp \
- android_media_SyncParams.cpp \
- android_media_Utils.cpp \
- android_mtp_MtpDatabase.cpp \
- android_mtp_MtpDevice.cpp \
- android_mtp_MtpServer.cpp \
- midi/android_media_midi_MidiDevice.cpp \
-
-LOCAL_SHARED_LIBRARIES := \
- libandroid_runtime \
- libnativehelper \
- libutils \
- libbinder \
- libmedia \
- libmediametrics \
- libmediadrm \
- libmidi \
- libskia \
- libui \
- liblog \
- libcutils \
- libgui \
- libstagefright \
- libstagefright_foundation \
- libcamera_client \
- libmtp \
- libexif \
- libpiex \
- libandroidfw
-
-LOCAL_STATIC_LIBRARIES := \
- libgrallocusage \
-
-LOCAL_C_INCLUDES += \
- external/libexif/ \
- external/piex/ \
- external/tremor/Tremor \
- frameworks/base/core/jni \
- frameworks/base/libs/hwui \
- frameworks/base/media/native \
- frameworks/av/media/libmedia \
- frameworks/av/media/libstagefright \
- frameworks/av/media/mtp \
- frameworks/native/include/media/openmax \
- $(call include-path-for, libhardware)/hardware \
- $(PV_INCLUDES) \
- $(JNI_H_INCLUDE)
-
-LOCAL_CFLAGS += -Wall -Werror -Wno-error=deprecated-declarations -Wunused -Wunreachable-code
-
-LOCAL_MODULE:= libmedia_jni
-
-include $(BUILD_SHARED_LIBRARY)
-
-# build libsoundpool.so
-# build libaudioeffect_jni.so
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 163c4b0..52fadfa 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -27,7 +27,6 @@
#include <gui/BufferItemConsumer.h>
#include <gui/Surface.h>
-#include <camera3.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_view_Surface.h>
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index b5ea632..7c424d9 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -25,7 +25,6 @@
#include <gui/Surface.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_view_Surface.h>
-#include <camera3.h>
#include <jni.h>
#include <JNIHelp.h>
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index f4e940d..0a3ce21 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -302,13 +302,11 @@
SkBitmap bitmap;
GraphicsJNI::getSkBitmap(env, jBitmap, &bitmap);
- bitmap.lockPixels();
rotate((uint16_t*)bitmap.getPixels(),
(uint16_t*)((char*)videoFrame + sizeof(VideoFrame)),
videoFrame->mWidth,
videoFrame->mHeight,
videoFrame->mRotationAngle);
- bitmap.unlockPixels();
if (videoFrame->mDisplayWidth != videoFrame->mWidth ||
videoFrame->mDisplayHeight != videoFrame->mHeight) {
diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp
index c62d930..458d847 100644
--- a/media/jni/android_media_Utils.cpp
+++ b/media/jni/android_media_Utils.cpp
@@ -17,6 +17,7 @@
// #define LOG_NDEBUG 0
#define LOG_TAG "AndroidMediaUtils"
+#include <hardware/camera3.h>
#include <utils/Log.h>
#include "android_media_Utils.h"
diff --git a/media/jni/android_media_Utils.h b/media/jni/android_media_Utils.h
index 39c1554..af2f2d7 100644
--- a/media/jni/android_media_Utils.h
+++ b/media/jni/android_media_Utils.h
@@ -21,7 +21,6 @@
#include "src/piex.h"
#include <android_runtime/AndroidRuntime.h>
-#include <camera3.h>
#include <gui/CpuConsumer.h>
#include <jni.h>
#include <JNIHelp.h>
diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp
new file mode 100644
index 0000000..2aca0c1
--- /dev/null
+++ b/media/jni/audioeffect/Android.bp
@@ -0,0 +1,25 @@
+cc_library_shared {
+ name: "libaudioeffect_jni",
+
+ srcs: [
+ "android_media_AudioEffect.cpp",
+ "android_media_Visualizer.cpp",
+ ],
+
+ shared_libs: [
+ "liblog",
+ "libcutils",
+ "libutils",
+ "libandroid_runtime",
+ "libnativehelper",
+ "libmedia",
+ "libaudioclient",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wunused",
+ "-Wunreachable-code",
+ ],
+}
diff --git a/media/jni/audioeffect/Android.mk b/media/jni/audioeffect/Android.mk
deleted file mode 100644
index 777636b..0000000
--- a/media/jni/audioeffect/Android.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
- android_media_AudioEffect.cpp \
- android_media_Visualizer.cpp
-
-LOCAL_SHARED_LIBRARIES := \
- liblog \
- libcutils \
- libutils \
- libandroid_runtime \
- libnativehelper \
- libmedia \
- libaudioclient \
-
-LOCAL_MODULE:= libaudioeffect_jni
-
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/media/jni/midi/android_media_midi_MidiDevice.cpp b/media/jni/midi/android_media_midi_MidiDevice.cpp
index 3743e4a..4df8436 100644
--- a/media/jni/midi/android_media_midi_MidiDevice.cpp
+++ b/media/jni/midi/android_media_midi_MidiDevice.cpp
@@ -18,7 +18,7 @@
#define LOG_TAG "Midi-JNI"
#include <android_util_Binder.h>
-#include <midi/midi_internal.h>
+#include <midi_internal.h>
#include <nativehelper/jni.h>
#include <utils/Log.h>
diff --git a/media/jni/soundpool/Android.bp b/media/jni/soundpool/Android.bp
new file mode 100644
index 0000000..35b7b01
--- /dev/null
+++ b/media/jni/soundpool/Android.bp
@@ -0,0 +1,28 @@
+cc_library_shared {
+ name: "libsoundpool",
+
+ srcs: [
+ "android_media_SoundPool.cpp",
+ "SoundPool.cpp",
+ "SoundPoolThread.cpp",
+ ],
+
+ shared_libs: [
+ "liblog",
+ "libcutils",
+ "libutils",
+ "libandroid_runtime",
+ "libnativehelper",
+ "libaudioclient",
+ "libmediandk",
+ "libbinder",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-error=deprecated-declarations",
+ "-Wunused",
+ "-Wunreachable-code",
+ ],
+}
diff --git a/media/jni/soundpool/Android.mk b/media/jni/soundpool/Android.mk
deleted file mode 100644
index 509a59b..0000000
--- a/media/jni/soundpool/Android.mk
+++ /dev/null
@@ -1,23 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
- android_media_SoundPool.cpp \
- SoundPool.cpp \
- SoundPoolThread.cpp
-
-LOCAL_SHARED_LIBRARIES := \
- liblog \
- libcutils \
- libutils \
- libandroid_runtime \
- libnativehelper \
- libaudioclient \
- libmediandk \
- libbinder
-
-LOCAL_MODULE:= libsoundpool
-
-LOCAL_CFLAGS += -Wall -Werror -Wno-error=deprecated-declarations -Wunused -Wunreachable-code
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 87092d0..5f1cf16 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -30,9 +30,9 @@
#include "SoundPool.h"
#include "SoundPoolThread.h"
#include <media/AudioPolicyHelper.h>
-#include <ndk/NdkMediaCodec.h>
-#include <ndk/NdkMediaExtractor.h>
-#include <ndk/NdkMediaFormat.h>
+#include <media/NdkMediaCodec.h>
+#include <media/NdkMediaExtractor.h>
+#include <media/NdkMediaFormat.h>
namespace android
{
diff --git a/media/jni/soundpool/SoundPoolThread.cpp b/media/jni/soundpool/SoundPoolThread.cpp
index ba3b482..3d98877 100644
--- a/media/jni/soundpool/SoundPoolThread.cpp
+++ b/media/jni/soundpool/SoundPoolThread.cpp
@@ -20,6 +20,8 @@
#include "SoundPoolThread.h"
+static const int kMaxWorkers = 3;
+
namespace android {
void SoundPoolThread::write(SoundPoolMsg msg) {
@@ -31,14 +33,21 @@
// if thread is quitting, don't add to queue
if (mRunning) {
mMsgQueue.push(msg);
- mCondition.signal();
+ if (mNumWorkers < kMaxWorkers) {
+ if (createThreadEtc(beginThread, this, "SoundPoolThread")) {
+ mNumWorkers++;
+ ALOGV("created worker thread");
+ }
+ }
}
}
const SoundPoolMsg SoundPoolThread::read() {
Mutex::Autolock lock(&mLock);
- while (mMsgQueue.size() == 0) {
- mCondition.wait(mLock);
+ if (mMsgQueue.size() == 0) {
+ mNumWorkers--;
+ mCondition.signal();
+ return SoundPoolMsg(SoundPoolMsg::KILL, 0);
}
SoundPoolMsg msg = mMsgQueue[0];
mMsgQueue.removeAt(0);
@@ -51,20 +60,20 @@
if (mRunning) {
mRunning = false;
mMsgQueue.clear();
- mMsgQueue.push(SoundPoolMsg(SoundPoolMsg::KILL, 0));
- mCondition.signal();
- mCondition.wait(mLock);
+ mCondition.broadcast(); // wake up any blocked writers
+ while (mNumWorkers > 0) {
+ mCondition.wait(mLock);
+ }
}
ALOGV("return from quit");
}
SoundPoolThread::SoundPoolThread(SoundPool* soundPool) :
- mSoundPool(soundPool)
+ mSoundPool(soundPool),
+ mNumWorkers(0),
+ mRunning(true)
{
mMsgQueue.setCapacity(maxMessages);
- if (createThreadEtc(beginThread, this, "SoundPoolThread")) {
- mRunning = true;
- }
}
SoundPoolThread::~SoundPoolThread()
diff --git a/media/jni/soundpool/SoundPoolThread.h b/media/jni/soundpool/SoundPoolThread.h
index 7b3e1dd..5d7bf0c 100644
--- a/media/jni/soundpool/SoundPoolThread.h
+++ b/media/jni/soundpool/SoundPoolThread.h
@@ -58,6 +58,7 @@
Condition mCondition;
Vector<SoundPoolMsg> mMsgQueue;
SoundPool* mSoundPool;
+ int32_t mNumWorkers;
bool mRunning;
};
diff --git a/media/native/Android.bp b/media/native/Android.bp
new file mode 100644
index 0000000..b44c296
--- /dev/null
+++ b/media/native/Android.bp
@@ -0,0 +1 @@
+subdirs = ["*"]
diff --git a/media/native/midi/Android.bp b/media/native/midi/Android.bp
index 3500805..11f0dea 100644
--- a/media/native/midi/Android.bp
+++ b/media/native/midi/Android.bp
@@ -12,10 +12,31 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// The headers module is in frameworks/media/native/midi/Android.bp.
-ndk_library {
- name: "libmidi.ndk",
- symbol_file: "libmidi.map.txt",
- first_version: "26",
-// unversioned_until: "current",
+cc_library_shared {
+ name: "libmidi",
+
+ srcs: [
+ "midi.cpp",
+ ":IMidiDeviceServer.aidl",
+ ],
+
+ aidl: {
+ include_dirs: ["frameworks/base/media/java"],
+ export_aidl_headers: true,
+ },
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-O0",
+ ],
+
+ shared_libs: [
+ "liblog",
+ "libbinder",
+ "libutils",
+ "libmedia",
+ ],
+
+ export_include_dirs: ["."],
}
diff --git a/media/native/midi/Android.mk b/media/native/midi/Android.mk
deleted file mode 100644
index dbc5eed..0000000
--- a/media/native/midi/Android.mk
+++ /dev/null
@@ -1,20 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
- ../../java/android/media/midi/IMidiDeviceServer.aidl \
- midi.cpp
-
-LOCAL_AIDL_INCLUDES := \
- $(FRAMEWORKS_BASE_JAVA_SRC_DIRS) \
- frameworks/native/aidl/binder
-
-LOCAL_CFLAGS += -Wall -Werror -O0
-
-LOCAL_MODULE := libmidi
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SHARED_LIBRARIES := liblog libbinder libutils libmedia
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java
index 6678f8b..5680d9f 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java
@@ -1139,9 +1139,18 @@
}
List<Integer> modeList = new ArrayList<Integer>();
for (int mode : modes) {
- modeList.add(mode);
+ // Skip vendor-added modes
+ if (mode <= CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE) {
+ modeList.add(mode);
+ }
}
checkTrueForKey(modesKey, "value is empty", !modeList.isEmpty());
+ modes = new int[modeList.size()];
+ for (int i = 0; i < modeList.size(); i++) {
+ modes[i] = modeList.get(i);
+ }
+
+ checkTrueForKey(modesKey, "value is empty", !modeList.isEmpty());
// All camera device must support ON
checkTrueForKey(modesKey, "values " + modeList.toString() + " must contain ON mode",
@@ -1227,7 +1236,17 @@
return new int[0];
}
- List<Integer> modesList = Arrays.asList(CameraTestUtils.toObject(afModes));
+ List<Integer> modesList = new ArrayList<Integer>();
+ for (int afMode : afModes) {
+ // Skip vendor-added AF modes
+ if (afMode > CameraCharacteristics.CONTROL_AF_MODE_EDOF) continue;
+ modesList.add(afMode);
+ }
+ afModes = new int[modesList.size()];
+ for (int i = 0; i < modesList.size(); i++) {
+ afModes[i] = modesList.get(i);
+ }
+
if (isHardwareLevelLimitedOrBetter()) {
// Some LEGACY mode devices do not support AF OFF
checkTrueForKey(key, " All camera devices must support OFF mode",
@@ -1417,6 +1436,16 @@
return fpsRanges;
}
+ public static String getAeModeName(int aeMode) {
+ return (aeMode >= AE_MODE_NAMES.length) ? String.format("VENDOR_AE_MODE_%d", aeMode) :
+ AE_MODE_NAMES[aeMode];
+ }
+
+ public static String getAfModeName(int afMode) {
+ return (afMode >= AF_MODE_NAMES.length) ? String.format("VENDOR_AF_MODE_%d", afMode) :
+ AF_MODE_NAMES[afMode];
+ }
+
/**
* Get the highest supported target FPS range.
* Prioritizes maximizing the min FPS, then the max FPS without lowering min FPS.
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 14f6ed3..00fe638 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -30,6 +30,53 @@
],
}
+cc_library_shared {
+ name: "libandroid",
+ defaults: ["libandroid_defaults"],
+
+ srcs: [
+ "asset_manager.cpp",
+ "choreographer.cpp",
+ "configuration.cpp",
+ "hardware_buffer_jni.cpp",
+ "input.cpp",
+ "looper.cpp",
+ "native_activity.cpp",
+ "native_window_jni.cpp",
+ "net.c",
+ "obb.cpp",
+ "sensor.cpp",
+ "sharedmem.cpp",
+ "storage_manager.cpp",
+ "trace.cpp",
+ ],
+
+ shared_libs: [
+ "liblog",
+ "libcutils",
+ "libandroidfw",
+ "libinput",
+ "libutils",
+ "libbinder",
+ "libui",
+ "libgui",
+ "libsensor",
+ "libandroid_runtime",
+ "libnetd_client",
+ ],
+
+ static_libs: [
+ "libstorage",
+ "libarect",
+ ],
+
+ whole_static_libs: ["libnativewindow"],
+
+ export_static_lib_headers: ["libarect"],
+
+ include_dirs: ["bionic/libc/dns/include"],
+}
+
// Network library.
cc_library_shared {
name: "libandroid_net",
diff --git a/native/android/Android.mk b/native/android/Android.mk
deleted file mode 100644
index 97dbf0f..0000000
--- a/native/android/Android.mk
+++ /dev/null
@@ -1,60 +0,0 @@
-BASE_PATH := $(call my-dir)
-LOCAL_PATH:= $(call my-dir)
-
-common_cflags := -Wall -Werror -Wunused -Wunreachable-code
-
-include $(CLEAR_VARS)
-
-# our source files
-#
-LOCAL_SRC_FILES:= \
- asset_manager.cpp \
- choreographer.cpp \
- configuration.cpp \
- hardware_buffer_jni.cpp \
- input.cpp \
- looper.cpp \
- native_activity.cpp \
- native_window_jni.cpp \
- net.c \
- obb.cpp \
- sensor.cpp \
- sharedmem.cpp \
- storage_manager.cpp \
- trace.cpp \
-
-LOCAL_SHARED_LIBRARIES := \
- liblog \
- libcutils \
- libandroidfw \
- libinput \
- libutils \
- libbinder \
- libui \
- libgui \
- libsensor \
- libandroid_runtime \
- libnetd_client \
-
-LOCAL_STATIC_LIBRARIES := \
- libstorage \
- libarect \
-
-LOCAL_WHOLE_STATIC_LIBRARIES := \
- libnativewindow
-
-LOCAL_C_INCLUDES += \
- frameworks/base/native/include \
- frameworks/base/core/jni/android \
- bionic/libc/dns/include \
- system/netd/include \
-
-LOCAL_EXPORT_STATIC_LIBRARY_HEADERS := \
- libarect \
- libnativewindow \
-
-LOCAL_MODULE := libandroid
-
-LOCAL_CFLAGS += $(common_cflags)
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/nfc-extras/Android.mk b/nfc-extras/Android.mk
index cd7a45b..dc45a50 100644
--- a/nfc-extras/Android.mk
+++ b/nfc-extras/Android.mk
@@ -4,10 +4,10 @@
LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+LOCAL_SRC_FILES := $(call all-java-files-under, java)
LOCAL_MODULE:= com.android.nfc_extras
include $(BUILD_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/nfc-extras/tests/Android.mk b/nfc-extras/tests/Android.mk
index 3eca76d..d8fe5a6 100644
--- a/nfc-extras/tests/Android.mk
+++ b/nfc-extras/tests/Android.mk
@@ -22,6 +22,8 @@
android.test.runner \
com.android.nfc_extras
+LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
+
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/obex/javax/obex/ApplicationParameter.java b/obex/javax/obex/ApplicationParameter.java
index a62210f..16770a1a 100644
--- a/obex/javax/obex/ApplicationParameter.java
+++ b/obex/javax/obex/ApplicationParameter.java
@@ -55,7 +55,7 @@
public static final byte LISTSTARTOFFSET_TAGID = 0x05;
- public static final byte FILTER_TAGID = 0x06;
+ public static final byte PROPERTY_SELECTOR_TAGID = 0x06;
public static final byte FORMAT_TAGID = 0x07;
@@ -64,6 +64,20 @@
// only used in "mch" in response
public static final byte NEWMISSEDCALLS_TAGID = 0x09;
+
+ public static final byte SUPPORTEDFEATURE_TAGID = 0x10;
+
+ public static final byte PRIMARYVERSIONCOUNTER_TAGID = 0x0A;
+
+ public static final byte SECONDARYVERSIONCOUNTER_TAGID = 0x0B;
+
+ public static final byte VCARDSELECTOR_TAGID = 0x0C;
+
+ public static final byte DATABASEIDENTIFIER_TAGID = 0x0D;
+
+ public static final byte VCARDSELECTOROPERATOR_TAGID = 0x0E;
+
+ public static final byte RESET_NEW_MISSED_CALLS_TAGID = 0x0F;
}
public static class TRIPLET_VALUE {
@@ -99,13 +113,27 @@
public static final byte LISTSTARTOFFSET_LENGTH = 2;
- public static final byte FILTER_LENGTH = 8;
+ public static final byte PROPERTY_SELECTOR_LENGTH = 8;
public static final byte FORMAT_LENGTH = 1;
public static final byte PHONEBOOKSIZE_LENGTH = 2;
public static final byte NEWMISSEDCALLS_LENGTH = 1;
+
+ public static final byte SUPPORTEDFEATURE_LENGTH = 4;
+
+ public static final byte PRIMARYVERSIONCOUNTER_LENGTH = 16;
+
+ public static final byte SECONDARYVERSIONCOUNTER_LENGTH = 16;
+
+ public static final byte VCARDSELECTOR_LENGTH = 8;
+
+ public static final byte DATABASEIDENTIFIER_LENGTH = 16;
+
+ public static final byte VCARDSELECTOROPERATOR_LENGTH = 1;
+
+ public static final byte RESETNEWMISSEDCALLS_LENGTH = 1;
}
public ApplicationParameter() {
diff --git a/opengl/java/com/google/android/gles_jni/EGLSurfaceImpl.java b/opengl/java/com/google/android/gles_jni/EGLSurfaceImpl.java
index 7a3ed24..21c350be 100644
--- a/opengl/java/com/google/android/gles_jni/EGLSurfaceImpl.java
+++ b/opengl/java/com/google/android/gles_jni/EGLSurfaceImpl.java
@@ -20,14 +20,11 @@
public class EGLSurfaceImpl extends EGLSurface {
long mEGLSurface;
- private long mNativePixelRef;
public EGLSurfaceImpl() {
mEGLSurface = 0;
- mNativePixelRef = 0;
}
public EGLSurfaceImpl(long surface) {
mEGLSurface = surface;
- mNativePixelRef = 0;
}
@Override
diff --git a/packages/BackupRestoreConfirmation/res/values-bs/strings.xml b/packages/BackupRestoreConfirmation/res/values-bs/strings.xml
index 53b0bce..e4852ed 100644
--- a/packages/BackupRestoreConfirmation/res/values-bs/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-bs/strings.xml
@@ -17,23 +17,23 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="backup_confirm_title" msgid="827563724209303345">"Napraviti potpunu rezervnu kopiju"</string>
- <string name="restore_confirm_title" msgid="5469365809567486602">"Izvrši potpuno obnavljanje"</string>
+ <string name="restore_confirm_title" msgid="5469365809567486602">"Izvrši potpuno vraćanje"</string>
<string name="backup_confirm_text" msgid="1878021282758896593">"Zatraženo je pravljenje potpune rezervne kopije svih podataka na povezani računar. Da li želite da dozvolite to?\n\nPrekinite radnju ukoliko to niste sami zatražili."</string>
- <string name="allow_backup_button_label" msgid="4217228747769644068">"Napravi rezervnu kopiju mojih podataka"</string>
+ <string name="allow_backup_button_label" msgid="4217228747769644068">"Napravi sigurnosnu kopiju mojih podataka"</string>
<string name="deny_backup_button_label" msgid="6009119115581097708">"Nemoj napraviti rezervnu kopiju"</string>
<string name="restore_confirm_text" msgid="7499866728030461776">"Sa povezanog računara je upućen zahtjev za potpuno obnavljanje svih podataka. Želite li to dozvoliti?\n\nUkoliko niste uputili zahtjev za obnavljanje, prekinite radnju. Ovim će zamijeniti svi podaci koji su trenutno na uređaju!"</string>
- <string name="allow_restore_button_label" msgid="3081286752277127827">"Obnovi moje podatke"</string>
- <string name="deny_restore_button_label" msgid="1724367334453104378">"Prekinuti obnavljanje podataka"</string>
- <string name="current_password_text" msgid="8268189555578298067">"Ispod unesite svoju trenutnu lozinku za rezervnu kopiju:"</string>
+ <string name="allow_restore_button_label" msgid="3081286752277127827">"Vrati moje podatke"</string>
+ <string name="deny_restore_button_label" msgid="1724367334453104378">"Ne vraćaj"</string>
+ <string name="current_password_text" msgid="8268189555578298067">"Ispod unesite svoju trenutnu lozinku za sigurnosnu kopiju:"</string>
<string name="device_encryption_restore_text" msgid="1570864916855208992">"Ispod unesite svoju lozinku za šifriranje uređaja."</string>
- <string name="device_encryption_backup_text" msgid="5866590762672844664">"Molimo vas unesite svoju lozinku za šifriranje uređaja ispod. Ona će se koristiti i za šifriranje arhive rezervnih kopija."</string>
- <string name="backup_enc_password_text" msgid="4981585714795233099">"Unesite lozinku za šifriranje potpune rezervne kopije podataka. Ukoliko ne unesete lozinku, primijenit će se vaša trenutna lozinka za rezervnu kopiju:"</string>
- <string name="backup_enc_password_optional" msgid="1350137345907579306">"Ukoliko želite šifrirati potpunu rezervnu kopiju podataka, unesite lozinku ispod:"</string>
- <string name="backup_enc_password_required" msgid="7889652203371654149">"Pošto je vaš uređaj šifriran, potrebno je šifrirati rezervnu kopiju. Unesite šifru ispod:"</string>
- <string name="restore_enc_password_text" msgid="6140898525580710823">"Ukoliko su podaci za obnavljanje šifrirani, unesite lozinku ispod:"</string>
+ <string name="device_encryption_backup_text" msgid="5866590762672844664">"Molimo vas unesite svoju lozinku za šifriranje uređaja ispod. Ona će se koristiti i za šifriranje arhive sigurnosnih kopija."</string>
+ <string name="backup_enc_password_text" msgid="4981585714795233099">"Unesite lozinku za šifriranje potpune sigurnosne kopije podataka. Ukoliko ne unesete lozinku, primijenit će se vaša trenutna lozinka za sigurnosnu kopiju:"</string>
+ <string name="backup_enc_password_optional" msgid="1350137345907579306">"Ukoliko želite šifrirati potpunu sigurnosnu kopiju podataka, unesite lozinku ispod:"</string>
+ <string name="backup_enc_password_required" msgid="7889652203371654149">"Pošto je vaš uređaj šifriran, potrebno je šifrirati sigurnosnu kopiju. Unesite šifru ispod:"</string>
+ <string name="restore_enc_password_text" msgid="6140898525580710823">"Ukoliko su podaci za vraćanje šifrirani, unesite lozinku ispod:"</string>
<string name="toast_backup_started" msgid="550354281452756121">"Pravljenje rezervne kopije..."</string>
<string name="toast_backup_ended" msgid="3818080769548726424">"Pravljenje rezervne kopije završeno"</string>
- <string name="toast_restore_started" msgid="7881679218971277385">"Počinje obnavljanje podataka..."</string>
- <string name="toast_restore_ended" msgid="1764041639199696132">"Obnavljanje podatka završeno"</string>
+ <string name="toast_restore_started" msgid="7881679218971277385">"Pokretanje vraćanja..."</string>
+ <string name="toast_restore_ended" msgid="1764041639199696132">"Vraćanje završeno"</string>
<string name="toast_timeout" msgid="5276598587087626877">"Isteklo je vrijeme za izvršenje radnje"</string>
</resources>
diff --git a/packages/BackupRestoreConfirmation/res/values-ca/strings.xml b/packages/BackupRestoreConfirmation/res/values-ca/strings.xml
index 3326ccb..c7c8346 100644
--- a/packages/BackupRestoreConfirmation/res/values-ca/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-ca/strings.xml
@@ -19,7 +19,7 @@
<string name="backup_confirm_title" msgid="827563724209303345">"Còpia de seguretat completa"</string>
<string name="restore_confirm_title" msgid="5469365809567486602">"Restaura completament"</string>
<string name="backup_confirm_text" msgid="1878021282758896593">"S\'ha sol·licitat una còpia de seguretat completa de totes les dades a un equip de sobretaula connectat. Vols permetre que això passi?\n \nSi no has sol·licitat la còpia de seguretat tu mateix, no permetis que continuï l\'operació."</string>
- <string name="allow_backup_button_label" msgid="4217228747769644068">"Còpia de seguretat de dades"</string>
+ <string name="allow_backup_button_label" msgid="4217228747769644068">"Còpia de seguretat de les meves dades"</string>
<string name="deny_backup_button_label" msgid="6009119115581097708">"No en facis una còpia de seguretat"</string>
<string name="restore_confirm_text" msgid="7499866728030461776">"S\'ha sol·licitat una restauració completa de totes les dades d\'un equip d\'escriptori connectat. Vols permetre que això passi?\n \nSi no has sol·licitat la restauració tu mateix, no permetis que continuï l\'operació. Això reemplaçarà les dades que hi hagi actualment a l\'equip."</string>
<string name="allow_restore_button_label" msgid="3081286752277127827">"Restaura les meves dades"</string>
diff --git a/packages/BackupRestoreConfirmation/res/values-es/strings.xml b/packages/BackupRestoreConfirmation/res/values-es/strings.xml
index ac0836d..58930dc 100644
--- a/packages/BackupRestoreConfirmation/res/values-es/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-es/strings.xml
@@ -19,7 +19,7 @@
<string name="backup_confirm_title" msgid="827563724209303345">"Copia de seguridad completa"</string>
<string name="restore_confirm_title" msgid="5469365809567486602">"Restauración completa"</string>
<string name="backup_confirm_text" msgid="1878021282758896593">"Se ha solicitado una copia de seguridad completa de todos los datos en un ordenador conectado. ¿Quieres permitir la copia de seguridad?\n\nNo debes permitir la copia de seguridad si no has realizado tú la solicitud."</string>
- <string name="allow_backup_button_label" msgid="4217228747769644068">"Copia de seguridad de datos"</string>
+ <string name="allow_backup_button_label" msgid="4217228747769644068">"Copia de seguridad de mis datos"</string>
<string name="deny_backup_button_label" msgid="6009119115581097708">"No hacer copia de seguridad"</string>
<string name="restore_confirm_text" msgid="7499866728030461776">"Se ha solicitado una restauración completa de todos los datos desde un ordenador conectado. ¿Quieres permitir la restauración?\n\nNo debes permitir la restauración si no has realizado tú la solicitud. Se sustituirán los datos actuales del dispositivo."</string>
<string name="allow_restore_button_label" msgid="3081286752277127827">"Restaurar mis datos"</string>
diff --git a/packages/BackupRestoreConfirmation/res/values-gl/strings.xml b/packages/BackupRestoreConfirmation/res/values-gl/strings.xml
index c973a31..0a12aa7 100644
--- a/packages/BackupRestoreConfirmation/res/values-gl/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-gl/strings.xml
@@ -24,10 +24,10 @@
<string name="restore_confirm_text" msgid="7499866728030461776">"Solicitouse unha restauración de todos os datos desde un ordenador de escritorio conectado. Queres permitir esta operación?\n\nSe non o solicitaches ti, non permitas que se realice. Substituiranse todos os datos que conteña o dispositivo."</string>
<string name="allow_restore_button_label" msgid="3081286752277127827">"Restaurar os meus datos"</string>
<string name="deny_restore_button_label" msgid="1724367334453104378">"Non restaurar"</string>
- <string name="current_password_text" msgid="8268189555578298067">"Insire o contrasinal de copia de seguranza actual a continuación:"</string>
+ <string name="current_password_text" msgid="8268189555578298067">"Insire o contrasinal da copia de seguranza actual a continuación:"</string>
<string name="device_encryption_restore_text" msgid="1570864916855208992">"Insire o contrasinal de encriptación do teu dispositivo a continuación."</string>
<string name="device_encryption_backup_text" msgid="5866590762672844664">"Insire o contrasinal de encriptación do dispositivo a continuación. Tamén se usará para encriptar o arquivo de copia de seguranza."</string>
- <string name="backup_enc_password_text" msgid="4981585714795233099">"Insire un contrasinal para encriptar os datos da copia de seguranza completa. Se queda en branco este campo, usarase o contrasinal de copia de seguranza actual."</string>
+ <string name="backup_enc_password_text" msgid="4981585714795233099">"Insire un contrasinal para encriptar os datos da copia de seguranza completa. Se queda en branco este campo, usarase o contrasinal da copia de seguranza actual."</string>
<string name="backup_enc_password_optional" msgid="1350137345907579306">"Se queres encriptar os datos da copia de seguranza completa, insire un contrasinal a continuación:"</string>
<string name="backup_enc_password_required" msgid="7889652203371654149">"Como o teu dispositivo está cifrado, debes cifrar a túa copia de seguranza. Introduce un contrasinal a continuación:"</string>
<string name="restore_enc_password_text" msgid="6140898525580710823">"Se os datos de restauración están encriptados, insire o contrasinal a continuación:"</string>
diff --git a/packages/BackupRestoreConfirmation/res/values-iw/strings.xml b/packages/BackupRestoreConfirmation/res/values-iw/strings.xml
index 4c13c73..3ad7c96 100644
--- a/packages/BackupRestoreConfirmation/res/values-iw/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-iw/strings.xml
@@ -26,7 +26,7 @@
<string name="deny_restore_button_label" msgid="1724367334453104378">"אל תשחזר"</string>
<string name="current_password_text" msgid="8268189555578298067">"הזן את סיסמת הגיבוי הנוכחית למטה:"</string>
<string name="device_encryption_restore_text" msgid="1570864916855208992">"הזן את סיסמת ההצפנה של המכשיר שלך בהמשך."</string>
- <string name="device_encryption_backup_text" msgid="5866590762672844664">"הזן את סיסמת ההצפנה של המכשיר שלך בהמשך. הסיסמה תשמש גם להצפנת ארכיון הגיבוי."</string>
+ <string name="device_encryption_backup_text" msgid="5866590762672844664">"עליך להזין את סיסמת ההצפנה של המכשיר שלך בהמשך. הסיסמה תשמש גם להצפנת ארכיון הגיבוי."</string>
<string name="backup_enc_password_text" msgid="4981585714795233099">"הזן סיסמה שתשמש להצפנה של נתוני הגיבוי המלא. אם תשאיר שדה זה ריק, ייעשה שימוש בסיסמת הגיבוי הנוכחית שלך:"</string>
<string name="backup_enc_password_optional" msgid="1350137345907579306">"אם אתה רוצה להצפין את נתוני הגיבוי המלא, הזן סיסמה בהמשך:"</string>
<string name="backup_enc_password_required" msgid="7889652203371654149">"מכיוון שהמכשיר מוצפן, אתה נדרש להצפין את הגיבוי. הזן סיסמה בהמשך:"</string>
diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index fa5c3ff..65a7cec 100644
--- a/packages/CarrierDefaultApp/res/values/strings.xml
+++ b/packages/CarrierDefaultApp/res/values/strings.xml
@@ -6,6 +6,8 @@
<string name="no_data_notification_id">Your mobile data has been deactivated</string>
<string name="portal_notification_detail">Tap to visit the %s website</string>
<string name="no_data_notification_detail">Please contact your service provider %s</string>
+ <string name="no_mobile_data_connection_title">No mobile data connection</string>
+ <string name="no_mobile_data_connection">Add data or roaming plan through %s</string>
<string name="mobile_data_status_notification_channel_name">Mobile data status</string>
<string name="action_bar_label">Sign in to mobile network</string>
<string name="ssl_error_warning">The network you’re trying to join has security issues.</string>
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/ServiceIntentSender.java b/packages/MtpDocumentsProvider/src/com/android/mtp/ServiceIntentSender.java
index fe79266..629f6df 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/ServiceIntentSender.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/ServiceIntentSender.java
@@ -23,6 +23,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
/**
@@ -30,7 +31,7 @@
*/
class ServiceIntentSender {
private final static String CHANNEL_ID = "device_notification_channel";
- private Context mContext;
+ private final Context mContext;
ServiceIntentSender(Context context) {
mContext = context;
@@ -46,6 +47,11 @@
notificationManager.createNotificationChannel(mChannel);
}
+ @VisibleForTesting
+ protected ServiceIntentSender() {
+ mContext = null;
+ }
+
/**
* Notify the change of opened device set.
* @param records List of opened devices. Can be empty.
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestServiceIntentSender.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestServiceIntentSender.java
index ed2dc38..17b3086 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestServiceIntentSender.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestServiceIntentSender.java
@@ -17,10 +17,6 @@
package com.android.mtp;
class TestServiceIntentSender extends ServiceIntentSender {
- TestServiceIntentSender() {
- super(null);
- }
-
@Override
void sendUpdateNotificationIntent(MtpDeviceRecord[] record) {}
}
diff --git a/packages/PrintSpooler/res/values-fr-rCA/strings.xml b/packages/PrintSpooler/res/values-fr-rCA/strings.xml
index 9329d2a..8e12525 100644
--- a/packages/PrintSpooler/res/values-fr-rCA/strings.xml
+++ b/packages/PrintSpooler/res/values-fr-rCA/strings.xml
@@ -49,7 +49,7 @@
<string name="print_options_collapsed" msgid="7455930445670414332">"Options d\'impression réduites"</string>
<string name="search" msgid="5421724265322228497">"Rechercher"</string>
<string name="all_printers_label" msgid="3178848870161526399">"Toutes les imprimantes"</string>
- <string name="add_print_service_label" msgid="5356702546188981940">"Ajouter le service"</string>
+ <string name="add_print_service_label" msgid="5356702546188981940">"Ajouter un service"</string>
<string name="print_search_box_shown_utterance" msgid="7967404953901376090">"Champ de recherche affiché"</string>
<string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Champ de recherche masqué"</string>
<string name="print_add_printer" msgid="1088656468360653455">"Ajouter une imprimante"</string>
diff --git a/packages/PrintSpooler/res/values-in/strings.xml b/packages/PrintSpooler/res/values-in/strings.xml
index 1e536f1..9a31f74 100644
--- a/packages/PrintSpooler/res/values-in/strings.xml
+++ b/packages/PrintSpooler/res/values-in/strings.xml
@@ -32,7 +32,7 @@
<string name="template_page_range" msgid="428638530038286328">"Rentang dari <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
<string name="pages_range_example" msgid="8558694453556945172">"misalnya 1—5,8,11—13"</string>
<string name="print_preview" msgid="8010217796057763343">"Pratinjau cetak"</string>
- <string name="install_for_print_preview" msgid="6366303997385509332">"Pasang penampil PDF untuk pratinjau"</string>
+ <string name="install_for_print_preview" msgid="6366303997385509332">"Instal penampil PDF untuk pratinjau"</string>
<string name="printing_app_crashed" msgid="854477616686566398">"Aplikasi pencetakan mogok"</string>
<string name="generating_print_job" msgid="3119608742651698916">"Membuat tugas pencetakan"</string>
<string name="save_as_pdf" msgid="5718454119847596853">"Simpan sebagai PDF"</string>
@@ -76,8 +76,8 @@
<string name="disabled_services_title" msgid="7313253167968363211">"Layanan dinonaktifkan"</string>
<string name="all_services_title" msgid="5578662754874906455">"Semua layanan"</string>
<plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138">
- <item quantity="other">Pasang untuk menemukan <xliff:g id="COUNT_1">%1$s</xliff:g> printer</item>
- <item quantity="one">Pasang untuk menemukan <xliff:g id="COUNT_0">%1$s</xliff:g> printer</item>
+ <item quantity="other">Instal untuk menemukan <xliff:g id="COUNT_1">%1$s</xliff:g> printer</item>
+ <item quantity="one">Instal untuk menemukan <xliff:g id="COUNT_0">%1$s</xliff:g> printer</item>
</plurals>
<string name="printing_notification_title_template" msgid="295903957762447362">"Mencetak <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"Membatalkan <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-pt-rPT/strings.xml b/packages/PrintSpooler/res/values-pt-rPT/strings.xml
index 5da31bd..174789f 100644
--- a/packages/PrintSpooler/res/values-pt-rPT/strings.xml
+++ b/packages/PrintSpooler/res/values-pt-rPT/strings.xml
@@ -56,8 +56,8 @@
<string name="print_select_printer" msgid="7388760939873368698">"Selecionar impressora"</string>
<string name="print_forget_printer" msgid="5035287497291910766">"Esquecer impressora"</string>
<plurals name="print_search_result_count_utterance" formatted="false" msgid="6997663738361080868">
+ <item quantity="one"><xliff:g id="COUNT_1">%1$s</xliff:g> printers found</item>
<item quantity="other"><xliff:g id="COUNT_1">%1$s</xliff:g> impressoras encontradas</item>
- <item quantity="one"><xliff:g id="COUNT_0">%1$s</xliff:g> impressora encontrada</item>
</plurals>
<string name="printer_extended_description_template" msgid="1366699227703381874">"<xliff:g id="PRINT_SERVICE_LABEL">%1$s</xliff:g> – <xliff:g id="PRINTER_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="printer_info_desc" msgid="7181988788991581654">"Mais informações acerca desta impressora"</string>
@@ -76,8 +76,8 @@
<string name="disabled_services_title" msgid="7313253167968363211">"Serviços desativados"</string>
<string name="all_services_title" msgid="5578662754874906455">"Todos os serviços"</string>
<plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138">
+ <item quantity="one">Install to discover <xliff:g id="COUNT_1">%1$s</xliff:g> printers</item>
<item quantity="other">Instale para detetar <xliff:g id="COUNT_1">%1$s</xliff:g> impressoras</item>
- <item quantity="one">Instale para detetar <xliff:g id="COUNT_0">%1$s</xliff:g> impressora</item>
</plurals>
<string name="printing_notification_title_template" msgid="295903957762447362">"A imprimir <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="cancelling_notification_title_template" msgid="1821759594704703197">"A cancelar <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
index 6140428..653a453 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
@@ -171,8 +171,11 @@
@Override
protected void finalize() throws Throwable {
try {
- if (mState != STATE_DESTROYED) {
+ if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
+ }
+
+ if (mState != STATE_DESTROYED) {
destroy(null);
}
} finally {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
index 54400b3..e172948 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
@@ -552,8 +552,11 @@
@Override
protected void finalize() throws Throwable {
try {
- if (mState != STATE_DESTROYED) {
+ if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
+ }
+
+ if (mState != STATE_DESTROYED) {
destroy(null);
}
} finally {
diff --git a/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml b/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml
index 7716992..72be35b 100644
--- a/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml
+++ b/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml
@@ -20,7 +20,7 @@
<option name="test-suite-tag" value="apct" />
<option name="test-tag" value="PrintSpoolerOutOfProcessTests" />
- <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.printspooler.outofprocess.tests" />
<option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
</test>
diff --git a/packages/SettingsLib/res/drawable/ic_info_outline_24dp.xml b/packages/SettingsLib/res/drawable/ic_info_outline_24dp.xml
new file mode 100644
index 0000000..3fe1e9e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_info_outline_24dp.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/textColorSecondary">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/list_divider_dark.xml b/packages/SettingsLib/res/drawable/list_divider_dark.xml
new file mode 100644
index 0000000..5773d9e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/list_divider_dark.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="#64000000" />
+ <size
+ android:height="1dp"
+ android:width="1dp" />
+</shape>
diff --git a/packages/SettingsLib/res/layout/preference_category_material_settings.xml b/packages/SettingsLib/res/layout/preference_category_material_settings.xml
new file mode 100644
index 0000000..741435e
--- /dev/null
+++ b/packages/SettingsLib/res/layout/preference_category_material_settings.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<!-- Based off frameworks/base/core/res/res/layout/preference_category_material.xml
+ except that this supports icon -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart" >
+
+ <LinearLayout
+ android:id="@+id/icon_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="start|center_vertical"
+ android:orientation="horizontal">
+ <com.android.internal.widget.PreferenceImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:tint="?android:attr/textColorPrimary"
+ android:maxWidth="18dp"
+ android:maxHeight="18dp"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="56dp"
+ android:orientation="vertical">
+ <TextView
+ android:id="@android:id/title"
+ android:layout_marginTop="16dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="@android:style/TextAppearance.Material.Body2"
+ android:textColor="?android:attr/colorAccent"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"/>
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorSecondary"
+ android:ellipsize="end"
+ android:singleLine="true" />
+ </LinearLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/layout/preference_footer.xml b/packages/SettingsLib/res/layout/preference_footer.xml
new file mode 100644
index 0000000..d83e249
--- /dev/null
+++ b/packages/SettingsLib/res/layout/preference_footer.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="56dp"
+ android:gravity="start|top"
+ android:orientation="horizontal"
+ android:paddingEnd="12dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="4dp">
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <com.android.settingslib.widget.LinkTextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="16dp"
+ android:paddingTop="16dp"
+ android:maxLines="10"
+ android:textColor="?android:attr/textColorSecondary"
+ android:ellipsize="marquee" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/layout/preference_two_target.xml b/packages/SettingsLib/res/layout/preference_two_target.xml
index 9fb956e..69b7204 100644
--- a/packages/SettingsLib/res/layout/preference_two_target.xml
+++ b/packages/SettingsLib/res/layout/preference_two_target.xml
@@ -20,7 +20,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeight"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:gravity="center_vertical"
android:background="@android:color/transparent"
android:clipToPadding="false">
@@ -31,18 +31,21 @@
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
android:gravity="start|center_vertical"
+ android:clipToPadding="false"
android:paddingStart="?android:attr/listPreferredItemPaddingStart">
<LinearLayout
- android:id="@+id/icon_container"
+ android:id="@+id/icon_frame"
+ style="@style/preference_icon_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minWidth="56dp"
+ android:gravity="start|center_vertical"
android:orientation="horizontal"
+ android:clipToPadding="false"
android:paddingEnd="12dp"
android:paddingTop="4dp"
android:paddingBottom="4dp">
- <com.android.internal.widget.PreferenceImageView
+ <android.support.v7.internal.widget.PreferenceImageView
android:id="@android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/packages/SettingsLib/res/layout/preference_two_target_divider.xml b/packages/SettingsLib/res/layout/preference_two_target_divider.xml
index ced6142..60efed4 100644
--- a/packages/SettingsLib/res/layout/preference_two_target_divider.xml
+++ b/packages/SettingsLib/res/layout/preference_two_target_divider.xml
@@ -27,5 +27,5 @@
<View
android:layout_width="1dp"
android:layout_height="match_parent"
- android:background="?android:attr/dividerVertical" />
+ android:background="@drawable/list_divider_dark" />
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/layout/usage_bottom_label.xml b/packages/SettingsLib/res/layout/usage_bottom_label.xml
deleted file mode 100644
index 6c16880..0000000
--- a/packages/SettingsLib/res/layout/usage_bottom_label.xml
+++ /dev/null
@@ -1,20 +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.
--->
-<TextView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmall" />
diff --git a/packages/SettingsLib/res/layout/usage_side_label.xml b/packages/SettingsLib/res/layout/usage_side_label.xml
deleted file mode 100644
index 6c16880..0000000
--- a/packages/SettingsLib/res/layout/usage_side_label.xml
+++ /dev/null
@@ -1,20 +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.
--->
-<TextView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmall" />
diff --git a/packages/SettingsLib/res/layout/usage_view.xml b/packages/SettingsLib/res/layout/usage_view.xml
deleted file mode 100644
index da66814..0000000
--- a/packages/SettingsLib/res/layout/usage_view.xml
+++ /dev/null
@@ -1,99 +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.
--->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/graph_label_group"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:orientation="horizontal"
- android:clipChildren="false"
- android:clipToPadding="false">
-
- <LinearLayout
- android:id="@+id/label_group"
- android:layout_width="@dimen/usage_graph_labels_width"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <include android:id="@+id/label_top"
- layout="@layout/usage_side_label" />
-
- <Space
- android:id="@+id/space1"
- android:layout_width="wrap_content"
- android:layout_height="0dp"
- android:layout_weight="1" />
-
- <include android:id="@+id/label_middle"
- layout="@layout/usage_side_label" />
-
- <Space
- android:id="@+id/space2"
- android:layout_width="wrap_content"
- android:layout_height="0dp"
- android:layout_weight="1" />
-
- <include android:id="@+id/label_bottom"
- layout="@layout/usage_side_label" />
-
- </LinearLayout>
-
- <com.android.settingslib.graph.UsageGraph
- android:id="@+id/usage_graph"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_marginTop="@dimen/usage_graph_margin_top_bottom"
- android:layout_marginBottom="@dimen/usage_graph_margin_top_bottom" />
-
- </LinearLayout>
-
- <LinearLayout
- android:id="@+id/bottom_label_group"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
- <Space
- android:id="@+id/bottom_label_space"
- android:layout_width="@dimen/usage_graph_labels_width"
- android:layout_height="wrap_content"/>
- <com.android.settingslib.graph.BottomLabelLayout
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:orientation="horizontal"
- android:layoutDirection="ltr">
- <include android:id="@+id/label_start"
- layout="@layout/usage_side_label" />
-
- <Space
- android:id="@+id/spacer"
- android:layout_width="40dp"
- android:layout_height="wrap_content"
- android:layout_weight="1" />
-
- <include android:id="@+id/label_end"
- layout="@layout/usage_side_label" />
- </com.android.settingslib.graph.BottomLabelLayout>
- </LinearLayout>
-
-</LinearLayout>
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 337a8eb..1058110 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Gekoppel via %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Beskikbaar via %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Gekoppel, geen internet nie"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Ontkoppel"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Ontkoppel tans…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Verbind tans…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Gekoppel (geen boodskaptoegang nie)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Gekoppel (geen foon of media nie)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Media-oudio"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Foonoudio"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Lêeroordrag"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Invoertoestel"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Internettoegang"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Kontakdeling"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Gebruik vir kontakdeling"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Deling van internetverbinding"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Boodskaptoegang"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM-toegang"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Gebruik hoëgehalte-oudio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Gebruik hoëgehalte-oudio"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Gekoppel aan media-oudio"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Gekoppel aan foonoudio"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Gekoppel aan lêeroordragbediener"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Hierdie kenmerk is eksperimenteel en kan werkverrigting beïnvloed."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Geneutraliseer deur <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Omtrent <xliff:g id="TIME">%1$s</xliff:g> oor"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Ongeveer <xliff:g id="TIME">%1$s</xliff:g> oor gegrond op jou gebruik"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> oor tot vol gelaai"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> oor"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> oor gegrond op jou gebruik"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – omtrent <xliff:g id="TIME">%2$s</xliff:g> oor"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – ongeveer <xliff:g id="TIME">%2$s</xliff:g> oor gegrond op jou gebruik"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> oor"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> tot vol gelaai"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 645aa49..dfa5aaa 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"በ%1$s በኩል መገናኘት"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"በ%1$s በኩል የሚገኝ"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"ተገናኝቷል፣ ምንም በይነመረብ የለም"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"ተለያይቷል"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"በመለያየት ላይ..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"በማገናኘት ላይ…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"ተገናኝቷል (ምንም የመልዕክት መዳረሻ የለም)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"ተያይዟል (ምንም ስልክ ወይም ማህደረ መረጃ የለም)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"የማህደረ መረጃ ኦዲዮ"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"የስልክ ኦዲዮ"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"ፋይል ማስተላለፍ"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"ግቤት መሣሪያ"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"የበይነመረብ ድረስ"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"እውቂያ ማጋራት"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"እውቂያን ለማጋራት ተጠቀም"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"የበይነ መረብ ተያያዥ ማጋሪያ"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"የመልዕክት መዳረሻ"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"የሲም መዳረሻ"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"ከፍተኛ ጥራት ያለውን ኦዲዮ ተጠቀም፦ <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"ከፍተኛ ጥራት ያለውን ኦዲዮ ተጠቀም"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"ወደ ማህደረ መረጃ አውዲዮ ተያይዟል"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"ወደ ስልክ አውዲዮ ተያይዟል"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"ወደ ፋይል ዝውውር አገልጋይ ተያይዟል"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"ይህ ባህሪ የሙከራ ነውና አፈጻጸም ላይ ተጽዕኖ ሊኖረው ይችላል።"</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"በ<xliff:g id="TITLE">%1$s</xliff:g> ተሽሯል"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"<xliff:g id="TIME">%1$s</xliff:g> አካባቢ ቀርቷል"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"በእርስዎ አጠቃቀም ላይ በመመስረት <xliff:g id="TIME">%1$s</xliff:g> ገደማ ቀርቷል"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"ሙሉ ኃይል እስኪሞላ ድረስ <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> ቀርቷል"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"በእርስዎ አጠቃቀም ላይ በመመስረት <xliff:g id="TIME">%1$s</xliff:g> ቀርቷል"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> አካባቢ ይቀራል"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - በእርስዎ አጠቃቀም ላይ በመመስረት <xliff:g id="TIME">%2$s</xliff:g> ገደማ ቀርቷል"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ይቀራል"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - ሙሉ ለሙሉ እስኪሞላ ድረስ <xliff:g id="TIME">%2$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 897d473..343c82f 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"تم الاتصال عبر %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"متوفرة عبر %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"متصلة، ولا يتوفر إنترنت"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"غير متصل"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"جارٍ قطع الاتصال..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"جارٍ الاتصال…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"تم الاتصال (يتعذر الدخول إلى الرسائل)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"متصل (بجهاز غير الهاتف أو الوسائط)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"الإعدادات الصوتية للوسائط"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"الإعدادات الصوتية للهاتف"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"نقل الملف"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"جهاز الإرسال"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"الدخول إلى الإنترنت"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"مشاركة جهة الاتصال"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"استخدام مع مشاركة جهة الاتصال"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"مشاركة اتصال الإنترنت"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"الدخول إلى الرسائل"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"الوصول إلى شريحة SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"استخدام صوت عالي الجودة: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"استخدام صوت عالي الجودة"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"متصل بالإعدادات الصوتية للوسائط"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"متصل بالإعدادات الصوتية للهاتف"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"متصل بخادم نقل الملف"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"هذه الميزة تجريبية وقد تؤثر في الأداء."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"تم الاستبدال بـ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"يتبقى حوالي <xliff:g id="TIME">%1$s</xliff:g> لإتمام شحن البطارية"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"يتبقى <xliff:g id="TIME">%1$s</xliff:g> تقريبًا بناءً على استخدامك"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"يتبقى <xliff:g id="TIME">%1$s</xliff:g> لشحن البطارية بالكامل"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"يتبقى <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"يتبقى <xliff:g id="TIME">%1$s</xliff:g> بناءً على استخدامك"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - يتبقى <xliff:g id="TIME">%2$s</xliff:g> تقريبًا"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - يتبقى <xliff:g id="TIME">%2$s</xliff:g> تقريبًا بناءً على استخدامك"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - يتبقى <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> حتى يكتمل الشحن"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 4144593..be22538 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s vasitəsilə qoşuludur"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s vasitəsilə əlçatandır"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Qoşuludur, internet yoxdur"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Ayrıldı"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Ayrılır..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Qoşulur..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Qoşulu (mesaj girişi yoxdur)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Bağlantı yaradılıb (telefon və ya media deyil)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Media audio"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Telefon audio"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Fayl transferi"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Daxiletmə cihazı"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"İnternet girişi"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Kontakt paylaşımı"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Kontakt paylaşımı üçün istifadə edin"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"internet bağlantı paylaşımı"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Mesaj Girişi"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM Girişi"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Yüksək keyfiyyətli audio istifadə edin: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Yüksək keyfiyyətli audio istifadə edin"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Media audioya birləşdirilib"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Telefon audiosuna qoşulu"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Fayl transfer serverinə qoşulu"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Bu funksiya eksperimentaldır və performansa təsir edə bilər."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> tərəfindən qəbul edilmir"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Təxminən <xliff:g id="TIME">%1$s</xliff:g> qalıb"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"İstifadəyə əsasən təxminən <xliff:g id="TIME">%1$s</xliff:g> qalıb"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Tam enerji yığmağına <xliff:g id="TIME">%1$s</xliff:g> qalıb"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> qalıb"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"İstifadəyə əsasən <xliff:g id="TIME">%1$s</xliff:g> qalıb"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - təxminən <xliff:g id="TIME">%2$s</xliff:g> qalıb"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - istifadəyə əsasən təxminən <xliff:g id="TIME">%2$s</xliff:g> qalıb"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> qalıb"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> tam enerji yığana kimi"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml b/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
index 1eeed3c..0bfda36 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/arrays.xml
@@ -198,7 +198,7 @@
<item msgid="1069584980746680398">"Razmera animacije 10x"</item>
</string-array>
<string-array name="overlay_display_devices_entries">
- <item msgid="1606809880904982133">"Nijedno"</item>
+ <item msgid="1606809880904982133">"Ništa"</item>
<item msgid="9033194758688161545">"480 piksela"</item>
<item msgid="1025306206556583600">"480 piksela (bezbedno)"</item>
<item msgid="1853913333042744661">"720 piksela"</item>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 73f0de0..e8fade2 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Veza je uspostavljena preko pristupne tačke %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Dostupna je preko pristupne tačke %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Veza je uspostavljena, nema interneta"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Veza je prekinuta"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Prekidanje veze..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Povezivanje…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Povezano je (nema pristupa porukama)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Povezano (bez telefona ili medija)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Zvuk medija"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Zvuk telefona"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Prenos datoteke"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Ulazni uređaj"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Pristup Internetu"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Deljenje kontakata"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Koristite za deljenje kontakata"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Deljenje internet veze"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Pristup porukama"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Pristup SIM kartici"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Koristi zvuk visokog kvaliteta: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Koristi zvuk visokog kvaliteta"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Povezano sa zvukom medija"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Povezano sa zvukom telefona"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Povezano sa serverom za prenos datoteka"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Ova funkcija je eksperimentalna i može da utiče na kvalitet rada."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Zamenjuje ga <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Još oko <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Na osnovu potrošnje imate još otprilike <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> do potpunog punjenja"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Preostalo vreme: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Na osnovu potrošnje imate još <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – ostalo je oko <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – na osnovu potrošnje imate još otprilike <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"Preostalo je <xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do potpunog punjenja"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 2e8aa9f..c69b314 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Падлучана праз %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Даступна праз %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Падлучана, няма інтэрнэту"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Адключана"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Адключэнне..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Злучэнне..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Падлучана (без доступу да паведамленняў)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Падключаны (без тэлефона або носьбіта)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Аўдыё медыяпрылады"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Аудыё тэлефона"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Перадача файлаў"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Прылада ўводу"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Доступ у інтэрнэт"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Абагуленне кантактаў"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Выкарыстоўваць для абагулення кантактаў"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Прадастаўленне доступу да Інтэрнэту"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Доступ да паведамленняў"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Доступ да SIM-карты"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Выбраць аўдыя высокай якасці: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Выбраць аўдыя высокай якасці"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Падключана да аўдыё медыа"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Падключана да аўдыё тэлефона"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Падключаны да серверу перадачы файлаў"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Гэта функцыя з\'яўляецца эксперыментальнай і можа паўплываць на прадукцыйнасць."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Перавызначаны <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Засталося каля <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Засталося каля <xliff:g id="TIME">%1$s</xliff:g> на аснове вашага выкарыстання"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Да поўнай зарадкі засталося <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Засталося <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Засталося <xliff:g id="TIME">%1$s</xliff:g> на аснове вашага выкарыстання"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – засталося каля <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – засталося каля <xliff:g id="TIME">%2$s</xliff:g> на аснове вашага выкарыстання"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – засталося <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> да поўнай зарадкі"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 4e24cec..b7713b9 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Установена е връзка през „%1$s“"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Мрежата е достъпна през „%1$s“"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Установена е връзка – няма достъп до интернет"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Изкл."</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Изключва се..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Установява се връзка…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Има връзка (няма достъп до съобщенията)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Свързано (без телефона или мултимедията)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Мултимедийно аудио"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Звук на телефона"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Прехвърляне на файл"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Входно устройство"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Достъп до интернет"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Споделяне на контакти"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Използване за споделяне на контакти"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Споделяне на връзката с интернет"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Достъп до съобщенията"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Достъп до SIM картата"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Използване на висококачествен звук: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Използване на висококачествен звук"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Установена е връзка с медийно аудио"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Връзка със звука на телефона"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Установена е връзка със сървър за трансфер на файлове"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Тази функция е експериментална и може да се отрази на ефективността."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Заменено от „<xliff:g id="TITLE">%1$s</xliff:g>“"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Оставащо време: около <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Още около <xliff:g id="TIME">%1$s</xliff:g> въз основа на използването"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Оставащо време до пълно зареждане: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Оставащо време: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Още <xliff:g id="TIME">%1$s</xliff:g> въз основа на използването"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – около <xliff:g id="TIME">%2$s</xliff:g> оставащо време"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – още около <xliff:g id="TIME">%2$s</xliff:g> въз основа на използването"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – оставащо време: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до пълно зареждане"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 6fb79cb..7b6cffe 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s মাধ্যমে সংযুক্ত হয়েছে"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s এর মাধ্যমে উপলব্ধ"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"সংযুক্ত, ইন্টারনেট নেই"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"সংযোগ বিচ্ছিন্ন করা হয়েছে"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"সংযোগ বিচ্ছিন্ন হচ্ছে..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"সংযুক্ত হচ্ছে..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"সংযুক্ত (কোনো বার্তা অ্যাক্সেস নেই)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"সংযুক্ত (কোনো ফোন বা মিডিয়া নেই)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"মিডিয়া অডিও"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"ফোন অডিও"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"ফাইল স্থানান্তর"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"ইনপুট ডিভাইস"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"ইন্টারনেট অ্যাক্সেস"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"পরিচিতি শেয়ার করা"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"পরিচিতি শেয়ার করার কাজে ব্যবহার করুন"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"ইন্টারনেট সংযোগ শেয়ার করা হচ্ছে"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"বার্তা অ্যাক্সেস"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"সিম -এর অ্যাক্সেস"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"উচ্চ মানের অডিও ব্যবহার করুন: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"উচ্চ মানের অডিও ব্যবহার করুন"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"মিডিয়া অডিওতে সংযুক্ত রয়েছে"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"ফোন অডিওতে সংযুক্ত"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"ফাইল স্থানান্তর সার্ভারের সঙ্গে সংযুক্ত"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"এই বৈশিষ্ট্যটি পরীক্ষামূলক এবং এটি কার্য-সম্পাদনা প্রভাবিত করতে পারে।"</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> এর দ্বারা ওভাররাইড করা হয়েছে"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"প্রায় <xliff:g id="TIME">%1$s</xliff:g> বাকি"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"বর্তমান ব্যাটারি ব্যবহার অনুযায়ী আর <xliff:g id="TIME">%1$s</xliff:g> বাকি"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"সম্পূর্ণ চার্জ হতে <xliff:g id="TIME">%1$s</xliff:g> বাকি"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> বাকী আছে"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"বর্তমান ব্যাটারি ব্যবহার অনুযায়ী আর <xliff:g id="TIME">%1$s</xliff:g> বাকি"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - প্রায় <xliff:g id="TIME">%2$s</xliff:g> বাকি আছে"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - বর্তমান ব্যাটারি ব্যবহার অনুযায়ী আর <xliff:g id="TIME">%2$s</xliff:g> বাকি"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> বাকী আছে"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - সম্পূর্ণ চার্জ হতে <xliff:g id="TIME">%2$s</xliff:g> লাগবে"</string>
diff --git a/packages/SettingsLib/res/values-bs/arrays.xml b/packages/SettingsLib/res/values-bs/arrays.xml
index e0b3e36..2bfb4a5 100644
--- a/packages/SettingsLib/res/values-bs/arrays.xml
+++ b/packages/SettingsLib/res/values-bs/arrays.xml
@@ -245,7 +245,7 @@
<item msgid="7899496259191969307">"Najviše 4 procesa"</item>
</string-array>
<string-array name="usb_configuration_titles">
- <item msgid="488237561639712799">"Puni se"</item>
+ <item msgid="488237561639712799">"Punjenje"</item>
<item msgid="5220695614993094977">"MTP (protokol za prijenos sadržaja medija)"</item>
<item msgid="2086000968159047375">"PTP (protokol za prijenos slika)"</item>
<item msgid="7398830860950841822">"RNDIS (USB Ethernet)"</item>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index be03695..58598a3 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Povezani preko %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Dostupan preko %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Povezano. Nema interneta"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Isključen"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Prekidanje veze…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Povezivanje…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Povezano (bez pristupa porukama)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Povezano (bez zvuka telefona ili medija)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Zvuk medija"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Zvuk telefona"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Prenošenje fajla"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Ulazni uređaj"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Pristup internetu"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Dijeljenje kontakta"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Koristi za dijeljenje kontakta"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Dijeljenje internet veze"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Pristup poruci"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Pristup SIM-u"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Koristi visokokvalitetan zvuk: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Koristi visokokvalitetan zvuk"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Povezano sa zvukom medija"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Povezano na zvuk telefona"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Povezan na server za prijenos podataka"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Ova funkcija je eksperimentalna i može uticati na performanse."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Zamjenjuje <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Preostalo je otprilike još <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Preostalo je još oko <xliff:g id="TIME">%1$s</xliff:g>, na osnovu vašeg korištenja"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Još <xliff:g id="TIME">%1$s</xliff:g> do potpune napunjenosti"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Imate još <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Preostalo je još <xliff:g id="TIME">%1$s</xliff:g>, na osnovu vašeg korištenja"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - imate još <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - preostalo je još oko <xliff:g id="TIME">%2$s</xliff:g>, na osnovu vašeg korištenja"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - imate još <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> do potpune napunjenosti"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index a56d994..d9cfb9c 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Connectada mitjançant %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Disponible mitjançant %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Connectada, sense Internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Desconnectat"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"S\'està desconnectant..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"S\'està connectant…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Connectat (no hi ha accés als missatges)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Connectat (sense telèfon o disp. mult.)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Àudio multimèdia"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Àudio del telèfon"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Transferència del fitxer"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Dispositiu d\'entrada"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Accés a Internet"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Compartir contactes"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"S\'utilitza per compartir contactes."</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Connexió compartida a Internet"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Accés al missatge"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Accés a la SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Utilitza àudio d\'alta qualitat: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Utilitza àudio d\'alta qualitat"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Connectat a l\'àudio del mitjà"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Connectat a àudio del telèfon"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Connectat al servidor de transferència de fitxers"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Aquesta funció és experimental i pot afectar el rendiment."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"S\'ha substituït per <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Temps restant aproximat: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Temps restant aproximat segons l\'ús que en fas: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> per completar la càrrega"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Temps restant: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Temps restant segons l\'ús que en fas: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> aproximadament per esgotar la bateria"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g>; temps restant aproximat segons l\'ús que en fas: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g>; temps restant: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> per completar la càrrega"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 36b3720..0ea6642 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Připojeno prostřednictvím %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Dostupné prostřednictvím %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Připojeno, není k dispozici internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Odpojeno"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Odpojování..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Připojování..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Připojeno (bez přístupu ke zprávám)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Připojeno (žádný telefon nebo média)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Zvuk médií"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Zvuk telefonu"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Přenos souborů"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Vstupní zařízení"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Přístup k internetu"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Sdílení kontaktů"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Použít ke sdílení kontaktů"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Sdílení internetového připojení"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Přístup ke zprávám"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Přístup k SIM kartě"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Použít vysokou kvalitu zvuku: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Použít vysokou kvalitu zvuku"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Připojeno ke zvukovému médiu"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Připojeno k náhlavní soupravě"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Připojeno k serveru pro přenos dat"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Funkce je experimentální a může mít vliv na výkon."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Přepsáno nastavením <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Zbývá asi <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Při vašem obvyklém využití zbývá asi <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Plně se nabije za <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Zbývající čas: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Při vašem obvyklém využití zbývá <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – zbývá přibližně <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – při vašem obvyklém využití zbývá asi <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – zbývá <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – plně se nabije za <xliff:g id="TIME">%2$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-da/arrays.xml b/packages/SettingsLib/res/values-da/arrays.xml
index 0647b4f..6c322ed 100644
--- a/packages/SettingsLib/res/values-da/arrays.xml
+++ b/packages/SettingsLib/res/values-da/arrays.xml
@@ -49,9 +49,9 @@
<item msgid="1805837518286731242">"Undgår midlertidigt dårlig forbindelse"</item>
</string-array>
<string-array name="hdcp_checking_titles">
- <item msgid="441827799230089869">"Kontrollér aldrig"</item>
- <item msgid="6042769699089883931">"Kontrollér kun for DRM-indhold"</item>
- <item msgid="9174900380056846820">"Kontrollér altid"</item>
+ <item msgid="441827799230089869">"Tjek aldrig"</item>
+ <item msgid="6042769699089883931">"Tjek kun for DRM-indhold"</item>
+ <item msgid="9174900380056846820">"Tjek altid"</item>
</string-array>
<string-array name="hdcp_checking_summaries">
<item msgid="505558545611516707">"Brug aldrig HDCP-kontrol"</item>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index a549082..5b2f75a 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Tilsluttet via %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Tilgængelig via %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Tilsluttet – intet internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Afbrudt"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Afbryder ..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Opretter forbindelse..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Forbundet (ingen adgang til meddelelse)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Forbundet (ingen telefon eller medier)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Medielyd"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Telefonlyd"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Filoverførsel"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Inputenhed"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Internetadgang"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Deling af kontaktpersoner"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Brug til deling af kontaktpersoner"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Deling af internetforbindelse"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Adgang til meddelelse"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM-adgang"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Brug lyd i høj kvalitet: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Brug lyd i høj kvalitet"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Forbundet til medielyd"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Forbundet til telefonlyd"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Forbundet til filoverførselsserver"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Denne funktion er eksperimentel og kan påvirke ydeevnen."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Tilsidesat af <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Ca. <xliff:g id="TIME">%1$s</xliff:g> tilbage"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Der er ca. <xliff:g id="TIME">%1$s</xliff:g> tilbage, alt efter hvordan du bruger enheden"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> til det er fuldt opladet"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> tilbage"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Der er <xliff:g id="TIME">%1$s</xliff:g> tilbage, alt efter hvordan du bruger enheden"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – ca. <xliff:g id="TIME">%2$s</xliff:g> tilbage"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – der er ca. <xliff:g id="TIME">%2$s</xliff:g> tilbage, alt efter hvordan du bruger enheden"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> tilbage"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> til det er fuldt opladet"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index af31792..729d872 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Über %1$s verbunden"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Verfügbar über %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Verbunden, kein Internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Nicht verbunden"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Verbindung wird getrennt..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Verbindung wird hergestellt..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Verbunden (ohne Nachrichtenzugriff)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Verbunden (außer Telefon- und Audiomedien)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Media-Audio"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Telefon-Audio"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Dateiübertragung"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Eingabegerät"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Internetzugriff"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Kontaktfreigabe"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Für Kontaktfreigabe nutzen"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Freigabe der Internetverbindung"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Nachrichtenzugriff"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Zugriff auf SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Hohe Audioqualität verwenden: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Hohe Audioqualität verwenden"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Verbunden mit Audiosystem von Medien"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Verbunden mit Audiosystem des Telefons"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Mit Dateiübertragungsserver verbunden"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Hierbei handelt es sich um eine experimentelle Funktion, die sich auf die Leistung auswirken kann."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Außer Kraft gesetzt von \"<xliff:g id="TITLE">%1$s</xliff:g>\""</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Ca. <xliff:g id="TIME">%1$s</xliff:g> übrig"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Noch ca. <xliff:g id="TIME">%1$s</xliff:g>, basierend auf deiner Nutzung"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> bis zur vollständigen Aufladung"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Noch <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Noch <xliff:g id="TIME">%1$s</xliff:g>, basierend auf deiner Nutzung"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – ungefähr noch <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – noch ca. <xliff:g id="TIME">%2$s</xliff:g>, basierend auf deiner Nutzung"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – noch <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> bis vollständig geladen"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 43f0f2c..fb9d57d 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Συνδέθηκε μέσω %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Διαθέσιμο μέσω %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Συνδέθηκε, χωρίς διαδίκτυο"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Αποσυνδέθηκε"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Αποσύνδεση..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Σύνδεση..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Συνδεδεμένο (χωρίς πρόσβαση μηνύματος)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Συνδεδεμένο (χωρίς τηλέφωνο ή πολυμέσα)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Ήχος πολυμέσων"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Ήχος τηλεφώνου"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Μεταφορά αρχείου"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Συσκευή εισόδου"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Πρόσβαση στο Διαδίκτυο"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Κοινή χρήση επαφών"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Για κοινή χρήση επαφών"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Κοινή χρήση σύνδεσης στο Διαδίκτυο"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Πρόσβαση στο μήνυμα"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Πρόσβαση SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Χρήση ήχου υψηλής ποιότητας: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Χρήση ήχου υψηλής ποιότητας"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Συνδέθηκε σε ήχο πολυμέσων"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Συνδεδεμένο στον ήχο τηλεφώνου"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Συνδεδεμένο σε διακομιστή μεταφοράς αρχείων"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Αυτή η λειτουργία είναι πειραματική και ενδεχομένως να επηρεάσει τις επιδόσεις."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Αντικαταστάθηκε από <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Απομένουν περίπου <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Απομένει/ουν περίπου <xliff:g id="TIME">%1$s</xliff:g> με βάση τη χρήση σας"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Απομένουν <xliff:g id="TIME">%1$s</xliff:g> έως την πλήρη φόρτιση"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Απομένει/ουν <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Απομένει/ουν <xliff:g id="TIME">%1$s</xliff:g> με βάση τη χρήση σας"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - απομένουν περίπου <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - Απομένει/ουν περίπου <xliff:g id="TIME">%2$s</xliff:g> με βάση τη χρήση σας"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - απομένει/ουν <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> για πλήρη φόρτιση"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 93e1aa8..e9ff46b 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Connected via %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Available via %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Connected, no Internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Disconnected"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Disconnecting…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Connecting…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Connected (no message access)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Connected (no phone or media)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Media audio"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Phone audio"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"File transfer"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Input device"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Internet access"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Contact sharing"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Use for contact sharing"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Internet connection sharing"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Message Access"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM Access"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Use high-quality audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Use high-quality audio"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Connected to media audio"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Connected to phone audio"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Connected to file-transfer server"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"This feature is experimental and may affect performance."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"About <xliff:g id="TIME">%1$s</xliff:g> left"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"About <xliff:g id="TIME">%1$s</xliff:g> left based on your usage"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> left until fully charged"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> left"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> left based on your usage"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – about <xliff:g id="TIME">%2$s</xliff:g> left"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> left"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> until fully charged"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 93e1aa8..e9ff46b 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Connected via %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Available via %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Connected, no Internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Disconnected"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Disconnecting…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Connecting…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Connected (no message access)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Connected (no phone or media)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Media audio"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Phone audio"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"File transfer"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Input device"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Internet access"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Contact sharing"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Use for contact sharing"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Internet connection sharing"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Message Access"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM Access"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Use high-quality audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Use high-quality audio"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Connected to media audio"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Connected to phone audio"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Connected to file-transfer server"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"This feature is experimental and may affect performance."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"About <xliff:g id="TIME">%1$s</xliff:g> left"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"About <xliff:g id="TIME">%1$s</xliff:g> left based on your usage"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> left until fully charged"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> left"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> left based on your usage"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – about <xliff:g id="TIME">%2$s</xliff:g> left"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> left"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> until fully charged"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 93e1aa8..e9ff46b 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Connected via %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Available via %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Connected, no Internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Disconnected"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Disconnecting…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Connecting…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Connected (no message access)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Connected (no phone or media)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Media audio"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Phone audio"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"File transfer"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Input device"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Internet access"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Contact sharing"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Use for contact sharing"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Internet connection sharing"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Message Access"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM Access"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Use high-quality audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Use high-quality audio"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Connected to media audio"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Connected to phone audio"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Connected to file-transfer server"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"This feature is experimental and may affect performance."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"About <xliff:g id="TIME">%1$s</xliff:g> left"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"About <xliff:g id="TIME">%1$s</xliff:g> left based on your usage"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> left until fully charged"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> left"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> left based on your usage"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – about <xliff:g id="TIME">%2$s</xliff:g> left"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> left"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> until fully charged"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 16d34a2..a28802b 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Conexión a través de %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Disponible a través de %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Conectado a Wi-Fi, sin conexión a Internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Desconectado"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Desconectando…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Conectando…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Conectado (sin acceso a mensajes)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Conectado (sin tel. ni audio multimedia)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Audio multimedia"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Audio del dispositivo"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Transferencia de archivos"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Dispositivo de entrada"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Acceso a Internet"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Compartir contactos"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Utilizar para compartir contactos"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Compartir conexión a Internet"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Acceso a mensajes"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Acceso SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Usar audio de alta calidad: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Usar audio de alta calidad"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Conectado al audio multimedia"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Conectado al audio del dispositivo"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Conectado al servidor de transferencia de archivo"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Esta función es experimental y puede afectar el rendimiento."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Reemplazado por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Tiempo restante aproximado: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Aproximadamente <xliff:g id="TIME">%1$s</xliff:g> restantes en función del uso"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> para completar la carga"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Tiempo restante: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> restantes en función del uso"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> (tiempo restante: <xliff:g id="TIME">%2$s</xliff:g>)"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> (aproximadamente <xliff:g id="TIME">%2$s</xliff:g> restantes en función del uso)"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - Tiempo restante: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> para completar la carga)"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index c51854d..f3d6053 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Conectado a través de %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Disponible a través de %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Conexión sin Internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Desconectado"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Desconectando…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Estableciendo conexión…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Conectado (sin acceso a mensajes)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Conectado (sin teléfono ni multimedia)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Audio multimedia"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Audio del teléfono"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Transferencia de archivos"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Dispositivo de entrada"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Acceso a Internet"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Compartir contactos"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Usar para compartir contactos"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Compartir conexión a Internet"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Acceso a mensajes"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Acceso a tarjeta SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Usar audio de alta calidad: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Usar audio de alta calidad"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Conectado al audio del medio"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Conectado al audio del teléfono"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Conectado con el servidor de transferencia de archivos"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Esta función es experimental y puede afectar al rendimiento."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Anulado por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Tiempo restante aproximado: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Tiempo restante aproximado según tu uso: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Tiempo restante hasta carga completa: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Tiempo restante: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Tiempo restante aproximado según tu uso: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - tiempo aproximado restante: <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> (tiempo restante aproximado según tu uso: <xliff:g id="TIME">%2$s</xliff:g>)"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - Tiempo restante: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> para completar la carga"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index b1a40a8..9617e61 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Ühendatud üksuse %1$s kaudu"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Saadaval üksuse %1$s kaudu"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Ühendatud, Interneti-ühendus puudub"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Ühendus katkestatud"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Ühenduse katkestamine ..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Ühendamine ..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Ühendatud (sõnumita juurdepääs)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Ühendatud (pole telefoni ega meediat)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Meedia heli"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Telefoni heli"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Failiedastus"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Sisendseade"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Internetti juurdepääs"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Kontakti jagamine"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Kasutamine kontaktide jagamiseks"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Interneti-ühenduse jagamine"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Juurdepääs sõnumile"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM-kaardi juurdepääs"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Kasuta kvaliteetset heli: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Kasuta kvaliteetset heli"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Ühendatud meediumiheliga"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Ühendatud telefoniheliga"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Ühendatud failiedastuse serveriga"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"See funktsioon on katseline ja võib mõjutada toimivust."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Alistas <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Umbes <xliff:g id="TIME">%1$s</xliff:g> on jäänud"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Teie kasutuse alusel on jäänud ligikaudu <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> täislaadimiseni"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> on jäänud"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Teie kasutuse alusel on jäänud <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – umbes <xliff:g id="TIME">%2$s</xliff:g> on jäänud"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – teie kasutuse alusel on jäänud ligikaudu <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> on jäänud"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> täislaadimiseni"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 2b6d964..77e05ef 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s bidez konektatuta"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s bidez erabilgarri"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Konektatuta, ez dago Interneteko konexiorik"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Deskonektatuta"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Deskonektatzen…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Konektatzen…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Konektatuta (mezuetarako sarbiderik ez)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Konektatuta (ez dago telef./euskarririk)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Euskarriaren audioa"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Telefonoaren audioa"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Fitxategi-transferentzia"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Sarrerako gailua"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Interneterako sarbidea"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Kontaktuak partekatzea"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Erabili kontaktuak partekatzeko"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Interneteko konexioa partekatzea"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Mezuetarako sarbidea"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM txartelerako sarbidea"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Erabili kalitate handiko audioa: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Erabili kalitate handiko audioa"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Euskarriaren audiora konektatuta"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Telefonoaren audiora konektatuta"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Fitxategi-transferentziako zerbitzarira konektatuta"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Eginbidea esperimentala da eta eragina izan dezake funtzionamenduan."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> hobespena gainjarri zaio"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"<xliff:g id="TIME">%1$s</xliff:g> inguru gelditzen dira"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"<xliff:g id="TIME">%1$s</xliff:g> inguru gelditzen dira, erabileraren arabera"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> falta dira guztiz kargatu arte"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> guztiz kargatu arte"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> gelditzen dira, erabileraren arabera"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> inguru gelditzen dira"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> inguru gelditzen dira, erabileraren arabera"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> guztiz kargatu arte"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> guztiz kargatu arte"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index ff76b60..4b0f39b 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"متصل از طریق %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"در دسترس از طریق %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"متصل، بدون اینترنت"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"اتصال قطع شد"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"در حال قطع اتصال..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"در حال اتصال…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"متصل (عدم دسترسی به پیام)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"متصل شد (بدون تلفن یا رسانه)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"رسانه صوتی"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"صدای تلفن"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"انتقال فایل"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"دستگاه ورودی"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"دسترسی به اینترنت"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"اشتراکگذاری مخاطب"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"استفاده برای اشتراکگذاری مخاطب"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"اشتراکگذاری اتصال اینترنت"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"دسترسی به پیام"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"دسترسی سیمکارت"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"استفاده از صوت با کیفیت بالا: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"استفاده از صوت با کیفیت بالا"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"به رسانه صوتی متصل شد"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"به تلفن صوتی متصل شد"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"به سرور انتقال فایل متصل شد"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"این قابلیت آزمایشی است و ممکن است عملکرد را تحت تأثیر قرار دهد."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"توسط <xliff:g id="TITLE">%1$s</xliff:g> لغو شد"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"حدود <xliff:g id="TIME">%1$s</xliff:g> باقی مانده است"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"براساس میزان مصرف شما، <xliff:g id="TIME">%1$s</xliff:g> باقیمانده است"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> تا شارژ شدن کامل باقی مانده است"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> باقی مانده"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"براساس میزان مصرف شما، <xliff:g id="TIME">%1$s</xliff:g> باقیمانده است"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - تقریباً <xliff:g id="TIME">%2$s</xliff:g> باقی مانده است"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - براساس میزان مصرف شما، <xliff:g id="TIME">%2$s</xliff:g> باقیمانده است"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> باقی مانده"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> مانده تا شارژ کامل"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index b675992..3f36ea4 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Yhdistetty seuraavan kautta: %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Käytettävissä seuraavan kautta: %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Yhdistetty, ei internetyhteyttä."</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Yhteys katkaistu"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Katkaistaan yhteyttä..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Yhdistetään…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Yhdistetty (ei MAP)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Yhdistetty (ei puhelimen/median ääntä)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Median ääni"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Puhelimen ääni"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Tiedostonsiirto"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Syöttölaite"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Internetyhteys"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Yhteystietojen jakaminen"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Käytä yhteystietojen jakamiseen"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Internetyhteyden jakaminen"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"MAP"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM-kortin käyttö"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Käytä korkealaatuista ääntä: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Käytä korkealaatuista ääntä"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Yhdistetty median ääneen"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Yhdistetty puhelimen ääneen"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Yhdistetty tiedostonsiirtopalvelimeen"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Tämä ominaisuus on kokeellinen ja voi vaikuttaa suorituskykyyn."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Tämän ohittaa <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Noin <xliff:g id="TIME">%1$s</xliff:g> jäljellä"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Noin <xliff:g id="TIME">%1$s</xliff:g> jäljellä käytön perusteella"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> kunnes täynnä"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> jäljellä"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> jäljellä käytön perusteella"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – noin <xliff:g id="TIME">%2$s</xliff:g> jäljellä"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – noin <xliff:g id="TIME">%2$s</xliff:g> jäljellä käytön perusteella"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> jäljellä"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> täyteen lataukseen"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 7488df78..b2c596c 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Connecté par %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Accessible par %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Connecté, aucun accès à Internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Déconnecté"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Déconnexion…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Connexion en cours…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Connecté (sans accès aux messages)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Connecté (sans audio tel./multimédia)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Paramètres audio du support"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Paramètres audio du téléphone"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Transfert de fichier"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Périphérique d\'entrée"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Accès Internet"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Partage de contact"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Utiliser pour le partage de contacts"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Partage de connexion Internet"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Accès aux messages"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Accès à la carte SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Utiliser l\'audio de haute qualité : <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Utiliser l\'audio de haute qualité"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Connecté aux paramètres audio du média"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Connecté à l\'audio du téléphone"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Connexion au serveur de transfert de fichiers"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Cette fonctionnalité est expérimentale et peut affecter les performances."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Il reste environ <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Il reste environ <xliff:g id="TIME">%1$s</xliff:g> en fonction de votre usage"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> jusqu\'à la charge complète"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Temps restant : <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Il reste <xliff:g id="TIME">%1$s</xliff:g> en fonction de votre usage"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> : il reste environ <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> : il reste environ <xliff:g id="TIME">%2$s</xliff:g> en fonction de votre usage"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – Temps restant : <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> : <xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la charge complète"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index c6562e2..b726f9d 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Connecté via %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Disponible via %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Connecté, aucun accès à Internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Déconnecté"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Déconnexion…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Connexion…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Connecté (sans accès aux messages)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Connecté (sans audio tel./multimédia)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Multimédia"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Appels et notifications"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Transfert de fichier"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Périphérique d\'entrée"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Accès Internet"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Partage de contacts"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Utiliser pour le partage de contacts"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Partage de connexion Internet"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Accès aux messages"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Accès à la carte SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Utiliser le codec audio haute qualité : <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Utiliser un codec audio haute qualité"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Connecté aux paramètres audio du média"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Connecté aux paramètres audio du téléphone"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Connexion au serveur de transfert de fichiers"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Cette fonctionnalité est expérimentale et peut affecter les performances."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Il reste environ <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Temps restant en fonction de votre utilisation : environ <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> avant charge complète"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Temps restant : <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Temps restant en fonction de votre utilisation : <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - encore environ <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – Temps restant en fonction de votre utilisation : environ <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – Temps restant : <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la charge complète"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 2fc72cb..6116f6f 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Conectado a través de %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Dispoñible a través de %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Conectado, pero sen Internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Desconectado"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Desconectando..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Conectando..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Conectado (sen acceso ás mensaxes)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Conectado (ningún teléfono nin soporte)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Audio multimedia"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Audio do teléfono"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Transferencia de ficheiros"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Dispositivo de entrada"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Acceso a Internet"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Compartir contactos"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Utilizar para compartir contactos"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Uso compartido da conexión a Internet"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Acceso ás mensaxes"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Acceso á SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Usar audio de alta calidade: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Usar audio de alta calidade"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Conectado ao audio multimedia"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Conectado ao audio do teléfono"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Conectado ao servidor de transferencia de ficheiros"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Esta función é experimental e pode afectar ao rendemento."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Anulado por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Tempo que queda aproximadamente: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Tempo restante aproximado en función do uso: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Tempo que queda ata cargar de todo: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Tempo restante: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Tempo restante en función do uso: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> (tempo restante aproximado: <xliff:g id="TIME">%2$s</xliff:g>)"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - Tempo restante aproximado en función do uso: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> (tempo restante: <xliff:g id="TIME">%2$s</xliff:g>)"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ata completar a carga"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index ed1a382..9bf91a3 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s દ્વારા કનેક્ટ થયેલ"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s દ્વારા ઉપલબ્ધ"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"કનેક્ટ કર્યું, કોઈ ઇન્ટરનેટ નથી"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"ડિસ્કનેક્ટ કર્યું"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"ડિસ્કનેક્ટ થઈ રહ્યું છે..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"કનેક્ટ થઈ રહ્યું છે…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"કનેક્ટ કર્યું (કોઇ સંદેશ ઍક્સેસ નથી)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"કનેક્ટ કરેલ (કોઈ ફોન અથવા મીડિયા નથી)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"મીડિયા ઑડિઓ"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"ફોન ઑડિઓ"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"ફાઇલ સ્થાનાંતરણ"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"ઇનપુટ ઉપકરણ"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"ઇન્ટરનેટ ઍક્સેસ"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"સંપર્ક શેરિંગ"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"સંપર્ક શેરિંગ માટે ઉપયોગ કરો"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"ઇન્ટરનેટ કનેક્શન શેરિંગ"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"સંદેશ ઍક્સેસ"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM ઍક્સેસ"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"ઉચ્ચ ગુણવત્તાવાળા ઑડિયોનો ઉપયોગ કરો: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"ઉચ્ચ ગુણવત્તાવાળા ઑડિયોનો ઉપયોગ કરો"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"મીડિયા ઑડિઓ સાથે કનેક્ટ કર્યુ"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"ફોન ઑડિઓ સાથે કનેક્ટ થયાં"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"ફાઇલ સ્થાનાંતરણ સેવાથી કનેક્ટ થયાં"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"આ સુવિધા પ્રાયોગિક છે અને કામગીરી પર અસર કરી શકે છે."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> દ્વારા ઓવરરાઇડ થયું"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"અંદાજે <xliff:g id="TIME">%1$s</xliff:g> બાકી"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"તમારા વપરાશનાં આધારે લગભગ <xliff:g id="TIME">%1$s</xliff:g> બાકી છે"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"સંપૂર્ણપણે ચાર્જ થવામાં <xliff:g id="TIME">%1$s</xliff:g> બાકી"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> બાકી"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"તમારા વપરાશનાં આધારે <xliff:g id="TIME">%1$s</xliff:g> બાકી છે"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - લગભગ <xliff:g id="TIME">%2$s</xliff:g> બાકી"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - તમારા વપરાશનાં આધારે લગભગ <xliff:g id="TIME">%2$s</xliff:g> બાકી છે"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> બાકી"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - સંપૂર્ણપણે ચાર્જ થવા માટે <xliff:g id="TIME">%2$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index a4a75c5..a7a01ec 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s के द्वारा उपलब्ध"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s के द्वारा उपलब्ध"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"कनेक्ट किया गया, इंटरनेट नहीं"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"डिस्कनेक्ट किया गया"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"डिस्कनेक्ट हो रहा है..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"कनेक्ट हो रहा है..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"कनेक्ट किया गया (कोई संदेश एक्सेस नहीं)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"कनेक्ट है (फ़ोन या मीडिया नहीं)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"मीडिया ऑडियो"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"फ़ोन ऑडियो"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"फ़ाइल स्थानांतरण"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"इनपुट डिवाइस"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"इंटरनेट पहुंच"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"संपर्क साझाकरण"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"संपर्क साझाकरण के लिए उपयोग करें"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"इंटरनेट कनेक्शन साझाकरण"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"संदेश एक्सेस"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"सिम ऐक्सेस"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"अच्छी गुणवत्ता वाले ऑडियो का उपयोग करें: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"अच्छी गुणवत्ता वाले ऑडियो का उपयोग करें"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"मीडिया ऑडियो से कनेक्ट किया गया"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"फ़ोन ऑडियो से कनेक्ट किया गया"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"फ़ाइल स्थानांतरण सर्वर से कनेक्ट किया गया"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"यह सुविधा प्रायोगिक है और निष्पादन को प्रभावित कर सकती है."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> के द्वारा ओवरराइड किया गया"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"लगभग <xliff:g id="TIME">%1$s</xliff:g> शेष"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"आपके उपयोग के आधार पर लगभग <xliff:g id="TIME">%1$s</xliff:g> का समय बचा है"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"पूरी तरह से चार्ज होने में <xliff:g id="TIME">%1$s</xliff:g> शेष"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> शेष"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"आपके उपयोग के आधार पर <xliff:g id="TIME">%1$s</xliff:g> का समय बचा है"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - लगभग <xliff:g id="TIME">%2$s</xliff:g> शेष"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - आपके उपयोग के आधार पर लगभग <xliff:g id="TIME">%2$s</xliff:g> का समय बचा है"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> शेष"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> पूरी तरह से चार्ज होने तक"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index f4a7497..05f13e4 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Povezano putem %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Dostupno putem %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Povezano, bez interneta"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Niste povezani"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Isključivanje…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Povezivanje…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Povezano (bez pristupa porukama)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Povezano (bez telefona ili medija)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Medijski zvuk"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Zvuk telefona"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Prijenos datoteke"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Ulazni uređaj"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Pristup internetu"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Dijeljenje kontakata"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Upotrijebi za dijeljenje kontakata"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Dijeljenje internetske veze"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Pristup porukama"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Pristup SIM-u"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Upotrebljavaj visokokvalitetan audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Upotrebljavaj visokokvalitetan audio"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Povezano s medijskim zvukom"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Povezano sa telefonskim zvukom"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Povezano s poslužiteljem za prijenos datoteka"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Ova je značajka eksperimentalna i može utjecati na performanse."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Premošćeno postavkom <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Još otprilike <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Još otprilike <xliff:g id="TIME">%1$s</xliff:g> na temelju vaše upotrebe"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Još <xliff:g id="TIME">%1$s</xliff:g> do potpune napunjenosti"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Još <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Još <xliff:g id="TIME">%1$s</xliff:g> na temelju vaše upotrebe"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – preostalo je približno <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – još otprilike <xliff:g id="TIME">%2$s</xliff:g> na temelju vaše upotrebe"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – još <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do potpune napunjenosti"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index f800782..eab7fc2 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Csatlakozva a következőn keresztül: %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Elérhető a következőn keresztül: %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Csatlakozva, nincs internetelérés"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Szétkapcsolva"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Szétkapcsolás..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Csatlakozás…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Csatlakoztatva (nincs üzenet-hozzáférés)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Csatlakoztatva (nincs telefon vagy hordozó)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Média audió"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Telefon hangja"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Fájlátvitel"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Beviteli eszköz"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Internetelérés"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Névjegyek megosztása"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Használja a névjegyek megosztására"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Internetkapcsolat megosztása"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Üzenet-hozzáférés"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM-elérés"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Jó audiominőség használata: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Jó audiominőség használata"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Csatlakoztatva az eszköz hangjához"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Csatlakoztatva a telefon hangjához"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Csatlakozva a fájlküldő szerverhez"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Ez egy kísérleti funkció, és hatással lehet a teljesítményre."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Felülírva erre: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Körülbelül <xliff:g id="TIME">%1$s</xliff:g> maradt hátra"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Körülbelül <xliff:g id="TIME">%1$s</xliff:g> van hátra az eszköz igénybevétele alapján"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> a teljes töltöttségig"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> van hátra"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> van hátra az eszköz igénybevétele alapján"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - körülbelül <xliff:g id="TIME">%2$s</xliff:g> van hátra"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – körülbelül <xliff:g id="TIME">%2$s</xliff:g> van hátra az eszköz igénybevétele alapján"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> van hátra"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> a teljes feltöltésig"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 7643240..62e3a2a 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Կապակցված է %1$s-ի միջոցով"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Հասանելի է %1$s-ի միջոցով"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Կապակցված է առանց համացանցի"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Անջատված է"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Անջատվում է..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Միանում է..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Միացված է (հաղորդագրությանը մուտք չկա)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Միացված է (առանց հեռախոսի և մեդիայի)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Մեդիա աուդիո"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Հեռախոսի աուդիո"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Ֆայլերի փոխանցում"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Ներմուծման սարք"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Ինտերնետի մուտք"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Կոնտակտի համօգտագործում"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Օգտագործել կոնտակտի համօգտագործման համար"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Ինտերնետ կապի տարածում"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Մուտք հաղորդագրություն"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM քարտի հասանելիություն"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Օգտագործել բարձրորակ ձայն՝ <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Օգտագործել բարձրորակ ձայն"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Միացված է մեդիա աուդիոյին"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Միացված է հեռախոսի ձայնային տվյալներին"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Միացված է ֆայլերի փոխանցման սերվերին"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Սա փորձնական գործառույթ է և կարող է ազդել սարքի աշխատանքի վրա:"</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Գերազանցված է <xliff:g id="TITLE">%1$s</xliff:g>-ից"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Մնացել է մոտ <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Մնացել է մոտ <xliff:g id="TIME">%1$s</xliff:g>՝ օգտագործման եղանակից կախված"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Մինչև լրիվ լիցքավորումը մնացել է <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Մնացել է <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Մնացել է <xliff:g id="TIME">%1$s</xliff:g>՝ օգտագործման եղանակից կախված"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – մնացել է մոտ <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - մնացել է մոտ <xliff:g id="TIME">%2$s</xliff:g>՝ օգտագործման եղանակից կախված"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - մնացել է <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> մինչև լրիվ լիցքավորումը"</string>
diff --git a/packages/SettingsLib/res/values-in/arrays.xml b/packages/SettingsLib/res/values-in/arrays.xml
index 0b19a7b..7f02a6a 100644
--- a/packages/SettingsLib/res/values-in/arrays.xml
+++ b/packages/SettingsLib/res/values-in/arrays.xml
@@ -45,7 +45,7 @@
<item msgid="7698638434317271902">"Diputus dari <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item>
<item msgid="197508606402264311">"Sambungan terputus"</item>
<item msgid="8578370891960825148">"Gagal"</item>
- <item msgid="5660739516542454527">"Dicekal"</item>
+ <item msgid="5660739516542454527">"Diblokir"</item>
<item msgid="1805837518286731242">"Menghindari sambungan buruk untuk sementara"</item>
</string-array>
<string-array name="hdcp_checking_titles">
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 3aea66b..5b5b452 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Terhubung melalui %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Tersedia melalui %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Tersambung, tidak ada internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Sambungan terputus"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Memutus sambungan..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Menyambung…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Tersambung (tidak ada akses pesan)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Terhubung (bukan telepon atau media)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Audio media"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Audio telepon"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Transfer file"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Perangkat masukan"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Akses Internet"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Berbagi kontak"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Gunakan untuk berbagi kontak"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Berbagi sambungan internet"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Akses Pesan"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Akses SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Gunakan audio berkualitas tinggi: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Gunakan audio berkualitas tinggi"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Tersambung ke media audio"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Tersambung ke audio ponsel"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Sambungkan ke server transfer file"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Fitur ini bersifat eksperimental dan dapat memengaruhi kinerja."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Digantikan oleh <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Sekitar <xliff:g id="TIME">%1$s</xliff:g> lagi"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Kira-kira <xliff:g id="TIME">%1$s</xliff:g> lagi berdasarkan penggunaan Anda"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> lagi hingga terisi penuh"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> tersisa"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> lagi berdasarkan penggunaan Anda"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - kira-kira <xliff:g id="TIME">%2$s</xliff:g> lagi"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - kira-kira <xliff:g id="TIME">%2$s</xliff:g> lagi berdasarkan penggunaan Anda"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> tersisa"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi terisi penuh"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 9e3eb22..3f9aafd 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Tengt í gegnum %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Í boði í gegnum %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Tengt, enginn internetaðgangur"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Aftengt"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Aftengist…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Tengist…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Tengt (enginn skilaboðaaðgangur)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Tengt (ekki sími eða efnisspilun)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Hljóð efnis"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Hljóð síma"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Skráaflutningur"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Inntakstæki"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Internetaðgangur"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Deiling tengiliða"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Nota til að deila tengiliðum"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Deiling nettengingar"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Skilaboðaaðgangur"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Aðgangur að SIM-korti"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Nota hágæðahljóð: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Nota hágæðahljóð"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Tengt við hljóðspilun efnis"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Tengt við hljóð símans"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Tengt við skráaflutningsþjón"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Þessi eiginleiki er á tilraunastigi og getur haft áhrif á frammistöðu."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Hnekkt af <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Um það bil <xliff:g id="TIME">%1$s</xliff:g> eftir"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"U.þ.b. <xliff:g id="TIME">%1$s</xliff:g> eftir miðað við notkun þína"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> þar til hleðslu er lokið"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> eftir"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> eftir miðað við notkun þína"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – um <xliff:g id="TIME">%2$s</xliff:g> eftir"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – u.þ.b. <xliff:g id="TIME">%2$s</xliff:g> eftir miðað við notkun þína"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> eftir"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> þar til fullri hleðslu er náð"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 5681b76..99377e1 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Collegato tramite %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Disponibile tramite %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Connesso senza Internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Disconnesso"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Disconnessione..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Connessione..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Connesso (nessun accesso ai messaggi)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Collegato (telef. o conten. mult. esclusi)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Audio multimediale"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Audio telefono"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Trasferimento file"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Dispositivo di input"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Accesso Internet"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Condivisione contatti"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Usa per condivisione contatti"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Condivisione connessione Internet"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Accesso ai messaggi"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Accesso alla SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Utilizza audio di alta qualità: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Utilizza audio di alta qualità"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Collegato ad audio media"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Collegato ad audio telefono"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Collegato al server di trasferimento file"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Questa funzione è sperimentale e potrebbe influire sulle prestazioni."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Valore sostituito da <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Tempo approssimativo rimanente: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Tempo rimanente in base al tuo utilizzo: <xliff:g id="TIME">%1$s</xliff:g> circa"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Tempo rimanente alla carica completa: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Tempo rimanente: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Tempo rimanente in base al tuo utilizzo: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - ancora circa <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - Tempo rimanente in base al tuo utilizzo: <xliff:g id="TIME">%2$s</xliff:g> circa"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - Tempo rimanente: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> alla carica completa"</string>
diff --git a/packages/SettingsLib/res/values-iw/arrays.xml b/packages/SettingsLib/res/values-iw/arrays.xml
index 239e8cb..917f710 100644
--- a/packages/SettingsLib/res/values-iw/arrays.xml
+++ b/packages/SettingsLib/res/values-iw/arrays.xml
@@ -152,11 +152,11 @@
</string-array>
<string-array name="select_logd_size_summaries">
<item msgid="6921048829791179331">"כבוי"</item>
- <item msgid="2969458029344750262">"64K לכל מאגר יומן"</item>
- <item msgid="1342285115665698168">"256K לכל מאגר יומן"</item>
- <item msgid="1314234299552254621">"1M לכל מאגר יומן"</item>
- <item msgid="3606047780792894151">"4M לכל מאגר יומן"</item>
- <item msgid="5431354956856655120">"16M לכל מאגר יומן"</item>
+ <item msgid="2969458029344750262">"64K לכל מאגר של יומן רישום"</item>
+ <item msgid="1342285115665698168">"256K לכל מאגר של יומן רישום"</item>
+ <item msgid="1314234299552254621">"1M לכל מאגר של יומן רישום"</item>
+ <item msgid="3606047780792894151">"4M לכל מאגר של יומן רישום"</item>
+ <item msgid="5431354956856655120">"16M לכל מאגר של יומן רישום"</item>
</string-array>
<string-array name="select_logpersist_titles">
<item msgid="1744840221860799971">"כבוי"</item>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index d0a8d58..3622a25 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"מחובר דרך %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"זמינה דרך %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"מחובר. אין אינטרנט"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"מנותק"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"מתנתק..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"מתחבר ..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"מחובר (אין גישה להודעות)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"מחובר (ללא טלפון או מדיה)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"אודיו של מדיה"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"אודיו של טלפון"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"העברת קבצים"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"מכשיר קלט"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"גישה לאינטרנט"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"שיתוף אנשי קשר"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"השתמש עבור שיתוף אנשי קשר"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"שיתוף חיבור לאינטרנט"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"גישה להודעות"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"גישה ל-SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"השתמש באודיו באיכות גבוהה: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"השתמש באודיו באיכות גבוהה"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"מחובר לאודיו של מדיה"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"מחובר לאודיו של הטלפון"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"מחובר לשרת העברת קבצים"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"תכונה זו היא ניסיונית ועשויה להשפיע על הביצועים."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"נעקף על ידי <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"עוד <xliff:g id="TIME">%1$s</xliff:g> בקירוב"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"על סמך השימוש במכשיר, הסוללה תתרוקן בעוד <xliff:g id="TIME">%1$s</xliff:g>, בקירוב"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> עד לטעינה מלאה"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"נותרו <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"על סמך השימוש במכשיר, הסוללה תתרוקן בעוד <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - הזמן הנותר: בערך <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - על סמך השימוש במכשיר, הסוללה תתרוקן בעוד <xliff:g id="TIME">%2$s</xliff:g>, בקירוב"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - נותרו <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> עד לטעינה מלאה"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 403677f..54eedc3 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s経由で接続"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s経由で使用可能"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"接続済み、インターネットは利用できません"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"切断"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"切断中..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"接続中..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"接続済み(メッセージへのアクセスなし)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"接続済み(電話/メディアを除く)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"メディアの音声"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"電話の音声"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"ファイル転送"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"入力デバイス"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"インターネットアクセス"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"連絡先の共有"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"連絡先の共有に使用"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"インターネット接続の共有"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"メッセージへのアクセス"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIMアクセス"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"高品質音声を使用: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"高品質音声を使用"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"メディアの音声に接続"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"携帯電話の音声に接続"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"ファイル転送サーバーに接続"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"この機能は試験運用機能であり、パフォーマンスに影響することがあります。"</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g>によって上書き済み"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"あと約 <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"残り時間: 約 <xliff:g id="TIME">%1$s</xliff:g>(使用状況に基づく)"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"フル充電まであと <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g>(残り時間)"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"残り時間: <xliff:g id="TIME">%1$s</xliff:g>(使用状況に基づく)"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - 残り約 <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - 残り時間: 約 <xliff:g id="TIME">%2$s</xliff:g>(使用状況に基づく)"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>(残り時間)"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - フル充電まで <xliff:g id="TIME">%2$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 30492ce..068cfef 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s-ით დაკავშირებული"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"ხელმისაწვდომია %1$s-ით"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"დაკავშირებულია, ინტერნეტის გარეშე"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"კავშირი გაწყვეტილია"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"მიმდინარეობს გათიშვა…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"მიმდინარეობს დაკავშირება…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"დაკავშირებულია (შეტყობინებაზე წვდომა არ არის)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"დაკავშირება (გარდა ტელეფონისა და მედიისა)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"მედია აუდიო"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"ტელეფონის აუდიო"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"ფაილების გადაცემა"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"შეყვანის მოწყობილობა"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"ინტერნეტზე წვდომა"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"კონტაქტის გაზიარება"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"კონტაქტის გაზიარებისთვის გამოყენება"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"ინტერნეტ კავშირის გაზიარება"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"შეტყობინებებზე წვდომა"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM წვდომა"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"მაღალხარისხიანი აუდიოს გამოყენება: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"მაღალხარისხიანი აუდიოს გამოყენება"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"დაკავშირებულია აუდიო მულტიმედიურ სისტემასთან"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"დაკავშირებულია ტელეფონის აუდიო მოწყობილობასთან"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"დაკავშირებულია ფაილების გადაცემის სერვერთან"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"ეს ფუნქცია საცდელია და შეიძლება გავლენა იქონიოს ფუნქციონალობაზე."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"უკუგებულია <xliff:g id="TITLE">%1$s</xliff:g>-ის მიერ"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"დარჩა დაახლოებით <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"დარჩა დაახლოებით <xliff:g id="TIME">%1$s</xliff:g>, ბატარეის მოხმარების გათვალისწინებით"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"სრულ დატენვამდე დარჩენილია <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"დარჩენილია <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"დარჩა <xliff:g id="TIME">%1$s</xliff:g>, ბატარეის მოხმარების გათვალისწინებით"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> — დარჩა დაახლოებით <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> — დარჩა დაახლოებით <xliff:g id="TIME">%2$s</xliff:g>, ბატარეის მოხმარების გათვალისწინებით"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> — დარჩენილია <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> — სრულ დატენვამდე დარჩა <xliff:g id="TIME">%2$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index e624348..cc52197 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s арқылы қосылған"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s арқылы қолжетімді"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Қосылған, интернет жоқ"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Ажыратылған"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Ажыратылуда…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Жалғауда..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Жалғанған (хабарлар қол жетімсіз)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Жалғанған (телефон және медиа жоқ)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Meдиа аудиосы"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Телефон аудиосы"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Файл жіберу"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Кіріс құрылғысы"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Интернетке қосылу"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Контактіні бөлісу"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Контактіні бөлісу үшін пайдалану"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Интернет байланысын ортақ қолдану"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Хабарға кіру"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM картасына кіру"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Жоғары сапалы аудио дыбысын пайдалану: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Жоғары сапалы аудио дыбысын пайдалану"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Медиа аудиосына жалғанған"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Телефон аудиосына қосылған"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Файл жіберу серверіне жалғанған"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Бұл мүмкіндік эксперименттік болып табылады және өнімділікке әсер етуі мүмкін."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> үстінен басқан"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Қалған <xliff:g id="TIME">%1$s</xliff:g> туралы"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Пайдалану негізінде шамамен <xliff:g id="TIME">%1$s</xliff:g> қалды"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Толық зарядқа <xliff:g id="TIME">%1$s</xliff:g> қалды"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> қалды"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Пайдалану негізінде <xliff:g id="TIME">%1$s</xliff:g> қалды"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – шамамен <xliff:g id="TIME">%2$s</xliff:g> қалды"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - пайдалану негізінде шамамен <xliff:g id="TIME">%2$s</xliff:g> қалды"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> қалды"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – толық зарядталғанға дейін <xliff:g id="TIME">%2$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 5498ef3..03d8c09 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"បានភ្ជាប់តាមរយៈ %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"មានតាមរយៈ %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"បានភ្ជាប់ ប៉ុន្តែគ្មានអ៊ីនធឺណិតទេ"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"បានផ្ដាច់"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"កំពុងផ្ដាច់…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"កំពុងតភ្ជាប់…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"បានភ្ជាប់ (គ្មានការចូលដំណើរការសារ)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"បានតភ្ជាប់ (គ្មានទូរស័ព្ទ ឬមេឌៀ)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"សំឡេងមេឌៀ"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"សំឡេងទូរស័ព្ទ"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"ផ្ទេរឯកសារ"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"ឧបករណ៍បញ្ចូល"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"ចូលអ៊ីនធឺណិត"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"ការចែករំលែកទំនាក់ទំនង"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"ប្រើសម្រាប់ការចែករំលែកទំនាក់ទំនង"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"ចែករំលែកការតភ្ជាប់អ៊ីនធឺណិត"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"ចូលដំណើរការសារ"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"ការចូលដំណើរការស៊ីម"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"ប្រើសំឡេងគុណភាពខ្ពស់៖ <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"ប្រើសំឡេងគុណភាពខ្ពស់"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"បានភ្ជាប់ទៅអូឌីយ៉ូមេឌៀ"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"តភ្ជាប់ទៅអូឌីយ៉ូទូរស័ព្ទ"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"បានតភ្ជាប់ទៅម៉ាស៊ីនមេផ្ទេរឯកសារ"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"មុខងារនេះគឺជាការពិសោធន៍ ហើយអាចប៉ះពាល់ដំណើរការ។"</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"បដិសេធដោយ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"សល់ប្រហែល <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"សល់ប្រហែល <xliff:g id="TIME">%1$s</xliff:g> ទៀតផ្អែកលើការប្រើប្រាស់របស់អ្នក"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"សល់ <xliff:g id="TIME">%1$s</xliff:g> ទើបសាកថ្មពេញ"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"នៅសល់ <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"សល់ <xliff:g id="TIME">%1$s</xliff:g> ទៀតផ្អែកលើការប្រើប្រាស់របស់អ្នក"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - សល់ប្រហែល <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - សល់ប្រហែល <xliff:g id="TIME">%2$s</xliff:g> ទៀតផ្អែកលើការប្រើប្រាស់របស់អ្នក"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - នៅសល់ <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> រហូតដល់សាកពេញ"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index ab2235a..893032c 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s ಮೂಲಕ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s ಮೂಲಕ ಲಭ್ಯವಿದೆ"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆ, ಇಂಟರ್ನೆಟ್ ಇಲ್ಲ"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲಾಗುತ್ತಿದೆ..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"ಸಂಪರ್ಕಗೊಳಿಸಲಾಗುತ್ತಿದೆ..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆ (ಯಾವುದೇ ಸಂದೇಶ ಪ್ರವೇಶವಿಲ್ಲ)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"ಸಂಪರ್ಕಗೊಂಡಿದೆ (ಫೋನ್ ಅಥವಾ ಮಾಧ್ಯಮವಿಲ್ಲ)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"ಮಾಧ್ಯಮ ಆಡಿಯೋ"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"ಫೋನ್ ಆಡಿಯೋ"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"ಫೈಲ್ ವರ್ಗಾವಣೆ"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"ಇನ್ಪುಟ್ ಸಾಧನ"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"ಇಂಟರ್ನೆಟ್ ಪ್ರವೇಶ"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"ಸಂಪರ್ಕ ಹಂಚಿಕೆ"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"ಸಂಪರ್ಕ ಹಂಚಿಕೆಗಾಗಿ ಬಳಸಿ"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"ಇಂಟರ್ನೆಟ್ ಸಂಪರ್ಕ ಹಂಚಿಕೊಳ್ಳುವಿಕೆ"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"ಸಂದೇಶ ಪ್ರವೇಶಿಸುವಿಕೆ"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"ಸಿಮ್ ಪ್ರವೇಶ"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"ಉನ್ನತ ಗುಣಮಟ್ಟದ ಆಡಿಯೋ ಬಳಸಿ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"ಉನ್ನತ ಗುಣಮಟ್ಟದ ಆಡಿಯೋ ಬಳಸಿ"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"ಮಾಧ್ಯಮ ಆಡಿಯೋಗೆ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"ಫೋನ್ ಆಡಿಯೋಗೆ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"ಫೈಲ್ ವರ್ಗಾವಣೆ ಸರ್ವರ್ಗೆ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"ಇದು ಪ್ರಾಯೋಗಿಕ ವೈಶಿಷ್ಟ್ಯವಾಗಿದೆ. ಕಾರ್ಯಕ್ಷಮತೆ ಮೇಲೆ ಪರಿಣಾಮ ಬೀರಬಹುದು."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> ಮೂಲಕ ಅತಿಕ್ರಮಿಸುತ್ತದೆ"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"ಸುಮಾರು <xliff:g id="TIME">%1$s</xliff:g> ಬಾಕಿಯಿದೆ"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"ನಿಮ್ಮ ಬಳಕೆಯ ಆಧಾರದ ಮೇಲೆ ಸುಮಾರು <xliff:g id="TIME">%1$s</xliff:g> ಉಳಿದಿದೆ"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"ಸಂಪೂರ್ಣ ಚಾರ್ಜ್ ಆಗಲು <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> ಉಳಿದಿದೆ"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"ನಿಮ್ಮ ಬಳಕೆಯ ಆಧಾರದ ಮೇಲೆ <xliff:g id="TIME">%1$s</xliff:g> ಉಳಿದಿದೆ"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಸುಮಾರು <xliff:g id="TIME">%2$s</xliff:g> ಬಾಕಿಯಿದೆ"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - ನಿಮ್ಮ ಬಳಕೆಯ ಆಧಾರದ ಮೇಲೆ <xliff:g id="TIME">%2$s</xliff:g> ಉಳಿದಿದೆ"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ಉಳಿದಿದೆ"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಸಂಪೂರ್ಣ ಚಾರ್ಜ್ ಆಗಲು <xliff:g id="TIME">%2$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 3369a26..74d4d1c 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s을(를) 통해 연결됨"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s을(를) 통해 사용 가능"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"인터넷을 사용하지 않고 연결됨"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"연결 끊김"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"연결을 끊는 중…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"연결 중…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"연결됨(메시지 액세스 없음)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"연결됨(전화 또는 미디어 없음)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"미디어 오디오"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"휴대폰 오디오"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"파일 전송"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"입력 장치"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"인터넷 액세스"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"연락처 공유"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"연락처 공유용"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"인터넷 연결 공유"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"메시지 액세스"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM 액세스"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"고품질 오디오 사용: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"고품질 오디오 사용"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"미디어 오디오에 연결됨"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"휴대전화 오디오에 연결됨"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"파일 전송 서버에 연결됨"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"실험실 기능이며 성능에 영향을 줄 수 있습니다."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> 우선 적용됨"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"약 <xliff:g id="TIME">%1$s</xliff:g> 남음"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"내 사용량을 기준으로 약 <xliff:g id="TIME">%1$s</xliff:g> 남음"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"충전 완료까지 <xliff:g id="TIME">%1$s</xliff:g> 남음"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> 남음"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"내 사용량을 기준으로 <xliff:g id="TIME">%1$s</xliff:g> 남음"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - 약 <xliff:g id="TIME">%2$s</xliff:g> 남음"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - 내 사용량을 기준으로 약 <xliff:g id="TIME">%2$s</xliff:g> 남음"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> 남음"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - 충전 완료까지 <xliff:g id="TIME">%2$s</xliff:g> 남음"</string>
diff --git a/packages/SettingsLib/res/values-ky/arrays.xml b/packages/SettingsLib/res/values-ky/arrays.xml
index 1d7a9a4..9c8e28a 100644
--- a/packages/SettingsLib/res/values-ky/arrays.xml
+++ b/packages/SettingsLib/res/values-ky/arrays.xml
@@ -50,8 +50,8 @@
</string-array>
<string-array name="hdcp_checking_titles">
<item msgid="441827799230089869">"Эч качан текшерилбесин"</item>
- <item msgid="6042769699089883931">"DRM мазмунун гана текшерүү"</item>
- <item msgid="9174900380056846820">"Ар дайым текшерүү"</item>
+ <item msgid="6042769699089883931">"DRM мазмуну гана текшерилсин"</item>
+ <item msgid="9174900380056846820">"Ар дайым текшерилсин"</item>
</string-array>
<string-array name="hdcp_checking_summaries">
<item msgid="505558545611516707">"Эч качан HDCP текшерүү колдонулбасын"</item>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 9b0a4d8..9989535 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s аркылуу жеткиликтүү"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s аркылуу жеткиликтүү"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Туташып турат, Интернет жок"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Ажыратылган"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Ажыратылууда…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Туташууда…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Байланышта (билдирүү алмашуу жок)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Туташып турат (телефониясыз же медиасыз)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Аудио"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Телефон"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Файл алмашуу"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Киргизүү түзмөгү"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Интернетке мүмкүнчүлүк алуу"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Байланышты бөлүшүү"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Байланышты бөлүшүү үчүн колдонуу"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Интернет байланышын бөлүшүү"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Билдирүү алмашуу"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM картаны пайдалануу мүмкүнчүлүгү"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Жогорку сапаттагы аудио кодекти колдонуу: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Жогорку сапаттагы аудио кодекти колдонуу"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Медиа аудиого туташты"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Телефон аудиосуна туташты"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Файл өткөрүү серверине туташты"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Бул сынамык мүмкүнчүлүк болгондуктан, түзмөктүн иштешине таасир этиши мүмкүн."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> менен алмаштырылган"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Батарея түгөнгөнгө чейин калган убакыт: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Колдонушуңузга караганда болжол менен <xliff:g id="TIME">%1$s</xliff:g> калды"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Батарея толгонго чейин калган убакыт: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> калды"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Колдонушуңузга караганда <xliff:g id="TIME">%1$s</xliff:g> калды"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – болжол менен <xliff:g id="TIME">%2$s</xliff:g> калды"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - колдонушуңузга караганда болжол менен <xliff:g id="TIME">%2$s</xliff:g> калды"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> калды"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> кийин толук кубатталат"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index aacee8b..3c236181 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"ເຊື່ອມຕໍ່ຜ່ານ %1$s ແລ້ວ"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"ມີໃຫ້ຜ່ານ %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"ເຊື່ອມຕໍ່ແລ້ວ, ບໍ່ມີອິນເຕີເນັດ"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"ຕັດການເຊື່ອມຕໍ່ແລ້ວ"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"ກຳລັງຢຸດການເຊື່ອມຕໍ່..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"ກຳລັງເຊື່ອມຕໍ່..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"ເຊື່ອມຕໍ່ (ບໍ່ມີການເຂົ້າເຖິງຂໍ້ຄວາມ)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"ເຊື່ອມຕໍ່ແລ້ວ (ບໍ່ມີໂທລະສັບ ຫຼືສື່)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"ສຽງ"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"ສຽງໂທລະສັບ"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"ການໂອນຍ້າຍໄຟລ໌"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"ອຸປະກອນປ້ອນຂໍ້ມູນ"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"ການເຂົ້າເຖິງອິນເຕີເນັດ"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"ການແບ່ງປັນລາຍຊື່ຜູ່ຕິດຕໍ່"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"ໃຊ້ສຳລັບການແບ່ງປັນລາຍຊື່ຜູ່ຕິດຕໍ່"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"ການແບ່ງປັນການເຊື່ອມຕໍ່ອິນເຕີເນັດ"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"ການເຂົ້າເຖິງຂໍ້ຄວາມ"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"ການເຂົ້າເຖິງ SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"ໃຊ້ສຽງຄຸນນະພາບສູງ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"ໃຊ້ສຽງຄຸນນະພາບສູງ"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"ເຊື່ອມຕໍ່ກັບສື່ດ້ານສຽງແລ້ວ"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"ເຊື່ອມຕໍ່ກັບສຽງໂທລະສັບແລ້ວ"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"ເຊື່ອມຕໍ່ກັບເຊີບເວີໂອນຍ້າຍໄຟລ໌ແລ້ວ"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"ຄຸນສົມບັດນີ້ກຳລັງຢູ່ໃນການທົດລອງແລະອາດມີຜົນຕໍ່ປະສິດທິພາບ."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"ຖືກແທນໂດຍ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"ອີກປະມານ <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"ເຫຼືອອີກປະມານ <xliff:g id="TIME">%1$s</xliff:g> ໂດຍອ້າງອີງຈາກການນຳໃຊ້ຂອງທ່ານ"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> ຈົນກວ່າຈະສາກເຕັມ"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"ຍັງເຫຼືອ <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"ເຫຼືອອີກ <xliff:g id="TIME">%1$s</xliff:g> ໂດຍອ້າງອີງຈາກການນຳໃຊ້ຂອງທ່ານ"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - ຍັງເຫຼືອປະມານ <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - ເຫຼືອອີກປະມານ <xliff:g id="TIME">%2$s</xliff:g> ໂດຍອ້າງອີງຈາກການນຳໃຊ້ຂອງທ່ານ"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - ຍັງເຫຼືອ <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ຈົນກວ່າຈະສາກເຕັມ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 97de7d5..8659754 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Prisijungta naudojant „%1$s“"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Pasiekiama naudojant „%1$s“"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Prisijungta, nėra interneto"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Atsijungęs (-usi)"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Atjungiama..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Prisijungiama..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Prisijungta (be prieigos prie pranešimų)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Prijungta (be telefono ar laikmenos)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Laikmenos garsas"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Telefono garsas"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Failo perkėlimas"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Įvesties įrenginys"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Prieiga prie interneto"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Kontaktų bendrinimas"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Naudoti kontaktams bendrinti"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Interneto ryšio bendrinimas"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Prieiga prie pranešimų"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM prieiga"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Naudoti aukštos kokybės garsą: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Naudoti aukštos kokybės garsą"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Prijungta prie medijos garso įrašo"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Prijungta prie telefono garso"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Prijungta prie failų perkėlimo serverio"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Ši funkcija yra eksperimentinė ir ji gali turėti įtakos našumui."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Nepaisyta naudojant nuostatą „<xliff:g id="TITLE">%1$s</xliff:g>“"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Liko maždaug <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Liko maždaug <xliff:g id="TIME">%1$s</xliff:g>, atsižvelgiant į naudojimą"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Iki visiškos įkrovos liko <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Liko <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Liko <xliff:g id="TIME">%1$s</xliff:g>, atsižvelgiant į naudojimą"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – liko maždaug <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – liko maždaug <xliff:g id="TIME">%2$s</xliff:g>, atsižvelgiant į naudojimą"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – liko <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> iki visiško įkrovimo"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 98c8156..54310a6 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Savienots, izmantojot %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Pieejams, izmantojot %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Savienots, nav piekļuves internetam"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Atvienots"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Notiek atvienošana..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Notiek savienojuma izveide…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Savienots (nav piekļuves ziņojumam)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Sav. ir izveidots (nav tel. vai multiv.)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Multivides audio"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Tālruņa audio"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Failu pārsūtīšana"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Ievades ierīce"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Interneta piekļuve"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Kontaktpersonas informācijas kopīgošana"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Izmantot kontaktpersonas informācijas kopīgošanai"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Interneta savienojuma koplietošana"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Piekļuve ziņojumam"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Piekļuve SIM kartei"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Izmantot augstas kvalitātes audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Izmantot augstas kvalitātes audio"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Savienots ar multivides audio"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Savienots ar tālruņa audio"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Savienots ar failu pārsūtīšanas serveri"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Šī funkcija ir eksperimentāla un var ietekmēt veiktspēju."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Jaunā preference: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Atlikušais laiks: aptuveni <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Atlikušais laiks: aptuveni <xliff:g id="TIME">%1$s</xliff:g> (ņemot vērā lietojumu)"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Atlikušais laiks līdz pilnai uzlādei: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Atlicis: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Atlikušais laiks: <xliff:g id="TIME">%1$s</xliff:g> (ņemot vērā lietojumu)"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - vēl apmēram <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> — atlikušais laiks: aptuveni <xliff:g id="TIME">%2$s</xliff:g> (ņemot vērā lietojumu)"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> — atlicis: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> — <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>, kamēr pilnībā uzlādēts"</string>
diff --git a/packages/SettingsLib/res/values-mk/arrays.xml b/packages/SettingsLib/res/values-mk/arrays.xml
index 16ba336..2da43f0 100644
--- a/packages/SettingsLib/res/values-mk/arrays.xml
+++ b/packages/SettingsLib/res/values-mk/arrays.xml
@@ -248,7 +248,7 @@
<item msgid="488237561639712799">"Се полни"</item>
<item msgid="5220695614993094977">"МТП (Протокол за трансфер на медиуми)"</item>
<item msgid="2086000968159047375">"ПТП (Протокол за трансфер на слика)"</item>
- <item msgid="7398830860950841822">"РНДИС (УСБ за етернет)"</item>
+ <item msgid="7398830860950841822">"РНДИС (USB за етернет)"</item>
<item msgid="1718924214939774352">"Аудиоизвор"</item>
<item msgid="8126315616613006284">"МИДИ"</item>
</string-array>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 2bafd1f..e33e7a0 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Поврзано преку %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Достапно преку %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Поврзана, нема интернет"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Исклучено"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Се исклучува..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Се поврзува..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Поврзано (без порака за пристап)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Поврзан (без телефон или медиуми)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Аудио на медиуми"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Аудио на телефон"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Пренос на датотека"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Влезен уред"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Пристап на интернет"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Споделување контакти"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Користи за споделување контакти"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Споделување конекција на интернет"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Порака за пристап"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Пристап до SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Користи висококвалитетно аудио: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Користи висококвалитетно аудио"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Поврзан со аудио на медиуми"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Поврзан со аудио на телефон"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Поврзан со сервер за пренос на датотеки"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Функцијата е експериментална и може да влијае на изведбата."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Прескокнато според <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Преостануваат околу <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Уште околу <xliff:g id="TIME">%1$s</xliff:g> според користењето"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Преостануваат <xliff:g id="TIME">%1$s</xliff:g> дури се наполни целосно"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"уште <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Уште <xliff:g id="TIME">%1$s</xliff:g> според користењето"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - уште околу <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - уште околу <xliff:g id="TIME">%2$s</xliff:g> според користењето"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - уште <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> дури се наполни целосно"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 2471879..3a9435e 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s വഴി ബന്ധിപ്പിച്ചു"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s വഴി ലഭ്യം"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"കണക്റ്റുചെയ്തിരിക്കുന്നു, ഇന്റർനെറ്റില്ല"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"വിച്ഛേദിച്ചു"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"വിച്ഛേദിക്കുന്നു..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"കണക്റ്റുചെയ്യുന്നു..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"കണക്റ്റുചെയ്തു (സന്ദേശ ആക്സസ്സില്ല)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"കണക്റ്റുചെയ്തു (ഫോണോ മീഡിയയോ അല്ല)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"മീഡിയ ഓഡിയോ"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"ഫോൺ ഓഡിയോ"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"ഫയൽ കൈമാറൽ"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"ഇൻപുട്ട് ഉപകരണം"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"ഇന്റർനെറ്റ് ആക്സസ്സ്"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"കോൺടാക്റ്റ് പങ്കിടൽ"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"കോൺടാക്റ്റ് പങ്കിടലിനായി ഉപയോഗിക്കുക"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"ഇന്റർനെറ്റ് കണക്ഷൻ പങ്കിടൽ"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"ആക്സസ്സ് നിയന്ത്രിക്കുക"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM ആക്സസ്"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"ഉയർന്ന നിലവാരമുള്ള ഓഡിയോ ഉപയോഗിക്കുക: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"ഉയർന്ന നിലവാരമുള്ള ഓഡിയോ ഉപയോഗിക്കുക"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"മീഡിയ ഓഡിയോയിലേക്ക് കണക്റ്റുചെയ്തു"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"ഫോൺ ഓഡിയോയിൽ കണക്റ്റുചെയ്തു"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"ഫയൽ കൈമാറ്റ സെർവറിലേക്ക് കണക്റ്റുചെയ്തു"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"ഈ ഫീച്ചർ പരീക്ഷണാത്മകമായതിനാൽ പ്രകടനത്തെ ബാധിച്ചേക്കാം."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> ഉപയോഗിച്ച് അസാധുവാക്കി"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"ഏകദേശം <xliff:g id="TIME">%1$s</xliff:g> ശേഷിക്കുന്നു"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"നിങ്ങളുടെ ഉപയോഗത്തെ അടിസ്ഥാനമാക്കി ഏതാണ്ട് <xliff:g id="TIME">%1$s</xliff:g> ശേഷിക്കുന്നു"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"മുഴുവൻ ചാർജാകാൻ <xliff:g id="TIME">%1$s</xliff:g> ശേഷിക്കുന്നു"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> ശേഷിക്കുന്നു"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"നിങ്ങളുടെ ഉപയോഗത്തെ അടിസ്ഥാനമാക്കി <xliff:g id="TIME">%1$s</xliff:g> ശേഷിക്കുന്നു"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - ഏതാണ്ട് <xliff:g id="TIME">%2$s</xliff:g> ശേഷിക്കുന്നു"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - നിങ്ങളുടെ ഉപയോഗത്തെ അടിസ്ഥാനമാക്കി ഏതാണ്ട് <xliff:g id="TIME">%2$s</xliff:g> ശേഷിക്കുന്നു"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ശേഷിക്കുന്നു"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - ഫുൾ ചാർജാകാൻ <xliff:g id="TIME">%2$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 769ef5c..02266da 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s-р холбогдсон"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s-р боломжтой"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Холбогдсон, интернэт байхгүй байна"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Салгагдсан"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Салгаж байна…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Холбогдож байна..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Холбогдсон (зурвас хандалт байхгүй)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Холбогдсон (утас буюу медиа байхгүй)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Медиа аудио"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Утасны аудио"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Файл дамжуулалт"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Оруулах төхөөрөмж"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Интернэт хандалт"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Харилцагч хуваалцах"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Харилцагч хуваалцахад ашиглах"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Интернэт холболтыг хуваалцах"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Зурвас хандалт"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM Хандалт"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Өндөр чанарын аудио ашиглах: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Өндөр чанарын аудио ашиглах"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Медиа аудиод холбогдсон"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Утасны аудид холбогдсон"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Файл дамжуулах серверт холбогдсон"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Энэ функц туршилтынх бөгөөд ажиллагаанд нөлөөлж болзошгүй."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Давхарласан <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Ойролцоогоор <xliff:g id="TIME">%1$s</xliff:g> үлдсэн"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Таны хэрэглээнд тулгуурлан <xliff:g id="TIME">%1$s</xliff:g> орчмын хугацаа үлдсэн байна"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Бүрэн цэнэглэх хүртэл <xliff:g id="TIME">%1$s</xliff:g> үлдсэн"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> үлдсэн"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Таны хэрэглээнд тулгуурлан <xliff:g id="TIME">%1$s</xliff:g> орчмын хугацаа үлдсэн байна"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g>-с <xliff:g id="TIME">%2$s</xliff:g> үлдсэн"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - таны хэрэглээнд тулгуурлан <xliff:g id="TIME">%2$s</xliff:g> орчмын хугацаа үлдсэн байна"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> үлдсэн"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"бүрэн цэнэглэх хүртэл <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 0e1499b..47376eb 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s द्वारे कनेक्ट केले"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s द्वारे उपलब्ध"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"कनेक्ट केले, इंटरनेट नाही"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"डिस्कनेक्ट केले"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"डिस्कनेक्ट करत आहे..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"कनेक्ट करीत आहे..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"कनेक्ट केलेले आहे (कोणत्याही संदेशामध्ये प्रवेश नाही)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"कनेक्ट केले (फोन किंवा मीडिया नाही)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"मीडिया ऑडिओ"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"फोन ऑडिओ"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"फाइल स्थानांतरण"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"इनपुट डिव्हाइस"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"इंटरनेट प्रवेश"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"संपर्क सामायिकरण"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"संपर्क सामायिकरणासाठी वापरा"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"इंटरनेट कनेक्शन सामायिकरण"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"संदेशात प्रवेश"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"सिम प्रवेश"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"उच्च दर्जाचा ऑडिओ वापरा: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"उच्च दर्जाचा ऑडिओ वापरा"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"मीडिया ऑडिओवर कनेक्ट केले"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"फोन ऑडिओ वर कनेक्ट केले"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"फाईल स्थानांतर सर्व्हरवर कनेक्ट केले"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"हे वैशिष्ट्य प्रायोगिक आहे आणि कदाचित कार्यप्रदर्शन प्रभावित करू शकते."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारे अधिलिखित"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"सुमारे <xliff:g id="TIME">%1$s</xliff:g> शिल्लक"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"तुमच्या वापरानुसार अंदाजे <xliff:g id="TIME">%1$s</xliff:g> पुरेल इतकी बॅटरी शिल्लक आहे"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"पूर्णपणे चार्ज होण्यास <xliff:g id="TIME">%1$s</xliff:g> शिल्लक"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> शिल्लक"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"तुमच्या वापरानुसार <xliff:g id="TIME">%1$s</xliff:g> पुरेल इतकी बॅटरी शिल्लक आहे"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - सुमारे <xliff:g id="TIME">%2$s</xliff:g> शिल्लक"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - तुमच्या वापरानुसार अंदाजे <xliff:g id="TIME">%2$s</xliff:g> पुरेल इतकी बॅटरी शिल्लक आहे"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> शिल्लक"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - पूर्णपणे चार्ज होण्यात <xliff:g id="TIME">%2$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 6667585..3f0a061 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Disambungkan melalui %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Tersedia melalui %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Disambungkan, tiada Internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Diputuskan sambungan"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Memutuskan sambungan..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Menyambung..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Disambungkan (tiada akses mesej)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Disambungkan (tiada telefon atau media)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Audio media"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Audio telefon"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Pemindahan fail"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Peranti input"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Akses Internet"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Perkongsian kenalan"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Gunakan untuk perkongsian kenalan"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Perkongsian sambungan Internet"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Akses Mesej"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Akses SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Gunakan audio berkualiti tinggi: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Gunakan audio berkualiti tinggi"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Disambungkan ke audio media"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Disambungkan ke audio telefon"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Bersambung ke pelayan pemindahan fail"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Ciri ini adalah percubaan dan boleh menjejaskan prestasi."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Diatasi oleh <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Kira-kira <xliff:g id="TIME">%1$s</xliff:g> lagi"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Tinggal kira-kira <xliff:g id="TIME">%1$s</xliff:g> berdasarkan penggunaan anda"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> lagi sehingga dicas penuh"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> lagi"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Tinggal <xliff:g id="TIME">%1$s</xliff:g> berdasarkan penggunaan anda"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - kira-kira <xliff:g id="TIME">%2$s</xliff:g> lagi"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - tinggal kira-kira <xliff:g id="TIME">%2$s</xliff:g> berdasarkan penggunaan anda"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> sehingga dicas penuh"</string>
diff --git a/packages/SettingsLib/res/values-my/arrays.xml b/packages/SettingsLib/res/values-my/arrays.xml
index 85d6002..97a9c53 100644
--- a/packages/SettingsLib/res/values-my/arrays.xml
+++ b/packages/SettingsLib/res/values-my/arrays.xml
@@ -137,7 +137,7 @@
<item msgid="364670732877872677">"အကောင်းဆုံးကြိုးပမ်းမှု (ပေးပို့နှုန်း အလိုက်)"</item>
</string-array>
<string-array name="select_logd_size_titles">
- <item msgid="8665206199209698501">"ပိတ်ပါ"</item>
+ <item msgid="8665206199209698501">"ပိတ်ရန်"</item>
<item msgid="1593289376502312923">"64K"</item>
<item msgid="487545340236145324">"256K"</item>
<item msgid="2423528675294333831">"1M"</item>
@@ -145,13 +145,13 @@
<item msgid="2803199102589126938">"16M"</item>
</string-array>
<string-array name="select_logd_size_lowram_titles">
- <item msgid="6089470720451068364">"ပိတ်ပါ"</item>
+ <item msgid="6089470720451068364">"ပိတ်ရန်"</item>
<item msgid="4622460333038586791">"64K"</item>
<item msgid="2212125625169582330">"256K"</item>
<item msgid="1704946766699242653">"1M"</item>
</string-array>
<string-array name="select_logd_size_summaries">
- <item msgid="6921048829791179331">"ပိတ်ပါ"</item>
+ <item msgid="6921048829791179331">"ပိတ်ရန်"</item>
<item msgid="2969458029344750262">"မှတ်တမ်းယာယီကြားခံနယ်တစ်ခုလျှင် 64K"</item>
<item msgid="1342285115665698168">"မှတ်တမ်းယာယီကြားခံနယ်တစ်ခုလျှင် 256K"</item>
<item msgid="1314234299552254621">"မှတ်တမ်းယာယီကြားခံနယ်တစ်ခုလျှင် 1M"</item>
@@ -159,13 +159,13 @@
<item msgid="5431354956856655120">"မှတ်တမ်းယာယီကြားခံနယ်တစ်ခုလျှင် 16M"</item>
</string-array>
<string-array name="select_logpersist_titles">
- <item msgid="1744840221860799971">"ပိတ်ပါ"</item>
+ <item msgid="1744840221860799971">"ပိတ်ရန်"</item>
<item msgid="3054662377365844197">"အားလုံး"</item>
<item msgid="688870735111627832">"ရေဒီယိုမှလွဲ၍ အားလုံး"</item>
<item msgid="2850427388488887328">"ကာနယ်သာ"</item>
</string-array>
<string-array name="select_logpersist_summaries">
- <item msgid="2216470072500521830">"ပိတ်ပါ"</item>
+ <item msgid="2216470072500521830">"ပိတ်ရန်"</item>
<item msgid="172978079776521897">"မှတ်တမ်းသိမ်းဆည်းရန် လျာထားချက်များ အားလုံး"</item>
<item msgid="3873873912383879240">"ရေဒီယို မှတ်တမ်းသိမ်းဆည်းရန်လျာထားချက်မှလွဲ၍ အားလုံး"</item>
<item msgid="8489661142527693381">"ကာနယ်မှတ်တမ်းသိမ်းဆည်းရန် လျာထားချက်သာ"</item>
@@ -218,17 +218,17 @@
<item msgid="1340692776955662664">"glGetError အမှားတက်လျှင်ခေါ်သောလုပ်ငန်းစဉ်"</item>
</string-array>
<string-array name="show_non_rect_clip_entries">
- <item msgid="993742912147090253">"ပိတ်"</item>
+ <item msgid="993742912147090253">"ပိတ်ရန်"</item>
<item msgid="675719912558941285">"စတုဂံမဟုတ်သော ဖောက်ရန်အပိုင်းကို အပြာရောင်ဖြင့်ဆွဲပါ"</item>
<item msgid="1064373276095698656">"စမ်းသပ်ထားသော ပုံဆွဲရန်ညွှန်ကြားချက်များကို အစိမ်းရောင်ဖြင့် အသားပေး ဖော်ပြပါ"</item>
</string-array>
<string-array name="track_frame_time_entries">
- <item msgid="2193584639058893150">"ပိတ်"</item>
+ <item msgid="2193584639058893150">"ပိတ်ရန်"</item>
<item msgid="2751513398307949636">"ဖန်သားပြင်ပေါ်မှာ မျဉ်းတန်းကဲ့သို့"</item>
<item msgid="2355151170975410323">"<xliff:g id="AS_TYPED_COMMAND">adb shell dumpsys gfxinfo</xliff:g> ဖြင့်"</item>
</string-array>
<string-array name="debug_hw_overdraw_entries">
- <item msgid="8190572633763871652">"ပိတ်"</item>
+ <item msgid="8190572633763871652">"ပိတ်ရန်"</item>
<item msgid="7688197031296835369">"ရှိရင်းစွဲထက်ပိုသော ဧရိယာများကိုပြရန်"</item>
<item msgid="2290859360633824369">"အရောင်ရောနှောသောဧရိယာများပြရန်"</item>
</string-array>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 2572d60..89712d9 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s မှတစ်ဆင့် ချိတ်ဆက်ထားသည်"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s မှတစ်ဆင့်ရနိုင်သည်"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"ချိတ်ဆက်ထားသည်၊ အင်တာနက်မရှိ"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"ချိတ်ဆက်မှုပြတ်တောက်သည်"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"အဆက်အသွယ်ဖြတ်တောက်သည်"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"ချိတ်ဆက်နေသည်"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"ချိတ်ဆက်မိသည် (သတင်းရယူမှုမရှိ)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"ချိတ်ဆက်ပြီး (ဖုန်း သို့ မီဒီယာမဟုတ်ပါ)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"မီဒီယာ အသံ"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"ဖုန်းအသံ"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"ဖိုင်လွဲပြောင်းခြင်း"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"ထည့်သွင်းသော စက်"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"အင်တာနက်ချိတ်ဆက်ခြင်း"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"အဆက်အသွယ်ကို မျှဝေရန်"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"အဆက်အသွယ်ကို မျှဝေရန် အတွက် သုံးရန်"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"အင်တာနက်ဆက်သွယ်မှု မျှဝေခြင်း"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"သတင်းရယူမှု"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM အသုံးပြုခြင်း"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"အရည်အသွေးမြင့် အသံအမျိုးအစားကို သုံးရန်− <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"အရည်အသွေးမြင့် အသံအမျိုးအစားကိုသုံးရန်"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"မီဒီယာအသံအား ချိတ်ဆက်ရန်"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"ဖုန်းအသံအား ချိတ်ဆက်ရန်"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"ဖိုင်လွှဲပြောင်းမည့်ဆာဗာနှင့် ချိတ်ဆက်ထားပြီး"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"ဤဝန်ဆောင်မှုမှာ စမ်းသပ်အဆင့်သာဖြစ်၍ လုပ်ဆောင်မှုအားနည်းနိုင်သည်။"</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> မှ ကျော်၍ လုပ်ထားသည်။"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"<xliff:g id="TIME">%1$s</xliff:g> ခန့်လိုပါသည်"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"သင့်အသုံးပြုမှုအရ <xliff:g id="TIME">%1$s</xliff:g> ခန့် ကျန်ပါသည်"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"အားပြည့်ရန် <xliff:g id="TIME">%1$s</xliff:g> လိုပါသည်"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> ကျန်သည်"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"သင့်အသုံးပြုမှုအရ <xliff:g id="TIME">%1$s</xliff:g> ကျန်ပါသည်"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> − <xliff:g id="TIME">%2$s</xliff:g> ခန့်ကျန်သည်"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - သင့်အသုံးပြုမှုအရ <xliff:g id="TIME">%2$s</xliff:g> ခန့် ကျန်ပါသည်"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ကျန်သည်"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> − အားပြည့်ရန် <xliff:g id="TIME">%2$s</xliff:g> ကျန်သည်"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index a59b89c..d2807ce 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Tilkoblet via %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Tilgjengelig via %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Tilkoblet – ingen Internett-forbindelse"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Frakoblet"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Kobler fra…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Kobler til…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Tilkoblet (ingen meldingstilgang)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Tilkoblet (ingen telefon eller media)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Medielyd"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Telefonlyd"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Filoverføring"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Inndataenhet"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Internett-tilgang"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Kontaktdeling"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Bruk til kontaktdeling"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Deling av Internett-tilkobling"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Meldingstilgang"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Tilgang til SIM-kortet"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Bruk lyd av høy kvalitet: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Bruk lyd av høy kvalitet"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Koblet til medielyd"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Koblet til telefonlyd"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Koblet til tjener for filoverføring"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Dette er en eksperimentell funksjon som kan gjøre at telefonen ikke fungerer optimalt."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Overstyres av <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Omtrent <xliff:g id="TIME">%1$s</xliff:g> gjenstår"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Omtrent <xliff:g id="TIME">%1$s</xliff:g> igjen basert på bruken din"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> til det er fulladet"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> gjenstår"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> igjen basert på bruken din"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – omtrent <xliff:g id="TIME">%2$s</xliff:g> gjenstår"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – omtrent <xliff:g id="TIME">%2$s</xliff:g> igjen basert på bruken din"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> gjenstår"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> til det er fulladet"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 87873ca..f830a87 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s मार्फत जडित"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s मार्फत उपलब्ध"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"जडित, इन्टरनेट चलेको छैन"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"विच्छेदन गरियो"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"जडान हटाइँदै ..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"जडान हुँदै..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"जडित छ (सन्देशमा पहुँच छैन)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"जडित (फोन वा मिडिया छैन)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"मिडिया अडियो"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"फोन अडियो"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"फाइल स्थानान्तरण"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"इनपुट उपकरण"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"इन्टरनेट पहुँच"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"सम्पर्क साझेदारी"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"सम्पर्क साझेदारीका लागि प्रयोग"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"इन्टरनेट जडान साझेदारी गर्दै"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"सन्देश पहुँच"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM पहुँच"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"उच्च गुणस्तरको अडियो प्रयोग गर्नुहोस्: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"उच्च गुणस्तरको अडियो प्रयोग गर्नुहोस्"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"मिडिया अडियोसँग जडित"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"फोन अडियोमा जडान गरियो"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"फाइल ट्रान्सफर सर्भरमा जडान गरियो"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"यो सुविधा प्रयोगात्मक छ र प्रदर्शनमा असर गर्न सक्छ।"</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारा अधिरोहित"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"लगभग <xliff:g id="TIME">%1$s</xliff:g> बाँकी"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"तपाईंको प्रयोगका आधारमा लगभग <xliff:g id="TIME">%1$s</xliff:g> बाँकी"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"पूर्णरूपमा चार्ज हुन <xliff:g id="TIME">%1$s</xliff:g> बाँकी"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"बाँकी समय <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"तपाईंको प्रयोगका आधारमा <xliff:g id="TIME">%1$s</xliff:g> बाँकी"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - करिब <xliff:g id="TIME">%2$s</xliff:g> बाँकी"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - तपाईंको प्रयोगका आधारमा लगभग <xliff:g id="TIME">%2$s</xliff:g> बाँकी"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"बाँकी समय <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - पूर्णरूपमा चार्ज हुन <xliff:g id="TIME">%2$s</xliff:g> बाँकी"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 34487f7..6625880 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Verbonden via %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Beschikbaar via %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Verbonden, geen internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Verbinding verbroken"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Verbinding verbreken..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Verbinding maken..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Verbonden (geen toegang tot berichten)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Gekoppeld (geen telefoon of media)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Media-audio"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Telefoonaudio"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Bestandsoverdracht"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Invoerapparaat"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Internettoegang"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Contacten delen"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Gebruiken voor contacten delen"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Internetverbinding delen"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Toegang tot berichten"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Sim-toegang"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Audio van hoge kwaliteit gebruiken: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Audio van hoge kwaliteit gebruiken"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Verbonden met audio van medium"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Verbonden met audio van telefoon"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Verbonden met server voor bestandsoverdracht"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Deze functie is experimenteel en kan invloed hebben op de prestaties."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Overschreven door <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Nog ongeveer <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Nog ongeveer <xliff:g id="TIME">%1$s</xliff:g> over op basis van je gebruik"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Nog <xliff:g id="TIME">%1$s</xliff:g> tot volledig opgeladen"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> resterend"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Nog <xliff:g id="TIME">%1$s</xliff:g> over op basis van je gebruik"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - ongeveer <xliff:g id="TIME">%2$s</xliff:g> resterend"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g>: nog ongeveer <xliff:g id="TIME">%2$s</xliff:g> over op basis van je gebruik"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> resterend"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> tot volledig opgeladen"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index d48a92b..8119ba5 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s ਰਾਹੀਂ ਕਨੈਕਟ ਕੀਤਾ"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s ਰਾਹੀਂ ਉਪਲਬਧ"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"ਕਨੈਕਟ ਕੀਤਾ, ਕੋਈ ਇੰਟਰਨੈਟ ਨਹੀਂ"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"ਡਿਸਕਨੈਕਟ ਕੀਤਾ"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"ਡਿਸਕਨੈਕਟ ਕਰ ਰਿਹਾ ਹੈ..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"ਕਨੈਕਟ ਕਰ ਰਿਹਾ ਹੈ…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"ਕਨੈਕਟ ਕੀਤਾ (ਕੋਈ ਸੁਨੇਹਾ ਪਹੁੰਚ ਨਹੀਂ)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"ਕਨੈਕਟ ਕੀਤਾ (ਕੋਈ ਫੋਨ ਜਾਂ ਮੀਡੀਆ ਨਹੀਂ)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"ਮੀਡੀਆ ਔਡੀਓ"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"ਫੋਨ ਔਡੀਓ"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"ਫਾਈਲ ਟ੍ਰਾਂਸਫਰ"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"ਇਨਪੁਟ ਡੀਵਾਈਸ"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"ਇੰਟਰਨੈਟ ਪਹੁੰਚ"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"ਸੰਪਰਕ ਸ਼ੇਅਰਿੰਗ"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"ਸੰਪਰਕ ਸ਼ੇਅਰਿੰਗ ਲਈ ਵਰਤੋ"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"ਇੰਟਰਨੈਟ ਕਨੈਕਸ਼ਨ ਸ਼ੇਅਰਿੰਗ"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"ਸੁਨੇਹਾ ਪਹੁੰਚ"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM ਪਹੁੰਚ"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"ਉੱਚ ਗੁਣਵੱਤਾ ਔਡੀਓ ਵਰਤੋ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"ਉੱਚ ਗੁਣਵੱਤਾ ਔਡੀਓ ਵਰਤੋ"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"ਮੀਡੀਆ ਔਡੀਓ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"ਫੋਨ ਔਡੀਓ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"ਫਾਈਲ ਟ੍ਰਾਂਸਫਰ ਸਰਵਰ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"ਇਹ ਵਿਸ਼ੇਸ਼ਤਾ ਪ੍ਰਯੋਗਾਤਮਿਕ ਹੈ ਅਤੇ ਪ੍ਰਦਰਸ਼ਨ ਤੇ ਅਸਰ ਪਾ ਸਕਦੀ ਹੈ।"</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> ਦੁਆਰਾ ਓਵਰਰਾਈਡ ਕੀਤਾ"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"ਲਗਭਗ <xliff:g id="TIME">%1$s</xliff:g> ਬਾਕੀ"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"ਤੁਹਾਡੀ ਵਰਤੋਂ ਦੇ ਆਧਾਰ \'ਤੇ ਲਗਭਗ <xliff:g id="TIME">%1$s</xliff:g> ਬਾਕੀ"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"ਪੂਰੀ ਤਰ੍ਹਾਂ ਚਾਰਜ ਹੋਣ ਲਈ <xliff:g id="TIME">%1$s</xliff:g> ਬਾਕੀ"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> ਬਾਕੀ"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"ਤੁਹਾਡੀ ਵਰਤੋਂ ਦੇ ਆਧਾਰ \'ਤੇ <xliff:g id="TIME">%1$s</xliff:g> ਬਾਕੀ"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਲਗਭਗ <xliff:g id="TIME">%2$s</xliff:g> ਬਾਕੀ"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - ਤੁਹਾਡੀ ਵਰਤੋਂ ਦੇ ਆਧਾਰ \'ਤੇ ਲਗਭਗ <xliff:g id="TIME">%2$s</xliff:g> ਬਾਕੀ"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ਬਾਕੀ"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"ਪੂਰੀ ਤਰ੍ਹਾਂ ਚਾਰਜ ਹੋਣ ਤੱਕ <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 823e9c8..ceee1e3 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Połączono przez %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Dostępne przez %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Połączono, brak internetu"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Rozłączona"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Rozłączanie..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Łączenie..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Połączono (brak dostępu do wiadomości)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Połączono (bez telefonu ani multimediów)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Dźwięk multimediów"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Dźwięk telefonu"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Przesyłanie pliku"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Urządzenie wejściowe"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Dostęp do internetu"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Udostępnianie kontaktów"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Używaj do udostępniania kontaktów"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Udostępnianie połączenia internetowego"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Dostęp do wiadomości"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Dostęp do karty SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Korzystaj z dźwięku wysokiej jakości: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Korzystaj z dźwięku wysokiej jakości"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Połączono z funkcją audio multimediów"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Połączono z funkcją audio telefonu"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Połączono z serwerem transferu plików"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"To jest funkcja eksperymentalna i może wpływać na działanie urządzenia."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Nadpisana przez <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Pozostało: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Jeszcze około <xliff:g id="TIME">%1$s</xliff:g> (na podstawie Twojego sposobu korzystania)"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> do pełnego naładowania"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Zostało <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Jeszcze <xliff:g id="TIME">%1$s</xliff:g> (na podstawie Twojego sposobu korzystania)"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – pozostało około <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – jeszcze około <xliff:g id="TIME">%2$s</xliff:g> (na podstawie Twojego sposobu korzystania)"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – zostało <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do pełnego naładowania"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index c37d2f4..e34c41e 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Conectado via %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Disponível via %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Conectada, sem Internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Desconectado"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Desconectando…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Conectando..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Conectado (sem acesso a mensagens)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Conectado (sem telefone ou mídia)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Áudio da mídia"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Áudio do smartphone"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Transferência de arquivo"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Dispositivo de entrada"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Acesso à Internet"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Compartilhamento de contatos"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Usar para compartilhamento de contatos"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Compartilhamento de conexão à Internet"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Acesso a mensagens"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Acesso SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Usar áudio de alta qualidade: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Usar áudio de alta qualidade"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Conectado ao áudio da mídia"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Conectado ao áudio do smartphone"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Conectado ao servidor de transferência de arquivo"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Este recurso é experimental e pode afetar o desempenho."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Cerca de <xliff:g id="TIME">%1$s</xliff:g> restante(s)"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Cerca de <xliff:g id="TIME">%1$s</xliff:g> restante(s) com base no seu uso"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> restante(s) até a carga completa"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> restante(s)"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> restante(s) com base no seu uso"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - cerca de <xliff:g id="TIME">%2$s</xliff:g> restante(s)"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g>: cerca de <xliff:g id="TIME">%2$s</xliff:g> restante(s) com base no seu uso"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> restante(s)"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> até a carga completa"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 3a803a8..f68fe36 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Ligado através de %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Disponível através de %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Ligado, sem Internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Desligado"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"A desligar..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"A ligar..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Ligado (sem acesso a mensagens)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Ligado (sem telefone ou multimédia)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Áudio de multimédia"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Áudio do telemóvel"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Transferência do ficheiro"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Dispositivo de entrada"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Acesso à internet"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Partilha de contactos"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Utilizar para a partilha de contactos"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Partilha da ligação à internet"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Acesso a mensagens"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Acesso ao SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Utilizar áudio de alta qualidade: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Utilizar áudio de alta qualidade"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Ligado ao áudio de multimédia"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Ligado ao áudio do telefone"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Ligado ao servidor de transferência de ficheiros"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Esta funcionalidade é experimental e pode afetar o desempenho."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Falta(m) cerca de <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Resta(m) cerca de <xliff:g id="TIME">%1$s</xliff:g> com base na sua utilização"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Falta(m) <xliff:g id="TIME">%1$s</xliff:g> para concluir o carregamento"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Resta(m) <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Resta(m) <xliff:g id="TIME">%1$s</xliff:g> com base na sua utilização"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – falta(m) cerca de <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – resta(m) cerca de <xliff:g id="TIME">%2$s</xliff:g> com base na sua utilização"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – resta(m) <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> até ficar totalmente carregada"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index c37d2f4..e34c41e 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Conectado via %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Disponível via %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Conectada, sem Internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Desconectado"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Desconectando…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Conectando..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Conectado (sem acesso a mensagens)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Conectado (sem telefone ou mídia)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Áudio da mídia"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Áudio do smartphone"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Transferência de arquivo"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Dispositivo de entrada"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Acesso à Internet"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Compartilhamento de contatos"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Usar para compartilhamento de contatos"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Compartilhamento de conexão à Internet"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Acesso a mensagens"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Acesso SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Usar áudio de alta qualidade: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Usar áudio de alta qualidade"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Conectado ao áudio da mídia"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Conectado ao áudio do smartphone"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Conectado ao servidor de transferência de arquivo"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Este recurso é experimental e pode afetar o desempenho."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Cerca de <xliff:g id="TIME">%1$s</xliff:g> restante(s)"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Cerca de <xliff:g id="TIME">%1$s</xliff:g> restante(s) com base no seu uso"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> restante(s) até a carga completa"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> restante(s)"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> restante(s) com base no seu uso"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - cerca de <xliff:g id="TIME">%2$s</xliff:g> restante(s)"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g>: cerca de <xliff:g id="TIME">%2$s</xliff:g> restante(s) com base no seu uso"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> restante(s)"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> até a carga completa"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index a861533..effc008 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Conectată prin %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Disponibilă prin %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Conectată, fără internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Deconectat"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"În curs de deconectare..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Se conectează..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Conectat (fără acces la mesaje)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Conectat (fără telefon sau conț. media)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Conținut media audio"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Componenta audio a telefonului"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Transfer de fișiere"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Dispozitiv de intrare"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Acces internet"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Acces la Agendă"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Utilizați pentru a permite accesul la Agendă"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Distribuirea conexiunii la internet"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Acces la mesaje"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Acces la SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Utilizați conținut audio de înaltă calitate: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Utilizați conținut audio de înaltă calitate"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Conectat la profilul pentru conținut media audio"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Conectat la componenta audio a telefonului"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Conectat la serverul de transfer de fișiere"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Această funcție este experimentală și poate afecta performanțele."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Valoare înlocuită de <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Timp rămas: aproximativ <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"În baza utilizării, timpul aproximativ rămas este: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Timp rămas până la încărcarea completă: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Timp rămas: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"În baza utilizării, timpul rămas este: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - încă aproximativ <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – în baza utilizării, timpul aproximativ rămas este: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – timp rămas: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> până la încărcarea completă"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 9eb75ae..640d3b5 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Подключено к %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Доступно через %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Подключено, без Интернета"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Нет подключения"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Отключение..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Подключение..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Подключено (нет доступа к сообщениям)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Подключено (кроме HSP/HFP/A2DP)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Профиль A2DP"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Профиль HSP/HFP"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Профиль OPP"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Профиль HID"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Интернет-доступ"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Обмен контактами"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Использовать для обмена контактами"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Профиль PAN"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Доступ к сообщениям"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Доступ к SIM-карте"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Использовать аудио высокого качества: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Использовать аудио высокого качества"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Подключено к мультимедийному аудиоустройству"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Подключено к аудиоустройству телефона"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Установлено подключение к серверу передачи файлов"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Это экспериментальная функция, она может снизить производительность устройства."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Новая настройка: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Осталось примерно <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Осталось примерно <xliff:g id="TIME">%1$s</xliff:g> при текущем уровне использования"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Ещё <xliff:g id="TIME">%1$s</xliff:g> до полной зарядки"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Осталось: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Осталось <xliff:g id="TIME">%1$s</xliff:g> при текущем уровне использования"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – осталось примерно <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> (осталось примерно <xliff:g id="TIME">%2$s</xliff:g> при текущем уровне использования)"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g>, осталось: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до полной зарядки"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 8774baa..5430b45 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s හරහා සම්බන්ධ විය"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s හරහා ලබා ගැනීමට හැකිය"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"සම්බන්ධයි, අන්තර්ජාලය නැත"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"විසන්ධි වුණි"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"විසන්ධි වෙමින්…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"සම්බන්ධ වෙමින්…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"සම්බන්ධිතයි (පණිවිඩ ප්රවේශ නොමැත)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"සම්බන්ධිතයි (දුරකතනයක් හෝ මාධ්යයක් නැත)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"මාධ්ය ශ්රව්ය"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"දුරකථන ශ්රව්ය"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"ගොනු හුවමාරුව"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"ආදාන උපාංගය"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"අන්තර්ජාල ප්රවේශය"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"සම්බන්ධතා බෙදාගැනීම"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"සම්බන්ධතා බෙදාගැනීම සඳහා භාවිතා කිරීම"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"අන්තර්ජාල සම්බන්ධතා බෙදාගැනීම"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"පණිවිඩ ප්රවේශය"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM ප්රවේශය"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"ඉහළ ගුණත්වයේ ශ්රව්ය භාවිත කරන්න: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"ඉහළ ගුණත්වයේ ශ්රව්ය භාවිත කරන්න"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"මාධ්ය ශ්රව්යට සම්බන්ධ විය"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"දුරකතනයේ ශ්රව්යට සම්බන්ධ විය"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"ගොනු හුවමාරු සේවාදායකය සමග සම්බන්ධ විය"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"මෙම විශේෂාංගය පරීක්ෂණාත්මක සහ ඇතැම් විට ක්රියාකාරිත්වයට බලපෑ හැක."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> මගින් ඉක්මවන ලදී"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"<xliff:g id="TIME">%1$s</xliff:g> පමණ ඉතිරියි"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"ඔබගේ භාවිතය මත පදනම්ව <xliff:g id="TIME">%1$s</xliff:g> පමණ ඉතිරිව ඇත"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"පූර්ණව ආරෝපණය වන තෙක් <xliff:g id="TIME">%1$s</xliff:g> ඉතිරියි"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"ඉතිරි <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"ඔබගේ භාවිතය මත පදනම්ව <xliff:g id="TIME">%1$s</xliff:g> ඉතිරිව ඇත"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>ක් පමණ ඇත"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - ඔබගේ භාවිතය මත පදනම්ව <xliff:g id="TIME">%2$s</xliff:g> පමණ ඉතිරිව ඇත"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - ඉතිරි <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> සම්පූර්ණයෙන් ආරෝපණය වන තෙක්"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 925e2c8..de17118 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Pripojené prostredníctvom %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"K dispozícii prostredníctvom %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Pripojené, žiadny internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Odpojený"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Prebieha odpájanie..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Prebieha pripájanie…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Pripojené (bez prístupu ku správam)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Pripojené (bez telefónu alebo média)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Zvuk medií"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Zvuk telefónu"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Prenos súborov"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Vstupné zariadenie"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Prístup na Internet"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Zdieľanie kontaktov"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Použiť na zdieľanie kontaktov"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Zdieľanie pripojenia na Internet"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Prístup ku správam"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Prístup k SIM karte"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Použiť zvuk s vysokou kvalitou: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Použiť zvuk s vysokou kvalitou"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Pripojené ku zvukovému médiu"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Pripojené ku zvuku telefónu"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Pripojené na server pre prenos údajov"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Funkcia je experimentálna a môže mať vplyv na výkonnosť."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Prekonané predvoľbou <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Približný zostávajúci čas: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Zostáva približne <xliff:g id="TIME">%1$s</xliff:g> v závislosti od intenzity využitia"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Zostávajúci čas do úplného nabitia: <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Zostávajúci čas: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Zostáva <xliff:g id="TIME">%1$s</xliff:g> v závislosti od intenzity využitia"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – približný zostávajúci čas: <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – zostáva približne <xliff:g id="TIME">%2$s</xliff:g> v závislosti od intenzity využitia"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – zostávajúci čas: <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabitia"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 6c2c355..563399a 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Vzpostavljena povezava prek: %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Na voljo prek: %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Vzpostavljena povezava, brez interneta"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Prekinjena povezava"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Prekinjanje povezave ..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Vzpostavljanje povezave ..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Povezava vzp. (ni dostopa do sporočil)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Povezava vzpostavljena (brez telefona ali predstavnosti)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Zvok predstavnosti"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Zvok telefona"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Prenos datoteke"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Vnosna naprava"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Internetni dostop"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Dajanje stikov v skupno rabo"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Uporabi za dajanje stikov v skupno rabo"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Skupna raba internetne povezave"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Dostop do sporočil"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Dostop do kartice SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Uporabi zvok visoke kakovosti: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Uporabi zvok visoke kakovosti"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Povezan s profilom za predstavnostni zvok"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Povezava s profilom za zvok telefona vzpostavljena"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Povezava s strežnikom za prenos datotek je vzpostavljena"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"To je preskusna funkcija in lahko vpliva na učinkovitost delovanja."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Preglasila nastavitev: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Še približno <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Glede na način uporabe imate na voljo še približno <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Še <xliff:g id="TIME">%1$s</xliff:g> do polne napolnjenosti"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Še <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Glede na način uporabe imate na voljo še <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – še približno <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – glede na način uporabe imate na voljo še približno <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – še <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do napolnjenosti"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 9c178d7..601ea6f 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"E lidhur përmes %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"E mundshme përmes %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"U lidh, nuk ka internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Shkëputur"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Po shkëputet..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Po lidhet..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"U lidh (pa qasje te mesazhet)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"I lidhur (pa telefon apo media)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Audioja e klipit \"media\""</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Audioja e telefonit"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Transferimi i skedarëve"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Pajisja e hyrjes"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Qasja në internet"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Ndarja e kontakteve"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Përdore për ndarjen e kontakteve"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Ndarja e lidhjes së internetit"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Qasja në mesazhe"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Qasje në kartën SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Përdor audion me cilësi të lartë: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Përdor audion me cilësi të lartë"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"U lidh me audion e medias"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"U lidh me audion e telefonit"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"U lidh me serverin e transferimit të skedarëve"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Ky funksion është eksperimental dhe mund të ndikojë në veprimtari."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Mbivendosur nga <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Rreth <xliff:g id="TIME">%1$s</xliff:g> të mbetura"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Rreth <xliff:g id="TIME">%1$s</xliff:g> të mbetura bazuar në përdorimin tënd"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> të mbetura deri në ngarkimin e plotë"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> të mbetura"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> të mbetura bazuar në përdorimin tënd"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - rreth <xliff:g id="TIME">%2$s</xliff:g> të mbetura"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - rreth <xliff:g id="TIME">%2$s</xliff:g> të mbetura bazuar në përdorimin tënd"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> të mbetura"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> derisa të mbushet plotësisht"</string>
diff --git a/packages/SettingsLib/res/values-sr/arrays.xml b/packages/SettingsLib/res/values-sr/arrays.xml
index f57989b..ef1c9de 100644
--- a/packages/SettingsLib/res/values-sr/arrays.xml
+++ b/packages/SettingsLib/res/values-sr/arrays.xml
@@ -198,7 +198,7 @@
<item msgid="1069584980746680398">"Размера анимације 10x"</item>
</string-array>
<string-array name="overlay_display_devices_entries">
- <item msgid="1606809880904982133">"Ниједно"</item>
+ <item msgid="1606809880904982133">"Ништа"</item>
<item msgid="9033194758688161545">"480 пиксела"</item>
<item msgid="1025306206556583600">"480 пиксела (безбедно)"</item>
<item msgid="1853913333042744661">"720 пиксела"</item>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 44fe06f..ab6e099 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Веза је успостављена преко приступне тачке %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Доступна је преко приступне тачке %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Веза је успостављена, нема интернета"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Веза је прекинута"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Прекидање везе..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Повезивање…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Повезано је (нема приступа порукама)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Повезано (без телефона или медија)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Звук медија"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Звук телефона"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Пренос датотеке"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Улазни уређај"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Приступ Интернету"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Дељење контаката"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Користите за дељење контаката"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Дељење интернет везе"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Приступ порукама"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Приступ SIM картици"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Користи звук високог квалитета: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Користи звук високог квалитета"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Повезано са звуком медија"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Повезано са звуком телефона"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Повезано са сервером за пренос датотека"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Ова функција је експериментална и може да утиче на квалитет рада."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Замењује га <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Још око <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"На основу потрошње имате још отприлике <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> до потпуног пуњења"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Преостало време: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"На основу потрошње имате још <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – остало је око <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – на основу потрошње имате још отприлике <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"Преостало је <xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до потпуног пуњења"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index d8cc76f..bca8a0a 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Anslutet via %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Tillgängligt via %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Ansluten, inget internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Kopplas ifrån"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Kopplar ifrån…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Ansluter…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Ansluten (ingen meddelandeåtkomst)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Ansluten (ingen telefon och inga media)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Medialjud"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Telefonljud"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Filöverföring"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Indataenhet"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Internetåtkomst"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Kontaktdelning"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Använd för kontaktdelning"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Delning av Internetanslutning"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Meddelandeåtkomst"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM-åtkomst"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Använd högkvalitativt ljud: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Använd högkvalitativt ljud"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Ansluten till medialjud"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Ansluten till telefonens ljud"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Ansluten till filöverföringsserver"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Den här funktionen är experimentell och kan påverka prestandan."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Har åsidosatts av <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Cirka <xliff:g id="TIME">%1$s</xliff:g> återstår"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Cirka <xliff:g id="TIME">%1$s</xliff:g> kvar utifrån din användning"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Batteriet är fulladdat om <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> kvar"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> kvar utifrån din användning"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – cirka <xliff:g id="TIME">%2$s</xliff:g> kvar"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – cirka <xliff:g id="TIME">%2$s</xliff:g> kvar utifrån din användning"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> kvar"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> tills det är fulladdat"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index d48cded..780c84c 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Imeunganishwa kupitia %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Inapatikana kupitia %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Imeunganishwa, hakuna Intaneti"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Haijaunganishwa"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Inatenganisha..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Inaunganisha…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Imeunganishwa (hakuna ufikiaji kwa ujumbe)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Imeunganishwa(hakuna simu au vyombo vya habari)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Media ya sauti"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Sauti ya simu"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Uhamishaji wa faili"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Kifaa cha kuingiza"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Ufikivu wa mtandao"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Kushiriki anwani"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Tumia kwa kushiriki anwani"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Kushiriki muunganisho wa tovuti"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Ufikiaji wa Ujumbe"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Ufikiaji wa SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Tumia sauti ya ubora wa juu: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Tumia sauti ya ubora wa juu"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Imeunganishwa kwenye sikika ya njia ya mawasiliano"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Imeunganishwa kwenye sauti ya simu"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Imeunganishwa kwenye seva ya kuhamisha faili"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Kipengele hiki ni cha majaribio na huenda kikaathiri utendaji wa kifaa chako."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Imetanguliwa na <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Zimesalia takribani <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Takriban <xliff:g id="TIME">%1$s</xliff:g> zimesalia kulingana na matumizi yako"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Imebaki <xliff:g id="TIME">%1$s</xliff:g> chaji ijae"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Zimesalia <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> zimesalia kulingana na matumizi yako"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - imesalia takribani <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - takriban <xliff:g id="TIME">%2$s</xliff:g> zimesalia kulingana na matumizi yako"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"Imechaji <xliff:g id="LEVEL">%1$s</xliff:g> - Zimesalia <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> hadi ijae chaji"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 44b13ec..d3bb467 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s வழியாக இணைக்கப்பட்டது"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s வழியாகக் கிடைக்கிறது"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"இணைக்கப்பட்டது, இணையம் இல்லை"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"தொடர்பு துண்டிக்கப்பட்டது"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"துண்டிக்கிறது..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"இணைக்கிறது..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"இணைக்கப்பட்டது (செய்திக்கான அணுகல் இல்லை)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"இணைக்கப்பட்டது (மொபைல் அல்லது மீடியாவுடன் அல்ல)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"மீடியா ஆடியோ"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"மொபைல் ஆடியோ"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"கோப்பு இடமாற்றம்"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"உள்ளீட்டுச் சாதனம்"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"இணைய அணுகல்"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"தொடர்புப் பகிர்தல்"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"தொடர்புப் பகிர்தலுக்குப் பயன்படுத்து"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"இணைய இணைப்பு பகிர்தல்"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"செய்திக்கான அணுகல்"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"சிம் அணுகல்"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"உயர்தர ஆடியோவைப் பயன்படுத்து: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"உயர்தர ஆடியோவைப் பயன்படுத்து"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"மீடியா ஆடியோவுடன் இணைக்கப்பட்டது"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"மொபைல் ஆடியோவுடன் இணைக்கப்பட்டது"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"கோப்பைப் பரிமாற்றும் சேவையகத்துடன் இணைக்கப்பட்டது"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"இது சோதனை முறையிலான அம்சம், இது செயல்திறனைப் பாதிக்கலாம்."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> மூலம் மேலெழுதப்பட்டது"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"கிட்டத்தட்ட <xliff:g id="TIME">%1$s</xliff:g> உள்ளது"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"உபயோகத்தின் அடிப்படையில் கிட்டத்தட்ட <xliff:g id="TIME">%1$s</xliff:g> மீதமுள்ளது"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"முழு சார்ஜாக <xliff:g id="TIME">%1$s</xliff:g> ஆகும்"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> மீதமுள்ளது"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"உபயோகத்தின் அடிப்படையில் <xliff:g id="TIME">%1$s</xliff:g> மீதமுள்ளது"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - கிட்டத்தட்ட <xliff:g id="TIME">%2$s</xliff:g> மீதமுள்ளது"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - உபயோகத்தின் அடிப்படையில் கிட்டத்தட்ட <xliff:g id="TIME">%2$s</xliff:g> மீதமுள்ளது"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> மீதமுள்ளது"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - முழு சார்ஜாக <xliff:g id="TIME">%2$s</xliff:g> ஆகும்"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 66f31d8..2ee802a 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s ద్వారా కనెక్ట్ చేయబడింది"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s ద్వారా అందుబాటులో ఉంది"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"కనెక్ట్ చేయబడింది, ఇంటర్నెట్ లేదు"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"డిస్కనెక్ట్ చేయబడింది"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"డిస్కనెక్ట్ చేస్తోంది..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"కనెక్ట్ చేస్తోంది..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"కనెక్ట్ చేయబడింది (సందేశ ప్రాప్యత లేదు)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"కనెక్ట్ చేయబడింది (ఫోన్ లేదా మీడియా కాకుండా)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"మీడియా ఆడియో"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"ఫోన్ ఆడియో"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"ఫైల్ బదిలీ"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"ఇన్పుట్ పరికరం"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"ఇంటర్నెట్ ప్రాప్యత"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"పరిచయ భాగస్వామ్యం"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"పరిచయ భాగస్వామ్యం కోసం ఉపయోగించు"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"ఇంటర్నెట్ కనెక్షన్ భాగస్వామ్యం"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"సందేశ ప్రాప్యత"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM ప్రాప్యత"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"అధిక నాణ్యత ఆడియోను ఉపయోగించండి: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"అధిక నాణ్యత ఆడియోను ఉపయోగించండి"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"మీడియా ఆడియోకు కనెక్ట్ చేయబడింది"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"ఫోన్ ఆడియోకు కనెక్ట్ చేయబడింది"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"ఫైల్ బదిలీ సర్వర్కు కనెక్ట్ చేయబడింది"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"ఈ లక్షణం ప్రయోగాత్మకమైనది మరియు పనితీరుపై ప్రభావం చూపవచ్చు."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> ద్వారా భర్తీ చేయబడింది"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"మిగిలి ఉన్న సమయం, <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"మీ వినియోగం ఆధారంగా సుమారు <xliff:g id="TIME">%1$s</xliff:g> సమయం మిగిలి ఉంది"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"పూర్తిగా ఛార్జ్ కావడానికి <xliff:g id="TIME">%1$s</xliff:g> పడుతుంది"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> మిగిలి ఉంది"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"మీ వినియోగం ఆధారంగా <xliff:g id="TIME">%1$s</xliff:g> సమయం మిగిలి ఉంది"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> పని చేస్తుంది"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - మీ వినియోగం ఆధారంగా సుమారు <xliff:g id="TIME">%2$s</xliff:g> సమయం మిగిలి ఉంది"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> మిగిలి ఉంది"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 0cc93a9..f5223e1 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"เชื่อมต่อผ่าน %1$s แล้ว"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"พร้อมใช้งานผ่านทาง %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"เชื่อมต่อแล้ว ไม่พบอินเทอร์เน็ต"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"ตัดการเชื่อมต่อ"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"กำลังตัดการเชื่อมต่อ..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"กำลังเชื่อมต่อ…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"เชื่อมต่อแล้ว (ไม่มีการเข้าถึงข้อความ)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"เชื่อมต่อ (ยกเว้นเสียงโทรศัพท์หรือสื่อ)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"เสียงสื่อ"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"เสียงโทรศัพท์"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"การถ่ายโอนไฟล์"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"อุปกรณ์อินพุต"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"การเข้าถึงอินเทอร์เน็ต"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"การแชร์รายชื่อผู้ติดต่อ"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"ใช้สำหรับการแชร์รายชื่อผู้ติดต่อ"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"การแชร์การเชื่อมต่ออินเทอร์เน็ต"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"การเข้าถึงข้อความ"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"การเข้าถึงซิม"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"ใช้เสียงคุณภาพสูง: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"ใช้เสียงคุณภาพสูง"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"เชื่อมต่อกับระบบเสียงของสื่อแล้ว"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"เชื่อมต่อกับระบบเสียงของโทรศัพท์แล้ว"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"เชื่อมต่อกับเซิร์ฟเวอร์สำหรับโอนไฟล์แล้ว"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"ฟีเจอร์นี้เป็นแบบทดลองและอาจส่งผลต่อประสิทธิภาพการทำงาน"</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"แทนที่โดย <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"อีกประมาณ <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"เหลืออีกราว <xliff:g id="TIME">%1$s</xliff:g> ขึ้นอยู่กับการใช้งานของคุณ"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"อีก <xliff:g id="TIME">%1$s</xliff:g> จึงจะชาร์จเต็ม"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"เหลืออีก <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"เหลืออีก <xliff:g id="TIME">%1$s</xliff:g> ขึ้นอยู่กับการใช้งานของคุณ"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - เหลือเวลาประมาณ <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - เหลืออีกราว <xliff:g id="TIME">%2$s</xliff:g> ขึ้นอยู่กับการใช้งานของคุณ"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - เหลืออีก <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> จนกว่าจะชาร์จเต็ม"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index f00440f..96388de 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Nakakonekta sa pamamagitan ng %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Available sa pamamagitan ng %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Nakakonekta, walang Internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Hindi nakakonekta"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Nadidiskonekta..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Kumukonekta…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Nakakonekta (walang access sa mensahe)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Nakakonekta (walang telepono o media)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Audio ng media"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Audio ng telepono"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Paglilipat ng file"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Device sa pag-input"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Access sa internet"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Pagbabahagi ng contact"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Gamitin para sa pagbabahagi ng contact"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Pagbabahagi ng koneksyon sa internet"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Access sa Mensahe"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Access sa SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Gumamit ng mataas na kalidad na audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Gumamit ng mataas na kalidad na audio"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Konektado sa media audio"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Nakakonekta sa audio ng telepono"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Nakakonekta sa server sa paglilipat ng file"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Ang feature na ito ay pinag-eeksperimentuhan at maaaring makaapekto sa performance."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Na-override ng <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Humigit-kumulang <xliff:g id="TIME">%1$s</xliff:g> ang natitira"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Humigit-kumulang <xliff:g id="TIME">%1$s</xliff:g> ang natitira batay sa iyong paggamit"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> ang natitira bago makumpleto ang charge"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> pa"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> ang natitira batay sa iyong paggamit"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - humigit-kumulang <xliff:g id="TIME">%2$s</xliff:g> pa ang natitira"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - humigit-kumulang <xliff:g id="TIME">%2$s</xliff:g> ang natitira batay sa iyong paggamit"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> pa"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> hanggang sa makumpleto ang charge"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 80f5dc4..b8a5dab 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s üzerinden bağlı"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s üzerinden kullanılabilir"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Bağlı, İnternet yok"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Bağlantı kesildi"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Bağlantı kesiliyor…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Bağlanıyor…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Bağlı (mesaj erişimi yok)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Bağlandı (telefon veya medya yok)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Medya sesi"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Telefon sesi"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Dosya aktarımı"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Giriş cihazı"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"İnternet erişimi"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Kişi paylaşma"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Kişi paylaşmak için kullan"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"İnternet bağlantısı paylaşımı"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Mesaj Erişimi"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM Erişimi"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Yüksek kalite ses kullan: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Yüksek kalite ses kullan"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Medya sesine bağlanıldı"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Telefon sesine bağlandı"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Dosya aktarım sunucusuna bağlandı"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Bu özellik deneyseldir ve performansı etkileyebilir."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> tarafından geçersiz kılındı"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Yaklaşık <xliff:g id="TIME">%1$s</xliff:g> kaldı"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Kullanımınıza dayalı olarak yaklaşık <xliff:g id="TIME">%1$s</xliff:g> kaldı"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Tam olarak şarj olmasına <xliff:g id="TIME">%1$s</xliff:g> kaldı"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> kaldı"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Kullanımınıza dayalı olarak <xliff:g id="TIME">%1$s</xliff:g> kaldı"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - yaklaşık <xliff:g id="TIME">%2$s</xliff:g> kaldı"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - kullanımınıza dayalı olarak yaklaşık <xliff:g id="TIME">%2$s</xliff:g> kaldı"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> kaldı"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - Tam şarj olmasına <xliff:g id="TIME">%2$s</xliff:g> kaldı"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 96f0c59..0f8affd 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Під’єднано через %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Доступ через %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Під’єднано, але немає доступу до Інтернету"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Роз’єднано"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Відключення..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Підключення…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Під’єднано (без доступу до повідомлень)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Під’єднано (без телефону чи медіа)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Звук медіа-файлів"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Звук телефону"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Передавання файлів"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Пристрій введення"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Доступ до Інтернету"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Надсилання контактів"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Використовувати для надсилання контактів"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Надання доступу до Інтернету"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Доступ до повідомлень"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Доступ до SIM-карти"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Використовувати аудіо високої якості: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Використовувати аудіо високої якості"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Підключено до аудіоджерела"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Підключено до звуку телеф."</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Підключ. до сервера передачі файлів"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Це експериментальна функція. Вона може вплинути на продуктивність."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Замінено на <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Залишилося близько <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"На основі використання залишилося близько <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"До повного зарядження залишилося <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Залишилося <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"На основі використання залишилося <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – ще <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> – на основі використання залишилося близько <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – залишилося <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до повного заряду"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index aed65fa..f6771dc 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"منسلک بذریعہ %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"دستیاب بذریعہ %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"منسلک، انٹرنیٹ نہیں ہے"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"منقطع"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"منقطع کیا جارہا ہے…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"مربوط ہو رہا ہے…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"مربوط (کسی پیغام تک رسائی نہیں ہے)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"مربوط (کوئی فون یا میڈیا نہیں ہے)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"میڈيا آڈیو"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"فون آڈیو"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"فائل کی منتقلی"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"ان پٹ آلہ"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"انٹرنیٹ تک رسائی"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"رابطہ کا اشتراک"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"رابطہ کے اشتراک کیلئے استعمال کریں"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"انٹرنیٹ کنکشن کا اشتراک کرنا"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"پیغام تک رسائی"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM رسائی"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"اعلی معیار کی آڈیو استعمال کریں: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"اعلی معیار کی آڈیو استعمال کریں"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"میڈیا آڈیو سے مربوط"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"فون آڈیو سے مربوط"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"فائل منتقلی سرور سے مربوط ہو گیا ہے"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"یہ خصوصیت تجرباتی ہے اور اس کی وجہ سے کاکردگی متاثر ہو سکتی ہے۔"</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> کے ذریعہ منسوخ کردیا گیا"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"تقریبًا <xliff:g id="TIME">%1$s</xliff:g> باقی ہے"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"آپ کے استعمال کی بنیاد پر تقریباً <xliff:g id="TIME">%1$s</xliff:g> باقی ہے"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"پوری طرح چارج ہونے میں <xliff:g id="TIME">%1$s</xliff:g> باقی ہے"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> باقی ہے"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"آپ کے استعمال کی بنیاد پر <xliff:g id="TIME">%1$s</xliff:g> باقی ہے"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - تقریباً <xliff:g id="TIME">%2$s</xliff:g> باقی ہے"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - آپ کے استعمال کی بنیاد پر تقریباً <xliff:g id="TIME">%2$s</xliff:g> باقی ہے"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> باقی ہے"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> پوری طرح چارج ہونے تک"</string>
diff --git a/packages/SettingsLib/res/values-uz/arrays.xml b/packages/SettingsLib/res/values-uz/arrays.xml
index 76be771..1d4e1e9 100644
--- a/packages/SettingsLib/res/values-uz/arrays.xml
+++ b/packages/SettingsLib/res/values-uz/arrays.xml
@@ -22,7 +22,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string-array name="wifi_status">
<item msgid="1922181315419294640"></item>
- <item msgid="8934131797783724664">"Tekshirib chiqilmoqda…"</item>
+ <item msgid="8934131797783724664">"Qidiruv…"</item>
<item msgid="8513729475867537913">"Ulanmoqda…"</item>
<item msgid="515055375277271756">"Tasdiqdan o‘tilmoqda…"</item>
<item msgid="1943354004029184381">"IP manzil o‘zlashtirilmoqda…"</item>
@@ -36,7 +36,7 @@
</string-array>
<string-array name="wifi_status_with_ssid">
<item msgid="7714855332363650812"></item>
- <item msgid="8878186979715711006">"Tekshirilmoqda…"</item>
+ <item msgid="8878186979715711006">"Qidiruv…"</item>
<item msgid="355508996603873860">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> tarmog‘iga ulanilmoqda…"</item>
<item msgid="554971459996405634">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> bilan aloqa o‘rnatilyapti…"</item>
<item msgid="7928343808033020343">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> IP manzil beryapti…"</item>
@@ -223,7 +223,7 @@
<item msgid="1064373276095698656">"Chizmaning sinov buyruqlarini yashil bilan"</item>
</string-array>
<string-array name="track_frame_time_entries">
- <item msgid="2193584639058893150">"O‘chirib qo‘yish"</item>
+ <item msgid="2193584639058893150">"Faolsizlantirish"</item>
<item msgid="2751513398307949636">"Ekranda bo‘laklar tarzida"</item>
<item msgid="2355151170975410323">"“<xliff:g id="AS_TYPED_COMMAND">adb shell dumpsys gfxinfo</xliff:g>” buyrug‘ida"</item>
</string-array>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index ae6ba0f..8a61cc45 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s orqali ulangan"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"%1$s orqali ishlaydi"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Ulangan, lekin internet aloqasi yo‘q"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Uzildi"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Uzilyapti…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Ulanmoqda…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Ulangan (xabarlarga kirib bo‘lmaydi)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Ulangan (telefon yoki media qurilma emas)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Media audio"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Telefon audiosi"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Fayl o‘tkazish"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Kiritish qurilmasi"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Internetga kirish"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Kontaktlarni ulashish"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Kontaktlarni ulashish uchun ishlatilsin"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Internet aloqasi ulashmasi"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Xabarga kirish"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM-kartaga kirish"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Yuqori sifatli audiodan foydalanish: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Yuqori sifatli audiodan foydalanish"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Audio qurilmasiga ulangan"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Telefon karnayiga ulanildi"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Fayl almashinish serveriga ulanildi"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Bu funksiya tajribaviy bo‘lib, u qurilma unumdorligiga ta’sir qilishi mumkin."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> bilan almashtirildi"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Taxminan <xliff:g id="TIME">%1$s</xliff:g> qoldi"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Joriy holatda taxminan <xliff:g id="TIME">%1$s</xliff:g> qoldi"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"To‘lishiga <xliff:g id="TIME">%1$s</xliff:g> qoldi"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> qoldi"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Joriy holatda <xliff:g id="TIME">%1$s</xliff:g> qoldi"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> – taxminan <xliff:g id="TIME">%2$s</xliff:g> qoldi"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> (joriy holatda taxminan <xliff:g id="TIME">%2$s</xliff:g> qoldi)"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> qoldi"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> ichida to‘ladi"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 87ba837..7dd93c6 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Được kết nối qua %1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Có sẵn qua %1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Đã kết nối, không có Internet"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Đã ngắt kết nối"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Đang ngắt kết nối…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Đang kết nối…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Đã kết nối (không truy cập tin nhắn)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Đã k.nối (kg có ĐT hoặc p.tiện nào)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Âm thanh của phương tiện"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Âm thanh điện thoại"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Chuyển tệp"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Thiết bị đầu vào"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Truy cập Internet"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Chia sẻ liên hệ"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Sử dụng để chia sẻ liên hệ"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Chia sẻ kết nối internet"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Truy cập tin nhắn"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Quyền truy cập SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Sử dụng âm thanh chất lượng cao: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Sử dụng âm thanh chất lượng cao"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Đã kết nối với âm thanh phương tiện"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Đã kết nối với âm thanh điện thoại"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Đã kết nối với máy chủ chuyển tệp"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Tính năng này là tính năng thử nghiệm và có thể ảnh hưởng đến hoạt động."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Bị ghi đè bởi <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Còn khoảng <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Còn khoảng <xliff:g id="TIME">%1$s</xliff:g> dựa trên mức sử dụng của bạn"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Còn <xliff:g id="TIME">%1$s</xliff:g> cho tới khi được sạc đầy"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"Còn lại <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"Còn <xliff:g id="TIME">%1$s</xliff:g> dựa trên mức sử dụng của bạn"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - còn khoảng <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - còn khoảng <xliff:g id="TIME">%2$s</xliff:g> dựa trên mức sử dụng của bạn"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - còn lại <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> cho tới khi được sạc đầy"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index ae98f2f..7a932d9 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"已通过%1$s连接"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"可通过%1$s连接"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"已连接,但无法访问互联网"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"已断开连接"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"正在断开连接..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"正在连接..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"已连接(无消息权限)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"已连接(没有手机或媒体信号)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"媒体音频"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"手机音频"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"文件传输"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"输入设备"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"互联网连接"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"共享联系人"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"用于共享联系人"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"共享互联网连接"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"消息权限"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM 卡存取权限"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"使用高品质音频:<xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"使用高品质音频"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"已连接到媒体音频"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"已连接到手机音频"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"已连接到文件传输服务器"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"这是实验性功能,性能可能不稳定。"</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"已被“<xliff:g id="TITLE">%1$s</xliff:g>”覆盖"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"还剩大约 <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"根据您的使用情况,大约还可使用 <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"还需 <xliff:g id="TIME">%1$s</xliff:g>充满电"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"还可用 <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"根据您的使用情况,大约还可使用 <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - 大约还剩 <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - 根据您的使用情况,大约还可使用 <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - 还可用 <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - 还需 <xliff:g id="TIME">%2$s</xliff:g>充满"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index e1ad0c4..28d5bf9 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"已透過 %1$s 連線"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"可透過 %1$s 連線"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"已連線,沒有互聯網"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"已中斷連線"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"正在中斷連線..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"正在連線..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"已連結 (無訊息存取權)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"已連線 (無手機或媒體)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"媒體音效"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"手機音效"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"檔案傳輸"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"輸入裝置"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"互聯網連線"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"聯絡人共用"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"用於聯絡人共用"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"互聯網連線分享"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"訊息存取權"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM 卡存取"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"使用優質音訊編解碼器:<xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"使用優質音訊編解碼器"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"已連接媒體音頻裝置"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"已連接手機耳機"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"已連線至檔案傳輸伺服器"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"這是實驗性功能,效能尚待改善。"</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"已由「<xliff:g id="TITLE">%1$s</xliff:g>」覆寫"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"剩餘約 <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"根據您的使用情況,剩餘約 <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g>後就能充滿電"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"尚餘 <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"根據您的使用情況,剩餘 <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - 剩餘約 <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - 根據您的使用情況,剩餘約 <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - 尚餘 <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - 還需 <xliff:g id="TIME">%2$s</xliff:g>才能充滿電"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 9ed1a26..d839cf4 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"已透過 %1$s 連線"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"可透過 %1$s 使用"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"已連線,沒有網際網路"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"已中斷連線"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"正在中斷連線…"</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"連線中…"</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"已連線 (無訊息存取權)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"已連線 (無手機或媒體音訊)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"媒體音訊"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"手機音訊"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"檔案傳輸"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"輸入裝置"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"網際網路連線"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"聯絡人共用"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"用於聯絡人共用"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"網際網路連線分享"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"訊息存取權"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"SIM 卡存取權"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"使用高品質音訊:<xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"使用高品質音訊"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"連接至媒體音訊"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"連接至電話音訊"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"已連線到檔案傳輸伺服器"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"這是一項實驗性功能,可能會對效能造成影響。"</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"已改為<xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"還有大約 <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"根據你的使用情形,剩餘時間大約還有 <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"再過 <xliff:g id="TIME">%1$s</xliff:g>就能完成充電"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"還剩 <xliff:g id="TIME">%1$s</xliff:g>"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"根據你的使用情形,剩餘時間還有 <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - 約剩 <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - 根據你的使用情形,剩餘時間大約還有 <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - 還剩 <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充飽"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 630a9da..15fbbc5 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -37,6 +37,18 @@
<string name="connected_via_passpoint" msgid="2826205693803088747">"Kuxhumeke nge-%1$s"</string>
<string name="available_via_passpoint" msgid="1617440946846329613">"Iyatholakala nge-%1$s"</string>
<string name="wifi_connected_no_internet" msgid="3149853966840874992">"Kuxhumekile, ayikho i-inthanethi"</string>
+ <!-- no translation found for speed_label_very_slow (1867055264243608530) -->
+ <skip />
+ <!-- no translation found for speed_label_slow (813109590815810235) -->
+ <skip />
+ <!-- no translation found for speed_label_okay (5941436233638654215) -->
+ <skip />
+ <!-- no translation found for speed_label_medium (3175763313268941953) -->
+ <skip />
+ <!-- no translation found for speed_label_fast (7715732164050975057) -->
+ <skip />
+ <!-- no translation found for speed_label_very_fast (2265363430784523409) -->
+ <skip />
<string name="bluetooth_disconnected" msgid="6557104142667339895">"Ayixhunyiwe"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"Inqamula uxhumano kwi-inthanethi..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"Iyaxhuma..."</string>
@@ -47,17 +59,21 @@
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"Kuxhunyiwe (akukho ukufinyelela umlayezo)"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="9195757766755553810">"Ixhunyiwe (ayikho ifoni noma imidiya)"</string>
<string name="bluetooth_profile_a2dp" msgid="2031475486179830674">"Umsindo wemidiya"</string>
- <string name="bluetooth_profile_headset" msgid="8658779596261212609">"Umsindo wefoni"</string>
+ <!-- no translation found for bluetooth_profile_headset (7815495680863246034) -->
+ <skip />
<string name="bluetooth_profile_opp" msgid="9168139293654233697">"Dlulisa ifayela"</string>
<string name="bluetooth_profile_hid" msgid="3680729023366986480">"Idivaysi yokufakwayo"</string>
<string name="bluetooth_profile_pan" msgid="3391606497945147673">"Ukufinyelela i-Inthanethi"</string>
<string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Ukwabelana kokuxhumana"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Sebenzisela ukwabelana kokuxhumana"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Ukwabelana ngoxhumano lwe-Inthanethi"</string>
- <string name="bluetooth_profile_map" msgid="5465271250454324383">"Ukufinyelela umlayezo"</string>
+ <!-- no translation found for bluetooth_profile_map (1019763341565580450) -->
+ <skip />
<string name="bluetooth_profile_sap" msgid="5764222021851283125">"Ukufinyelela kwe-SIM"</string>
- <string name="bluetooth_profile_a2dp_high_quality" msgid="2221025895896419505">"Sebenzisa umsindo wekhwalithi ephezulu: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="5860996587596508755">"Sebenzisa umsindo wekhwalithi ephezulu"</string>
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality (5444517801472820055) -->
+ <skip />
+ <!-- no translation found for bluetooth_profile_a2dp_high_quality_unknown_codec (8510588052415438887) -->
+ <skip />
<string name="bluetooth_a2dp_profile_summary_connected" msgid="963376081347721598">"Ixhume emsindweni wemidiya"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="7661070206715520671">"Ixhunywe kumsindo wefoni"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2611913495968309066">"Ixhunywe kwiseva yokudlulisa ifayela"</string>
@@ -321,9 +337,12 @@
<string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Lesi sici esesilingo futhi singathinta ukusebenza."</string>
<string name="daltonizer_type_overridden" msgid="3116947244410245916">"Igitshezwe ngaphezulu yi-<xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="845431008899029842">"Cishe u-<xliff:g id="TIME">%1$s</xliff:g> osele"</string>
+ <string name="power_remaining_duration_only_enhanced" msgid="5992456722677973678">"Cishe kusele okungu-<xliff:g id="TIME">%1$s</xliff:g> kusukela ekusetshenzisweni kwakho"</string>
<string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> kushiywe ishaja"</string>
<string name="power_remaining_duration_only_short" msgid="5329694252258605547">"<xliff:g id="TIME">%1$s</xliff:g> esisele"</string>
+ <string name="power_remaining_duration_only_short_enhanced" msgid="7450425624026394823">"<xliff:g id="TIME">%1$s</xliff:g> esele kusukela ekusetshenzisweni kwakho"</string>
<string name="power_discharging_duration" msgid="2843747179907396142">"<xliff:g id="LEVEL">%1$s</xliff:g> - cishe ngu-<xliff:g id="TIME">%2$s</xliff:g> osele"</string>
+ <string name="power_discharging_duration_enhanced" msgid="4401782117770255046">"<xliff:g id="LEVEL">%1$s</xliff:g> - cishe ngu-<xliff:g id="TIME">%2$s</xliff:g> osele kusukela ekusetshenzisweni kwakho"</string>
<string name="power_discharging_duration_short" msgid="4192244429001842403">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> okusele"</string>
<string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> kuze ligcwale ngokuphelele"</string>
diff --git a/packages/SettingsLib/res/values/attrs.xml b/packages/SettingsLib/res/values/attrs.xml
index ea538fb..a8a1793 100644
--- a/packages/SettingsLib/res/values/attrs.xml
+++ b/packages/SettingsLib/res/values/attrs.xml
@@ -46,12 +46,6 @@
<attr name="wifi_signal" format="reference" />
<attr name="wifi_friction" format="reference" />
- <declare-styleable name="UsageView">
- <attr name="android:colorAccent" />
- <attr name="sideLabels" format="reference" />
- <attr name="bottomLabels" format="reference" />
- <attr name="textColor" format="color" />
- <attr name="android:gravity" />
- </declare-styleable>
+ <attr name="footerPreferenceStyle" format="reference" />
</resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 8f7a3dd..fd994b5 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -64,6 +64,14 @@
<!-- Status for networks disabled from authentication failure (wrong password
or certificate). -->
<string name="wifi_disabled_password_failure">Authentication problem</string>
+
+ <!-- Status detail for a network that can't be connected to for some reason -->
+ <string name="wifi_cant_connect">Can\'t connect</string>
+ <!-- Status for a named network that can't be connected to for some reason-->
+ <string name="wifi_cant_connect_to_ap">Can\'t connect to \'<xliff:g id="ap_name">%1$s</xliff:g>\'</string>
+ <!-- Message shown when the user likely entered an incorrect password for a wifi network -->
+ <string name="wifi_check_password_try_again">Check password and try again</string>
+
<!-- Summary for the remembered network but currently not in range. -->
<string name="wifi_not_in_range">Not in range</string>
<!-- Summary for the network but no internet connection was detected. -->
@@ -89,6 +97,19 @@
<!-- Summary for Connected wifi network without internet -->
<string name="wifi_connected_no_internet">Connected, no Internet</string>
+ <!-- Speed label for very slow network speed -->
+ <string name="speed_label_very_slow">Very Slow</string>
+ <!-- Speed label for slow network speed -->
+ <string name="speed_label_slow">Slow</string>
+ <!-- Speed label for okay network speed -->
+ <string name="speed_label_okay">OK</string>
+ <!-- Speed label for medium network speed -->
+ <string name="speed_label_medium">Medium</string>
+ <!-- Speed label for fast network speed -->
+ <string name="speed_label_fast">Fast</string>
+ <!-- Speed label for very fast network speed -->
+ <string name="speed_label_very_fast">Very Fast</string>
+
<!-- Bluetooth settings. Message when a device is disconnected -->
<string name="bluetooth_disconnected">Disconnected</string>
<!-- Bluetooth settings. Message when disconnecting from a device -->
@@ -112,7 +133,7 @@
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the A2DP profile. -->
<string name="bluetooth_profile_a2dp">Media audio</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the headset or handsfree profile. -->
- <string name="bluetooth_profile_headset">Phone audio</string>
+ <string name="bluetooth_profile_headset">Phone calls</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the OPP profile. -->
<string name="bluetooth_profile_opp">File transfer</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the HID profile. -->
@@ -126,15 +147,15 @@
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (sharing this device's Internet connection). [CHAR LIMIT=40] -->
<string name="bluetooth_profile_pan_nap">Internet connection sharing</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the map profile. -->
- <string name="bluetooth_profile_map">Message Access</string>
+ <string name="bluetooth_profile_map">Text Messages</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the SAP profile (sharing SIM card). -->
<string name="bluetooth_profile_sap">SIM Access</string>
<!-- Bluetooth settings. The user-visible string for the setting controlling whether to use a high-quality codec if the device supports it, along with the name of the codec (eg AAC, LDAC, aptX) -->
- <string name="bluetooth_profile_a2dp_high_quality">Use high quality audio: <xliff:g id="codec_name">%1$s</xliff:g></string>
+ <string name="bluetooth_profile_a2dp_high_quality">HD audio: <xliff:g id="codec_name">%1$s</xliff:g></string>
<!-- Bluetooth settings. Similar to bluetooth_profile_a2dp_high_quality, but used when the device supports high quality audio but we don't know which codec that will be used. -->
- <string name="bluetooth_profile_a2dp_high_quality_unknown_codec">Use high quality audio</string>
+ <string name="bluetooth_profile_a2dp_high_quality_unknown_codec">HD audio</string>
<!-- Bluetooth settings. Connection options screen. The summary for the A2DP checkbox preference when A2DP is connected. -->
<string name="bluetooth_a2dp_profile_summary_connected">Connected to media audio</string>
@@ -794,30 +815,31 @@
<string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string>
<!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery discharging -->
- <string name="power_remaining_duration_only">About <xliff:g id="time">%1$s</xliff:g> left</string>
+ <string name="power_remaining_duration_only">About <xliff:g id="time">^1</xliff:g> left</string>
+ <!-- [CHAR_LIMIT=60] Label for estimated remaining duration of battery discharging -->
+ <string name="power_remaining_duration_only_enhanced">About <xliff:g id="time">^1</xliff:g> left based on your usage</string>
<!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging -->
- <string name="power_remaining_charging_duration_only"><xliff:g id="time">%1$s</xliff:g> left until fully charged</string>
+ <string name="power_remaining_charging_duration_only"><xliff:g id="time">^1</xliff:g> left until fully charged</string>
<!-- [CHAR_LIMIT=40] Short label for estimated remaining duration of battery charging/discharging -->
- <string name="power_remaining_duration_only_short"><xliff:g id="time">%1$s</xliff:g> left</string>
+ <string name="power_remaining_duration_only_short"><xliff:g id="time">^1</xliff:g> left</string>
+ <!-- [CHAR_LIMIT=60] Short label for estimated remaining duration of battery charging/discharging -->
+ <string name="power_remaining_duration_only_short_enhanced"><xliff:g id="time">^1</xliff:g> left based on your usage</string>
<!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration -->
- <string name="power_discharging_duration"><xliff:g id="level">%1$s</xliff:g>
- - about <xliff:g id="time">%2$s</xliff:g> left</string>
+ <string name="power_discharging_duration"><xliff:g id="level">^1</xliff:g> - about <xliff:g id="time">^2</xliff:g> left</string>
+ <!-- [CHAR_LIMIT=60] Label for battery level chart when discharging with duration and using enhanced estimate -->
+ <string name="power_discharging_duration_enhanced"><xliff:g id="level">^1</xliff:g> - about <xliff:g id="time">^2</xliff:g> left based on your usage</string>
<!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration -->
- <string name="power_discharging_duration_short"><xliff:g id="level">%1$s</xliff:g>
- - <xliff:g id="time">%2$s</xliff:g> left</string>
+ <string name="power_discharging_duration_short"><xliff:g id="level">^1</xliff:g> - <xliff:g id="time">^2</xliff:g> left</string>
<!-- [CHAR_LIMIT=40] Label for battery level chart when charging -->
- <string name="power_charging"><xliff:g id="level">%1$s</xliff:g> -
- <xliff:g id="state">%2$s</xliff:g></string>
+ <string name="power_charging"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="state">%2$s</xliff:g></string>
<!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
- <string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> -
- <xliff:g id="time">%2$s</xliff:g> until fully charged</string>
+ <string name="power_charging_duration"><xliff:g id="level">^1</xliff:g> - <xliff:g id="time">^2</xliff:g> until fully charged</string>
<!-- [CHAR_LIMIT=40] Short label for battery level chart when charging with duration -->
- <string name="power_charging_duration_short"><xliff:g id="level">%1$s</xliff:g> -
- <xliff:g id="time">%2$s</xliff:g></string>
+ <string name="power_charging_duration_short"><xliff:g id="level">^1</xliff:g> - <xliff:g id="time">^2</xliff:g></string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_unknown">Unknown</string>
diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml
index 3f312f4..b7ea1d4 100644
--- a/packages/SettingsLib/res/values/styles.xml
+++ b/packages/SettingsLib/res/values/styles.xml
@@ -21,4 +21,9 @@
<style name="TextAppearanceMedium">
<item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
</style>
+
+ <style name="preference_icon_frame">
+ <item name="android:layout_marginStart">-4dp</item>
+ <item name="android:minWidth">60dp</item>
+ </style>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java b/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java
deleted file mode 100644
index 2d8defa..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.settingslib;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.os.AsyncTask;
-import android.os.BatteryManager;
-import android.os.BatteryStats;
-import android.os.BatteryStats.HistoryItem;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.text.format.Formatter;
-import android.util.SparseIntArray;
-import com.android.internal.os.BatteryStatsHelper;
-import com.android.settingslib.graph.UsageView;
-
-public class BatteryInfo {
-
- public String chargeLabelString;
- public int batteryLevel;
- public boolean discharging = true;
- public long remainingTimeUs = 0;
- public String batteryPercentString;
- public String remainingLabel;
- public String statusLabel;
- private boolean mCharging;
- private BatteryStats mStats;
- private long timePeriod;
-
- public interface Callback {
- void onBatteryInfoLoaded(BatteryInfo info);
- }
-
- public void bindHistory(final UsageView view, BatteryDataParser... parsers) {
- BatteryDataParser parser = new BatteryDataParser() {
- SparseIntArray points = new SparseIntArray();
-
- @Override
- public void onParsingStarted(long startTime, long endTime) {
- timePeriod = endTime - startTime - remainingTimeUs / 1000;
- view.clearPaths();
- view.configureGraph((int) (endTime - startTime), 100, remainingTimeUs != 0,
- mCharging);
- }
-
- @Override
- public void onDataPoint(long time, HistoryItem record) {
- points.put((int) time, record.batteryLevel);
- }
-
- @Override
- public void onDataGap() {
- if (points.size() > 1) {
- view.addPath(points);
- }
- points.clear();
- }
-
- @Override
- public void onParsingDone() {
- if (points.size() > 1) {
- view.addPath(points);
- }
- }
- };
- BatteryDataParser[] parserList = new BatteryDataParser[parsers.length + 1];
- for (int i = 0; i < parsers.length; i++) {
- parserList[i] = parsers[i];
- }
- parserList[parsers.length] = parser;
- parse(mStats, remainingTimeUs, parserList);
- final Context context = view.getContext();
- String timeString = context.getString(R.string.charge_length_format,
- Formatter.formatShortElapsedTime(context, timePeriod));
- String remaining = "";
- if (remainingTimeUs != 0) {
- remaining = context.getString(R.string.remaining_length_format,
- Formatter.formatShortElapsedTime(context, remainingTimeUs / 1000));
- }
- view.setBottomLabels(new CharSequence[]{timeString, remaining});
- }
-
- public static void getBatteryInfo(final Context context, final Callback callback) {
- BatteryInfo.getBatteryInfo(context, callback, false /* shortString */);
- }
-
- public static void getBatteryInfo(final Context context, final Callback callback,
- boolean shortString) {
- new AsyncTask<Void, Void, BatteryStats>() {
- @Override
- protected BatteryStats doInBackground(Void... params) {
- BatteryStatsHelper statsHelper = new BatteryStatsHelper(context, true);
- statsHelper.create((Bundle) null);
- return statsHelper.getStats();
- }
-
- @Override
- protected void onPostExecute(BatteryStats batteryStats) {
- final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
- Intent batteryBroadcast = context.registerReceiver(null,
- new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
- BatteryInfo batteryInfo = BatteryInfo.getBatteryInfo(context, batteryBroadcast,
- batteryStats, elapsedRealtimeUs, shortString);
- callback.onBatteryInfoLoaded(batteryInfo);
- }
- }.execute();
- }
-
- public static BatteryInfo getBatteryInfo(Context context, Intent batteryBroadcast,
- BatteryStats stats, long elapsedRealtimeUs) {
- return BatteryInfo.getBatteryInfo(context, batteryBroadcast, stats, elapsedRealtimeUs,
- false /* shortString */);
- }
-
- public static BatteryInfo getBatteryInfo(Context context, Intent batteryBroadcast,
- BatteryStats stats, long elapsedRealtimeUs, boolean shortString) {
- BatteryInfo info = new BatteryInfo();
- info.mStats = stats;
- info.batteryLevel = Utils.getBatteryLevel(batteryBroadcast);
- info.batteryPercentString = Utils.formatPercentage(info.batteryLevel);
- info.mCharging = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
- final Resources resources = context.getResources();
-
- info.statusLabel = Utils.getBatteryStatus(resources, batteryBroadcast);
- if (!info.mCharging) {
- final long drainTime = stats.computeBatteryTimeRemaining(elapsedRealtimeUs);
- if (drainTime > 0) {
- info.remainingTimeUs = drainTime;
- String timeString = Formatter.formatShortElapsedTime(context,
- drainTime / 1000);
- info.remainingLabel = resources.getString(
- shortString ? R.string.power_remaining_duration_only_short
- : R.string.power_remaining_duration_only,
- timeString);
- info.chargeLabelString = resources.getString(
- shortString ? R.string.power_discharging_duration_short
- : R.string.power_discharging_duration,
- info.batteryPercentString, timeString);
- } else {
- info.remainingLabel = null;
- info.chargeLabelString = info.batteryPercentString;
- }
- } else {
- final long chargeTime = stats.computeChargeTimeRemaining(elapsedRealtimeUs);
- final int status = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_STATUS,
- BatteryManager.BATTERY_STATUS_UNKNOWN);
- info.discharging = false;
- if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
- info.remainingTimeUs = chargeTime;
- String timeString = Formatter.formatShortElapsedTime(context,
- chargeTime / 1000);
- int resId = shortString ? R.string.power_charging_duration_short
- : R.string.power_charging_duration;
- info.remainingLabel = resources.getString(
- R.string.power_remaining_charging_duration_only, timeString);
- info.chargeLabelString = resources.getString(
- resId, info.batteryPercentString, timeString);
- } else {
- final String chargeStatusLabel = resources.getString(
- R.string.battery_info_status_charging_lower);
- info.remainingLabel = null;
- info.chargeLabelString = resources.getString(
- R.string.power_charging, info.batteryPercentString, chargeStatusLabel);
- }
- }
- return info;
- }
-
- public interface BatteryDataParser {
- void onParsingStarted(long startTime, long endTime);
-
- void onDataPoint(long time, HistoryItem record);
-
- void onDataGap();
-
- void onParsingDone();
- }
-
- private static void parse(BatteryStats stats, long remainingTimeUs,
- BatteryDataParser... parsers) {
- long startWalltime = 0;
- long endDateWalltime = 0;
- long endWalltime = 0;
- long historyStart = 0;
- long historyEnd = 0;
- byte lastLevel = -1;
- long curWalltime = startWalltime;
- long lastWallTime = 0;
- long lastRealtime = 0;
- int lastInteresting = 0;
- int pos = 0;
- boolean first = true;
- if (stats.startIteratingHistoryLocked()) {
- final HistoryItem rec = new HistoryItem();
- while (stats.getNextHistoryLocked(rec)) {
- pos++;
- if (first) {
- first = false;
- historyStart = rec.time;
- }
- if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
- || rec.cmd == HistoryItem.CMD_RESET) {
- // If there is a ridiculously large jump in time, then we won't be
- // able to create a good chart with that data, so just ignore the
- // times we got before and pretend like our data extends back from
- // the time we have now.
- // Also, if we are getting a time change and we are less than 5 minutes
- // since the start of the history real time, then also use this new
- // time to compute the base time, since whatever time we had before is
- // pretty much just noise.
- if (rec.currentTime > (lastWallTime + (180 * 24 * 60 * 60 * 1000L))
- || rec.time < (historyStart + (5 * 60 * 1000L))) {
- startWalltime = 0;
- }
- lastWallTime = rec.currentTime;
- lastRealtime = rec.time;
- if (startWalltime == 0) {
- startWalltime = lastWallTime - (lastRealtime - historyStart);
- }
- }
- if (rec.isDeltaData()) {
- if (rec.batteryLevel != lastLevel || pos == 1) {
- lastLevel = rec.batteryLevel;
- }
- lastInteresting = pos;
- historyEnd = rec.time;
- }
- }
- }
- stats.finishIteratingHistoryLocked();
- endDateWalltime = lastWallTime + historyEnd - lastRealtime;
- endWalltime = endDateWalltime + (remainingTimeUs / 1000);
-
- int i = 0;
- final int N = lastInteresting;
-
- for (int j = 0; j < parsers.length; j++) {
- parsers[j].onParsingStarted(startWalltime, endWalltime);
- }
- if (endDateWalltime > startWalltime && stats.startIteratingHistoryLocked()) {
- final HistoryItem rec = new HistoryItem();
- while (stats.getNextHistoryLocked(rec) && i < N) {
- if (rec.isDeltaData()) {
- curWalltime += rec.time - lastRealtime;
- lastRealtime = rec.time;
- long x = (curWalltime - startWalltime);
- if (x < 0) {
- x = 0;
- }
- for (int j = 0; j < parsers.length; j++) {
- parsers[j].onDataPoint(x, rec);
- }
- } else {
- long lastWalltime = curWalltime;
- if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
- || rec.cmd == HistoryItem.CMD_RESET) {
- if (rec.currentTime >= startWalltime) {
- curWalltime = rec.currentTime;
- } else {
- curWalltime = startWalltime + (rec.time - historyStart);
- }
- lastRealtime = rec.time;
- }
-
- if (rec.cmd != HistoryItem.CMD_OVERFLOW
- && (rec.cmd != HistoryItem.CMD_CURRENT_TIME
- || Math.abs(lastWalltime - curWalltime) > (60 * 60 * 1000))) {
- for (int j = 0; j < parsers.length; j++) {
- parsers[j].onDataGap();
- }
- }
- }
- i++;
- }
- }
-
- stats.finishIteratingHistoryLocked();
-
- for (int j = 0; j < parsers.length; j++) {
- parsers[j].onParsingDone();
- }
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java b/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java
deleted file mode 100644
index 4b0ab59..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java
+++ /dev/null
@@ -1,461 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.settingslib;
-
-import android.Manifest;
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.annotation.RequiresPermission;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Xml;
-import android.view.InflateException;
-import com.android.settingslib.drawer.Tile;
-import com.android.settingslib.drawer.TileUtils;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-public class SuggestionParser {
-
- private static final String TAG = "SuggestionParser";
-
- // If defined, only returns this suggestion if the feature is supported.
- public static final String META_DATA_REQUIRE_FEATURE = "com.android.settings.require_feature";
-
- // If defined, only display this optional step if an account of that type exists.
- private static final String META_DATA_REQUIRE_ACCOUNT = "com.android.settings.require_account";
-
- // If defined and not true, do not should optional step.
- private static final String META_DATA_IS_SUPPORTED = "com.android.settings.is_supported";
-
- // If defined, only display this optional step if the current user is of that type.
- private static final String META_DATA_REQUIRE_USER_TYPE =
- "com.android.settings.require_user_type";
-
- // If defined, only display this optional step if a connection is available.
- private static final String META_DATA_IS_CONNECTION_REQUIRED =
- "com.android.settings.require_connection";
-
- // The valid values that setup wizard recognizes for differentiating user types.
- private static final String META_DATA_PRIMARY_USER_TYPE_VALUE = "primary";
- private static final String META_DATA_ADMIN_USER_TYPE_VALUE = "admin";
- private static final String META_DATA_GUEST_USER_TYPE_VALUE = "guest";
- private static final String META_DATA_RESTRICTED_USER_TYPE_VALUE = "restricted";
-
- /**
- * Allows suggestions to appear after a certain number of days, and to re-appear if dismissed.
- * For instance:
- * 0,10
- * Will appear immediately, but if the user removes it, it will come back after 10 days.
- *
- * Another example:
- * 10,30
- * Will only show up after 10 days, and then again after 30.
- */
- public static final String META_DATA_DISMISS_CONTROL = "com.android.settings.dismiss";
-
- // Shared prefs keys for storing dismissed state.
- // Index into current dismissed state.
- private static final String DISMISS_INDEX = "_dismiss_index";
- private static final String SETUP_TIME = "_setup_time";
- private static final String IS_DISMISSED = "_is_dismissed";
-
- private static final long MILLIS_IN_DAY = 24 * 60 * 60 * 1000;
-
- // Default dismiss control for smart suggestions.
- private static final String DEFAULT_SMART_DISMISS_CONTROL = "0,10";
-
- private final Context mContext;
- private final List<SuggestionCategory> mSuggestionList;
- private final ArrayMap<Pair<String, String>, Tile> mAddCache = new ArrayMap<>();
- private final SharedPreferences mSharedPrefs;
- private final String mSmartDismissControl;
-
-
- public SuggestionParser(
- Context context, SharedPreferences sharedPrefs, int orderXml, String smartDismissControl) {
- mContext = context;
- mSuggestionList = (List<SuggestionCategory>) new SuggestionOrderInflater(mContext)
- .parse(orderXml);
- mSharedPrefs = sharedPrefs;
- mSmartDismissControl = smartDismissControl;
- }
-
- public SuggestionParser(Context context, SharedPreferences sharedPrefs, int orderXml) {
- this(context, sharedPrefs, orderXml, DEFAULT_SMART_DISMISS_CONTROL);
- }
-
- @VisibleForTesting
- public SuggestionParser(Context context, SharedPreferences sharedPrefs) {
- mContext = context;
- mSuggestionList = new ArrayList<SuggestionCategory>();
- mSharedPrefs = sharedPrefs;
- mSmartDismissControl = DEFAULT_SMART_DISMISS_CONTROL;
- Log.wtf(TAG, "Only use this constructor for testing");
- }
-
- public List<Tile> getSuggestions() {
- return getSuggestions(false);
- }
-
- public List<Tile> getSuggestions(boolean isSmartSuggestionEnabled) {
- List<Tile> suggestions = new ArrayList<>();
- final int N = mSuggestionList.size();
- for (int i = 0; i < N; i++) {
- readSuggestions(mSuggestionList.get(i), suggestions, isSmartSuggestionEnabled);
- }
- return suggestions;
- }
-
- public boolean dismissSuggestion(Tile suggestion) {
- return dismissSuggestion(suggestion, false);
- }
-
- /**
- * Dismisses a suggestion, returns true if the suggestion has no more dismisses left and should
- * be disabled.
- */
- public boolean dismissSuggestion(Tile suggestion, boolean isSmartSuggestionEnabled) {
- String keyBase = suggestion.intent.getComponent().flattenToShortString();
- int index = mSharedPrefs.getInt(keyBase + DISMISS_INDEX, 0);
- String dismissControl = getDismissControl(suggestion, isSmartSuggestionEnabled);
- if (dismissControl == null || parseDismissString(dismissControl).length == index) {
- return true;
- }
- mSharedPrefs.edit()
- .putBoolean(keyBase + IS_DISMISSED, true)
- .commit();
- return false;
- }
-
- @VisibleForTesting
- public void filterSuggestions(
- List<Tile> suggestions, int countBefore, boolean isSmartSuggestionEnabled) {
- for (int i = countBefore; i < suggestions.size(); i++) {
- if (!isAvailable(suggestions.get(i)) ||
- !isSupported(suggestions.get(i)) ||
- !satisifesRequiredUserType(suggestions.get(i)) ||
- !satisfiesRequiredAccount(suggestions.get(i)) ||
- !satisfiesConnectivity(suggestions.get(i)) ||
- isDismissed(suggestions.get(i), isSmartSuggestionEnabled)) {
- suggestions.remove(i--);
- }
- }
- }
-
- @VisibleForTesting
- void readSuggestions(
- SuggestionCategory category, List<Tile> suggestions, boolean isSmartSuggestionEnabled) {
- int countBefore = suggestions.size();
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.addCategory(category.category);
- if (category.pkg != null) {
- intent.setPackage(category.pkg);
- }
- TileUtils.getTilesForIntent(mContext, new UserHandle(UserHandle.myUserId()), intent,
- mAddCache, null, suggestions, true, false);
- filterSuggestions(suggestions, countBefore, isSmartSuggestionEnabled);
- if (!category.multiple && suggestions.size() > (countBefore + 1)) {
- // If there are too many, remove them all and only re-add the one with the highest
- // priority.
- Tile item = suggestions.remove(suggestions.size() - 1);
- while (suggestions.size() > countBefore) {
- Tile last = suggestions.remove(suggestions.size() - 1);
- if (last.priority > item.priority) {
- item = last;
- }
- }
- // If category is marked as done, do not add any item.
- if (!isCategoryDone(category.category)) {
- suggestions.add(item);
- }
- }
- }
-
- private boolean isAvailable(Tile suggestion) {
- final String featuresRequired = suggestion.metaData.getString(META_DATA_REQUIRE_FEATURE);
- if (featuresRequired != null) {
- for (String feature : featuresRequired.split(",")) {
- if (TextUtils.isEmpty(feature)) {
- Log.w(TAG, "Found empty substring when parsing required features: "
- + featuresRequired);
- } else if (!mContext.getPackageManager().hasSystemFeature(feature)) {
- Log.i(TAG, suggestion.title + " requires unavailable feature " + feature);
- return false;
- }
- }
- }
- return true;
- }
-
- @RequiresPermission(Manifest.permission.MANAGE_USERS)
- private boolean satisifesRequiredUserType(Tile suggestion) {
- final String requiredUser = suggestion.metaData.getString(META_DATA_REQUIRE_USER_TYPE);
- if (requiredUser != null) {
- final UserManager userManager = mContext.getSystemService(UserManager.class);
- UserInfo userInfo = userManager.getUserInfo(UserHandle.myUserId());
- for (String userType : requiredUser.split("\\|")) {
- final boolean primaryUserCondtionMet = userInfo.isPrimary()
- && META_DATA_PRIMARY_USER_TYPE_VALUE.equals(userType);
- final boolean adminUserConditionMet = userInfo.isAdmin()
- && META_DATA_ADMIN_USER_TYPE_VALUE.equals(userType);
- final boolean guestUserCondtionMet = userInfo.isGuest()
- && META_DATA_GUEST_USER_TYPE_VALUE.equals(userType);
- final boolean restrictedUserCondtionMet = userInfo.isRestricted()
- && META_DATA_RESTRICTED_USER_TYPE_VALUE.equals(userType);
- if (primaryUserCondtionMet || adminUserConditionMet || guestUserCondtionMet
- || restrictedUserCondtionMet) {
- return true;
- }
- }
- Log.i(TAG, suggestion.title + " requires user type " + requiredUser);
- return false;
- }
- return true;
- }
-
- public boolean satisfiesRequiredAccount(Tile suggestion) {
- final String requiredAccountType = suggestion.metaData.getString(META_DATA_REQUIRE_ACCOUNT);
- if (requiredAccountType == null) {
- return true;
- }
- AccountManager accountManager = AccountManager.get(mContext);
- Account[] accounts = accountManager.getAccountsByType(requiredAccountType);
- boolean satisfiesRequiredAccount = accounts.length > 0;
- if (!satisfiesRequiredAccount) {
- Log.i(TAG, suggestion.title + " requires unavailable account type "
- + requiredAccountType);
- }
- return satisfiesRequiredAccount;
- }
-
- public boolean isSupported(Tile suggestion) {
- final int isSupportedResource = suggestion.metaData.getInt(META_DATA_IS_SUPPORTED);
- try {
- if (suggestion.intent == null) {
- return false;
- }
- final Resources res = mContext.getPackageManager().getResourcesForActivity(
- suggestion.intent.getComponent());
- boolean isSupported =
- isSupportedResource != 0 ? res.getBoolean(isSupportedResource) : true;
- if (!isSupported) {
- Log.i(TAG, suggestion.title + " requires unsupported resource "
- + isSupportedResource);
- }
- return isSupported;
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Cannot find resources for " + suggestion.intent.getComponent());
- return false;
- } catch (Resources.NotFoundException e) {
- Log.w(TAG, "Cannot find resources for " + suggestion.intent.getComponent(), e);
- return false;
- }
- }
-
- private boolean satisfiesConnectivity(Tile suggestion) {
- final boolean isConnectionRequired =
- suggestion.metaData.getBoolean(META_DATA_IS_CONNECTION_REQUIRED);
- if (!isConnectionRequired) {
- return true;
- }
- ConnectivityManager cm =
- (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo netInfo = cm.getActiveNetworkInfo();
- boolean satisfiesConnectivity = netInfo != null && netInfo.isConnectedOrConnecting();
- if (!satisfiesConnectivity) {
- Log.i(TAG, suggestion.title + " is missing required connection.");
- }
- return satisfiesConnectivity;
- }
-
- public boolean isCategoryDone(String category) {
- String name = Settings.Secure.COMPLETED_CATEGORY_PREFIX + category;
- return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) != 0;
- }
-
- public void markCategoryDone(String category) {
- String name = Settings.Secure.COMPLETED_CATEGORY_PREFIX + category;
- Settings.Secure.putInt(mContext.getContentResolver(), name, 1);
- }
-
- private boolean isDismissed(Tile suggestion, boolean isSmartSuggestionEnabled) {
- String dismissControl = getDismissControl(suggestion, isSmartSuggestionEnabled);
- if (dismissControl == null) {
- return false;
- }
- String keyBase = suggestion.intent.getComponent().flattenToShortString();
- if (!mSharedPrefs.contains(keyBase + SETUP_TIME)) {
- mSharedPrefs.edit()
- .putLong(keyBase + SETUP_TIME, System.currentTimeMillis())
- .commit();
- }
- // Default to dismissed, so that we can have suggestions that only first appear after
- // some number of days.
- if (!mSharedPrefs.getBoolean(keyBase + IS_DISMISSED, true)) {
- return false;
- }
- int index = mSharedPrefs.getInt(keyBase + DISMISS_INDEX, 0);
- int currentDismiss = parseDismissString(dismissControl)[index];
- long time = getEndTime(mSharedPrefs.getLong(keyBase + SETUP_TIME, 0), currentDismiss);
- if (System.currentTimeMillis() >= time) {
- // Dismiss timeout has passed, undismiss it.
- mSharedPrefs.edit()
- .putBoolean(keyBase + IS_DISMISSED, false)
- .putInt(keyBase + DISMISS_INDEX, index + 1)
- .commit();
- return false;
- }
- return true;
- }
-
- private long getEndTime(long startTime, int daysDelay) {
- long days = daysDelay * MILLIS_IN_DAY;
- return startTime + days;
- }
-
- private int[] parseDismissString(String dismissControl) {
- String[] dismissStrs = dismissControl.split(",");
- int[] dismisses = new int[dismissStrs.length];
- for (int i = 0; i < dismissStrs.length; i++) {
- dismisses[i] = Integer.parseInt(dismissStrs[i]);
- }
- return dismisses;
- }
-
- private String getDismissControl(Tile suggestion, boolean isSmartSuggestionEnabled) {
- if (isSmartSuggestionEnabled) {
- return mSmartDismissControl;
- } else {
- return suggestion.metaData.getString(META_DATA_DISMISS_CONTROL);
- }
- }
-
- @VisibleForTesting
- static class SuggestionCategory {
- public String category;
- public String pkg;
- public boolean multiple;
- }
-
- private static class SuggestionOrderInflater {
- private static final String TAG_LIST = "optional-steps";
- private static final String TAG_ITEM = "step";
-
- private static final String ATTR_CATEGORY = "category";
- private static final String ATTR_PACKAGE = "package";
- private static final String ATTR_MULTIPLE = "multiple";
-
- private final Context mContext;
-
- public SuggestionOrderInflater(Context context) {
- mContext = context;
- }
-
- public Object parse(int resource) {
- XmlPullParser parser = mContext.getResources().getXml(resource);
- final AttributeSet attrs = Xml.asAttributeSet(parser);
- try {
- // Look for the root node.
- int type;
- do {
- type = parser.next();
- } while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT);
-
- if (type != XmlPullParser.START_TAG) {
- throw new InflateException(parser.getPositionDescription()
- + ": No start tag found!");
- }
-
- // Temp is the root that was found in the xml
- Object xmlRoot = onCreateItem(parser.getName(), attrs);
-
- // Inflate all children under temp
- rParse(parser, xmlRoot, attrs);
- return xmlRoot;
- } catch (XmlPullParserException | IOException e) {
- Log.w(TAG, "Problem parser resource " + resource, e);
- return null;
- }
- }
-
- /**
- * Recursive method used to descend down the xml hierarchy and instantiate
- * items, instantiate their children.
- */
- private void rParse(XmlPullParser parser, Object parent, final AttributeSet attrs)
- throws XmlPullParserException, IOException {
- final int depth = parser.getDepth();
-
- int type;
- while (((type = parser.next()) != XmlPullParser.END_TAG ||
- parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
- if (type != XmlPullParser.START_TAG) {
- continue;
- }
-
- final String name = parser.getName();
-
- Object item = onCreateItem(name, attrs);
- onAddChildItem(parent, item);
- rParse(parser, item, attrs);
- }
- }
-
- protected void onAddChildItem(Object parent, Object child) {
- if (parent instanceof List<?> && child instanceof SuggestionCategory) {
- ((List<SuggestionCategory>) parent).add((SuggestionCategory) child);
- } else {
- throw new IllegalArgumentException("Parent was not a list");
- }
- }
-
- protected Object onCreateItem(String name, AttributeSet attrs) {
- if (name.equals(TAG_LIST)) {
- return new ArrayList<SuggestionCategory>();
- } else if (name.equals(TAG_ITEM)) {
- SuggestionCategory category = new SuggestionCategory();
- category.category = attrs.getAttributeValue(null, ATTR_CATEGORY);
- category.pkg = attrs.getAttributeValue(null, ATTR_PACKAGE);
- String multiple = attrs.getAttributeValue(null, ATTR_MULTIPLE);
- category.multiple = !TextUtils.isEmpty(multiple) && Boolean.parseBoolean(multiple);
- return category;
- } else {
- throw new IllegalArgumentException("Unknown item " + name);
- }
- }
- }
-}
-
diff --git a/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
index 151e0ea..abbef70 100644
--- a/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
@@ -17,13 +17,19 @@
import android.content.Context;
import android.os.SystemProperties;
+import android.support.annotation.VisibleForTesting;
import android.telephony.CarrierConfigManager;
public class TetherUtil {
- private static boolean isEntitlementCheckRequired(Context context) {
+ @VisibleForTesting
+ static boolean isEntitlementCheckRequired(Context context) {
final CarrierConfigManager configManager = (CarrierConfigManager) context
.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (configManager == null || configManager.getConfig() == null) {
+ // return service default
+ return true;
+ }
return configManager.getConfig().getBoolean(CarrierConfigManager
.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 3135f1d..b21f2fa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -21,6 +21,7 @@
import android.os.BatteryManager;
import android.os.UserManager;
import android.print.PrintManager;
+import android.provider.Settings;
import android.view.View;
import com.android.internal.util.UserIcons;
@@ -313,4 +314,20 @@
"No badge resource found for badge value: " + badge);
}
}
+
+ public static int getDefaultStorageManagerDaysToRetain(Resources resources) {
+ int defaultDays = Settings.Secure.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN_DEFAULT;
+ try {
+ defaultDays =
+ resources.getInteger(
+ com.android
+ .internal
+ .R
+ .integer
+ .config_storageManagerDaystoRetainDefault);
+ } catch (Resources.NotFoundException e) {
+ // We are likely in a test environment.
+ }
+ return defaultDays;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
index 2fb6843..22674cb 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -90,6 +90,10 @@
return mAdapter.disable();
}
+ public String getAddress() {
+ return mAdapter.getAddress();
+ }
+
void getProfileProxy(Context context,
BluetoothProfile.ServiceListener listener, int profile) {
mAdapter.getProfileProxy(context, listener, profile);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
index e6a152f..0b45f51 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
@@ -34,7 +34,7 @@
/**
* MapProfile handles Bluetooth MAP profile.
*/
-public final class MapProfile implements LocalBluetoothProfile {
+public class MapProfile implements LocalBluetoothProfile {
private static final String TAG = "MapProfile";
private static boolean V = true;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
index bd37abe..28137ff 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -32,7 +32,7 @@
import java.util.Collection;
import java.util.List;
-final class PbapClientProfile implements LocalBluetoothProfile {
+public final class PbapClientProfile implements LocalBluetoothProfile {
private static final String TAG = "PbapClientProfile";
private static boolean V = false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
index 9e76933..f3b6912 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -25,19 +25,21 @@
import android.os.ParcelUuid;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.R;
/**
* PBAPServer Profile
*/
-public final class PbapServerProfile implements LocalBluetoothProfile {
+public class PbapServerProfile implements LocalBluetoothProfile {
private static final String TAG = "PbapServerProfile";
private static boolean V = true;
private BluetoothPbap mService;
private boolean mIsProfileReady;
- static final String NAME = "PBAP Server";
+ @VisibleForTesting
+ public static final String NAME = "PBAP Server";
// Order of this profile in device profiles list
private static final int ORDINAL = 6;
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/Lifecycle.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/Lifecycle.java
new file mode 100644
index 0000000..b2351a9
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/Lifecycle.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.lifecycle;
+
+import android.annotation.UiThread;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.preference.PreferenceScreen;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import com.android.settingslib.core.lifecycle.events.OnAttach;
+import com.android.settingslib.core.lifecycle.events.OnCreate;
+import com.android.settingslib.core.lifecycle.events.OnCreateOptionsMenu;
+import com.android.settingslib.core.lifecycle.events.OnDestroy;
+import com.android.settingslib.core.lifecycle.events.OnOptionsItemSelected;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnPrepareOptionsMenu;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+import com.android.settingslib.core.lifecycle.events.SetPreferenceScreen;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Dispatcher for lifecycle events.
+ */
+public class Lifecycle {
+
+ protected final List<LifecycleObserver> mObservers = new ArrayList<>();
+
+ /**
+ * Registers a new observer of lifecycle events.
+ */
+ @UiThread
+ public <T extends LifecycleObserver> T addObserver(T observer) {
+ ThreadUtils.ensureMainThread();
+ mObservers.add(observer);
+ return observer;
+ }
+
+ public void onAttach(Context context) {
+ for (int i = 0, size = mObservers.size(); i < size; i++) {
+ final LifecycleObserver observer = mObservers.get(i);
+ if (observer instanceof OnAttach) {
+ ((OnAttach) observer).onAttach(context);
+ }
+ }
+ }
+
+ public void onCreate(Bundle savedInstanceState) {
+ for (int i = 0, size = mObservers.size(); i < size; i++) {
+ final LifecycleObserver observer = mObservers.get(i);
+ if (observer instanceof OnCreate) {
+ ((OnCreate) observer).onCreate(savedInstanceState);
+ }
+ }
+ }
+
+ public void onStart() {
+ for (int i = 0, size = mObservers.size(); i < size; i++) {
+ final LifecycleObserver observer = mObservers.get(i);
+ if (observer instanceof OnStart) {
+ ((OnStart) observer).onStart();
+ }
+ }
+ }
+
+ public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
+ for (int i = 0, size = mObservers.size(); i < size; i++) {
+ final LifecycleObserver observer = mObservers.get(i);
+ if (observer instanceof SetPreferenceScreen) {
+ ((SetPreferenceScreen) observer).setPreferenceScreen(preferenceScreen);
+ }
+ }
+ }
+
+ public void onResume() {
+ for (int i = 0, size = mObservers.size(); i < size; i++) {
+ final LifecycleObserver observer = mObservers.get(i);
+ if (observer instanceof OnResume) {
+ ((OnResume) observer).onResume();
+ }
+ }
+ }
+
+ public void onPause() {
+ for (int i = 0, size = mObservers.size(); i < size; i++) {
+ final LifecycleObserver observer = mObservers.get(i);
+ if (observer instanceof OnPause) {
+ ((OnPause) observer).onPause();
+ }
+ }
+ }
+
+ public void onSaveInstanceState(Bundle outState) {
+ for (int i = 0, size = mObservers.size(); i < size; i++) {
+ final LifecycleObserver observer = mObservers.get(i);
+ if (observer instanceof OnSaveInstanceState) {
+ ((OnSaveInstanceState) observer).onSaveInstanceState(outState);
+ }
+ }
+ }
+
+ public void onStop() {
+ for (int i = 0, size = mObservers.size(); i < size; i++) {
+ final LifecycleObserver observer = mObservers.get(i);
+ if (observer instanceof OnStop) {
+ ((OnStop) observer).onStop();
+ }
+ }
+ }
+
+ public void onDestroy() {
+ for (int i = 0, size = mObservers.size(); i < size; i++) {
+ final LifecycleObserver observer = mObservers.get(i);
+ if (observer instanceof OnDestroy) {
+ ((OnDestroy) observer).onDestroy();
+ }
+ }
+ }
+
+ public void onCreateOptionsMenu(final Menu menu, final @Nullable MenuInflater inflater) {
+ for (int i = 0, size = mObservers.size(); i < size; i++) {
+ final LifecycleObserver observer = mObservers.get(i);
+ if (observer instanceof OnCreateOptionsMenu) {
+ ((OnCreateOptionsMenu) observer).onCreateOptionsMenu(menu, inflater);
+ }
+ }
+ }
+
+ public void onPrepareOptionsMenu(final Menu menu) {
+ for (int i = 0, size = mObservers.size(); i < size; i++) {
+ final LifecycleObserver observer = mObservers.get(i);
+ if (observer instanceof OnPrepareOptionsMenu) {
+ ((OnPrepareOptionsMenu) observer).onPrepareOptionsMenu(menu);
+ }
+ }
+ }
+
+ public boolean onOptionsItemSelected(final MenuItem menuItem) {
+ for (int i = 0, size = mObservers.size(); i < size; i++) {
+ final LifecycleObserver observer = mObservers.get(i);
+ if (observer instanceof OnOptionsItemSelected) {
+ if (((OnOptionsItemSelected) observer).onOptionsItemSelected(menuItem)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/LifecycleObserver.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/LifecycleObserver.java
new file mode 100644
index 0000000..6c41072
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/LifecycleObserver.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.lifecycle;
+
+/**
+ * Observer of lifecycle events.
+ */
+public interface LifecycleObserver {
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
new file mode 100644
index 0000000..727bec7
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.lifecycle;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.view.Menu;
+import android.view.MenuItem;
+
+/**
+ * {@link Activity} that has hooks to observe activity lifecycle events.
+ */
+public class ObservableActivity extends Activity {
+
+ private final Lifecycle mLifecycle = new Lifecycle();
+
+ protected Lifecycle getLifecycle() {
+ return mLifecycle;
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ mLifecycle.onAttach(this);
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState,
+ @Nullable PersistableBundle persistentState) {
+ mLifecycle.onAttach(this);
+ super.onCreate(savedInstanceState, persistentState);
+ }
+
+ @Override
+ protected void onStart() {
+ mLifecycle.onStart();
+ super.onStart();
+ }
+
+ @Override
+ protected void onResume() {
+ mLifecycle.onResume();
+ super.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ mLifecycle.onPause();
+ super.onPause();
+ }
+
+ @Override
+ protected void onStop() {
+ mLifecycle.onStop();
+ super.onStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ mLifecycle.onDestroy();
+ super.onDestroy();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ if (super.onCreateOptionsMenu(menu)) {
+ mLifecycle.onCreateOptionsMenu(menu, null);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(final Menu menu) {
+ if (super.onPrepareOptionsMenu(menu)) {
+ mLifecycle.onPrepareOptionsMenu(menu);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem menuItem) {
+ boolean lifecycleHandled = mLifecycle.onOptionsItemSelected(menuItem);
+ if (!lifecycleHandled) {
+ return super.onOptionsItemSelected(menuItem);
+ }
+ return lifecycleHandled;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
new file mode 100644
index 0000000..315bedc
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.lifecycle;
+
+import android.app.DialogFragment;
+import android.content.Context;
+import android.support.annotation.VisibleForTesting;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+/**
+ * {@link DialogFragment} that has hooks to observe fragment lifecycle events.
+ */
+public class ObservableDialogFragment extends DialogFragment {
+
+ protected final Lifecycle mLifecycle = createLifecycle();
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mLifecycle.onAttach(context);
+ }
+
+ @Override
+ public void onStart() {
+ mLifecycle.onStart();
+ super.onStart();
+ }
+
+ @Override
+ public void onResume() {
+ mLifecycle.onResume();
+ super.onResume();
+ }
+
+ @Override
+ public void onPause() {
+ mLifecycle.onPause();
+ super.onPause();
+ }
+
+ @Override
+ public void onStop() {
+ mLifecycle.onStop();
+ super.onStop();
+ }
+
+ @Override
+ public void onDestroy() {
+ mLifecycle.onDestroy();
+ super.onDestroy();
+ }
+
+ @Override
+ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+ mLifecycle.onCreateOptionsMenu(menu, inflater);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(final Menu menu) {
+ mLifecycle.onPrepareOptionsMenu(menu);
+ super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem menuItem) {
+ boolean lifecycleHandled = mLifecycle.onOptionsItemSelected(menuItem);
+ if (!lifecycleHandled) {
+ return super.onOptionsItemSelected(menuItem);
+ }
+ return lifecycleHandled;
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ /** @return a new lifecycle. */
+ public static Lifecycle createLifecycle() {
+ return new Lifecycle();
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java
new file mode 100644
index 0000000..3a00eba
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.core.lifecycle;
+
+import android.annotation.CallSuper;
+import android.app.Fragment;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+public class ObservableFragment extends Fragment {
+
+ private final Lifecycle mLifecycle = new Lifecycle();
+
+ protected Lifecycle getLifecycle() {
+ return mLifecycle;
+ }
+
+ @CallSuper
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mLifecycle.onAttach(context);
+ }
+
+ @CallSuper
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ mLifecycle.onCreate(savedInstanceState);
+ super.onCreate(savedInstanceState);
+ }
+
+ @CallSuper
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ mLifecycle.onSaveInstanceState(outState);
+ }
+
+ @CallSuper
+ @Override
+ public void onStart() {
+ mLifecycle.onStart();
+ super.onStart();
+ }
+
+ @CallSuper
+ @Override
+ public void onStop() {
+ mLifecycle.onStop();
+ super.onStop();
+ }
+
+ @CallSuper
+ @Override
+ public void onResume() {
+ mLifecycle.onResume();
+ super.onResume();
+ }
+
+ @CallSuper
+ @Override
+ public void onPause() {
+ mLifecycle.onPause();
+ super.onPause();
+ }
+
+ @CallSuper
+ @Override
+ public void onDestroy() {
+ mLifecycle.onDestroy();
+ super.onDestroy();
+ }
+
+ @CallSuper
+ @Override
+ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+ mLifecycle.onCreateOptionsMenu(menu, inflater);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @CallSuper
+ @Override
+ public void onPrepareOptionsMenu(final Menu menu) {
+ mLifecycle.onPrepareOptionsMenu(menu);
+ super.onPrepareOptionsMenu(menu);
+ }
+
+ @CallSuper
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem menuItem) {
+ boolean lifecycleHandled = mLifecycle.onOptionsItemSelected(menuItem);
+ if (!lifecycleHandled) {
+ return super.onOptionsItemSelected(menuItem);
+ }
+ return lifecycleHandled;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
new file mode 100644
index 0000000..76e5c85
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.lifecycle;
+
+
+import android.annotation.CallSuper;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.PreferenceScreen;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+/**
+ * {@link PreferenceFragment} that has hooks to observe fragment lifecycle events.
+ */
+public abstract class ObservablePreferenceFragment extends PreferenceFragment {
+
+ private final Lifecycle mLifecycle = new Lifecycle();
+
+ protected Lifecycle getLifecycle() {
+ return mLifecycle;
+ }
+
+ @CallSuper
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mLifecycle.onAttach(context);
+ }
+
+ @CallSuper
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ mLifecycle.onCreate(savedInstanceState);
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
+ mLifecycle.setPreferenceScreen(preferenceScreen);
+ super.setPreferenceScreen(preferenceScreen);
+ }
+
+ @CallSuper
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ mLifecycle.onSaveInstanceState(outState);
+ }
+
+ @CallSuper
+ @Override
+ public void onStart() {
+ mLifecycle.onStart();
+ super.onStart();
+ }
+
+ @CallSuper
+ @Override
+ public void onStop() {
+ mLifecycle.onStop();
+ super.onStop();
+ }
+
+ @CallSuper
+ @Override
+ public void onResume() {
+ mLifecycle.onResume();
+ super.onResume();
+ }
+
+ @CallSuper
+ @Override
+ public void onPause() {
+ mLifecycle.onPause();
+ super.onPause();
+ }
+
+ @CallSuper
+ @Override
+ public void onDestroy() {
+ mLifecycle.onDestroy();
+ super.onDestroy();
+ }
+
+ @CallSuper
+ @Override
+ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+ mLifecycle.onCreateOptionsMenu(menu, inflater);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @CallSuper
+ @Override
+ public void onPrepareOptionsMenu(final Menu menu) {
+ mLifecycle.onPrepareOptionsMenu(menu);
+ super.onPrepareOptionsMenu(menu);
+ }
+
+ @CallSuper
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem menuItem) {
+ boolean lifecycleHandled = mLifecycle.onOptionsItemSelected(menuItem);
+ if (!lifecycleHandled) {
+ return super.onOptionsItemSelected(menuItem);
+ }
+ return lifecycleHandled;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnAttach.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnAttach.java
new file mode 100644
index 0000000..152cbac
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnAttach.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.lifecycle.events;
+
+import android.content.Context;
+
+public interface OnAttach {
+ void onAttach(Context context);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnCreate.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnCreate.java
new file mode 100644
index 0000000..44cbf8d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnCreate.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.lifecycle.events;
+
+
+import android.os.Bundle;
+
+public interface OnCreate {
+ void onCreate(Bundle savedInstanceState);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnCreateOptionsMenu.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnCreateOptionsMenu.java
new file mode 100644
index 0000000..c14b7d7
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnCreateOptionsMenu.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.core.lifecycle.events;
+
+import android.view.Menu;
+import android.view.MenuInflater;
+
+public interface OnCreateOptionsMenu {
+ void onCreateOptionsMenu(Menu menu, MenuInflater inflater);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnDestroy.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnDestroy.java
new file mode 100644
index 0000000..ffa3d16
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnDestroy.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.lifecycle.events;
+
+public interface OnDestroy {
+ void onDestroy();
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnOptionsItemSelected.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnOptionsItemSelected.java
new file mode 100644
index 0000000..bcb3032
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnOptionsItemSelected.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.core.lifecycle.events;
+
+import android.view.MenuItem;
+
+public interface OnOptionsItemSelected {
+ boolean onOptionsItemSelected(MenuItem menuItem);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnPause.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnPause.java
new file mode 100644
index 0000000..4a71105
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnPause.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.lifecycle.events;
+
+public interface OnPause {
+ void onPause();
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnPrepareOptionsMenu.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnPrepareOptionsMenu.java
new file mode 100644
index 0000000..b9f1371
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnPrepareOptionsMenu.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.core.lifecycle.events;
+
+import android.view.Menu;
+import android.view.MenuInflater;
+
+public interface OnPrepareOptionsMenu {
+ void onPrepareOptionsMenu(Menu menu);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnResume.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnResume.java
new file mode 100644
index 0000000..8dd24e9
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnResume.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.lifecycle.events;
+
+public interface OnResume {
+ void onResume();
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnSaveInstanceState.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnSaveInstanceState.java
new file mode 100644
index 0000000..9d46110
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnSaveInstanceState.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.lifecycle.events;
+
+import android.os.Bundle;
+
+
+public interface OnSaveInstanceState {
+ void onSaveInstanceState(Bundle outState);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnStart.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnStart.java
new file mode 100644
index 0000000..c88ddaa
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnStart.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.lifecycle.events;
+
+public interface OnStart {
+
+ void onStart();
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnStop.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnStop.java
new file mode 100644
index 0000000..32f61d9
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnStop.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.lifecycle.events;
+
+public interface OnStop {
+
+ void onStop();
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/SetPreferenceScreen.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/SetPreferenceScreen.java
new file mode 100644
index 0000000..cdd72fb
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/SetPreferenceScreen.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.core.lifecycle.events;
+
+import android.support.v7.preference.PreferenceScreen;
+
+public interface SetPreferenceScreen {
+
+ void setPreferenceScreen(PreferenceScreen preferenceScreen);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java
index c7efb07..5a1e603 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java
@@ -23,6 +23,7 @@
import android.os.Parcelable;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.widget.RemoteViews;
import java.util.ArrayList;
@@ -84,6 +85,11 @@
*/
public String key;
+ /**
+ * Optional remote view which will be displayed instead of the regular title-summary item.
+ */
+ public RemoteViews remoteViews;
+
public Tile() {
// Empty
}
@@ -119,6 +125,7 @@
dest.writeInt(priority);
dest.writeBundle(metaData);
dest.writeString(key);
+ dest.writeParcelable(remoteViews, flags);
}
public void readFromParcel(Parcel in) {
@@ -139,6 +146,7 @@
priority = in.readInt();
metaData = in.readBundle();
key = in.readString();
+ remoteViews = in.readParcelable(RemoteViews.class.getClassLoader());
}
Tile(Parcel in) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index fb48054..f52a6b5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -21,7 +21,6 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
@@ -35,6 +34,7 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
+import android.widget.RemoteViews;
import java.util.ArrayList;
import java.util.Collections;
@@ -161,6 +161,14 @@
public static final String META_DATA_PREFERENCE_SUMMARY_URI =
"com.android.settings.summary_uri";
+ /**
+ * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the
+ * custom view which should be displayed for the preference. The custom view will be inflated
+ * as a remote view.
+ */
+ public static final String META_DATA_PREFERENCE_CUSTOM_VIEW =
+ "com.android.settings.custom_view";
+
public static final String SETTING_PKG = "com.android.settings";
/**
@@ -361,6 +369,7 @@
String summary = null;
String keyHint = null;
Uri uri = null;
+ RemoteViews remoteViews = null;
// Get the activity's meta-data
try {
@@ -402,6 +411,10 @@
keyHint = metaData.getString(META_DATA_PREFERENCE_KEYHINT);
}
}
+ if (metaData.containsKey(META_DATA_PREFERENCE_CUSTOM_VIEW)) {
+ int layoutId = metaData.getInt(META_DATA_PREFERENCE_CUSTOM_VIEW);
+ remoteViews = new RemoteViews(applicationInfo.packageName, layoutId);
+ }
}
} catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e);
@@ -431,6 +444,7 @@
activityInfo.name);
// Suggest a key for this tile
tile.key = keyHint;
+ tile.remoteViews = remoteViews;
return true;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index e5cdc85..988060e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -16,6 +16,7 @@
package com.android.settingslib.dream;
+import android.annotation.IntDef;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -35,6 +36,8 @@
import android.util.Log;
import android.util.Xml;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -68,6 +71,15 @@
}
}
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({WHILE_CHARGING, WHILE_DOCKED, EITHER, NEVER})
+ public @interface WhenToDream{}
+
+ public static final int WHILE_CHARGING = 0;
+ public static final int WHILE_DOCKED = 1;
+ public static final int EITHER = 2;
+ public static final int NEVER = 3;
+
private final Context mContext;
private final IDreamManager mDreamManager;
private final DreamInfoComparator mComparator;
@@ -75,6 +87,15 @@
private final boolean mDreamsActivatedOnSleepByDefault;
private final boolean mDreamsActivatedOnDockByDefault;
+ private static DreamBackend sInstance;
+
+ public static DreamBackend getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new DreamBackend(context);
+ }
+ return sInstance;
+ }
+
public DreamBackend(Context context) {
mContext = context;
mDreamManager = IDreamManager.Stub.asInterface(
@@ -138,6 +159,42 @@
return null;
}
+ public @WhenToDream int getWhenToDreamSetting() {
+ if (!isEnabled()) {
+ return NEVER;
+ }
+ return isActivatedOnDock() && isActivatedOnSleep() ? EITHER
+ : isActivatedOnDock() ? WHILE_DOCKED
+ : isActivatedOnSleep() ? WHILE_CHARGING
+ : NEVER;
+ }
+
+ public void setWhenToDream(@WhenToDream int whenToDream) {
+ setEnabled(whenToDream != NEVER);
+
+ switch (whenToDream) {
+ case WHILE_CHARGING:
+ setActivatedOnDock(false);
+ setActivatedOnSleep(true);
+ break;
+
+ case WHILE_DOCKED:
+ setActivatedOnDock(true);
+ setActivatedOnSleep(false);
+ break;
+
+ case EITHER:
+ setActivatedOnDock(true);
+ setActivatedOnSleep(true);
+ break;
+
+ case NEVER:
+ default:
+ break;
+ }
+
+ }
+
public boolean isEnabled() {
return getBoolean(Settings.Secure.SCREENSAVER_ENABLED, mDreamsEnabledByDefault);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
index 3d50f23..1bb1417 100755
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
@@ -26,6 +26,8 @@
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
+import android.graphics.Path.Direction;
+import android.graphics.Path.FillType;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
@@ -36,8 +38,9 @@
public class BatteryMeterDrawableBase extends Drawable {
- private static final float ASPECT_RATIO = 9.5f / 14.5f;
+ private static final float ASPECT_RATIO = .58f;
public static final String TAG = BatteryMeterDrawableBase.class.getSimpleName();
+ private static final float RADIUS_RATIO = 1.0f / 17f;
protected final Context mContext;
protected final Paint mFramePaint;
@@ -263,28 +266,20 @@
final int width = (int) (ASPECT_RATIO * mHeight);
int px = (mWidth - width) / 2;
- final int buttonHeight = (int) (height * mButtonHeightFraction);
+ final int buttonHeight = Math.round(height * mButtonHeightFraction);
mFrame.set(0, 0, width, height);
mFrame.offset(px, 0);
// button-frame: area above the battery body
mButtonFrame.set(
- mFrame.left + Math.round(width * 0.25f),
+ mFrame.left + Math.round(width * 0.28f),
mFrame.top,
- mFrame.right - Math.round(width * 0.25f),
+ mFrame.right - Math.round(width * 0.28f),
mFrame.top + buttonHeight);
- mButtonFrame.top += mSubpixelSmoothingLeft;
- mButtonFrame.left += mSubpixelSmoothingLeft;
- mButtonFrame.right -= mSubpixelSmoothingRight;
-
// frame: battery body area
mFrame.top += buttonHeight;
- mFrame.left += mSubpixelSmoothingLeft;
- mFrame.top += mSubpixelSmoothingLeft;
- mFrame.right -= mSubpixelSmoothingRight;
- mFrame.bottom -= mSubpixelSmoothingRight;
// set the battery charging color
mBatteryPaint.setColor(mCharging ? mChargeColor : getColorForLevel(level));
@@ -300,21 +295,17 @@
// define the battery shape
mShapePath.reset();
- mShapePath.moveTo(mButtonFrame.left, mButtonFrame.top);
- mShapePath.lineTo(mButtonFrame.right, mButtonFrame.top);
- mShapePath.lineTo(mButtonFrame.right, mFrame.top);
- mShapePath.lineTo(mFrame.right, mFrame.top);
- mShapePath.lineTo(mFrame.right, mFrame.bottom);
- mShapePath.lineTo(mFrame.left, mFrame.bottom);
- mShapePath.lineTo(mFrame.left, mFrame.top);
- mShapePath.lineTo(mButtonFrame.left, mFrame.top);
- mShapePath.lineTo(mButtonFrame.left, mButtonFrame.top);
+ final float radius = RADIUS_RATIO * (mFrame.height() + buttonHeight);
+ mShapePath.setFillType(FillType.WINDING);
+ mShapePath.addRoundRect(mFrame, radius, radius, Direction.CW);
+ mShapePath.addRect(mButtonFrame, Direction.CW);
if (mCharging) {
// define the bolt shape
- final float bl = mFrame.left + mFrame.width() / 4f;
+ // Shift right by 1px for maximal bolt-goodness
+ final float bl = mFrame.left + mFrame.width() / 4f + 1;
final float bt = mFrame.top + mFrame.height() / 6f;
- final float br = mFrame.right - mFrame.width() / 4f;
+ final float br = mFrame.right - mFrame.width() / 4f + 1;
final float bb = mFrame.bottom - mFrame.height() / 10f;
if (mBoltFrame.left != bl || mBoltFrame.top != bt
|| mBoltFrame.right != br || mBoltFrame.bottom != bb) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BottomLabelLayout.java b/packages/SettingsLib/src/com/android/settingslib/graph/BottomLabelLayout.java
deleted file mode 100644
index 8161dd4..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BottomLabelLayout.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- *
- */
-
-package com.android.settingslib.graph;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.support.annotation.VisibleForTesting;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import com.android.settingslib.R;
-
-/**
- * An extension of LinearLayout that automatically switches to vertical
- * orientation when it can't fit its child views horizontally.
- *
- * Main logic in this class comes from {@link android.support.v7.widget.ButtonBarLayout}.
- * Compared with {@link android.support.v7.widget.ButtonBarLayout}, this layout won't reverse
- * children's order and won't update the minimum height
- */
-public class BottomLabelLayout extends LinearLayout {
- private static final String TAG = "BottomLabelLayout";
-
- public BottomLabelLayout(Context context,
- @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- final boolean isStacked = isStacked();
- boolean needsRemeasure = false;
-
- // If we're not stacked, make sure the measure spec is AT_MOST rather
- // than EXACTLY. This ensures that we'll still get TOO_SMALL so that we
- // know to stack the buttons.
- final int initialWidthMeasureSpec;
- if (!isStacked && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
- initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
-
- // We'll need to remeasure again to fill excess space.
- needsRemeasure = true;
- } else {
- initialWidthMeasureSpec = widthMeasureSpec;
- }
-
- super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec);
- if (!isStacked) {
- final int measuredWidth = getMeasuredWidthAndState();
- final int measuredWidthState = measuredWidth & View.MEASURED_STATE_MASK;
-
- if (measuredWidthState == View.MEASURED_STATE_TOO_SMALL) {
- setStacked(true);
- // Measure again in the new orientation.
- needsRemeasure = true;
- }
- }
-
- if (needsRemeasure) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- }
-
- @VisibleForTesting
- void setStacked(boolean stacked) {
- setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
- setGravity(stacked ? Gravity.START : Gravity.BOTTOM);
-
- final View spacer = findViewById(R.id.spacer);
- if (spacer != null) {
- spacer.setVisibility(stacked ? View.GONE : View.VISIBLE);
- }
- }
-
- private boolean isStacked() {
- return getOrientation() == LinearLayout.VERTICAL;
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/UsageGraph.java b/packages/SettingsLib/src/com/android/settingslib/graph/UsageGraph.java
deleted file mode 100644
index 1fff0fb..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/graph/UsageGraph.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.settingslib.graph;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.CornerPathEffect;
-import android.graphics.DashPathEffect;
-import android.graphics.LinearGradient;
-import android.graphics.Paint;
-import android.graphics.Paint.Cap;
-import android.graphics.Paint.Join;
-import android.graphics.Paint.Style;
-import android.graphics.Path;
-import android.graphics.Shader.TileMode;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.util.SparseIntArray;
-import android.util.TypedValue;
-import android.view.View;
-import com.android.settingslib.R;
-
-public class UsageGraph extends View {
-
- private static final int PATH_DELIM = -1;
-
- private final Paint mLinePaint;
- private final Paint mFillPaint;
- private final Paint mDottedPaint;
-
- private final Drawable mDivider;
- private final Drawable mTintedDivider;
- private final int mDividerSize;
-
- private final Path mPath = new Path();
-
- // Paths in coordinates they are passed in.
- private final SparseIntArray mPaths = new SparseIntArray();
- // Paths in local coordinates for drawing.
- private final SparseIntArray mLocalPaths = new SparseIntArray();
- private final int mCornerRadius;
-
- private int mAccentColor;
- private boolean mShowProjection;
- private boolean mProjectUp;
-
- private float mMaxX = 100;
- private float mMaxY = 100;
-
- private float mMiddleDividerLoc = .5f;
- private int mMiddleDividerTint = -1;
- private int mTopDividerTint = -1;
-
- public UsageGraph(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- final Resources resources = context.getResources();
-
- mLinePaint = new Paint();
- mLinePaint.setStyle(Style.STROKE);
- mLinePaint.setStrokeCap(Cap.ROUND);
- mLinePaint.setStrokeJoin(Join.ROUND);
- mLinePaint.setAntiAlias(true);
- mCornerRadius = resources.getDimensionPixelSize(R.dimen.usage_graph_line_corner_radius);
- mLinePaint.setPathEffect(new CornerPathEffect(mCornerRadius));
- mLinePaint.setStrokeWidth(resources.getDimensionPixelSize(R.dimen.usage_graph_line_width));
-
- mFillPaint = new Paint(mLinePaint);
- mFillPaint.setStyle(Style.FILL);
-
- mDottedPaint = new Paint(mLinePaint);
- mDottedPaint.setStyle(Style.STROKE);
- float dots = resources.getDimensionPixelSize(R.dimen.usage_graph_dot_size);
- float interval = resources.getDimensionPixelSize(R.dimen.usage_graph_dot_interval);
- mDottedPaint.setStrokeWidth(dots * 3);
- mDottedPaint.setPathEffect(new DashPathEffect(new float[] {dots, interval}, 0));
- mDottedPaint.setColor(context.getColor(R.color.usage_graph_dots));
-
- TypedValue v = new TypedValue();
- context.getTheme().resolveAttribute(com.android.internal.R.attr.listDivider, v, true);
- mDivider = context.getDrawable(v.resourceId);
- mTintedDivider = context.getDrawable(v.resourceId);
- mDividerSize = resources.getDimensionPixelSize(R.dimen.usage_graph_divider_size);
- }
-
- void clearPaths() {
- mPaths.clear();
- }
-
- void setMax(int maxX, int maxY) {
- mMaxX = maxX;
- mMaxY = maxY;
- }
-
- void setDividerLoc(int height) {
- mMiddleDividerLoc = 1 - height / mMaxY;
- }
-
- void setDividerColors(int middleColor, int topColor) {
- mMiddleDividerTint = middleColor;
- mTopDividerTint = topColor;
- }
-
- public void addPath(SparseIntArray points) {
- for (int i = 0; i < points.size(); i++) {
- mPaths.put(points.keyAt(i), points.valueAt(i));
- }
- mPaths.put(points.keyAt(points.size() - 1) + 1, PATH_DELIM);
- calculateLocalPaths();
- postInvalidate();
- }
-
- void setAccentColor(int color) {
- mAccentColor = color;
- mLinePaint.setColor(mAccentColor);
- updateGradient();
- postInvalidate();
- }
-
- void setShowProjection(boolean showProjection, boolean projectUp) {
- mShowProjection = showProjection;
- mProjectUp = projectUp;
- postInvalidate();
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- updateGradient();
- calculateLocalPaths();
- }
-
- private void calculateLocalPaths() {
- if (getWidth() == 0) return;
- mLocalPaths.clear();
- int pendingXLoc = 0;
- int pendingYLoc = PATH_DELIM;
- for (int i = 0; i < mPaths.size(); i++) {
- int x = mPaths.keyAt(i);
- int y = mPaths.valueAt(i);
- if (y == PATH_DELIM) {
- if (i == mPaths.size() - 1 && pendingYLoc != PATH_DELIM) {
- // Connect to the end of the graph.
- mLocalPaths.put(pendingXLoc, pendingYLoc);
- }
- // Clear out any pending points.
- pendingYLoc = PATH_DELIM;
- mLocalPaths.put(pendingXLoc + 1, PATH_DELIM);
- } else {
- final int lx = getX(x);
- final int ly = getY(y);
- pendingXLoc = lx;
- if (mLocalPaths.size() > 0) {
- int lastX = mLocalPaths.keyAt(mLocalPaths.size() - 1);
- int lastY = mLocalPaths.valueAt(mLocalPaths.size() - 1);
- if (lastY != PATH_DELIM && !hasDiff(lastX, lx) && !hasDiff(lastY, ly)) {
- pendingYLoc = ly;
- continue;
- }
- }
- mLocalPaths.put(lx, ly);
- }
- }
- }
-
- private boolean hasDiff(int x1, int x2) {
- return Math.abs(x2 - x1) >= mCornerRadius;
- }
-
- private int getX(float x) {
- return (int) (x / mMaxX * getWidth());
- }
-
- private int getY(float y) {
- return (int) (getHeight() * (1 - (y / mMaxY)));
- }
-
- private void updateGradient() {
- mFillPaint.setShader(new LinearGradient(0, 0, 0, getHeight(),
- getColor(mAccentColor, .2f), 0, TileMode.CLAMP));
- }
-
- private int getColor(int color, float alphaScale) {
- return (color & (((int) (0xff * alphaScale) << 24) | 0xffffff));
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- // Draw lines across the top, middle, and bottom.
- if (mMiddleDividerLoc != 0) {
- drawDivider(0, canvas, mTopDividerTint);
- }
- drawDivider((int) ((canvas.getHeight() - mDividerSize) * mMiddleDividerLoc), canvas,
- mMiddleDividerTint);
- drawDivider(canvas.getHeight() - mDividerSize, canvas, -1);
-
- if (mLocalPaths.size() == 0) {
- return;
- }
- if (mShowProjection) {
- drawProjection(canvas);
- }
- drawFilledPath(canvas);
- drawLinePath(canvas);
- }
-
- private void drawProjection(Canvas canvas) {
- mPath.reset();
- int x = mLocalPaths.keyAt(mLocalPaths.size() - 2);
- int y = mLocalPaths.valueAt(mLocalPaths.size() - 2);
- mPath.moveTo(x, y);
- mPath.lineTo(canvas.getWidth(), mProjectUp ? 0 : canvas.getHeight());
- canvas.drawPath(mPath, mDottedPaint);
- }
-
- private void drawLinePath(Canvas canvas) {
- mPath.reset();
- mPath.moveTo(mLocalPaths.keyAt(0), mLocalPaths.valueAt(0));
- for (int i = 1; i < mLocalPaths.size(); i++) {
- int x = mLocalPaths.keyAt(i);
- int y = mLocalPaths.valueAt(i);
- if (y == PATH_DELIM) {
- if (++i < mLocalPaths.size()) {
- mPath.moveTo(mLocalPaths.keyAt(i), mLocalPaths.valueAt(i));
- }
- } else {
- mPath.lineTo(x, y);
- }
- }
- canvas.drawPath(mPath, mLinePaint);
- }
-
- private void drawFilledPath(Canvas canvas) {
- mPath.reset();
- float lastStartX = mLocalPaths.keyAt(0);
- mPath.moveTo(mLocalPaths.keyAt(0), mLocalPaths.valueAt(0));
- for (int i = 1; i < mLocalPaths.size(); i++) {
- int x = mLocalPaths.keyAt(i);
- int y = mLocalPaths.valueAt(i);
- if (y == PATH_DELIM) {
- mPath.lineTo(mLocalPaths.keyAt(i - 1), getHeight());
- mPath.lineTo(lastStartX, getHeight());
- mPath.close();
- if (++i < mLocalPaths.size()) {
- lastStartX = mLocalPaths.keyAt(i);
- mPath.moveTo(mLocalPaths.keyAt(i), mLocalPaths.valueAt(i));
- }
- } else {
- mPath.lineTo(x, y);
- }
- }
- canvas.drawPath(mPath, mFillPaint);
- }
-
- private void drawDivider(int y, Canvas canvas, int tintColor) {
- Drawable d = mDivider;
- if (tintColor != -1) {
- mTintedDivider.setTint(tintColor);
- d = mTintedDivider;
- }
- d.setBounds(0, y, canvas.getWidth(), y + mDividerSize);
- d.draw(canvas);
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/UsageView.java b/packages/SettingsLib/src/com/android/settingslib/graph/UsageView.java
deleted file mode 100644
index e2c05a1..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/graph/UsageView.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.settingslib.graph;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.util.AttributeSet;
-import android.util.SparseIntArray;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import com.android.settingslib.R;
-
-public class UsageView extends FrameLayout {
-
- private final UsageGraph mUsageGraph;
- private final TextView[] mLabels;
- private final TextView[] mBottomLabels;
-
- public UsageView(Context context, AttributeSet attrs) {
- super(context, attrs);
- LayoutInflater.from(context).inflate(R.layout.usage_view, this);
- mUsageGraph = findViewById(R.id.usage_graph);
- mLabels = new TextView[] {
- findViewById(R.id.label_bottom),
- findViewById(R.id.label_middle),
- findViewById(R.id.label_top),
- };
- mBottomLabels = new TextView[] {
- findViewById(R.id.label_start),
- findViewById(R.id.label_end),
- };
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UsageView, 0, 0);
- if (a.hasValue(R.styleable.UsageView_sideLabels)) {
- setSideLabels(a.getTextArray(R.styleable.UsageView_sideLabels));
- }
- if (a.hasValue(R.styleable.UsageView_bottomLabels)) {
- setBottomLabels(a.getTextArray(R.styleable.UsageView_bottomLabels));
- }
- if (a.hasValue(R.styleable.UsageView_textColor)) {
- int color = a.getColor(R.styleable.UsageView_textColor, 0);
- for (TextView v : mLabels) {
- v.setTextColor(color);
- }
- for (TextView v : mBottomLabels) {
- v.setTextColor(color);
- }
- }
- if (a.hasValue(R.styleable.UsageView_android_gravity)) {
- int gravity = a.getInt(R.styleable.UsageView_android_gravity, 0);
- if (gravity == Gravity.END) {
- LinearLayout layout = findViewById(R.id.graph_label_group);
- LinearLayout labels = findViewById(R.id.label_group);
- // Swap the children order.
- layout.removeView(labels);
- layout.addView(labels);
- // Set gravity.
- labels.setGravity(Gravity.END);
- // Swap the bottom space order.
- LinearLayout bottomLabels = findViewById(R.id.bottom_label_group);
- View bottomSpace = bottomLabels.findViewById(R.id.bottom_label_space);
- bottomLabels.removeView(bottomSpace);
- bottomLabels.addView(bottomSpace);
- } else if (gravity != Gravity.START) {
- throw new IllegalArgumentException("Unsupported gravity " + gravity);
- }
- }
- mUsageGraph.setAccentColor(a.getColor(R.styleable.UsageView_android_colorAccent, 0));
- }
-
- public void clearPaths() {
- mUsageGraph.clearPaths();
- }
-
- public void addPath(SparseIntArray points) {
- mUsageGraph.addPath(points);
- }
-
- public void configureGraph(int maxX, int maxY, boolean showProjection, boolean projectUp) {
- mUsageGraph.setMax(maxX, maxY);
- mUsageGraph.setShowProjection(showProjection, projectUp);
- }
-
- public void setAccentColor(int color) {
- mUsageGraph.setAccentColor(color);
- }
-
- public void setDividerLoc(int dividerLoc) {
- mUsageGraph.setDividerLoc(dividerLoc);
- }
-
- public void setDividerColors(int middleColor, int topColor) {
- mUsageGraph.setDividerColors(middleColor, topColor);
- }
-
- public void setSideLabelWeights(float before, float after) {
- setWeight(R.id.space1, before);
- setWeight(R.id.space2, after);
- }
-
- private void setWeight(int id, float weight) {
- View v = findViewById(id);
- LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) v.getLayoutParams();
- params.weight = weight;
- v.setLayoutParams(params);
- }
-
- public void setSideLabels(CharSequence[] labels) {
- if (labels.length != mLabels.length) {
- throw new IllegalArgumentException("Invalid number of labels");
- }
- for (int i = 0; i < mLabels.length; i++) {
- mLabels[i].setText(labels[i]);
- }
- }
-
- public void setBottomLabels(CharSequence[] labels) {
- if (labels.length != mBottomLabels.length) {
- throw new IllegalArgumentException("Invalid number of labels");
- }
- for (int i = 0; i < mBottomLabels.length; i++) {
- mBottomLabels[i].setText(labels[i]);
- }
- }
-
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionCategory.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionCategory.java
new file mode 100644
index 0000000..19e556a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionCategory.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.suggestions;
+
+public class SuggestionCategory {
+ public String category;
+ public String pkg;
+ public boolean multiple;
+ public boolean exclusive;
+ public long exclusiveExpireDaysInMillis;
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionList.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionList.java
new file mode 100644
index 0000000..2151ba0
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionList.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.suggestions;
+
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.settingslib.drawer.Tile;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class SuggestionList {
+ // Category -> list of suggestion map
+ private final Map<SuggestionCategory, List<Tile>> mSuggestions;
+
+ // A flatten list of all suggestions.
+ private List<Tile> mSuggestionList;
+
+ public SuggestionList() {
+ mSuggestions = new ArrayMap<>();
+ }
+
+ public void addSuggestions(SuggestionCategory category, List<Tile> suggestions) {
+ mSuggestions.put(category, suggestions);
+ }
+
+ public List<Tile> getSuggestions() {
+ if (mSuggestionList != null) {
+ return mSuggestionList;
+ }
+ mSuggestionList = new ArrayList<>();
+ for (List<Tile> suggestions : mSuggestions.values()) {
+ mSuggestionList.addAll(suggestions);
+ }
+ dedupeSuggestions(mSuggestionList);
+ return mSuggestionList;
+ }
+
+ public boolean isExclusiveSuggestionCategory() {
+ if (mSuggestions.size() != 1) {
+ // If there is no category, or more than 1 category, it's not exclusive by definition.
+ return false;
+ }
+ for (SuggestionCategory category : mSuggestions.keySet()) {
+ if (category.exclusive) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public List<Tile> getSuggestionForCategory(String category) {
+ for (Map.Entry<SuggestionCategory, List<Tile>> entry : mSuggestions.entrySet()) {
+ if (TextUtils.equals(entry.getKey().category, category)) {
+ return entry.getValue();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Filter suggestions list so they are all unique.
+ */
+ private void dedupeSuggestions(List<Tile> suggestions) {
+ final Set<String> intents = new ArraySet<>();
+ for (int i = suggestions.size() - 1; i >= 0; i--) {
+ final Tile suggestion = suggestions.get(i);
+ final String intentUri = suggestion.intent.toUri(Intent.URI_INTENT_SCHEME);
+ if (intents.contains(intentUri)) {
+ suggestions.remove(i);
+ } else {
+ intents.add(intentUri);
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java
new file mode 100644
index 0000000..a28bece
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.suggestions;
+
+import android.Manifest;
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Xml;
+import android.view.InflateException;
+
+import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.drawer.TileUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class SuggestionParser {
+
+ private static final String TAG = "SuggestionParser";
+
+ // If defined, only returns this suggestion if the feature is supported.
+ public static final String META_DATA_REQUIRE_FEATURE = "com.android.settings.require_feature";
+
+ // If defined, only display this optional step if an account of that type exists.
+ private static final String META_DATA_REQUIRE_ACCOUNT = "com.android.settings.require_account";
+
+ // If defined and not true, do not should optional step.
+ private static final String META_DATA_IS_SUPPORTED = "com.android.settings.is_supported";
+
+ // If defined, only display this optional step if the current user is of that type.
+ private static final String META_DATA_REQUIRE_USER_TYPE =
+ "com.android.settings.require_user_type";
+
+ // If defined, only display this optional step if a connection is available.
+ private static final String META_DATA_IS_CONNECTION_REQUIRED =
+ "com.android.settings.require_connection";
+
+ // The valid values that setup wizard recognizes for differentiating user types.
+ private static final String META_DATA_PRIMARY_USER_TYPE_VALUE = "primary";
+ private static final String META_DATA_ADMIN_USER_TYPE_VALUE = "admin";
+ private static final String META_DATA_GUEST_USER_TYPE_VALUE = "guest";
+ private static final String META_DATA_RESTRICTED_USER_TYPE_VALUE = "restricted";
+
+ /**
+ * Allows suggestions to appear after a certain number of days, and to re-appear if dismissed.
+ * For instance:
+ * 0,10
+ * Will appear immediately, but if the user removes it, it will come back after 10 days.
+ *
+ * Another example:
+ * 10,30
+ * Will only show up after 10 days, and then again after 30.
+ */
+ public static final String META_DATA_DISMISS_CONTROL = "com.android.settings.dismiss";
+
+ // Shared prefs keys for storing dismissed state.
+ // Index into current dismissed state.
+ private static final String DISMISS_INDEX = "_dismiss_index";
+ private static final String SETUP_TIME = "_setup_time";
+ private static final String IS_DISMISSED = "_is_dismissed";
+
+ private static final long MILLIS_IN_DAY = 24 * 60 * 60 * 1000;
+
+ // Default dismiss control for smart suggestions.
+ private static final String DEFAULT_SMART_DISMISS_CONTROL = "0,10";
+
+ private final Context mContext;
+ private final List<SuggestionCategory> mSuggestionList;
+ private final ArrayMap<Pair<String, String>, Tile> mAddCache = new ArrayMap<>();
+ private final SharedPreferences mSharedPrefs;
+ private final String mSmartDismissControl;
+
+
+ public SuggestionParser(Context context, SharedPreferences sharedPrefs, int orderXml,
+ String smartDismissControl) {
+ this(
+ context,
+ sharedPrefs,
+ (List<SuggestionCategory>) new SuggestionOrderInflater(context).parse(orderXml),
+ smartDismissControl);
+ }
+
+ public SuggestionParser(Context context, SharedPreferences sharedPrefs, int orderXml) {
+ this(context, sharedPrefs, orderXml, DEFAULT_SMART_DISMISS_CONTROL);
+ }
+
+ @VisibleForTesting
+ public SuggestionParser(
+ Context context,
+ SharedPreferences sharedPrefs,
+ List<SuggestionCategory> suggestionList,
+ String smartDismissControl) {
+ mContext = context;
+ mSuggestionList = suggestionList;
+ mSharedPrefs = sharedPrefs;
+ mSmartDismissControl = smartDismissControl;
+ }
+
+ public SuggestionList getSuggestions(boolean isSmartSuggestionEnabled) {
+ final SuggestionList suggestionList = new SuggestionList();
+ final int N = mSuggestionList.size();
+ for (int i = 0; i < N; i++) {
+ final SuggestionCategory category = mSuggestionList.get(i);
+ if (category.exclusive && !isExclusiveCategoryExpired(category)) {
+ // If suggestions from an exclusive category are present, parsing is stopped
+ // and only suggestions from that category are displayed. Note that subsequent
+ // exclusive categories are also ignored.
+ final List<Tile> exclusiveSuggestions = new ArrayList<>();
+
+ // Read suggestion and force isSmartSuggestion to be false so the rule defined
+ // from each suggestion itself is used.
+ readSuggestions(category, exclusiveSuggestions, false /* isSmartSuggestion */);
+ if (!exclusiveSuggestions.isEmpty()) {
+ final SuggestionList exclusiveList = new SuggestionList();
+ exclusiveList.addSuggestions(category, exclusiveSuggestions);
+ return exclusiveList;
+ }
+ } else {
+ // Either the category is not exclusive, or the exclusiveness expired so we should
+ // treat it as a normal category.
+ final List<Tile> suggestions = new ArrayList<>();
+ readSuggestions(category, suggestions, isSmartSuggestionEnabled);
+ suggestionList.addSuggestions(category, suggestions);
+ }
+ }
+ return suggestionList;
+ }
+
+ public boolean dismissSuggestion(Tile suggestion) {
+ return dismissSuggestion(suggestion, false);
+ }
+
+ /**
+ * Dismisses a suggestion, returns true if the suggestion has no more dismisses left and should
+ * be disabled.
+ */
+ public boolean dismissSuggestion(Tile suggestion, boolean isSmartSuggestionEnabled) {
+ String keyBase = suggestion.intent.getComponent().flattenToShortString();
+ int index = mSharedPrefs.getInt(keyBase + DISMISS_INDEX, 0);
+ String dismissControl = getDismissControl(suggestion, isSmartSuggestionEnabled);
+ if (dismissControl == null || parseDismissString(dismissControl).length == index) {
+ return true;
+ }
+ mSharedPrefs.edit()
+ .putBoolean(keyBase + IS_DISMISSED, true)
+ .commit();
+ return false;
+ }
+
+ @VisibleForTesting
+ public void filterSuggestions(
+ List<Tile> suggestions, int countBefore, boolean isSmartSuggestionEnabled) {
+ for (int i = countBefore; i < suggestions.size(); i++) {
+ if (!isAvailable(suggestions.get(i)) ||
+ !isSupported(suggestions.get(i)) ||
+ !satisifesRequiredUserType(suggestions.get(i)) ||
+ !satisfiesRequiredAccount(suggestions.get(i)) ||
+ !satisfiesConnectivity(suggestions.get(i)) ||
+ isDismissed(suggestions.get(i), isSmartSuggestionEnabled)) {
+ suggestions.remove(i--);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void readSuggestions(
+ SuggestionCategory category, List<Tile> suggestions, boolean isSmartSuggestionEnabled) {
+ int countBefore = suggestions.size();
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(category.category);
+ if (category.pkg != null) {
+ intent.setPackage(category.pkg);
+ }
+ TileUtils.getTilesForIntent(mContext, new UserHandle(UserHandle.myUserId()), intent,
+ mAddCache, null, suggestions, true, false);
+ filterSuggestions(suggestions, countBefore, isSmartSuggestionEnabled);
+ if (!category.multiple && suggestions.size() > (countBefore + 1)) {
+ // If there are too many, remove them all and only re-add the one with the highest
+ // priority.
+ Tile item = suggestions.remove(suggestions.size() - 1);
+ while (suggestions.size() > countBefore) {
+ Tile last = suggestions.remove(suggestions.size() - 1);
+ if (last.priority > item.priority) {
+ item = last;
+ }
+ }
+ // If category is marked as done, do not add any item.
+ if (!isCategoryDone(category.category)) {
+ suggestions.add(item);
+ }
+ }
+ }
+
+ private boolean isAvailable(Tile suggestion) {
+ final String featuresRequired = suggestion.metaData.getString(META_DATA_REQUIRE_FEATURE);
+ if (featuresRequired != null) {
+ for (String feature : featuresRequired.split(",")) {
+ if (TextUtils.isEmpty(feature)) {
+ Log.w(TAG, "Found empty substring when parsing required features: "
+ + featuresRequired);
+ } else if (!mContext.getPackageManager().hasSystemFeature(feature)) {
+ Log.i(TAG, suggestion.title + " requires unavailable feature " + feature);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ @RequiresPermission(Manifest.permission.MANAGE_USERS)
+ private boolean satisifesRequiredUserType(Tile suggestion) {
+ final String requiredUser = suggestion.metaData.getString(META_DATA_REQUIRE_USER_TYPE);
+ if (requiredUser != null) {
+ final UserManager userManager = mContext.getSystemService(UserManager.class);
+ UserInfo userInfo = userManager.getUserInfo(UserHandle.myUserId());
+ for (String userType : requiredUser.split("\\|")) {
+ final boolean primaryUserCondtionMet = userInfo.isPrimary()
+ && META_DATA_PRIMARY_USER_TYPE_VALUE.equals(userType);
+ final boolean adminUserConditionMet = userInfo.isAdmin()
+ && META_DATA_ADMIN_USER_TYPE_VALUE.equals(userType);
+ final boolean guestUserCondtionMet = userInfo.isGuest()
+ && META_DATA_GUEST_USER_TYPE_VALUE.equals(userType);
+ final boolean restrictedUserCondtionMet = userInfo.isRestricted()
+ && META_DATA_RESTRICTED_USER_TYPE_VALUE.equals(userType);
+ if (primaryUserCondtionMet || adminUserConditionMet || guestUserCondtionMet
+ || restrictedUserCondtionMet) {
+ return true;
+ }
+ }
+ Log.i(TAG, suggestion.title + " requires user type " + requiredUser);
+ return false;
+ }
+ return true;
+ }
+
+ public boolean satisfiesRequiredAccount(Tile suggestion) {
+ final String requiredAccountType = suggestion.metaData.getString(META_DATA_REQUIRE_ACCOUNT);
+ if (requiredAccountType == null) {
+ return true;
+ }
+ AccountManager accountManager = AccountManager.get(mContext);
+ Account[] accounts = accountManager.getAccountsByType(requiredAccountType);
+ boolean satisfiesRequiredAccount = accounts.length > 0;
+ if (!satisfiesRequiredAccount) {
+ Log.i(TAG, suggestion.title + " requires unavailable account type "
+ + requiredAccountType);
+ }
+ return satisfiesRequiredAccount;
+ }
+
+ public boolean isSupported(Tile suggestion) {
+ final int isSupportedResource = suggestion.metaData.getInt(META_DATA_IS_SUPPORTED);
+ try {
+ if (suggestion.intent == null) {
+ return false;
+ }
+ final Resources res = mContext.getPackageManager().getResourcesForActivity(
+ suggestion.intent.getComponent());
+ boolean isSupported =
+ isSupportedResource != 0 ? res.getBoolean(isSupportedResource) : true;
+ if (!isSupported) {
+ Log.i(TAG, suggestion.title + " requires unsupported resource "
+ + isSupportedResource);
+ }
+ return isSupported;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Cannot find resources for " + suggestion.intent.getComponent());
+ return false;
+ } catch (Resources.NotFoundException e) {
+ Log.w(TAG, "Cannot find resources for " + suggestion.intent.getComponent(), e);
+ return false;
+ }
+ }
+
+ private boolean satisfiesConnectivity(Tile suggestion) {
+ final boolean isConnectionRequired =
+ suggestion.metaData.getBoolean(META_DATA_IS_CONNECTION_REQUIRED);
+ if (!isConnectionRequired) {
+ return true;
+ }
+ ConnectivityManager cm =
+ (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo netInfo = cm.getActiveNetworkInfo();
+ boolean satisfiesConnectivity = netInfo != null && netInfo.isConnectedOrConnecting();
+ if (!satisfiesConnectivity) {
+ Log.i(TAG, suggestion.title + " is missing required connection.");
+ }
+ return satisfiesConnectivity;
+ }
+
+ public boolean isCategoryDone(String category) {
+ String name = Settings.Secure.COMPLETED_CATEGORY_PREFIX + category;
+ return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) != 0;
+ }
+
+ public void markCategoryDone(String category) {
+ String name = Settings.Secure.COMPLETED_CATEGORY_PREFIX + category;
+ Settings.Secure.putInt(mContext.getContentResolver(), name, 1);
+ }
+
+ /**
+ * Whether or not the category's exclusiveness has expired.
+ */
+ private boolean isExclusiveCategoryExpired(SuggestionCategory category) {
+ final String keySetupTime = category.category + SETUP_TIME;
+ final long currentTime = System.currentTimeMillis();
+ if (!mSharedPrefs.contains(keySetupTime)) {
+ mSharedPrefs.edit()
+ .putLong(keySetupTime, currentTime)
+ .commit();
+ }
+ if (category.exclusiveExpireDaysInMillis < 0) {
+ // negative means never expires
+ return false;
+ }
+ final long setupTime = mSharedPrefs.getLong(keySetupTime, 0);
+ final long elapsedTime = currentTime - setupTime;
+ Log.d(TAG, "Day " + elapsedTime / DateUtils.DAY_IN_MILLIS + " for " + category.category);
+ return elapsedTime > category.exclusiveExpireDaysInMillis;
+ }
+
+ private boolean isDismissed(Tile suggestion, boolean isSmartSuggestionEnabled) {
+ String dismissControl = getDismissControl(suggestion, isSmartSuggestionEnabled);
+ if (dismissControl == null) {
+ return false;
+ }
+ String keyBase = suggestion.intent.getComponent().flattenToShortString();
+ if (!mSharedPrefs.contains(keyBase + SETUP_TIME)) {
+ mSharedPrefs.edit()
+ .putLong(keyBase + SETUP_TIME, System.currentTimeMillis())
+ .commit();
+ }
+ // Default to dismissed, so that we can have suggestions that only first appear after
+ // some number of days.
+ if (!mSharedPrefs.getBoolean(keyBase + IS_DISMISSED, true)) {
+ return false;
+ }
+ int index = mSharedPrefs.getInt(keyBase + DISMISS_INDEX, 0);
+ int currentDismiss = parseDismissString(dismissControl)[index];
+ long time = getEndTime(mSharedPrefs.getLong(keyBase + SETUP_TIME, 0), currentDismiss);
+ if (System.currentTimeMillis() >= time) {
+ // Dismiss timeout has passed, undismiss it.
+ mSharedPrefs.edit()
+ .putBoolean(keyBase + IS_DISMISSED, false)
+ .putInt(keyBase + DISMISS_INDEX, index + 1)
+ .commit();
+ return false;
+ }
+ return true;
+ }
+
+ private long getEndTime(long startTime, int daysDelay) {
+ long days = daysDelay * MILLIS_IN_DAY;
+ return startTime + days;
+ }
+
+ private int[] parseDismissString(String dismissControl) {
+ String[] dismissStrs = dismissControl.split(",");
+ int[] dismisses = new int[dismissStrs.length];
+ for (int i = 0; i < dismissStrs.length; i++) {
+ dismisses[i] = Integer.parseInt(dismissStrs[i]);
+ }
+ return dismisses;
+ }
+
+ private String getDismissControl(Tile suggestion, boolean isSmartSuggestionEnabled) {
+ if (isSmartSuggestionEnabled) {
+ return mSmartDismissControl;
+ } else {
+ return suggestion.metaData.getString(META_DATA_DISMISS_CONTROL);
+ }
+ }
+
+ private static class SuggestionOrderInflater {
+ private static final String TAG_LIST = "optional-steps";
+ private static final String TAG_ITEM = "step";
+
+ private static final String ATTR_CATEGORY = "category";
+ private static final String ATTR_PACKAGE = "package";
+ private static final String ATTR_MULTIPLE = "multiple";
+ private static final String ATTR_EXCLUSIVE = "exclusive";
+ private static final String ATTR_EXCLUSIVE_EXPIRE_DAYS = "exclusiveExpireDays";
+
+ private final Context mContext;
+
+ public SuggestionOrderInflater(Context context) {
+ mContext = context;
+ }
+
+ public Object parse(int resource) {
+ XmlPullParser parser = mContext.getResources().getXml(resource);
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+ try {
+ // Look for the root node.
+ int type;
+ do {
+ type = parser.next();
+ } while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT);
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new InflateException(parser.getPositionDescription()
+ + ": No start tag found!");
+ }
+
+ // Temp is the root that was found in the xml
+ Object xmlRoot = onCreateItem(parser.getName(), attrs);
+
+ // Inflate all children under temp
+ rParse(parser, xmlRoot, attrs);
+ return xmlRoot;
+ } catch (XmlPullParserException | IOException e) {
+ Log.w(TAG, "Problem parser resource " + resource, e);
+ return null;
+ }
+ }
+
+ /**
+ * Recursive method used to descend down the xml hierarchy and instantiate
+ * items, instantiate their children.
+ */
+ private void rParse(XmlPullParser parser, Object parent, final AttributeSet attrs)
+ throws XmlPullParserException, IOException {
+ final int depth = parser.getDepth();
+
+ int type;
+ while (((type = parser.next()) != XmlPullParser.END_TAG ||
+ parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ final String name = parser.getName();
+
+ Object item = onCreateItem(name, attrs);
+ onAddChildItem(parent, item);
+ rParse(parser, item, attrs);
+ }
+ }
+
+ protected void onAddChildItem(Object parent, Object child) {
+ if (parent instanceof List<?> && child instanceof SuggestionCategory) {
+ ((List<SuggestionCategory>) parent).add((SuggestionCategory) child);
+ } else {
+ throw new IllegalArgumentException("Parent was not a list");
+ }
+ }
+
+ protected Object onCreateItem(String name, AttributeSet attrs) {
+ if (name.equals(TAG_LIST)) {
+ return new ArrayList<SuggestionCategory>();
+ } else if (name.equals(TAG_ITEM)) {
+ SuggestionCategory category = new SuggestionCategory();
+ category.category = attrs.getAttributeValue(null, ATTR_CATEGORY);
+ category.pkg = attrs.getAttributeValue(null, ATTR_PACKAGE);
+ String multiple = attrs.getAttributeValue(null, ATTR_MULTIPLE);
+ category.multiple = !TextUtils.isEmpty(multiple) && Boolean.parseBoolean(multiple);
+ String exclusive = attrs.getAttributeValue(null, ATTR_EXCLUSIVE);
+ category.exclusive =
+ !TextUtils.isEmpty(exclusive) && Boolean.parseBoolean(exclusive);
+ String expireDaysAttr = attrs.getAttributeValue(null,
+ ATTR_EXCLUSIVE_EXPIRE_DAYS);
+ long expireDays = !TextUtils.isEmpty(expireDaysAttr)
+ ? Integer.parseInt(expireDaysAttr)
+ : -1;
+ category.exclusiveExpireDaysInMillis = DateUtils.DAY_IN_MILLIS * expireDays;
+ return category;
+ } else {
+ throw new IllegalArgumentException("Unknown item " + name);
+ }
+ }
+ }
+}
+
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java b/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java
new file mode 100644
index 0000000..2024991
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.utils;
+
+import android.os.Looper;
+
+public class ThreadUtils {
+ private static volatile Thread sMainThread;
+
+ /**
+ * Returns true if the current thread is the UI thread.
+ */
+ public static boolean isMainThread() {
+ if (sMainThread == null) {
+ sMainThread = Looper.getMainLooper().getThread();
+ }
+ return Thread.currentThread() == sMainThread;
+ }
+
+ /**
+ * Checks that the current thread is the UI thread. Otherwise throws an exception.
+ */
+ public static void ensureMainThread() {
+ if (!isMainThread()) {
+ throw new RuntimeException("Must be called on the UI thread");
+ }
+ }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java
new file mode 100644
index 0000000..0c3a5e9
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget;
+
+import android.content.Context;
+import android.support.v4.content.res.TypedArrayUtils;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.text.method.LinkMovementMethod;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import com.android.settingslib.R;
+
+/**
+ * A custom preference acting as "footer" of a page. It has a field for icon and text. It is added
+ * to screen as the last preference.
+ */
+public class FooterPreference extends Preference {
+
+ static final int ORDER_FOOTER = Integer.MAX_VALUE - 1;
+ static final String KEY_FOOTER = "footer_preference";
+
+ public FooterPreference(Context context, AttributeSet attrs) {
+ super(context, attrs, TypedArrayUtils.getAttr(
+ context, R.attr.footerPreferenceStyle, android.R.attr.preferenceStyle));
+ init();
+ }
+
+ public FooterPreference(Context context) {
+ this(context, null);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ TextView title = holder.itemView.findViewById(android.R.id.title);
+ title.setMovementMethod(new LinkMovementMethod());
+ title.setClickable(false);
+ title.setLongClickable(false);
+ }
+
+ private void init() {
+ setIcon(R.drawable.ic_info_outline_24dp);
+ setKey(KEY_FOOTER);
+ setOrder(ORDER_FOOTER);
+ setSelectable(false);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java
new file mode 100644
index 0000000..2edcd9e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget;
+
+import android.content.Context;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.SetPreferenceScreen;
+
+public class FooterPreferenceMixin implements LifecycleObserver, SetPreferenceScreen {
+
+ private final PreferenceFragment mFragment;
+ private FooterPreference mFooterPreference;
+
+ public FooterPreferenceMixin(PreferenceFragment fragment, Lifecycle lifecycle) {
+ mFragment = fragment;
+ lifecycle.addObserver(this);
+ }
+
+ @Override
+ public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
+ if (mFooterPreference != null) {
+ preferenceScreen.addPreference(mFooterPreference);
+ }
+ }
+
+ /**
+ * Creates a new {@link FooterPreference}.
+ */
+ public FooterPreference createFooterPreference() {
+ final PreferenceScreen screen = mFragment.getPreferenceScreen();
+ if (mFooterPreference != null && screen != null) {
+ screen.removePreference(mFooterPreference);
+ }
+ mFooterPreference = new FooterPreference(getPrefContext());
+
+ if (screen != null) {
+ screen.addPreference(mFooterPreference);
+ }
+ return mFooterPreference;
+ }
+
+ /**
+ * Returns an UI context with theme properly set for new Preference objects.
+ */
+ private Context getPrefContext() {
+ return mFragment.getPreferenceManager().getContext();
+ }
+
+ public boolean hasFooter() {
+ return mFooterPreference != null;
+ }
+}
+
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 623d1a6..0f9b2ff 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -16,13 +16,13 @@
package com.android.settingslib.wifi;
+import android.annotation.Nullable;
import android.app.AppGlobals;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
-import android.net.NetworkBadging;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
@@ -82,6 +82,35 @@
*/
public static final int HIGHER_FREQ_5GHZ = 5900;
+ /**
+ * Constant value representing an unlabeled / unscored network.
+ */
+ @VisibleForTesting
+ static final int SPEED_NONE = 0;
+
+ /**
+ * Constant value representing a slow speed network connection.
+ */
+ @VisibleForTesting
+ static final int SPEED_SLOW = 5;
+
+ /**
+ * Constant value representing a medium speed network connection.
+ */
+ @VisibleForTesting
+ static final int SPEED_MEDIUM = 10;
+
+ /**
+ * Constant value representing a fast speed network connection.
+ */
+ @VisibleForTesting
+ static final int SPEED_FAST = 20;
+
+ /**
+ * Constant value representing a very fast speed network connection.
+ */
+ @VisibleForTesting
+ static final int SPEED_VERY_FAST = 30;
/**
* Experimental: we should be able to show the user the list of BSSIDs and bands
@@ -148,7 +177,7 @@
private Object mTag;
private int mRankingScore = Integer.MIN_VALUE;
- private int mBadge = NetworkBadging.BADGING_NONE;
+ private int mSpeed = AccessPoint.SPEED_NONE;
private boolean mIsScoredNetworkMetered = false;
// used to co-relate internal vs returned accesspoint.
@@ -248,7 +277,7 @@
this.mScanResultCache.clear();
this.mScanResultCache.putAll(that.mScanResultCache);
this.mId = that.mId;
- this.mBadge = that.mBadge;
+ this.mSpeed = that.mSpeed;
this.mIsScoredNetworkMetered = that.mIsScoredNetworkMetered;
this.mRankingScore = that.mRankingScore;
}
@@ -335,13 +364,12 @@
if (security != SECURITY_NONE) {
builder.append(',').append(securityToString(security, pskType));
}
- builder.append(",mRssi=").append(mRssi);
builder.append(",level=").append(getLevel());
if (mRankingScore != Integer.MIN_VALUE) {
builder.append(",rankingScore=").append(mRankingScore);
}
- if (mBadge != NetworkBadging.BADGING_NONE) {
- builder.append(",badge=").append(mBadge);
+ if (mSpeed != SPEED_NONE) {
+ builder.append(",speed=").append(mSpeed);
}
builder.append(",metered=").append(isMetered());
@@ -349,7 +377,7 @@
}
/**
- * Updates the AccessPoint rankingScore, metering, and badge, returning true if the data has
+ * Updates the AccessPoint rankingScore, metering, and speed, returning true if the data has
* changed.
*
* @param scoreCache The score cache to use to retrieve scores.
@@ -364,14 +392,14 @@
}
/**
- * Updates the AccessPoint rankingScore and badge, returning true if the data has changed.
+ * Updates the AccessPoint rankingScore and speed, returning true if the data has changed.
*
* @param scoreCache The score cache to use to retrieve scores.
*/
private boolean updateScores(WifiNetworkScoreCache scoreCache) {
- int oldBadge = mBadge;
+ int oldSpeed = mSpeed;
int oldRankingScore = mRankingScore;
- mBadge = NetworkBadging.BADGING_NONE;
+ mSpeed = SPEED_NONE;
mRankingScore = Integer.MIN_VALUE;
for (ScanResult result : mScanResultCache.values()) {
@@ -383,10 +411,11 @@
if (score.hasRankingScore()) {
mRankingScore = Math.max(mRankingScore, score.calculateRankingScore(result.level));
}
- mBadge = Math.max(mBadge, score.calculateBadge(result.level));
+ // TODO(sghuman): Rename calculateBadge API
+ mSpeed = Math.max(mSpeed, score.calculateBadge(result.level));
}
- return (oldBadge != mBadge || oldRankingScore != mRankingScore);
+ return (oldSpeed != mSpeed || oldRankingScore != mRankingScore);
}
/**
@@ -640,6 +669,13 @@
// Update to new summary
StringBuilder summary = new StringBuilder();
+ // TODO(b/62354743): Standardize and international delimiter usage
+ final String concatenator = " / ";
+
+ if (mSpeed != SPEED_NONE) {
+ summary.append(getSpeedLabel() + concatenator);
+ }
+
if (isActive() && config != null && config.isPasspoint()) {
// This is the active connection on passpoint
summary.append(getSummary(mContext, getDetailedState(),
@@ -719,6 +755,14 @@
}
}
}
+
+ // Strip trailing delimiter if applicable
+ int concatLength = concatenator.length();
+ if (summary.length() >= concatLength && summary.substring(
+ summary.length() - concatLength, summary.length()).equals(concatenator)) {
+ summary.delete(summary.length() - concatLength, summary.length());
+ }
+
return summary.toString();
}
@@ -747,10 +791,10 @@
visibility.append(" ");
visibility.append(" score=").append(mInfo.score);
if (mRankingScore != Integer.MIN_VALUE) {
- visibility.append(" rankingScore=").append(getRankingScore());
+ visibility.append(" rankingScore=").append(getRankingScore());
}
- if (mBadge != NetworkBadging.BADGING_NONE) {
- visibility.append(" badge=").append(getBadge());
+ if (mSpeed != SPEED_NONE) {
+ visibility.append(" speed=").append(getSpeedLabel());
}
visibility.append(String.format(" tx=%.1f,", mInfo.txSuccessRate));
visibility.append(String.format("%.1f,", mInfo.txRetriesRate));
@@ -1047,8 +1091,23 @@
return mRankingScore;
}
- int getBadge() {
- return mBadge;
+ int getSpeed() { return mSpeed;}
+
+ @Nullable
+ String getSpeedLabel() {
+ switch (mSpeed) {
+ case SPEED_VERY_FAST:
+ return mContext.getString(R.string.speed_label_very_fast);
+ case SPEED_FAST:
+ return mContext.getString(R.string.speed_label_fast);
+ case SPEED_MEDIUM:
+ return mContext.getString(R.string.speed_label_okay);
+ case SPEED_SLOW:
+ return mContext.getString(R.string.speed_label_slow);
+ case SPEED_NONE:
+ default:
+ return null;
+ }
}
/** Return true if the current RSSI is reachable, and false otherwise. */
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index 7f95379..e7525f3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -60,9 +60,10 @@
private int mLevel;
private CharSequence mContentDescription;
private int mDefaultIconResId;
- private int mWifiBadge = NetworkBadging.BADGING_NONE;
+ private int mWifiSpeed = NetworkBadging.BADGING_NONE;
static final int[] WIFI_CONNECTION_STRENGTH = {
+ R.string.accessibility_no_wifi,
R.string.accessibility_wifi_one_bar,
R.string.accessibility_wifi_two_bars,
R.string.accessibility_wifi_three_bars,
@@ -160,8 +161,11 @@
safeSetDefaultIcon();
return;
}
- TronUtils.logWifiSettingsBadge(context, mWifiBadge);
- Drawable drawable = NetworkBadging.getWifiIcon(level, mWifiBadge, getContext().getTheme());
+ TronUtils.logWifiSettingsBadge(context, mWifiSpeed);
+
+ // TODO(b/62355275): Revert this to N code after deleting NetworkBadging API
+ Drawable drawable = NetworkBadging.getWifiIcon(
+ level, NetworkBadging.BADGING_NONE, getContext().getTheme());
if (!mForSavedNetworks && drawable != null) {
drawable.setTint(Utils.getColorAttr(context, android.R.attr.colorControlNormal));
setIcon(drawable);
@@ -219,10 +223,10 @@
final Context context = getContext();
int level = mAccessPoint.getLevel();
- int wifiBadge = mAccessPoint.getBadge();
- if (level != mLevel || wifiBadge != mWifiBadge) {
+ int wifiSpeed = mAccessPoint.getSpeed();
+ if (level != mLevel || wifiSpeed != mWifiSpeed) {
mLevel = level;
- mWifiBadge = wifiBadge;
+ mWifiSpeed = wifiSpeed;
updateIcon(mLevel, context);
notifyChanged();
}
diff --git a/packages/SettingsLib/tests/integ/AndroidTest.xml b/packages/SettingsLib/tests/integ/AndroidTest.xml
index 8581acb..96621eb 100644
--- a/packages/SettingsLib/tests/integ/AndroidTest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidTest.xml
@@ -20,7 +20,7 @@
<option name="test-suite-tag" value="apct" />
<option name="test-tag" value="SettingsLibTests" />
- <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.settingslib" />
<option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
</test>
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 801844b..72ac544 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -19,12 +19,16 @@
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
+import android.net.NetworkKey;
+import android.net.RssiCurve;
import android.net.ScoredNetwork;
+import android.net.WifiKey;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
@@ -40,6 +44,8 @@
import android.text.SpannableString;
import android.text.style.TtsSpan;
+import com.android.settingslib.R;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -55,7 +61,8 @@
private static final String TEST_SSID = "test_ssid";
private Context mContext;
- @Mock private WifiNetworkScoreCache mWifiNetworkScoreCache;
+ @Mock private RssiCurve mockBadgeCurve;
+ @Mock private WifiNetworkScoreCache mockWifiNetworkScoreCache;
@Before
public void setUp() {
@@ -294,13 +301,13 @@
public void testIsMetered_returnTrueWhenScoredNetworkIsMetered() {
AccessPoint ap = createAccessPointWithScanResultCache();
- when(mWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
+ when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
.thenReturn(
new ScoredNetwork(
null /* NetworkKey */,
null /* rssiCurve */,
true /* metered */));
- ap.update(mWifiNetworkScoreCache, false /* scoringUiEnabled */);
+ ap.update(mockWifiNetworkScoreCache, false /* scoringUiEnabled */);
assertThat(ap.isMetered()).isTrue();
}
@@ -321,6 +328,106 @@
assertThat(accessPoint.isMetered()).isFalse();
}
+ @Test
+ public void testSpeedLabel_returnsVeryFast() {
+ AccessPoint ap = createAccessPointWithScanResultCache();
+
+ when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
+ .thenReturn(buildScoredNetworkWithMockBadgeCurve());
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_VERY_FAST);
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ assertThat(ap.getSpeed()).isEqualTo(AccessPoint.SPEED_VERY_FAST);
+ assertThat(ap.getSpeedLabel())
+ .isEqualTo(mContext.getString(R.string.speed_label_very_fast));
+ }
+
+ @Test
+ public void testSpeedLabel_returnsFast() {
+ AccessPoint ap = createAccessPointWithScanResultCache();
+
+ when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
+ .thenReturn(buildScoredNetworkWithMockBadgeCurve());
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_FAST);
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ assertThat(ap.getSpeed()).isEqualTo(AccessPoint.SPEED_FAST);
+ assertThat(ap.getSpeedLabel())
+ .isEqualTo(mContext.getString(R.string.speed_label_fast));
+ }
+
+ @Test
+ public void testSpeedLabel_returnsOkay() {
+ AccessPoint ap = createAccessPointWithScanResultCache();
+
+ when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
+ .thenReturn(buildScoredNetworkWithMockBadgeCurve());
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_MEDIUM);
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ assertThat(ap.getSpeed()).isEqualTo(AccessPoint.SPEED_MEDIUM);
+ assertThat(ap.getSpeedLabel())
+ .isEqualTo(mContext.getString(R.string.speed_label_okay));
+ }
+
+ @Test
+ public void testSpeedLabel_returnsSlow() {
+ AccessPoint ap = createAccessPointWithScanResultCache();
+
+ when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
+ .thenReturn(buildScoredNetworkWithMockBadgeCurve());
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_SLOW);
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ assertThat(ap.getSpeed()).isEqualTo(AccessPoint.SPEED_SLOW);
+ assertThat(ap.getSpeedLabel())
+ .isEqualTo(mContext.getString(R.string.speed_label_slow));
+ }
+
+ @Test
+ public void testSummaryString_showsSpeedLabel() {
+ AccessPoint ap = createAccessPointWithScanResultCache();
+
+ when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
+ .thenReturn(buildScoredNetworkWithMockBadgeCurve());
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_VERY_FAST);
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ assertThat(ap.getSummary()).isEqualTo(mContext.getString(R.string.speed_label_very_fast));
+ }
+
+ @Test
+ public void testSummaryString_concatenatesSpeedLabel() {
+ AccessPoint ap = createAccessPointWithScanResultCache();
+ ap.update(new WifiConfiguration());
+
+ when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
+ .thenReturn(buildScoredNetworkWithMockBadgeCurve());
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_VERY_FAST);
+
+ ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
+
+ String expectedString = mContext.getString(R.string.speed_label_very_fast) + " / "
+ + mContext.getString(R.string.wifi_remembered);
+ assertThat(ap.getSummary()).isEqualTo(expectedString);
+ }
+
+ private ScoredNetwork buildScoredNetworkWithMockBadgeCurve() {
+ Bundle attr1 = new Bundle();
+ attr1.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve);
+ return new ScoredNetwork(
+ new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00")),
+ mockBadgeCurve,
+ false /* meteredHint */,
+ attr1);
+
+ }
+
private AccessPoint createAccessPointWithScanResultCache() {
Bundle bundle = new Bundle();
ArrayList<ScanResult> scanResults = new ArrayList<>();
@@ -333,7 +440,7 @@
scanResults.add(scanResult);
}
- bundle.putParcelableArrayList("key_scanresultcache", scanResults);
+ bundle.putParcelableArrayList(AccessPoint.KEY_SCANRESULTCACHE, scanResults);
return new AccessPoint(mContext, bundle);
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index 086e10c..46ea319 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -37,7 +37,6 @@
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.Network;
-import android.net.NetworkBadging;
import android.net.NetworkInfo;
import android.net.NetworkKey;
import android.net.NetworkScoreManager;
@@ -94,7 +93,7 @@
new NetworkKey(new WifiKey('"' + SSID_1 + '"', BSSID_1));
private static final int RSSI_1 = -30;
private static final byte SCORE_1 = 10;
- private static final int BADGE_1 = NetworkBadging.BADGING_SD;
+ private static final int BADGE_1 = AccessPoint.SPEED_MEDIUM;
private static final String SSID_2 = "ssid2";
private static final String BSSID_2 = "AA:AA:AA:AA:AA:AA";
@@ -102,7 +101,7 @@
new NetworkKey(new WifiKey('"' + SSID_2 + '"', BSSID_2));
private static final int RSSI_2 = -30;
private static final byte SCORE_2 = 15;
- private static final int BADGE_2 = NetworkBadging.BADGING_HD;
+ private static final int BADGE_2 = AccessPoint.SPEED_FAST;
private static final int CONNECTED_NETWORK_ID = 123;
private static final int CONNECTED_RSSI = -50;
@@ -511,7 +510,8 @@
}
@Test
- public void scoreCacheUpdateScoresShouldInsertBadgeIntoAccessPoint() throws InterruptedException {
+ public void scoreCacheUpdateScoresShouldInsertSpeedIntoAccessPoint()
+ throws InterruptedException {
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
updateScoresAndWaitForAccessPointsChangedCallback();
@@ -519,9 +519,9 @@
for (AccessPoint ap : aps) {
if (ap.getSsidStr().equals(SSID_1)) {
- assertEquals(BADGE_1, ap.getBadge());
+ assertEquals(BADGE_1, ap.getSpeed());
} else if (ap.getSsidStr().equals(SSID_2)) {
- assertEquals(BADGE_2, ap.getBadge());
+ assertEquals(BADGE_2, ap.getSpeed());
}
}
}
@@ -544,7 +544,7 @@
}
@Test
- public void noBadgesShouldBeInsertedIntoAccessPointWhenScoringUiDisabled()
+ public void noSpeedsShouldBeInsertedIntoAccessPointWhenScoringUiDisabled()
throws InterruptedException {
Settings.Global.putInt(
InstrumentationRegistry.getTargetContext().getContentResolver(),
@@ -558,9 +558,9 @@
for (AccessPoint ap : aps) {
if (ap.getSsidStr().equals(SSID_1)) {
- assertEquals(NetworkBadging.BADGING_NONE, ap.getBadge());
+ assertEquals(AccessPoint.SPEED_NONE, ap.getSpeed());
} else if (ap.getSsidStr().equals(SSID_2)) {
- assertEquals(NetworkBadging.BADGING_NONE, ap.getBadge());
+ assertEquals(AccessPoint.SPEED_NONE, ap.getSpeed());
}
}
}
diff --git a/packages/SettingsLib/tests/robotests/res/drawable/ic_info_outline_24dp.xml b/packages/SettingsLib/tests/robotests/res/drawable/ic_info_outline_24dp.xml
new file mode 100644
index 0000000..3fe1e9e
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/res/drawable/ic_info_outline_24dp.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/textColorSecondary">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
+</vector>
diff --git a/packages/SettingsLib/tests/robotests/res/layout/preference_footer.xml b/packages/SettingsLib/tests/robotests/res/layout/preference_footer.xml
new file mode 100644
index 0000000..c47bff7
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/res/layout/preference_footer.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:id="@+id/icon_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="60dp"
+ android:gravity="start|top"
+ android:orientation="horizontal"
+ android:paddingEnd="12dp"
+ android:paddingTop="20dp"
+ android:paddingBottom="4dp">
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <com.android.settingslib.widget.LinkTextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="16dp"
+ android:paddingTop="16dp"
+ android:maxLines="10"
+ android:textColor="?android:attr/textColorSecondary"
+ android:ellipsize="marquee" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml b/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml
index 1eeafba..f02ac15 100644
--- a/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml
+++ b/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml
@@ -15,10 +15,14 @@
-->
<optional-steps>
+ <step category="com.android.settings.suggested.category.DEFERRED_SETUP"
+ exclusive="true" />
<step category="com.android.settings.suggested.category.LOCK_SCREEN" />
+ <step category="com.android.settings.suggested.category.TRUST_AGENT" />
<step category="com.android.settings.suggested.category.EMAIL" />
<step category="com.android.settings.suggested.category.PARTNER_ACCOUNT"
multiple="true" />
+ <step category="com.android.settings.suggested.category.GESTURE" />
<step category="com.android.settings.suggested.category.HOTWORD" />
<step category="com.android.settings.suggested.category.DEFAULT"
multiple="true" />
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/BatteryInfoTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/BatteryInfoTest.java
deleted file mode 100644
index 69efc9e..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/BatteryInfoTest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.BatteryManager;
-import android.os.BatteryStats;
-import android.os.SystemClock;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.when;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
-public class BatteryInfoTest {
- private static final String STATUS_FULL = "Full";
- private static final String STATUS_CHARGING_NO_TIME = "Charging";
- private static final String STATUS_CHARGING_TIME = "Charging - 2h left";
- private static final int PLUGGED_IN = 1;
- private static final long REMAINING_TIME_NULL = -1;
- private static final long REMAINING_TIME = 2;
- private Intent mDisChargingBatteryBroadcast;
- private Intent mChargingBatteryBroadcast;
-
- @Mock(answer = Answers.RETURNS_DEEP_STUBS)
- private BatteryStats mBatteryStats;
- @Mock(answer = Answers.RETURNS_DEEP_STUBS)
- private Context mContext;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mDisChargingBatteryBroadcast = new Intent();
- mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_PLUGGED, 0);
- mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 0);
- mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_SCALE, 100);
- mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_STATUS,
- BatteryManager.BATTERY_STATUS_FULL);
-
- mChargingBatteryBroadcast = new Intent();
- mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_PLUGGED,
- BatteryManager.BATTERY_PLUGGED_AC);
- mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 50);
- mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_SCALE, 100);
- mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_STATUS,
- BatteryManager.BATTERY_STATUS_UNKNOWN);
-
- when(mContext.getResources().getString(R.string.battery_info_status_full))
- .thenReturn(STATUS_FULL);
- when(mContext.getResources().getString(eq(R.string.power_charging), any(),
- any())).thenReturn(STATUS_CHARGING_NO_TIME);
- when(mContext.getResources().getString(eq(R.string.power_charging_duration), any(),
- any())).thenReturn(STATUS_CHARGING_TIME);
- }
-
- @Test
- public void testGetBatteryInfo_hasStatusLabel() {
- doReturn(REMAINING_TIME_NULL).when(mBatteryStats).computeBatteryTimeRemaining(anyLong());
- BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast,
- mBatteryStats, SystemClock.elapsedRealtime() * 1000, true);
-
- assertThat(info.statusLabel).isEqualTo(STATUS_FULL);
- }
-
- @Test
- public void testGetBatteryInfo_doNotShowChargingMethod_hasRemainingTime() {
- doReturn(REMAINING_TIME).when(mBatteryStats).computeChargeTimeRemaining(anyLong());
- BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mChargingBatteryBroadcast,
- mBatteryStats, SystemClock.elapsedRealtime() * 1000, false);
-
- assertThat(info.chargeLabelString).isEqualTo(STATUS_CHARGING_TIME);
- }
-
- @Test
- public void testGetBatteryInfo_doNotShowChargingMethod_noRemainingTime() {
- doReturn(REMAINING_TIME_NULL).when(mBatteryStats).computeChargeTimeRemaining(anyLong());
- BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mChargingBatteryBroadcast,
- mBatteryStats, SystemClock.elapsedRealtime() * 1000, false);
-
- assertThat(info.chargeLabelString).isEqualTo(STATUS_CHARGING_NO_TIME);
- }
-
- @Test
- public void testGetBatteryInfo_pluggedIn_dischargingFalse() {
- BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mChargingBatteryBroadcast,
- mBatteryStats, SystemClock.elapsedRealtime() * 1000, true);
-
- assertThat(info.discharging).isEqualTo(false);
- }
-}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SuggestionParserTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SuggestionParserTest.java
deleted file mode 100644
index 7729dec..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SuggestionParserTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-
-import com.android.settingslib.drawer.Tile;
-import com.android.settingslib.drawer.TileUtilsTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-@RunWith(SettingLibRobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
-public class SuggestionParserTest {
-
- @Mock
- private PackageManager mPackageManager;
- private Context mContext;
- private SuggestionParser mSuggestionParser;
- private SuggestionParser.SuggestionCategory mSuggestioCategory;
- private List<Tile> mSuggestionsBeforeDismiss;
- private List<Tile> mSuggestionsAfterDismiss;
- private SharedPreferences mPrefs;
- private Tile mSuggestion;
- private List<ResolveInfo> mInfo;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mContext = spy(RuntimeEnvironment.application);
- when(mContext.getPackageManager()).thenReturn(mPackageManager);
- mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
- mSuggestion = new Tile();
- mSuggestion.intent = new Intent("action");
- mSuggestion.intent.setComponent(new ComponentName("pkg", "cls"));
- mSuggestion.metaData = new Bundle();
- mSuggestionParser = new SuggestionParser(
- mContext, mPrefs, R.xml.suggestion_ordering, "0,0");
- mSuggestioCategory = new SuggestionParser.SuggestionCategory();
- mSuggestioCategory.category = "category1";
- mSuggestioCategory.multiple = true;
- mInfo = new ArrayList<>();
- ResolveInfo info1 = TileUtilsTest.newInfo(true, "category1");
- info1.activityInfo.packageName = "pkg";
- ResolveInfo info2 = TileUtilsTest.newInfo(true, "category1");
- info2.activityInfo.packageName = "pkg2";
- mInfo.add(info1);
- mInfo.add(info2);
- when(mPackageManager.queryIntentActivitiesAsUser(
- any(Intent.class), anyInt(), anyInt())).thenReturn(mInfo);
- }
-
- @Test
- public void testDismissSuggestion_withoutSmartSuggestion() {
- assertThat(mSuggestionParser.dismissSuggestion(mSuggestion, false)).isTrue();
- }
-
- @Test
- public void testDismissSuggestion_withSmartSuggestion() {
- assertThat(mSuggestionParser.dismissSuggestion(mSuggestion, true)).isFalse();
- }
-
- @Test
- public void testGetSuggestions_withoutSmartSuggestions() {
- readAndDismissSuggestion(false);
- mSuggestionParser.readSuggestions(mSuggestioCategory, mSuggestionsAfterDismiss, false);
- assertThat(mSuggestionsBeforeDismiss.size()).isEqualTo(2);
- assertThat(mSuggestionsAfterDismiss.size()).isEqualTo(1);
- assertThat(mSuggestionsBeforeDismiss.get(1)).isEqualTo(mSuggestionsAfterDismiss.get(0));
- }
-
- @Test
- public void testGetSuggestions_withSmartSuggestions() {
- readAndDismissSuggestion(true);
- assertThat(mSuggestionsBeforeDismiss.size()).isEqualTo(2);
- assertThat(mSuggestionsAfterDismiss.size()).isEqualTo(2);
- assertThat(mSuggestionsBeforeDismiss).isEqualTo(mSuggestionsAfterDismiss);
- }
-
- private void readAndDismissSuggestion(boolean isSmartSuggestionEnabled) {
- mSuggestionsBeforeDismiss = new ArrayList<Tile>();
- mSuggestionsAfterDismiss = new ArrayList<Tile>();
- mSuggestionParser.readSuggestions(
- mSuggestioCategory, mSuggestionsBeforeDismiss, isSmartSuggestionEnabled);
- if (mSuggestionParser.dismissSuggestion(
- mSuggestionsBeforeDismiss.get(0), isSmartSuggestionEnabled)) {
- mInfo.remove(0);
- }
- mSuggestionParser.readSuggestions(
- mSuggestioCategory, mSuggestionsAfterDismiss, isSmartSuggestionEnabled);
- }
-}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TetherUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TetherUtilTest.java
new file mode 100644
index 0000000..3b1c3ac
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TetherUtilTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib;
+
+
+import android.content.Context;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class TetherUtilTest {
+
+ @Mock
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void isEntitlementCheckRequired_noConfigManager_returnTrue() {
+ when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)).thenReturn(null);
+
+ assertThat(TetherUtil.isEntitlementCheckRequired(mContext)).isTrue();
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index e42c35b..0164f80 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -21,6 +21,12 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+
@RunWith(SettingLibRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class UtilsTest {
@@ -54,4 +60,18 @@
assertThat(percentage).isEqualTo(expectedPercentages[i]);
}
}
+
+ @Test
+ public void testStorageManagerDaysToRetainUsesResources() {
+ Resources resources = mock(Resources.class);
+ when(resources.getInteger(
+ eq(
+ com.android
+ .internal
+ .R
+ .integer
+ .config_storageManagerDaystoRetainDefault)))
+ .thenReturn(60);
+ assertThat(Utils.getDefaultStorageManagerDaysToRetain(resources)).isEqualTo(60);
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
new file mode 100644
index 0000000..5856332
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.lifecycle;
+
+import android.content.Context;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import com.android.settingslib.SettingLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.core.lifecycle.events.OnAttach;
+import com.android.settingslib.core.lifecycle.events.OnCreateOptionsMenu;
+import com.android.settingslib.core.lifecycle.events.OnDestroy;
+import com.android.settingslib.core.lifecycle.events.OnOptionsItemSelected;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnPrepareOptionsMenu;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ActivityController;
+import org.robolectric.util.FragmentController;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class LifecycleTest {
+
+ public static class TestDialogFragment extends ObservableDialogFragment {
+
+ final TestObserver mFragObserver;
+
+ public TestDialogFragment() {
+ mFragObserver = new TestObserver();
+ mLifecycle.addObserver(mFragObserver);
+ }
+ }
+
+ public static class TestFragment extends ObservableFragment {
+
+ final TestObserver mFragObserver;
+
+ public TestFragment() {
+ mFragObserver = new TestObserver();
+ getLifecycle().addObserver(mFragObserver);
+ }
+ }
+
+ public static class TestActivity extends ObservableActivity {
+
+ final TestObserver mActObserver;
+
+ public TestActivity() {
+ mActObserver = new TestObserver();
+ getLifecycle().addObserver(mActObserver);
+ }
+
+ }
+
+ public static class TestObserver implements LifecycleObserver, OnAttach, OnStart, OnResume,
+ OnPause, OnStop, OnDestroy, OnCreateOptionsMenu, OnPrepareOptionsMenu,
+ OnOptionsItemSelected {
+
+ boolean mOnAttachObserved;
+ boolean mOnAttachHasContext;
+ boolean mOnStartObserved;
+ boolean mOnResumeObserved;
+ boolean mOnPauseObserved;
+ boolean mOnStopObserved;
+ boolean mOnDestroyObserved;
+ boolean mOnCreateOptionsMenuObserved;
+ boolean mOnPrepareOptionsMenuObserved;
+ boolean mOnOptionsItemSelectedObserved;
+
+ @Override
+ public void onAttach(Context context) {
+ mOnAttachObserved = true;
+ mOnAttachHasContext = context != null;
+ }
+
+ @Override
+ public void onStart() {
+ mOnStartObserved = true;
+ }
+
+ @Override
+ public void onPause() {
+ mOnPauseObserved = true;
+ }
+
+ @Override
+ public void onResume() {
+ mOnResumeObserved = true;
+ }
+
+ @Override
+ public void onStop() {
+ mOnStopObserved = true;
+ }
+
+ @Override
+ public void onDestroy() {
+ mOnDestroyObserved = true;
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ mOnCreateOptionsMenuObserved = true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem menuItem) {
+ mOnOptionsItemSelectedObserved = true;
+ return true;
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ mOnPrepareOptionsMenuObserved = true;
+ }
+ }
+
+ @Test
+ public void runThroughActivityLifecycles_shouldObserveEverything() {
+ ActivityController<TestActivity> ac = Robolectric.buildActivity(TestActivity.class);
+ TestActivity activity = ac.get();
+
+ ac.start();
+ assertThat(activity.mActObserver.mOnStartObserved).isTrue();
+ ac.resume();
+ assertThat(activity.mActObserver.mOnResumeObserved).isTrue();
+ activity.onCreateOptionsMenu(null);
+ assertThat(activity.mActObserver.mOnCreateOptionsMenuObserved).isTrue();
+ activity.onPrepareOptionsMenu(null);
+ assertThat(activity.mActObserver.mOnPrepareOptionsMenuObserved).isTrue();
+ activity.onOptionsItemSelected(null);
+ assertThat(activity.mActObserver.mOnOptionsItemSelectedObserved).isTrue();
+ ac.pause();
+ assertThat(activity.mActObserver.mOnPauseObserved).isTrue();
+ ac.stop();
+ assertThat(activity.mActObserver.mOnStopObserved).isTrue();
+ ac.destroy();
+ assertThat(activity.mActObserver.mOnDestroyObserved).isTrue();
+ }
+
+ @Test
+ public void runThroughDialogFragmentLifecycles_shouldObserveEverything() {
+ FragmentController<TestDialogFragment> fragmentController =
+ Robolectric.buildFragment(TestDialogFragment.class);
+ TestDialogFragment fragment = fragmentController.get();
+
+ fragmentController.attach().create().start().resume();
+ fragment.onCreateOptionsMenu(null, null);
+ fragment.onPrepareOptionsMenu(null);
+ fragment.onOptionsItemSelected(null);
+ fragmentController.pause().stop().destroy();
+
+ assertThat(fragment.mFragObserver.mOnAttachObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnAttachHasContext).isTrue();
+ assertThat(fragment.mFragObserver.mOnStartObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnResumeObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnPauseObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnStopObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnDestroyObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnCreateOptionsMenuObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnPrepareOptionsMenuObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnOptionsItemSelectedObserved).isTrue();
+ }
+
+ @Test
+ public void runThroughFragmentLifecycles_shouldObserveEverything() {
+ FragmentController<TestFragment> fragmentController =
+ Robolectric.buildFragment(TestFragment.class);
+ TestFragment fragment = fragmentController.get();
+
+ fragmentController.attach().create().start().resume();
+ fragment.onCreateOptionsMenu(null, null);
+ fragment.onPrepareOptionsMenu(null);
+ fragment.onOptionsItemSelected(null);
+ fragmentController.pause().stop().destroy();
+
+ assertThat(fragment.mFragObserver.mOnAttachObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnAttachHasContext).isTrue();
+ assertThat(fragment.mFragObserver.mOnStartObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnResumeObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnPauseObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnStopObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnDestroyObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnCreateOptionsMenuObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnPrepareOptionsMenuObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnOptionsItemSelectedObserved).isTrue();
+ }
+
+ @Test
+ public void addObserverDuringObserve_shoudNotCrash() {
+ Lifecycle lifecycle = new Lifecycle();
+ lifecycle.addObserver(new OnStartObserver(lifecycle));
+ lifecycle.onStart();
+ }
+
+ private static class OptionItemAccepter implements LifecycleObserver, OnOptionsItemSelected {
+ public boolean wasCalled = false;
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem menuItem) {
+ wasCalled = true;
+ return false;
+ }
+ }
+
+ @Test
+ public void onOptionItemSelectedShortCircuitsIfAnObserverHandlesTheMenuItem() {
+ FragmentController<TestFragment> fragmentController =
+ Robolectric.buildFragment(TestFragment.class);
+ TestFragment fragment = fragmentController.get();
+ OptionItemAccepter accepter = new OptionItemAccepter();
+ fragment.getLifecycle().addObserver(accepter);
+
+ fragmentController.attach().create().start().resume();
+ fragment.onCreateOptionsMenu(null, null);
+ fragment.onPrepareOptionsMenu(null);
+ fragment.onOptionsItemSelected(null);
+ fragmentController.pause().stop().destroy();
+
+ assertThat(accepter.wasCalled).isFalse();
+ }
+
+ private class OnStartObserver implements LifecycleObserver, OnStart {
+
+ private final Lifecycle mLifecycle;
+
+ public OnStartObserver(Lifecycle lifecycle) {
+ mLifecycle = lifecycle;
+ }
+
+ @Override
+ public void onStart() {
+ mLifecycle.addObserver(new LifecycleObserver() {
+ });
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index 8415dc5..a2e0e2c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -17,10 +17,9 @@
package com.android.settingslib.drawer;
import android.app.ActivityManager;
-import static org.mockito.Mockito.verify;
-import android.content.IContentProvider;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.IContentProvider;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -38,23 +37,23 @@
import android.util.ArrayMap;
import android.util.Pair;
-import com.android.settingslib.SuggestionParser;
+import com.android.settingslib.R;
import com.android.settingslib.TestConfig;
-import com.android.settingslib.drawer.TileUtilsTest;
-import static org.mockito.Mockito.atLeastOnce;
+import com.android.settingslib.suggestions.SuggestionParser;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.robolectric.shadows.ShadowApplication;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -64,9 +63,10 @@
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import org.mockito.ArgumentCaptor;
@RunWith(RobolectricTestRunner.class)
@@ -179,7 +179,11 @@
false /* checkCategory */);
assertThat(outTiles.size()).isEqualTo(1);
- SuggestionParser parser = new SuggestionParser(mContext, null);
+ SuggestionParser parser = new SuggestionParser(
+ mContext,
+ null,
+ Collections.emptyList(),
+ "0,10");
parser.filterSuggestions(outTiles, 0, false);
assertThat(outTiles.size()).isEqualTo(0);
}
@@ -204,8 +208,8 @@
}), anyInt(), anyInt())).thenReturn(info);
List<DashboardCategory> categoryList = TileUtils.getCategories(
- mContext, cache, false /* categoryDefinedInManifest */, testAction,
- TileUtils.SETTING_PKG);
+ mContext, cache, false /* categoryDefinedInManifest */, testAction,
+ TileUtils.SETTING_PKG);
assertThat(categoryList.get(0).tiles.get(0).category).isEqualTo(testCategory);
}
@@ -221,13 +225,13 @@
when(mUserManager.getUserProfiles()).thenReturn(userHandleList);
TileUtils.getCategories(
- mContext, cache, false /* categoryDefinedInManifest */, null /* action */,
- TileUtils.SETTING_PKG);
+ mContext, cache, false /* categoryDefinedInManifest */, null /* action */,
+ TileUtils.SETTING_PKG);
verify(mPackageManager, atLeastOnce()).queryIntentActivitiesAsUser(
- intentCaptor.capture(), anyInt(), anyInt());
+ intentCaptor.capture(), anyInt(), anyInt());
assertThat(intentCaptor.getAllValues().get(0).getPackage())
- .isEqualTo(TileUtils.SETTING_PKG);
+ .isEqualTo(TileUtils.SETTING_PKG);
}
@Test
@@ -336,6 +340,30 @@
assertThat(outTiles.size()).isEqualTo(1);
}
+ @Test
+ public void getTilesForIntent_shouldShowRemoteViewIfSpecified() {
+ Intent intent = new Intent();
+ Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+ List<Tile> outTiles = new ArrayList<>();
+ List<ResolveInfo> info = new ArrayList<>();
+ ResolveInfo resolveInfo = newInfo(true, null /* category */);
+ resolveInfo.activityInfo.metaData.putInt("com.android.settings.custom_view",
+ R.layout.user_preference);
+ info.add(resolveInfo);
+
+ when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
+ .thenReturn(info);
+
+ TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
+ null /* defaultCategory */, outTiles, false /* usePriority */,
+ false /* checkCategory */);
+
+ assertThat(outTiles.size()).isEqualTo(1);
+ Tile tile = outTiles.get(0);
+ assertThat(tile.remoteViews).isNotNull();
+ assertThat(tile.remoteViews.getLayoutId()).isEqualTo(R.layout.user_preference);
+ }
+
public static ResolveInfo newInfo(boolean systemApp, String category) {
return newInfo(systemApp, category, null);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BottomLabelLayoutTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BottomLabelLayoutTest.java
deleted file mode 100644
index ec21723..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BottomLabelLayoutTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- *
- */
-
-package com.android.settingslib.graph;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.Space;
-
-import com.android.settingslib.R;
-import com.android.settingslib.TestConfig;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
-public class BottomLabelLayoutTest {
- private BottomLabelLayout mBottomLabelLayout;
- private Context mContext;
- private Space mSpace;
-
- @Before
- public void setUp() {
- mContext = RuntimeEnvironment.application;
- mBottomLabelLayout = new BottomLabelLayout(mContext, null);
- mBottomLabelLayout.setOrientation(LinearLayout.HORIZONTAL);
-
- mSpace = new Space(mContext);
- mSpace.setId(R.id.spacer);
- mBottomLabelLayout.addView(mSpace);
- }
-
- @Test
- public void testSetStacked_stackedTrue_layoutVertical() {
- mBottomLabelLayout.setStacked(true);
-
- assertThat(mBottomLabelLayout.getOrientation()).isEqualTo(LinearLayout.VERTICAL);
- assertThat(mSpace.getVisibility()).isEqualTo(View.GONE);
- }
-
- @Test
- public void testSetStacked_stackedFalse_layoutHorizontal() {
- mBottomLabelLayout.setStacked(false);
-
- assertThat(mBottomLabelLayout.getOrientation()).isEqualTo(LinearLayout.HORIZONTAL);
- assertThat(mSpace.getVisibility()).isEqualTo(View.VISIBLE);
- }
-}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionParserTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionParserTest.java
new file mode 100644
index 0000000..60933cf
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionParserTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.suggestions;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+
+import com.android.settingslib.SettingLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.drawer.TileUtilsTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.res.ResourceLoader;
+import org.robolectric.res.builder.DefaultPackageManager;
+import org.robolectric.res.builder.RobolectricPackageManager;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SuggestionParserTest {
+
+ private Context mContext;
+ private RobolectricPackageManager mPackageManager;
+ private SuggestionParser mSuggestionParser;
+ private SuggestionCategory mMultipleCategory;
+ private SuggestionCategory mExclusiveCategory;
+ private SuggestionCategory mExpiredExclusiveCategory;
+ private List<Tile> mSuggestionsBeforeDismiss;
+ private List<Tile> mSuggestionsAfterDismiss;
+ private SharedPreferences mPrefs;
+ private Tile mSuggestion;
+
+ @Before
+ public void setUp() {
+ RuntimeEnvironment.setRobolectricPackageManager(
+ new TestPackageManager(RuntimeEnvironment.getAppResourceLoader()));
+ mContext = RuntimeEnvironment.application;
+ mPackageManager = RuntimeEnvironment.getRobolectricPackageManager();
+ mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+ mSuggestion = new Tile();
+ mSuggestion.intent = new Intent("action");
+ mSuggestion.intent.setComponent(new ComponentName("pkg", "cls"));
+ mSuggestion.metaData = new Bundle();
+ mMultipleCategory = new SuggestionCategory();
+ mMultipleCategory.category = "category1";
+ mMultipleCategory.multiple = true;
+ mExclusiveCategory = new SuggestionCategory();
+ mExclusiveCategory.category = "category2";
+ mExclusiveCategory.exclusive = true;
+ mExpiredExclusiveCategory = new SuggestionCategory();
+ mExpiredExclusiveCategory.category = "category3";
+ mExpiredExclusiveCategory.exclusive = true;
+ mExpiredExclusiveCategory.exclusiveExpireDaysInMillis = 0;
+
+ mSuggestionParser = new SuggestionParser(mContext, mPrefs,
+ Arrays.asList(mMultipleCategory, mExclusiveCategory, mExpiredExclusiveCategory),
+ "0,0");
+
+ ResolveInfo info1 = TileUtilsTest.newInfo(true, null);
+ info1.activityInfo.packageName = "pkg";
+ ResolveInfo infoDupe1 = TileUtilsTest.newInfo(true, null);
+ infoDupe1.activityInfo.packageName = "pkg";
+
+ ResolveInfo info2 = TileUtilsTest.newInfo(true, null);
+ info2.activityInfo.packageName = "pkg2";
+ ResolveInfo info3 = TileUtilsTest.newInfo(true, null);
+ info3.activityInfo.packageName = "pkg3";
+ ResolveInfo info4 = TileUtilsTest.newInfo(true, null);
+ info4.activityInfo.packageName = "pkg4";
+
+ Intent intent1 = new Intent(Intent.ACTION_MAIN).addCategory("category1");
+ Intent intent2 = new Intent(Intent.ACTION_MAIN).addCategory("category2");
+ Intent intent3 = new Intent(Intent.ACTION_MAIN).addCategory("category3");
+
+ mPackageManager.addResolveInfoForIntent(intent1, info1);
+ mPackageManager.addResolveInfoForIntent(intent1, info2);
+ mPackageManager.addResolveInfoForIntent(intent1, infoDupe1);
+ mPackageManager.addResolveInfoForIntent(intent2, info3);
+ mPackageManager.addResolveInfoForIntent(intent3, info4);
+ }
+
+ @Test
+ public void testDismissSuggestion_withoutSmartSuggestion() {
+ assertThat(mSuggestionParser.dismissSuggestion(mSuggestion, false)).isTrue();
+ }
+
+ @Test
+ public void testDismissSuggestion_withSmartSuggestion() {
+ assertThat(mSuggestionParser.dismissSuggestion(mSuggestion, true)).isFalse();
+ }
+
+ @Test
+ public void testGetSuggestions_withoutSmartSuggestions() {
+ readAndDismissSuggestion(false);
+ mSuggestionParser.readSuggestions(mMultipleCategory, mSuggestionsAfterDismiss, false);
+ assertThat(mSuggestionsBeforeDismiss).hasSize(2);
+ assertThat(mSuggestionsAfterDismiss).hasSize(1);
+ assertThat(mSuggestionsBeforeDismiss.get(1)).isEqualTo(mSuggestionsAfterDismiss.get(0));
+ }
+
+ @Test
+ public void testGetSuggestions_withSmartSuggestions() {
+ readAndDismissSuggestion(true);
+ assertThat(mSuggestionsBeforeDismiss).hasSize(2);
+ assertThat(mSuggestionsAfterDismiss).hasSize(2);
+ assertThat(mSuggestionsBeforeDismiss).isEqualTo(mSuggestionsAfterDismiss);
+ }
+
+ @Test
+ public void testGetSuggestion_exclusiveNotAvailable_onlyRegularCategoryAndNoDupe() {
+ mPackageManager.removeResolveInfosForIntent(
+ new Intent(Intent.ACTION_MAIN).addCategory("category2"),
+ "pkg3");
+ mPackageManager.removeResolveInfosForIntent(
+ new Intent(Intent.ACTION_MAIN).addCategory("category3"),
+ "pkg4");
+
+ // If exclusive item is not available, the other categories should be shown
+ final SuggestionList sl =
+ mSuggestionParser.getSuggestions(false /* isSmartSuggestionEnabled */);
+ final List<Tile> suggestions = sl.getSuggestions();
+ assertThat(suggestions).hasSize(2);
+
+ assertThat(suggestions.get(0).intent.getComponent().getPackageName()).isEqualTo("pkg");
+ assertThat(suggestions.get(1).intent.getComponent().getPackageName()).isEqualTo("pkg2");
+ }
+
+ @Test
+ public void testGetSuggestion_exclusiveExpiredAvailable_shouldLoadWithRegularCategory() {
+ // First remove permanent exclusive
+ mPackageManager.removeResolveInfosForIntent(
+ new Intent(Intent.ACTION_MAIN).addCategory("category2"),
+ "pkg3");
+ // Set the other exclusive to be expired.
+ mPrefs.edit()
+ .putLong(mExpiredExclusiveCategory.category + "_setup_time",
+ System.currentTimeMillis() - 1000)
+ .commit();
+
+ // If exclusive is expired, they should be shown together with the other categories
+ final SuggestionList sl =
+ mSuggestionParser.getSuggestions(true /* isSmartSuggestionEnabled */);
+ final List<Tile> suggestions = sl.getSuggestions();
+
+ assertThat(suggestions).hasSize(3);
+
+ final List<Tile> category1Suggestions = sl.getSuggestionForCategory("category1");
+ final List<Tile> category3Suggestions = sl.getSuggestionForCategory("category3");
+
+ assertThat(category1Suggestions).hasSize(2);
+ assertThat(category3Suggestions).hasSize(1);
+ }
+
+ @Test
+ public void testGetSuggestions_exclusive() {
+ final SuggestionList sl =
+ mSuggestionParser.getSuggestions(false /* isSmartSuggestionEnabled */);
+ final List<Tile> suggestions = sl.getSuggestions();
+
+ assertThat(suggestions).hasSize(1);
+ assertThat(sl.getSuggestionForCategory("category2")).hasSize(1);
+ }
+
+ private void readAndDismissSuggestion(boolean isSmartSuggestionEnabled) {
+ mSuggestionsBeforeDismiss = new ArrayList<>();
+ mSuggestionsAfterDismiss = new ArrayList<>();
+ mSuggestionParser.readSuggestions(
+ mMultipleCategory, mSuggestionsBeforeDismiss, isSmartSuggestionEnabled);
+
+ final Tile suggestion = mSuggestionsBeforeDismiss.get(0);
+ if (mSuggestionParser.dismissSuggestion(suggestion, isSmartSuggestionEnabled)) {
+ RuntimeEnvironment.getRobolectricPackageManager().removeResolveInfosForIntent(
+ new Intent(Intent.ACTION_MAIN).addCategory(mMultipleCategory.category),
+ suggestion.intent.getComponent().getPackageName());
+ }
+ mSuggestionParser.readSuggestions(
+ mMultipleCategory, mSuggestionsAfterDismiss, isSmartSuggestionEnabled);
+ }
+
+ private static class TestPackageManager extends DefaultPackageManager {
+
+ TestPackageManager(ResourceLoader appResourceLoader) {
+ super(appResourceLoader);
+ }
+
+ @Override
+ public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) {
+ return super.queryIntentActivities(intent, flags);
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/ThreadUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/ThreadUtilsTest.java
new file mode 100644
index 0000000..425d162
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/ThreadUtilsTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.utils;
+
+
+import com.android.settingslib.TestConfig;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class ThreadUtilsTest {
+
+ @Test
+ public void testMainThread() throws InterruptedException {
+ assertThat(ThreadUtils.isMainThread()).isTrue();
+ Thread background = new Thread(new Runnable() {
+ public void run() {
+ assertThat(ThreadUtils.isMainThread()).isFalse();
+ }
+ });
+ background.start();
+ background.join();
+ }
+
+ @Test
+ public void testEnsureMainThread() throws InterruptedException {
+ ThreadUtils.ensureMainThread();
+ Thread background = new Thread(new Runnable() {
+ public void run() {
+ try {
+ ThreadUtils.ensureMainThread();
+ fail("Should not pass ensureMainThread in a background thread");
+ } catch (RuntimeException e) {
+ }
+ }
+ });
+ background.start();
+ background.join();
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
new file mode 100644
index 0000000..ded1574
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget;
+
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.PreferenceManager;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settingslib.SettingLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class FooterPreferenceMixinTest {
+
+ @Mock
+ private PreferenceFragment mFragment;
+ @Mock
+ private PreferenceScreen mScreen;
+
+ private Lifecycle mLifecycle;
+ private FooterPreferenceMixin mMixin;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mLifecycle = new Lifecycle();
+ when(mFragment.getPreferenceManager()).thenReturn(mock(PreferenceManager.class));
+ when(mFragment.getPreferenceManager().getContext())
+ .thenReturn(ShadowApplication.getInstance().getApplicationContext());
+ mMixin = new FooterPreferenceMixin(mFragment, mLifecycle);
+ }
+
+ @Test
+ public void createFooter_screenNotAvailable_noCrash() {
+ assertThat(mMixin.createFooterPreference()).isNotNull();
+ }
+
+ @Test
+ public void createFooter_screenAvailable_canAttachToScreen() {
+ when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
+
+ final FooterPreference preference = mMixin.createFooterPreference();
+
+ assertThat(preference).isNotNull();
+ verify(mScreen).addPreference(preference);
+ }
+
+ @Test
+ public void createFooter_screenAvailableDelayed_canAttachToScreen() {
+ final FooterPreference preference = mMixin.createFooterPreference();
+
+ mLifecycle.setPreferenceScreen(mScreen);
+
+ assertThat(preference).isNotNull();
+ verify(mScreen).addPreference(preference);
+ }
+
+ @Test
+ public void createFooterTwice_screenAvailable_replaceOldFooter() {
+ when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
+
+ mMixin.createFooterPreference();
+ mMixin.createFooterPreference();
+
+ verify(mScreen).removePreference(any(FooterPreference.class));
+ verify(mScreen, times(2)).addPreference(any(FooterPreference.class));
+ }
+
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
new file mode 100644
index 0000000..17b3de0
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget;
+
+import android.content.Context;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.text.method.LinkMovementMethod;
+import android.view.LayoutInflater;
+import android.widget.TextView;
+
+import com.android.settingslib.R;
+import com.android.settingslib.SettingLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class FooterPreferenceTest {
+
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = ShadowApplication.getInstance().getApplicationContext();
+ }
+
+ @Test
+ public void createNewPreference_shouldSetKeyAndOrder() {
+ final FooterPreference preference = new FooterPreference(mContext);
+
+ assertThat(preference.getKey()).isEqualTo(FooterPreference.KEY_FOOTER);
+ assertThat(preference.getOrder()).isEqualTo(FooterPreference.ORDER_FOOTER);
+ }
+
+ @Test
+ public void bindPreference_shouldLinkifyContent() {
+ final FooterPreference preference = new FooterPreference(mContext);
+ final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(
+ LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null));
+
+ preference.onBindViewHolder(holder);
+ assertThat(((TextView) holder.findViewById(android.R.id.title)).getMovementMethod())
+ .isInstanceOf(LinkMovementMethod.class);
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 920df2e..533c52b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -16,6 +16,7 @@
package com.android.providers.settings;
+import android.annotation.UserIdInt;
import android.app.backup.BackupAgentHelper;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
@@ -133,6 +134,10 @@
// Keys within the lock settings section
private static final String KEY_LOCK_SETTINGS_OWNER_INFO_ENABLED = "owner_info_enabled";
private static final String KEY_LOCK_SETTINGS_OWNER_INFO = "owner_info";
+ private static final String KEY_LOCK_SETTINGS_VISIBLE_PATTERN_ENABLED =
+ "visible_pattern_enabled";
+ private static final String KEY_LOCK_SETTINGS_POWER_BUTTON_INSTANTLY_LOCKS =
+ "power_button_instantly_locks";
// Name of the temporary file we use during full backup/restore. This is
// stored in the full-backup tarfile as well, so should not be changed.
@@ -158,7 +163,7 @@
byte[] systemSettingsData = getSystemSettings();
byte[] secureSettingsData = getSecureSettings();
byte[] globalSettingsData = getGlobalSettings();
- byte[] lockSettingsData = getLockSettings();
+ byte[] lockSettingsData = getLockSettings(UserHandle.myUserId());
byte[] locale = mSettingsHelper.getLocaleData();
byte[] softApConfigData = getSoftAPConfiguration();
byte[] netPoliciesData = getNetworkPolicies();
@@ -236,7 +241,7 @@
break;
case KEY_LOCK_SETTINGS :
- restoreLockSettings(data);
+ restoreLockSettings(UserHandle.myUserId(), data);
break;
case KEY_SOFTAP_CONFIG :
@@ -275,7 +280,7 @@
byte[] systemSettingsData = getSystemSettings();
byte[] secureSettingsData = getSecureSettings();
byte[] globalSettingsData = getGlobalSettings();
- byte[] lockSettingsData = getLockSettings();
+ byte[] lockSettingsData = getLockSettings(UserHandle.myUserId());
byte[] locale = mSettingsHelper.getLocaleData();
byte[] softApConfigData = getSoftAPConfiguration();
byte[] netPoliciesData = getNetworkPolicies();
@@ -405,7 +410,7 @@
if (nBytes > buffer.length) buffer = new byte[nBytes];
if (nBytes > 0) {
in.readFully(buffer, 0, nBytes);
- restoreLockSettings(buffer, nBytes);
+ restoreLockSettings(UserHandle.myUserId(), buffer, nBytes);
}
}
// softap config
@@ -531,12 +536,16 @@
}
/**
- * Serialize the owner info settings
+ * Serialize the owner info and other lock settings
*/
- private byte[] getLockSettings() {
+ private byte[] getLockSettings(@UserIdInt int userId) {
final LockPatternUtils lockPatternUtils = new LockPatternUtils(this);
- final boolean ownerInfoEnabled = lockPatternUtils.isOwnerInfoEnabled(UserHandle.myUserId());
- final String ownerInfo = lockPatternUtils.getOwnerInfo(UserHandle.myUserId());
+ final boolean ownerInfoEnabled = lockPatternUtils.isOwnerInfoEnabled(userId);
+ final String ownerInfo = lockPatternUtils.getOwnerInfo(userId);
+ final boolean lockPatternEnabled = lockPatternUtils.isLockPatternEnabled(userId);
+ final boolean visiblePatternEnabled = lockPatternUtils.isVisiblePatternEnabled(userId);
+ final boolean powerButtonInstantlyLocks =
+ lockPatternUtils.getPowerButtonInstantlyLocks(userId);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(baos);
@@ -547,6 +556,14 @@
out.writeUTF(KEY_LOCK_SETTINGS_OWNER_INFO);
out.writeUTF(ownerInfo != null ? ownerInfo : "");
}
+ if (lockPatternUtils.isVisiblePatternEverChosen(userId)) {
+ out.writeUTF(KEY_LOCK_SETTINGS_VISIBLE_PATTERN_ENABLED);
+ out.writeUTF(visiblePatternEnabled ? "1" : "0");
+ }
+ if (lockPatternUtils.isPowerButtonInstantlyLocksEverChosen(userId)) {
+ out.writeUTF(KEY_LOCK_SETTINGS_POWER_BUTTON_INSTANTLY_LOCKS);
+ out.writeUTF(powerButtonInstantlyLocks ? "1" : "0");
+ }
// End marker
out.writeUTF("");
out.flush();
@@ -632,12 +649,12 @@
}
/**
- * Restores the owner info enabled and owner info settings in LockSettings.
+ * Restores the owner info enabled and other settings in LockSettings.
*
* @param buffer
* @param nBytes
*/
- private void restoreLockSettings(byte[] buffer, int nBytes) {
+ private void restoreLockSettings(@UserIdInt int userId, byte[] buffer, int nBytes) {
final LockPatternUtils lockPatternUtils = new LockPatternUtils(this);
ByteArrayInputStream bais = new ByteArrayInputStream(buffer, 0, nBytes);
@@ -652,11 +669,17 @@
}
switch (key) {
case KEY_LOCK_SETTINGS_OWNER_INFO_ENABLED:
- lockPatternUtils.setOwnerInfoEnabled("1".equals(value),
- UserHandle.myUserId());
+ lockPatternUtils.setOwnerInfoEnabled("1".equals(value), userId);
break;
case KEY_LOCK_SETTINGS_OWNER_INFO:
- lockPatternUtils.setOwnerInfo(value, UserHandle.myUserId());
+ lockPatternUtils.setOwnerInfo(value, userId);
+ break;
+ case KEY_LOCK_SETTINGS_VISIBLE_PATTERN_ENABLED:
+ lockPatternUtils.reportPatternWasChosen(userId);
+ lockPatternUtils.setVisiblePatternEnabled("1".equals(value), userId);
+ break;
+ case KEY_LOCK_SETTINGS_POWER_BUTTON_INSTANTLY_LOCKS:
+ lockPatternUtils.setPowerButtonInstantlyLocks("1".equals(value), userId);
break;
}
}
@@ -665,7 +688,7 @@
}
}
- private void restoreLockSettings(BackupDataInput data) {
+ private void restoreLockSettings(@UserIdInt int userId, BackupDataInput data) {
final byte[] settings = new byte[data.getDataSize()];
try {
data.readEntityData(settings, 0, settings.length);
@@ -673,7 +696,7 @@
Log.e(TAG, "Couldn't read entity data");
return;
}
- restoreLockSettings(settings, settings.length);
+ restoreLockSettings(userId, settings, settings.length);
}
/**
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 455d9cb..f5d7dd8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2212,11 +2212,7 @@
throw new IllegalStateException("Key is corrupted", e);
}
- // Mac the package name and each of the signatures.
- final String packageName = callingPkg.packageName;
- byte[] packageNameBytes = packageName.getBytes(StandardCharsets.UTF_8);
- m.update(getLengthPrefix(packageNameBytes), 0, 4);
- m.update(packageNameBytes);
+ // Mac each of the developer signatures.
for (int i = 0; i < callingPkg.signatures.length; i++) {
byte[] sig = callingPkg.signatures[i].toByteArray();
m.update(getLengthPrefix(sig), 0, 4);
@@ -2231,7 +2227,7 @@
final String uid = Integer.toString(callingPkg.applicationInfo.uid);
final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID, userId);
final boolean success = ssaidSettings.insertSettingLocked(uid, ssaid, null, true,
- packageName);
+ callingPkg.packageName);
if (!success) {
throw new IllegalStateException("Ssaid settings not accessible");
diff --git a/packages/SettingsProvider/test/Android.mk b/packages/SettingsProvider/test/Android.mk
index d039f03..a4cbf34 100644
--- a/packages/SettingsProvider/test/Android.mk
+++ b/packages/SettingsProvider/test/Android.mk
@@ -17,4 +17,6 @@
LOCAL_CERTIFICATE := platform
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
include $(BUILD_PACKAGE)
diff --git a/packages/SettingsProvider/test/AndroidTest.xml b/packages/SettingsProvider/test/AndroidTest.xml
new file mode 100644
index 0000000..3a156895
--- /dev/null
+++ b/packages/SettingsProvider/test/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Run Settings Provider Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="SettingsProviderTest.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="SettingsProviderTest" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.providers.setting.test" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 348b02a..988b986 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -786,14 +786,6 @@
*/
private void onBugreportFinished(int id, Intent intent) {
final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
- // Since BugreportProvider and BugreportProgressService aren't tightly coupled,
- // we need to make sure they are explicitly tied to a single unique notification URI
- // so that the service can alert the provider of changes it has done (ie. new bug
- // reports)
- // See { @link Cursor#setNotificationUri } and {@link ContentResolver#notifyChanges }
- final Uri notificationUri = BugreportStorageProvider.getNotificationUri();
- mContext.getContentResolver().notifyChange(notificationUri, null, false);
-
if (bugreportFile == null) {
// Should never happen, dumpstate always set the file.
Log.wtf(TAG, "Missing " + EXTRA_BUGREPORT + " on intent " + intent);
diff --git a/packages/Shell/src/com/android/shell/BugreportStorageProvider.java b/packages/Shell/src/com/android/shell/BugreportStorageProvider.java
index b9b77a4..1bb36fb 100644
--- a/packages/Shell/src/com/android/shell/BugreportStorageProvider.java
+++ b/packages/Shell/src/com/android/shell/BugreportStorageProvider.java
@@ -26,22 +26,22 @@
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
-import android.provider.DocumentsProvider;
-import android.webkit.MimeTypeMap;
+
+import com.android.internal.content.FileSystemProvider;
import java.io.File;
import java.io.FileNotFoundException;
-public class BugreportStorageProvider extends DocumentsProvider {
+public class BugreportStorageProvider extends FileSystemProvider {
private static final String AUTHORITY = "com.android.shell.documents";
private static final String DOC_ID_ROOT = "bugreport";
- private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
+ private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{
Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
Root.COLUMN_DOCUMENT_ID,
};
- private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
+ private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{
Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
};
@@ -50,6 +50,7 @@
@Override
public boolean onCreate() {
+ super.onCreate(DEFAULT_DOCUMENT_PROJECTION);
mRoot = new File(getContext().getFilesDir(), "bugreports");
return true;
}
@@ -69,35 +70,13 @@
@Override
public Cursor queryDocument(String documentId, String[] projection)
throws FileNotFoundException {
- final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
if (DOC_ID_ROOT.equals(documentId)) {
- final RowBuilder row = result.newRow();
- row.add(Document.COLUMN_DOCUMENT_ID, documentId);
- row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
- row.add(Document.COLUMN_DISPLAY_NAME, mRoot.getName());
- row.add(Document.COLUMN_LAST_MODIFIED, mRoot.lastModified());
- row.add(Document.COLUMN_FLAGS, Document.FLAG_DIR_PREFERS_LAST_MODIFIED);
+ final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+ includeDefaultDocument(result);
+ return result;
} else {
- addFileRow(result, getFileForDocId(documentId));
+ return super.queryDocument(documentId, projection);
}
- return result;
- }
-
- @Override
- public Cursor queryChildDocuments(
- String parentDocumentId, String[] projection, String sortOrder)
- throws FileNotFoundException {
- final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
- if (DOC_ID_ROOT.equals(parentDocumentId)) {
- final File[] files = mRoot.listFiles();
- if (files != null) {
- for (File file : files) {
- addFileRow(result, file);
- }
- result.setNotificationUri(getContext().getContentResolver(), getNotificationUri());
- }
- }
- return result;
}
@Override
@@ -112,17 +91,8 @@
}
@Override
- public void deleteDocument(String documentId) throws FileNotFoundException {
- if (!getFileForDocId(documentId).delete()) {
- throw new FileNotFoundException("Failed to delete: " + documentId);
- }
- getContext().getContentResolver().notifyChange(getNotificationUri(), null);
- }
-
- // This is used by BugreportProgressService so that the notification uri shared by
- // BugreportProgressService and BugreportStorageProvider are guaranteed the same and unique
- protected static Uri getNotificationUri() {
- return DocumentsContract.buildChildDocumentsUri(AUTHORITY, DOC_ID_ROOT);
+ protected Uri buildNotificationUri(String docId) {
+ return DocumentsContract.buildChildDocumentsUri(AUTHORITY, docId);
}
private static String[] resolveRootProjection(String[] projection) {
@@ -133,45 +103,45 @@
return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
}
- private static String getTypeForName(String name) {
- final int lastDot = name.lastIndexOf('.');
- if (lastDot >= 0) {
- final String extension = name.substring(lastDot + 1).toLowerCase();
- final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
- if (mime != null) {
- return mime;
- }
- }
- return "application/octet-stream";
- }
-
- private String getDocIdForFile(File file) {
+ @Override
+ protected String getDocIdForFile(File file) {
return DOC_ID_ROOT + ":" + file.getName();
}
- private File getFileForDocId(String documentId) throws FileNotFoundException {
- final int splitIndex = documentId.indexOf(':', 1);
- final String name = documentId.substring(splitIndex + 1);
- if (splitIndex == -1 || !DOC_ID_ROOT.equals(documentId.substring(0, splitIndex)) ||
- !FileUtils.isValidExtFilename(name)) {
- throw new FileNotFoundException("Invalid document ID: " + documentId);
+ @Override
+ protected File getFileForDocId(String documentId, boolean visible)
+ throws FileNotFoundException {
+ if (DOC_ID_ROOT.equals(documentId)) {
+ return mRoot;
+ } else {
+ final int splitIndex = documentId.indexOf(':', 1);
+ final String name = documentId.substring(splitIndex + 1);
+ if (splitIndex == -1 || !DOC_ID_ROOT.equals(documentId.substring(0, splitIndex)) ||
+ !FileUtils.isValidExtFilename(name)) {
+ throw new FileNotFoundException("Invalid document ID: " + documentId);
+ }
+ final File file = new File(mRoot, name);
+ if (!file.exists()) {
+ throw new FileNotFoundException("File not found: " + documentId);
+ }
+ return file;
}
- final File file = new File(mRoot, name);
- if (!file.exists()) {
- throw new FileNotFoundException("File not found: " + documentId);
- }
- return file;
}
- private void addFileRow(MatrixCursor result, File file) {
- String mimeType = getTypeForName(file.getName());
- int flags = Document.FLAG_SUPPORTS_DELETE;
+ @Override
+ protected RowBuilder includeFile(MatrixCursor result, String docId, File file)
+ throws FileNotFoundException {
+ RowBuilder row = super.includeFile(result, docId, file);
+ row.add(Document.COLUMN_FLAGS, Document.FLAG_SUPPORTS_DELETE);
+ return row;
+ }
+
+ private void includeDefaultDocument(MatrixCursor result) {
final RowBuilder row = result.newRow();
- row.add(Document.COLUMN_DOCUMENT_ID, getDocIdForFile(file));
- row.add(Document.COLUMN_MIME_TYPE, mimeType);
- row.add(Document.COLUMN_DISPLAY_NAME, file.getName());
- row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
- row.add(Document.COLUMN_FLAGS, flags);
- row.add(Document.COLUMN_SIZE, file.length());
+ row.add(Document.COLUMN_DOCUMENT_ID, DOC_ID_ROOT);
+ row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
+ row.add(Document.COLUMN_DISPLAY_NAME, mRoot.getName());
+ row.add(Document.COLUMN_LAST_MODIFIED, mRoot.lastModified());
+ row.add(Document.COLUMN_FLAGS, Document.FLAG_DIR_PREFERS_LAST_MODIFIED);
}
}
diff --git a/packages/Shell/src/com/android/shell/Screenshooter.java b/packages/Shell/src/com/android/shell/Screenshooter.java
index 92c5fcc..8e27edf 100644
--- a/packages/Shell/src/com/android/shell/Screenshooter.java
+++ b/packages/Shell/src/com/android/shell/Screenshooter.java
@@ -100,7 +100,7 @@
// Rotate the screenshot to the current orientation
if (rotation != ROTATION_FREEZE_0) {
Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight,
- Bitmap.Config.ARGB_8888);
+ Bitmap.Config.ARGB_8888, screenShot.hasAlpha(), screenShot.getColorSpace());
Canvas canvas = new Canvas(unrotatedScreenShot);
canvas.translate(unrotatedScreenShot.getWidth() / 2,
unrotatedScreenShot.getHeight() / 2);
diff --git a/packages/Shell/tests/AndroidTest.xml b/packages/Shell/tests/AndroidTest.xml
index ed9cb2b..bd37712c 100644
--- a/packages/Shell/tests/AndroidTest.xml
+++ b/packages/Shell/tests/AndroidTest.xml
@@ -20,7 +20,7 @@
<option name="test-suite-tag" value="apct" />
<option name="test-tag" value="ShellTests" />
- <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.shell.tests" />
<option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
</test>
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 2fd7e87..8fa217d 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -38,7 +38,8 @@
android-support-v7-mediarouter \
android-support-v7-palette \
android-support-v14-preference \
- android-support-v17-leanback
+ android-support-v17-leanback \
+ colorextraction
LOCAL_STATIC_JAVA_LIBRARIES := \
SystemUI-tags \
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index c4e134b..e322acc 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -22,6 +22,11 @@
android:sharedUserId="android.uid.systemui"
coreApp="true">
+ <!-- SysUI must be the one to define this permission; its name is
+ referenced by the core OS. -->
+ <permission android:name="android.permission.systemui.IDENTITY"
+ android:protectionLevel="signature" />
+
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@@ -190,6 +195,9 @@
<!-- to access instant apps -->
<uses-permission android:name="android.permission.ACCESS_INSTANT_APPS" />
+ <!-- to change themes - light or dark -->
+ <uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES" />
+
<application
android:name=".SystemUIApplication"
android:persistent="true"
diff --git a/packages/SystemUI/colorextraction/Android.mk b/packages/SystemUI/colorextraction/Android.mk
new file mode 100644
index 0000000..e818c99
--- /dev/null
+++ b/packages/SystemUI/colorextraction/Android.mk
@@ -0,0 +1,26 @@
+# 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_USE_AAPT2 := true
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_ANDROID_LIBRARIES := android-support-annotations \
+ android-support-v7-palette \
+ android-support-v4
+LOCAL_MODULE := colorextraction
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/packages/SystemUI/colorextraction/AndroidManifest.xml b/packages/SystemUI/colorextraction/AndroidManifest.xml
new file mode 100644
index 0000000..6e082cf
--- /dev/null
+++ b/packages/SystemUI/colorextraction/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.colorextraction">
+</manifest>
diff --git a/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/ColorExtractor.java b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/ColorExtractor.java
new file mode 100644
index 0000000..4a5d8b4
--- /dev/null
+++ b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/ColorExtractor.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.google.android.colorextraction;
+
+import android.app.WallpaperColors;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.google.android.colorextraction.types.ExtractionType;
+import com.google.android.colorextraction.types.Tonal;
+
+import java.util.ArrayList;
+
+/**
+ * Class to process wallpaper colors and generate a tonal palette based on them.
+ */
+public class ColorExtractor implements WallpaperManager.OnColorsChangedListener {
+
+ public static final int TYPE_NORMAL = 0;
+ public static final int TYPE_DARK = 1;
+ public static final int TYPE_EXTRA_DARK = 2;
+ private static final int[] sGradientTypes = new int[]{TYPE_NORMAL, TYPE_DARK, TYPE_EXTRA_DARK};
+
+ private static final String TAG = "ColorExtractor";
+
+ @VisibleForTesting
+ static final int FALLBACK_COLOR = 0xff83888d;
+
+ private int mMainFallbackColor = FALLBACK_COLOR;
+ private int mSecondaryFallbackColor = FALLBACK_COLOR;
+ private final SparseArray<GradientColors[]> mGradientColors;
+ private final ArrayList<OnColorsChangedListener> mOnColorsChangedListeners;
+ // Colors to return when the wallpaper isn't visible
+ private final GradientColors mWpHiddenColors;
+ private final Context mContext;
+ private final ExtractionType mExtractionType;
+
+ public ColorExtractor(Context context) {
+ this(context, new Tonal());
+ }
+
+ @VisibleForTesting
+ public ColorExtractor(Context context, ExtractionType extractionType) {
+ mContext = context;
+ mWpHiddenColors = new GradientColors();
+ mWpHiddenColors.setMainColor(FALLBACK_COLOR);
+ mWpHiddenColors.setSecondaryColor(FALLBACK_COLOR);
+ mExtractionType = extractionType;
+
+ mGradientColors = new SparseArray<>();
+ for (int which : new int[] { WallpaperManager.FLAG_LOCK, WallpaperManager.FLAG_SYSTEM}) {
+ GradientColors[] colors = new GradientColors[sGradientTypes.length];
+ mGradientColors.append(which, colors);
+ for (int type : sGradientTypes) {
+ colors[type] = new GradientColors();
+ }
+ }
+
+ mOnColorsChangedListeners = new ArrayList<>();
+
+ WallpaperManager wallpaperManager = mContext.getSystemService(WallpaperManager.class);
+ if (wallpaperManager == null) {
+ Log.w(TAG, "Can't listen to color changes!");
+ } else {
+ wallpaperManager.addOnColorsChangedListener(this);
+
+ // Initialize all gradients with the current colors
+ GradientColors[] systemColors = mGradientColors.get(WallpaperManager.FLAG_SYSTEM);
+ extractInto(wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM),
+ systemColors[TYPE_NORMAL],
+ systemColors[TYPE_DARK],
+ systemColors[TYPE_EXTRA_DARK]);
+
+ GradientColors[] lockColors = mGradientColors.get(WallpaperManager.FLAG_LOCK);
+ extractInto(wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_LOCK),
+ lockColors[TYPE_NORMAL],
+ lockColors[TYPE_DARK],
+ lockColors[TYPE_EXTRA_DARK]);
+ }
+ }
+
+ /**
+ * Retrieve TYPE_NORMAL gradient colors considering wallpaper visibility.
+ *
+ * @param which FLAG_LOCK or FLAG_SYSTEM
+ * @return colors
+ */
+ @NonNull
+ public GradientColors getColors(int which) {
+ return getColors(which, TYPE_NORMAL);
+ }
+
+ /**
+ * Get current gradient colors for one of the possible gradient types
+ *
+ * @param which FLAG_LOCK or FLAG_SYSTEM
+ * @param type TYPE_NORMAL, TYPE_DARK or TYPE_EXTRA_DARK
+ * @return colors
+ */
+ public GradientColors getColors(int which, int type) {
+ if (type != TYPE_NORMAL && type != TYPE_DARK && type != TYPE_EXTRA_DARK) {
+ throw new IllegalArgumentException(
+ "type should be TYPE_NORMAL, TYPE_DARK or TYPE_EXTRA_DARK");
+ }
+ if (which != WallpaperManager.FLAG_LOCK && which != WallpaperManager.FLAG_SYSTEM) {
+ throw new IllegalArgumentException("which should be FLAG_SYSTEM or FLAG_NORMAL");
+ }
+
+ return mGradientColors.get(which)[type];
+ }
+
+ @Override
+ public void onColorsChanged(WallpaperColors colors, int which) {
+ boolean changed = false;
+ if ((which & WallpaperManager.FLAG_LOCK) != 0) {
+ GradientColors[] lockColors = mGradientColors.get(WallpaperManager.FLAG_LOCK);
+ extractInto(colors, lockColors[TYPE_NORMAL], lockColors[TYPE_DARK],
+ lockColors[TYPE_EXTRA_DARK]);
+
+ changed = true;
+ }
+ if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
+ GradientColors[] systemColors = mGradientColors.get(WallpaperManager.FLAG_SYSTEM);
+ extractInto(colors, systemColors[TYPE_NORMAL], systemColors[TYPE_DARK],
+ systemColors[TYPE_EXTRA_DARK]);
+ changed = true;
+ }
+
+ if (changed) {
+ triggerColorsChanged(which);
+ }
+ }
+
+ private void triggerColorsChanged(int which) {
+ for (OnColorsChangedListener listener: mOnColorsChangedListeners) {
+ listener.onColorsChanged(this, which);
+ }
+ }
+
+ private void extractInto(WallpaperColors inWallpaperColors,
+ GradientColors outGradientColorsNormal, GradientColors outGradientColorsDark,
+ GradientColors outGradientColorsExtraDark) {
+ if (inWallpaperColors == null) {
+ applyFallback(outGradientColorsNormal);
+ applyFallback(outGradientColorsDark);
+ applyFallback(outGradientColorsExtraDark);
+ return;
+ }
+ boolean success = mExtractionType.extractInto(inWallpaperColors, outGradientColorsNormal,
+ outGradientColorsDark, outGradientColorsExtraDark);
+ if (!success) {
+ applyFallback(outGradientColorsNormal);
+ applyFallback(outGradientColorsDark);
+ applyFallback(outGradientColorsExtraDark);
+ }
+ }
+
+ private void applyFallback(GradientColors outGradientColors) {
+ outGradientColors.setMainColor(mMainFallbackColor);
+ outGradientColors.setSecondaryColor(mSecondaryFallbackColor);
+ outGradientColors.setSupportsDarkText(false);
+ }
+
+ public void destroy() {
+ WallpaperManager wallpaperManager = mContext.getSystemService(WallpaperManager.class);
+ if (wallpaperManager != null) {
+ wallpaperManager.removeOnColorsChangedListener(this);
+ }
+ }
+
+ public void addOnColorsChangedListener(@NonNull OnColorsChangedListener listener) {
+ mOnColorsChangedListeners.add(listener);
+ }
+
+ public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener listener) {
+ mOnColorsChangedListeners.remove(listener);
+ }
+
+ public static class GradientColors {
+ private int mMainColor = FALLBACK_COLOR;
+ private int mSecondaryColor = FALLBACK_COLOR;
+ private boolean mSupportsDarkText;
+
+ public void setMainColor(int mainColor) {
+ mMainColor = mainColor;
+ }
+
+ public void setSecondaryColor(int secondaryColor) {
+ mSecondaryColor = secondaryColor;
+ }
+
+ public void setSupportsDarkText(boolean supportsDarkText) {
+ mSupportsDarkText = supportsDarkText;
+ }
+
+ public void set(GradientColors other) {
+ mMainColor = other.mMainColor;
+ mSecondaryColor = other.mSecondaryColor;
+ mSupportsDarkText = other.mSupportsDarkText;
+ }
+
+ public int getMainColor() {
+ return mMainColor;
+ }
+
+ public int getSecondaryColor() {
+ return mSecondaryColor;
+ }
+
+ public boolean supportsDarkText() {
+ return mSupportsDarkText;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || o.getClass() != getClass()) {
+ return false;
+ }
+ GradientColors other = (GradientColors) o;
+ return other.mMainColor == mMainColor &&
+ other.mSecondaryColor == mSecondaryColor &&
+ other.mSupportsDarkText == mSupportsDarkText;
+ }
+
+ @Override
+ public int hashCode() {
+ int code = mMainColor;
+ code = 31 * code + mSecondaryColor;
+ code = 31 * code + (mSupportsDarkText ? 0 : 1);
+ return code;
+ }
+
+ @Override
+ public String toString() {
+ return "GradientColors(" + Integer.toHexString(mMainColor) + ", "
+ + Integer.toHexString(mSecondaryColor) + ")";
+ }
+ }
+
+ public interface OnColorsChangedListener {
+ void onColorsChanged(ColorExtractor colorExtractor, int which);
+ }
+}
diff --git a/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/drawable/GradientDrawable.java b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/drawable/GradientDrawable.java
new file mode 100644
index 0000000..2b212c1
--- /dev/null
+++ b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/drawable/GradientDrawable.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.google.android.colorextraction.drawable;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.Xfermode;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.graphics.ColorUtils;
+import android.view.animation.DecelerateInterpolator;
+
+import com.google.android.colorextraction.ColorExtractor;
+
+/**
+ * Draws a gradient based on a Palette
+ */
+public class GradientDrawable extends Drawable {
+ private static final String TAG = "GradientDrawable";
+
+ private static final float CENTRALIZED_CIRCLE_1 = -2;
+ private static final int GRADIENT_RADIUS = 480; // in dp
+ private static final long COLOR_ANIMATION_DURATION = 2000;
+
+ private int mAlpha = 255;
+
+ private float mDensity;
+ private final Paint mPaint;
+ private final Rect mWindowBounds;
+ private final Splat mSplat;
+
+ private int mMainColor;
+ private int mSecondaryColor;
+ private ValueAnimator mColorAnimation;
+
+ public GradientDrawable(@NonNull Context context) {
+ mDensity = context.getResources().getDisplayMetrics().density;
+ mSplat = new Splat(0.50f, 1.00f, GRADIENT_RADIUS, CENTRALIZED_CIRCLE_1);
+ mWindowBounds = new Rect();
+
+ mPaint = new Paint();
+ mPaint.setStyle(Paint.Style.FILL);
+ }
+
+ public void setColors(@NonNull ColorExtractor.GradientColors colors) {
+ setColors(colors.getMainColor(), colors.getSecondaryColor(), true);
+ }
+
+ public void setColors(@NonNull ColorExtractor.GradientColors colors, boolean animated) {
+ setColors(colors.getMainColor(), colors.getSecondaryColor(), animated);
+ }
+
+ public void setColors(int mainColor, int secondaryColor, boolean animated) {
+ if (mainColor == mMainColor && secondaryColor == mSecondaryColor) {
+ return;
+ }
+
+ if (mColorAnimation != null && mColorAnimation.isRunning()) {
+ mColorAnimation.cancel();
+ }
+
+ if (animated) {
+ final int mainFrom = mMainColor;
+ final int secFrom = mSecondaryColor;
+
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+ anim.setDuration(COLOR_ANIMATION_DURATION);
+ anim.addUpdateListener(animation -> {
+ float ratio = (float) animation.getAnimatedValue();
+ mMainColor = ColorUtils.blendARGB(mainFrom, mainColor, ratio);
+ mSecondaryColor = ColorUtils.blendARGB(secFrom, secondaryColor, ratio);
+ buildPaints();
+ invalidateSelf();
+ });
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation, boolean isReverse) {
+ if (mColorAnimation == animation) {
+ mColorAnimation = null;
+ }
+ }
+ });
+ anim.setInterpolator(new DecelerateInterpolator());
+ anim.start();
+ mColorAnimation = anim;
+ } else {
+ mMainColor = mainColor;
+ mSecondaryColor = secondaryColor;
+ buildPaints();
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ if (alpha != mAlpha) {
+ mAlpha = alpha;
+ mPaint.setAlpha(mAlpha);
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public int getAlpha() {
+ return mAlpha;
+ }
+
+ @Override
+ public void setXfermode(@Nullable Xfermode mode) {
+ mPaint.setXfermode(mode);
+ invalidateSelf();
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ mPaint.setColorFilter(colorFilter);
+ }
+
+ @Override
+ public ColorFilter getColorFilter() {
+ return mPaint.getColorFilter();
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ public void setScreenSize(int width, int height) {
+ mWindowBounds.set(0, 0, width, height);
+ setBounds(0, 0, width, height);
+ buildPaints();
+ }
+
+ private void buildPaints() {
+ Rect bounds = mWindowBounds;
+ if (bounds.width() == 0) {
+ return;
+ }
+
+ float w = bounds.width();
+ float h = bounds.height();
+
+ float x = mSplat.x * w;
+ float y = mSplat.y * h;
+
+ float radius = mSplat.radius * mDensity;
+
+ // When we have only a single alpha gradient, we increase quality
+ // (avoiding banding) by merging the background solid color into
+ // the gradient directly
+ RadialGradient radialGradient = new RadialGradient(x, y, radius,
+ mSecondaryColor, mMainColor, Shader.TileMode.CLAMP);
+ mPaint.setShader(radialGradient);
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas) {
+ Rect bounds = mWindowBounds;
+ if (bounds.width() == 0) {
+ throw new IllegalStateException("You need to call setScreenSize before drawing.");
+ }
+
+ // Splat each gradient
+ float w = bounds.width();
+ float h = bounds.height();
+
+ float x = mSplat.x * w;
+ float y = mSplat.y * h;
+
+ float radius = Math.max(w, h);
+ canvas.drawRect(x - radius, y - radius, x + radius, y + radius, mPaint);
+ }
+
+ @VisibleForTesting
+ public int getMainColor() {
+ return mMainColor;
+ }
+
+ @VisibleForTesting
+ public int getSecondaryColor() {
+ return mSecondaryColor;
+ }
+
+ static final class Splat {
+ final float x;
+ final float y;
+ final float radius;
+ final float colorIndex;
+
+ Splat(float x, float y, float radius, float colorIndex) {
+ this.x = x;
+ this.y = y;
+ this.radius = radius;
+ this.colorIndex = colorIndex;
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/ExtractionType.java b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/ExtractionType.java
new file mode 100644
index 0000000..4c3ac3e
--- /dev/null
+++ b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/ExtractionType.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.google.android.colorextraction.types;
+
+import android.app.WallpaperColors;
+
+import com.google.android.colorextraction.ColorExtractor;
+
+/**
+ * Interface to allow various color extraction implementations.
+ */
+public interface ExtractionType {
+
+ /**
+ * Executes color extraction by reading WallpaperColors and setting
+ * main and secondary colors on GradientColors.
+ *
+ * Extraction is expected to happen with 3 different gradient types:
+ * Normal, with the main extracted colors
+ * Dark, with extra contrast
+ * ExtraDark, for places where GAR is mandatory, like the emergency dialer
+ *
+ * @param inWallpaperColors where to read from
+ * @param outGradientColorsNormal object that should receive normal colors
+ * @param outGradientColorsDark object that should receive dark colors
+ * @param outGradientColorsExtraDark object that should receive extra dark colors
+ * @return true if successful.
+ */
+ boolean extractInto(WallpaperColors inWallpaperColors,
+ ColorExtractor.GradientColors outGradientColorsNormal,
+ ColorExtractor.GradientColors outGradientColorsDark,
+ ColorExtractor.GradientColors outGradientColorsExtraDark);
+}
diff --git a/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/Tonal.java b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/Tonal.java
new file mode 100644
index 0000000..5b4b3ed
--- /dev/null
+++ b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/Tonal.java
@@ -0,0 +1,797 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.google.android.colorextraction.types;
+
+import android.app.WallpaperColors;
+import android.graphics.Color;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.graphics.ColorUtils;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.Pair;
+import android.util.Range;
+
+import com.google.android.colorextraction.ColorExtractor.GradientColors;
+
+/**
+ * Implementation of tonal color extraction
+ */
+public class Tonal implements ExtractionType {
+ private static final String TAG = "Tonal";
+
+ // Used for tonal palette fitting
+ private static final float FIT_WEIGHT_H = 1.0f;
+ private static final float FIT_WEIGHT_S = 1.0f;
+ private static final float FIT_WEIGHT_L = 10.0f;
+
+ // When extracting the main color, only consider colors
+ // present in at least MIN_COLOR_OCCURRENCE of the image
+ private static final float MIN_COLOR_OCCURRENCE = 0.1f;
+ private static final boolean DEBUG = true;
+
+ // Temporary variable to avoid allocations
+ private float[] mTmpHSL = new float[3];
+
+ /**
+ * Grab colors from WallpaperColors as set them into GradientColors
+ *
+ * @param inWallpaperColors input
+ * @param outColorsNormal colors for normal theme
+ * @param outColorsDark colors for dar theme
+ * @param outColorsExtraDark colors for extra dark theme
+ * @return true if successful
+ */
+ public boolean extractInto(@NonNull WallpaperColors inWallpaperColors,
+ @NonNull GradientColors outColorsNormal, @NonNull GradientColors outColorsDark,
+ @NonNull GradientColors outColorsExtraDark) {
+
+ if (inWallpaperColors.getColors().size() == 0) {
+ return false;
+ }
+ // Tonal is not really a sort, it takes a color from the extracted
+ // palette and finds a best fit amongst a collection of pre-defined
+ // palettes. The best fit is tweaked to be closer to the source color
+ // and replaces the original palette
+
+ // First find the most representative color in the image
+ populationSort(inWallpaperColors);
+ // Calculate total
+ int total = 0;
+ for (Pair<Color, Integer> weightedColor : inWallpaperColors.getColors()) {
+ total += weightedColor.second;
+ }
+
+ // Get bright colors that occur often enough in this image
+ Pair<Color, Integer> bestColor = null;
+ float[] hsl = new float[3];
+ for (Pair<Color, Integer> weightedColor : inWallpaperColors.getColors()) {
+ float colorOccurrence = weightedColor.second / (float) total;
+ if (colorOccurrence < MIN_COLOR_OCCURRENCE) {
+ break;
+ }
+
+ int colorValue = weightedColor.first.toArgb();
+ ColorUtils.RGBToHSL(Color.red(colorValue), Color.green(colorValue),
+ Color.blue(colorValue), hsl);
+
+ // Stop when we find a color that meets our criteria
+ if (!isBlacklisted(hsl)) {
+ bestColor = weightedColor;
+ break;
+ }
+ }
+
+ // Fail if not found
+ if (bestColor == null) {
+ return false;
+ }
+
+ int colorValue = bestColor.first.toArgb();
+ ColorUtils.RGBToHSL(Color.red(colorValue), Color.green(colorValue), Color.blue(colorValue),
+ hsl);
+
+ // The Android HSL definition requires the hue to go from 0 to 360 but
+ // the Material Tonal Palette defines hues from 0 to 1.
+ hsl[0] /= 360f;
+
+ // Find the palette that contains the closest color
+ TonalPalette palette = findTonalPalette(hsl[0]);
+ if (palette == null) {
+ Log.w(TAG, "Could not find a tonal palette!");
+ return false;
+ }
+
+ // Figure out what's the main color index in the optimal palette
+ int fitIndex = bestFit(palette, hsl[0], hsl[1], hsl[2]);
+ if (fitIndex == -1) {
+ Log.w(TAG, "Could not find best fit!");
+ return false;
+ }
+
+ // Generate the 10 colors palette by offsetting each one of them
+ float[] h = fit(palette.h, hsl[0], fitIndex,
+ Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY);
+ float[] s = fit(palette.s, hsl[1], fitIndex, 0.0f, 1.0f);
+ float[] l = fit(palette.l, hsl[2], fitIndex, 0.0f, 1.0f);
+
+ final int textInversionIndex = h.length - 3;
+ if (DEBUG) {
+ StringBuilder builder = new StringBuilder("Tonal Palette - index: " + fitIndex +
+ ". Main color: " + Integer.toHexString(getColorInt(fitIndex, h, s, l)) +
+ "\nColors: ");
+
+ for (int i=0; i < h.length; i++) {
+ builder.append(Integer.toHexString(getColorInt(i, h, s, l)));
+ if (i < h.length - 1) {
+ builder.append(", ");
+ }
+ }
+ Log.d(TAG, builder.toString());
+ }
+
+ // Normal colors:
+ // best fit + a 2 colors offset
+ int primaryIndex = fitIndex;
+ int secondaryIndex = primaryIndex + (primaryIndex >= 2 ? -2 : 2);
+ outColorsNormal.setMainColor(getColorInt(primaryIndex, h, s, l));
+ outColorsNormal.setSecondaryColor(getColorInt(secondaryIndex, h, s, l));
+
+ // Dark colors:
+ // Stops at 4th color, only lighter if dark text is supported
+ if (fitIndex < 2) {
+ primaryIndex = 0;
+ } else if (fitIndex < textInversionIndex) {
+ primaryIndex = Math.min(fitIndex, 3);
+ } else {
+ primaryIndex = h.length - 1;
+ }
+ secondaryIndex = primaryIndex + (primaryIndex >= 2 ? -2 : 2);
+ outColorsDark.setMainColor(getColorInt(primaryIndex, h, s, l));
+ outColorsDark.setSecondaryColor(getColorInt(secondaryIndex, h, s, l));
+
+ // Extra Dark:
+ // Stay close to dark colors until dark text is supported
+ if (fitIndex < 2) {
+ primaryIndex = 0;
+ } else if (fitIndex < textInversionIndex) {
+ primaryIndex = 2;
+ } else {
+ primaryIndex = h.length - 1;
+ }
+ secondaryIndex = primaryIndex + (primaryIndex >= 2 ? -2 : 2);
+ outColorsExtraDark.setMainColor(getColorInt(primaryIndex, h, s, l));
+ outColorsExtraDark.setSecondaryColor(getColorInt(secondaryIndex, h, s, l));
+
+ final boolean supportsDarkText = fitIndex >= textInversionIndex;
+ outColorsNormal.setSupportsDarkText(supportsDarkText);
+ outColorsDark.setSupportsDarkText(supportsDarkText);
+ outColorsExtraDark.setSupportsDarkText(supportsDarkText);
+
+ if (DEBUG) {
+ Log.d(TAG, "Gradients: \n\tNormal " + outColorsNormal + "\n\tDark " + outColorsDark
+ + "\n\tExtra dark: " + outColorsExtraDark);
+ }
+
+ return true;
+ }
+
+ private int getColorInt(int fitIndex, float[] h, float[] s, float[] l) {
+ mTmpHSL[0] = fract(h[fitIndex]) * 360.0f;
+ mTmpHSL[1] = s[fitIndex];
+ mTmpHSL[2] = l[fitIndex];
+ return ColorUtils.HSLToColor(mTmpHSL);
+ }
+
+ /**
+ * Checks if a given color exists in the blacklist
+ * @param hsl float array with 3 components (H 0..360, S 0..1 and L 0..1)
+ * @return true if color should be avoided
+ */
+ private boolean isBlacklisted(float[] hsl) {
+ for (ColorRange badRange: BLACKLISTED_COLORS) {
+ if (badRange.containsColor(hsl[0], hsl[1], hsl[2])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static void populationSort(@NonNull WallpaperColors wallpaperColors) {
+ wallpaperColors.getColors().sort((a, b) -> b.second - a.second);
+ }
+
+ /**
+ * Offsets all colors by a delta, clamping values that go beyond what's
+ * supported on the color space.
+ * @param data what you want to fit
+ * @param v how big should be the offset
+ * @param index which index to calculate the delta against
+ * @param min minimum accepted value (clamp)
+ * @param max maximum accepted value (clamp)
+ * @return new shifted palette
+ */
+ private static float[] fit(float[] data, float v, int index, float min, float max) {
+ float[] fitData = new float[data.length];
+ float delta = v - data[index];
+
+ for (int i = 0; i < data.length; i++) {
+ fitData[i] = MathUtils.constrain(data[i] + delta, min, max);
+ }
+
+ return fitData;
+ }
+
+ /**
+ * Finds the closest color in a palette, given another HSL color
+ *
+ * @param palette where to search
+ * @param h hue
+ * @param s saturation
+ * @param l lightness
+ * @return closest index or -1 if palette is empty.
+ */
+ private static int bestFit(@NonNull TonalPalette palette, float h, float s, float l) {
+ int minErrorIndex = -1;
+ float minError = Float.POSITIVE_INFINITY;
+
+ for (int i = 0; i < palette.h.length; i++) {
+ float error =
+ FIT_WEIGHT_H * Math.abs(h - palette.h[i])
+ + FIT_WEIGHT_S * Math.abs(s - palette.s[i])
+ + FIT_WEIGHT_L * Math.abs(l - palette.l[i]);
+ if (error < minError) {
+ minError = error;
+ minErrorIndex = i;
+ }
+ }
+
+ return minErrorIndex;
+ }
+
+ @Nullable
+ private static TonalPalette findTonalPalette(float h) {
+ TonalPalette best = null;
+ float error = Float.POSITIVE_INFINITY;
+
+ for (TonalPalette candidate : TONAL_PALETTES) {
+ if (h >= candidate.minHue && h <= candidate.maxHue) {
+ best = candidate;
+ break;
+ }
+
+ if (candidate.maxHue > 1.0f && h >= 0.0f && h <= fract(candidate.maxHue)) {
+ best = candidate;
+ break;
+ }
+
+ if (candidate.minHue < 0.0f && h >= fract(candidate.minHue) && h <= 1.0f) {
+ best = candidate;
+ break;
+ }
+
+ if (h <= candidate.minHue && candidate.minHue - h < error) {
+ best = candidate;
+ error = candidate.minHue - h;
+ } else if (h >= candidate.maxHue && h - candidate.maxHue < error) {
+ best = candidate;
+ error = h - candidate.maxHue;
+ } else if (candidate.maxHue > 1.0f && h >= fract(candidate.maxHue)
+ && h - fract(candidate.maxHue) < error) {
+ best = candidate;
+ error = h - fract(candidate.maxHue);
+ } else if (candidate.minHue < 0.0f && h <= fract(candidate.minHue)
+ && fract(candidate.minHue) - h < error) {
+ best = candidate;
+ error = fract(candidate.minHue) - h;
+ }
+ }
+
+ return best;
+ }
+
+ private static float fract(float v) {
+ return v - (float) Math.floor(v);
+ }
+
+ static class TonalPalette {
+ final float[] h;
+ final float[] s;
+ final float[] l;
+ final float minHue;
+ final float maxHue;
+
+ TonalPalette(float[] h, float[] s, float[] l) {
+ this.h = h;
+ this.s = s;
+ this.l = l;
+
+ float minHue = Float.POSITIVE_INFINITY;
+ float maxHue = Float.NEGATIVE_INFINITY;
+
+ for (float v : h) {
+ minHue = Math.min(v, minHue);
+ maxHue = Math.max(v, maxHue);
+ }
+
+ this.minHue = minHue;
+ this.maxHue = maxHue;
+ }
+ }
+
+ // Data definition of Material Design tonal palettes
+ // When the sort type is set to TONAL, these palettes are used to find
+ // a best fit. Each palette is defined as 22 HSL colors
+ private static final TonalPalette[] TONAL_PALETTES = {
+ new TonalPalette(
+ new float[]{0.991f, 0.9833333333333333f, 0f, 0f, 0f, 0.01134380453752181f,
+ 0.015625000000000003f, 0.024193548387096798f, 0.027397260273972573f,
+ 0.017543859649122865f},
+ new float[]{1f, 1f, 1f, 1f, 0.8434782608695652f, 1f, 1f, 1f, 1f, 1f},
+ new float[]{0.2f, 0.27450980392156865f, 0.34901960784313724f,
+ 0.4235294117647059f, 0.5490196078431373f, 0.6254901960784314f,
+ 0.6862745098039216f, 0.7568627450980392f, 0.8568627450980393f,
+ 0.9254901960784314f}
+ ),
+ new TonalPalette(
+ new float[]{0.6385767790262171f, 0.6301169590643275f, 0.6223958333333334f,
+ 0.6151079136690647f, 0.6065400843881856f, 0.5986964618249534f,
+ 0.5910746812386157f, 0.5833333333333334f, 0.5748031496062993f,
+ 0.5582010582010583f},
+ new float[]{1f, 1f, 0.9014084507042253f, 0.8128654970760234f,
+ 0.7979797979797981f, 0.7816593886462883f, 0.778723404255319f,
+ 1f, 1f, 1f},
+ new float[]{0.17450980392156862f, 0.2235294117647059f, 0.2784313725490196f,
+ 0.3352941176470588f, 0.388235294117647f, 0.44901960784313727f,
+ 0.5392156862745098f, 0.6509803921568628f, 0.7509803921568627f,
+ 0.8764705882352941f}
+ ),
+ new TonalPalette(
+ new float[]{0.5669934640522876f, 0.5748031496062993f,
+ 0.5595238095238095f, 0.5473118279569893f, 0.5393258426966292f,
+ 0.5315955766192734f, 0.524031007751938f, 0.5154711673699016f,
+ 0.508080808080808f, 0.5f},
+ new float[]{1f, 1f, 1f, 1f, 1f, 1f, 0.8847736625514403f, 1f, 1f, 1f},
+ new float[]{0.2f, 0.24901960784313726f, 0.27450980392156865f,
+ 0.30392156862745096f, 0.34901960784313724f, 0.4137254901960784f,
+ 0.47647058823529415f, 0.5352941176470588f, 0.6764705882352942f, 0.8f}
+ ),
+ new TonalPalette(
+ new float[]{0.5082304526748972f, 0.5069444444444444f, 0.5f, 0.5f,
+ 0.5f, 0.48724954462659376f, 0.4800347222222222f,
+ 0.4755134281200632f, 0.4724409448818897f, 0.4671052631578947f},
+ new float[]{1f, 0.8888888888888887f, 0.9242424242424242f, 1f, 1f,
+ 0.8133333333333332f, 0.7868852459016393f, 1f, 1f, 1f},
+ new float[]{0.1588235294117647f, 0.21176470588235297f,
+ 0.25882352941176473f, 0.3f, 0.34901960784313724f,
+ 0.44117647058823534f, 0.5215686274509804f, 0.5862745098039216f,
+ 0.7509803921568627f, 0.8509803921568627f}
+ ),
+ new TonalPalette(
+ new float[]{0.3333333333333333f, 0.3333333333333333f,
+ 0.34006734006734f, 0.34006734006734f, 0.34006734006734f,
+ 0.34259259259259256f, 0.3475783475783476f, 0.34767025089605735f,
+ 0.3467741935483871f, 0.3703703703703704f},
+ new float[]{0.6703296703296703f, 0.728813559322034f,
+ 0.5657142857142856f, 0.5076923076923077f, 0.3944223107569721f,
+ 0.6206896551724138f, 0.8931297709923666f, 1f, 1f, 1f},
+ new float[]{0.1784313725490196f, 0.23137254901960785f,
+ 0.3431372549019608f, 0.38235294117647056f, 0.49215686274509807f,
+ 0.6588235294117647f, 0.7431372549019608f, 0.8176470588235294f,
+ 0.8784313725490196f, 0.9294117647058824f}
+ ),
+ new TonalPalette(
+ new float[]{0.162280701754386f, 0.15032679738562088f,
+ 0.15879265091863518f, 0.16236559139784948f, 0.17443868739205526f,
+ 0.17824074074074076f, 0.18674698795180725f,
+ 0.18692449355432778f, 0.1946778711484594f, 0.18604651162790695f},
+ new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f},
+ new float[]{0.14901960784313725f, 0.2f, 0.24901960784313726f,
+ 0.30392156862745096f, 0.3784313725490196f, 0.4235294117647059f,
+ 0.48823529411764705f, 0.6450980392156863f, 0.7666666666666666f,
+ 0.8313725490196078f}
+ ),
+ new TonalPalette(
+ new float[]{0.10619469026548674f, 0.11924686192468618f,
+ 0.13046448087431692f, 0.14248366013071895f, 0.1506024096385542f,
+ 0.16220238095238093f, 0.16666666666666666f,
+ 0.16666666666666666f, 0.162280701754386f, 0.15686274509803924f},
+ new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f},
+ new float[]{0.44313725490196076f, 0.46862745098039216f,
+ 0.47843137254901963f, 0.5f, 0.5117647058823529f,
+ 0.5607843137254902f, 0.6509803921568628f, 0.7509803921568627f,
+ 0.8509803921568627f, 0.9f}
+ ),
+ new TonalPalette(
+ new float[]{0.03561253561253561f, 0.05098039215686275f,
+ 0.07516339869281045f, 0.09477124183006536f, 0.1150326797385621f,
+ 0.134640522875817f, 0.14640522875816991f, 0.1582397003745319f,
+ 0.15773809523809523f, 0.15359477124183002f},
+ new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f},
+ new float[]{0.4588235294117647f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.6509803921568628f, 0.7803921568627451f, 0.9f}
+ ),
+ new TonalPalette(
+ new float[]{0.9596491228070175f, 0.9593837535014005f,
+ 0.9514767932489452f, 0.943859649122807f, 0.9396825396825397f,
+ 0.9395424836601307f, 0.9393939393939394f, 0.9362745098039216f,
+ 0.9754098360655739f, 0.9824561403508771f},
+ new float[]{0.84070796460177f, 0.8206896551724138f,
+ 0.7979797979797981f, 0.7661290322580644f, 0.9051724137931036f,
+ 1f, 1f, 1f, 1f, 1f},
+ new float[]{0.22156862745098038f, 0.2843137254901961f,
+ 0.388235294117647f, 0.48627450980392156f, 0.5450980392156863f,
+ 0.6f, 0.6764705882352942f, 0.8f, 0.8803921568627451f,
+ 0.9254901960784314f}
+ ),
+ new TonalPalette(
+ new float[]{0.841025641025641f, 0.8333333333333334f,
+ 0.8285256410256411f, 0.821522309711286f, 0.8083333333333333f,
+ 0.8046594982078853f, 0.8005822416302766f, 0.7842377260981912f,
+ 0.7771084337349398f, 0.7747747747747749f},
+ new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f,
+ 0.737142857142857f, 0.6434108527131781f, 0.46835443037974644f},
+ new float[]{0.12745098039215685f, 0.15490196078431373f,
+ 0.20392156862745098f, 0.24901960784313726f, 0.3137254901960784f,
+ 0.36470588235294116f, 0.44901960784313727f,
+ 0.6568627450980392f, 0.7470588235294118f, 0.8450980392156863f}
+ ),
+ new TonalPalette(
+ new float[]{0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f},
+ new float[]{0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f},
+ new float[]{0.14901960784313725f, 0.2f, 0.2980392156862745f, 0.4f,
+ 0.4980392156862745f, 0.6196078431372549f, 0.7176470588235294f,
+ 0.8196078431372549f, 0.9176470588235294f, 0.9490196078431372f}
+ ),
+ new TonalPalette(
+ new float[]{0.955952380952381f, 0.9681069958847737f,
+ 0.9760479041916167f, 0.9873563218390804f, 0f, 0f,
+ 0.009057971014492771f, 0.026748971193415648f,
+ 0.041666666666666616f, 0.05303030303030304f},
+ new float[]{1f, 0.8350515463917526f, 0.6929460580912863f,
+ 0.6387665198237885f, 0.6914893617021276f, 0.7583892617449666f,
+ 0.8070175438596495f, 0.9310344827586209f, 1f, 1f},
+ new float[]{0.27450980392156865f, 0.3803921568627451f,
+ 0.4725490196078432f, 0.5549019607843138f, 0.6313725490196078f,
+ 0.707843137254902f, 0.7764705882352941f, 0.8294117647058823f,
+ 0.9058823529411765f, 0.9568627450980391f}
+ ),
+ new TonalPalette(
+ new float[]{0.7514619883040936f, 0.7679738562091503f,
+ 0.7802083333333333f, 0.7844311377245509f, 0.796875f,
+ 0.8165618448637316f, 0.8487179487179487f, 0.8582375478927203f,
+ 0.8562091503267975f, 0.8666666666666667f},
+ new float[]{1f, 1f, 0.8163265306122449f, 0.6653386454183268f,
+ 0.7547169811320753f, 0.929824561403509f, 0.9558823529411766f,
+ 0.9560439560439562f, 1f, 1f},
+ new float[]{0.2235294117647059f, 0.3f, 0.38431372549019605f,
+ 0.492156862745098f, 0.5843137254901961f, 0.6647058823529411f,
+ 0.7333333333333334f, 0.8215686274509804f, 0.9f,
+ 0.9411764705882353f}
+ ),
+ new TonalPalette(
+ new float[]{0.6666666666666666f, 0.6666666666666666f,
+ 0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f,
+ 0.6666666666666666f, 0.6666666666666666f, 0.6666666666666666f,
+ 0.6666666666666666f, 0.6666666666666666f},
+ new float[]{0.24590163934426232f, 0.17880794701986752f,
+ 0.14606741573033713f, 0.13761467889908252f, 0.14893617021276592f,
+ 0.16756756756756758f, 0.20312500000000017f,
+ 0.26086956521739135f, 0.29999999999999966f, 0.5000000000000004f},
+ new float[]{0.2392156862745098f, 0.296078431372549f,
+ 0.34901960784313724f, 0.4274509803921569f, 0.5392156862745098f,
+ 0.6372549019607843f, 0.7490196078431373f, 0.8196078431372549f,
+ 0.8823529411764706f, 0.9372549019607843f}
+ ),
+ new TonalPalette(
+ new float[]{0.9678571428571429f, 0.9944812362030905f, 0f, 0f,
+ 0.0047348484848484815f, 0.00316455696202532f, 0f,
+ 0.9980392156862745f, 0.9814814814814816f, 0.9722222222222221f},
+ new float[]{1f, 0.7023255813953488f, 0.6638655462184874f,
+ 0.6521739130434782f, 0.7719298245614035f, 0.8315789473684211f,
+ 0.6867469879518071f, 0.7264957264957265f, 0.8181818181818182f,
+ 0.8181818181818189f},
+ new float[]{0.27450980392156865f, 0.4215686274509804f,
+ 0.4666666666666667f, 0.503921568627451f, 0.5529411764705883f,
+ 0.6274509803921569f, 0.6745098039215687f, 0.7705882352941176f,
+ 0.892156862745098f, 0.9568627450980391f}
+ ),
+ new TonalPalette(
+ new float[]{0.9052287581699346f, 0.9112021857923498f, 0.9270152505446624f,
+ 0.9343137254901961f, 0.9391534391534391f, 0.9437984496124031f,
+ 0.943661971830986f, 0.9438943894389439f, 0.9426229508196722f,
+ 0.9444444444444444f},
+ new float[]{1f, 0.8133333333333332f, 0.7927461139896375f, 0.7798165137614679f,
+ 0.7777777777777779f, 0.8190476190476191f, 0.8255813953488372f,
+ 0.8211382113821142f, 0.8133333333333336f, 0.8000000000000006f},
+ new float[]{0.2f, 0.29411764705882354f, 0.3784313725490196f,
+ 0.42745098039215684f, 0.4764705882352941f, 0.5882352941176471f,
+ 0.6627450980392157f, 0.7588235294117647f, 0.8529411764705882f,
+ 0.9411764705882353f}
+ ),
+ new TonalPalette(
+ new float[]{0.6884057971014492f, 0.6974789915966387f, 0.7079889807162534f,
+ 0.7154471544715447f, 0.7217741935483872f, 0.7274143302180687f,
+ 0.7272727272727273f, 0.7258064516129031f, 0.7252252252252251f,
+ 0.7333333333333333f},
+ new float[]{0.8214285714285715f, 0.6878612716763006f, 0.6080402010050251f,
+ 0.5774647887323943f, 0.5391304347826086f, 0.46724890829694316f,
+ 0.4680851063829788f, 0.462686567164179f, 0.45679012345678977f,
+ 0.4545454545454551f},
+ new float[]{0.2196078431372549f, 0.33921568627450976f, 0.39019607843137255f,
+ 0.4176470588235294f, 0.45098039215686275f,
+ 0.5509803921568628f, 0.6313725490196078f, 0.7372549019607844f,
+ 0.8411764705882353f, 0.9352941176470588f}
+ ),
+ new TonalPalette(
+ new float[]{0.6470588235294118f, 0.6516666666666667f, 0.6464174454828661f,
+ 0.6441441441441442f, 0.6432748538011696f, 0.6416666666666667f,
+ 0.6402439024390243f, 0.6412429378531074f, 0.6435185185185186f,
+ 0.6428571428571429f},
+ new float[]{0.8095238095238095f, 0.6578947368421053f, 0.5721925133689839f,
+ 0.5362318840579711f, 0.5f, 0.4424778761061947f, 0.44086021505376327f,
+ 0.44360902255639095f,
+ 0.4499999999999997f, 0.4375000000000006f},
+ new float[]{0.16470588235294117f, 0.2980392156862745f, 0.36666666666666664f,
+ 0.40588235294117647f, 0.44705882352941173f,
+ 0.5568627450980392f, 0.6352941176470588f, 0.7392156862745098f,
+ 0.8431372549019608f, 0.9372549019607843f}
+ ),
+ new TonalPalette(
+ new float[]{0.46732026143790845f, 0.4718614718614719f, 0.4793650793650794f,
+ 0.48071625344352614f, 0.4829683698296837f, 0.484375f,
+ 0.4841269841269842f, 0.48444444444444457f, 0.48518518518518516f,
+ 0.4907407407407408f},
+ new float[]{1f, 1f, 1f, 1f, 1f, 0.6274509803921569f, 0.41832669322709176f,
+ 0.41899441340782106f, 0.4128440366972478f,
+ 0.4090909090909088f},
+ new float[]{0.1f, 0.15098039215686274f, 0.20588235294117646f,
+ 0.2372549019607843f, 0.26862745098039215f, 0.4f, 0.5078431372549019f,
+ 0.6490196078431372f, 0.7862745098039216f, 0.9137254901960784f}
+ ),
+ new TonalPalette(
+ new float[]{0.5444444444444444f, 0.5555555555555556f, 0.5555555555555556f,
+ 0.553763440860215f, 0.5526315789473684f, 0.5555555555555556f,
+ 0.5555555555555555f, 0.5555555555555556f, 0.5512820512820514f,
+ 0.5666666666666667f},
+ new float[]{0.24590163934426232f, 0.19148936170212766f, 0.1791044776119403f,
+ 0.18343195266272191f, 0.18446601941747576f,
+ 0.1538461538461539f, 0.15625000000000003f, 0.15328467153284678f,
+ 0.15662650602409653f, 0.151515151515151f},
+ new float[]{0.1196078431372549f, 0.1843137254901961f, 0.2627450980392157f,
+ 0.33137254901960783f, 0.403921568627451f, 0.5411764705882354f,
+ 0.6235294117647059f, 0.7313725490196079f, 0.8372549019607843f,
+ 0.9352941176470588f}
+ ),
+ new TonalPalette(
+ new float[]{0.022222222222222223f, 0.02469135802469136f, 0.031249999999999997f,
+ 0.03947368421052631f, 0.04166666666666668f,
+ 0.043650793650793655f, 0.04411764705882352f, 0.04166666666666652f,
+ 0.04444444444444459f, 0.05555555555555529f},
+ new float[]{0.33333333333333337f, 0.2783505154639175f, 0.2580645161290323f,
+ 0.25675675675675674f, 0.2528735632183908f, 0.17500000000000002f,
+ 0.15315315315315312f, 0.15189873417721522f,
+ 0.15789473684210534f, 0.15789473684210542f},
+ new float[]{0.08823529411764705f, 0.19019607843137254f, 0.2431372549019608f,
+ 0.2901960784313725f, 0.3411764705882353f, 0.47058823529411764f,
+ 0.5647058823529412f, 0.6901960784313725f, 0.8137254901960784f,
+ 0.9254901960784314f}
+ ),
+ new TonalPalette(
+ new float[]{0.050884955752212385f, 0.07254901960784313f, 0.0934640522875817f,
+ 0.10457516339869281f, 0.11699346405228758f,
+ 0.1255813953488372f, 0.1268939393939394f, 0.12533333333333332f,
+ 0.12500000000000003f, 0.12777777777777777f},
+ new float[]{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f},
+ new float[]{0.44313725490196076f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5784313725490196f,
+ 0.6549019607843137f, 0.7549019607843137f, 0.8509803921568627f,
+ 0.9411764705882353f}
+ )
+ };
+
+ @SuppressWarnings("WeakerAccess")
+ @VisibleForTesting
+ static final ColorRange[] BLACKLISTED_COLORS = new ColorRange[] {
+
+ // Red
+ new ColorRange(
+ new Range<>(0f, 20f) /* H */,
+ new Range<>(0.7f, 1f) /* S */,
+ new Range<>(0.21f, 0.79f)) /* L */,
+ new ColorRange(
+ new Range<>(0f, 20f),
+ new Range<>(0.3f, 0.7f),
+ new Range<>(0.355f, 0.653f)),
+
+ // Red Orange
+ new ColorRange(
+ new Range<>(20f, 40f),
+ new Range<>(0.7f, 1f),
+ new Range<>(0.28f, 0.643f)),
+ new ColorRange(
+ new Range<>(20f, 40f),
+ new Range<>(0.3f, 0.7f),
+ new Range<>(0.414f, 0.561f)),
+ new ColorRange(
+ new Range<>(20f, 40f),
+ new Range<>(0f, 3f),
+ new Range<>(0.343f, 0.584f)),
+
+ // Orange
+ new ColorRange(
+ new Range<>(40f, 60f),
+ new Range<>(0.7f, 1f),
+ new Range<>(0.173f, 0.349f)),
+ new ColorRange(
+ new Range<>(40f, 60f),
+ new Range<>(0.3f, 0.7f),
+ new Range<>(0.233f, 0.427f)),
+ new ColorRange(
+ new Range<>(40f, 60f),
+ new Range<>(0f, 0.3f),
+ new Range<>(0.231f, 0.484f)),
+
+ // Yellow 60
+ new ColorRange(
+ new Range<>(60f, 80f),
+ new Range<>(0.7f, 1f),
+ new Range<>(0.488f, 0.737f)),
+ new ColorRange(
+ new Range<>(60f, 80f),
+ new Range<>(0.3f, 0.7f),
+ new Range<>(0.673f, 0.837f)),
+
+ // Yellow Green 80
+ new ColorRange(
+ new Range<>(80f, 100f),
+ new Range<>(0.7f, 1f),
+ new Range<>(0.469f, 0.61f)),
+
+ // Yellow green 100
+ new ColorRange(
+ new Range<>(100f, 120f),
+ new Range<>(0.7f, 1f),
+ new Range<>(0.388f, 0.612f)),
+ new ColorRange(
+ new Range<>(100f, 120f),
+ new Range<>(0.3f, 0.7f),
+ new Range<>(0.424f, 0.541f)),
+
+ // Green
+ new ColorRange(
+ new Range<>(120f, 140f),
+ new Range<>(0.7f, 1f),
+ new Range<>(0.375f, 0.52f)),
+ new ColorRange(
+ new Range<>(120f, 140f),
+ new Range<>(0.3f, 0.7f),
+ new Range<>(0.435f, 0.524f)),
+
+ // Green Blue 140
+ new ColorRange(
+ new Range<>(140f, 160f),
+ new Range<>(0.7f, 1f),
+ new Range<>(0.496f, 0.641f)),
+
+ // Seafoam
+ new ColorRange(
+ new Range<>(160f, 180f),
+ new Range<>(0.7f, 1f),
+ new Range<>(0.496f, 0.567f)),
+
+ // Cyan
+ new ColorRange(
+ new Range<>(180f, 200f),
+ new Range<>(0.7f, 1f),
+ new Range<>(0.52f, 0.729f)),
+
+ // Blue
+ new ColorRange(
+ new Range<>(220f, 240f),
+ new Range<>(0.7f, 1f),
+ new Range<>(0.396f, 0.571f)),
+ new ColorRange(
+ new Range<>(220f, 240f),
+ new Range<>(0.3f, 0.7f),
+ new Range<>(0.425f, 0.551f)),
+
+ // Blue Purple 240
+ new ColorRange(
+ new Range<>(240f, 260f),
+ new Range<>(0.7f, 1f),
+ new Range<>(0.418f, 0.639f)),
+ new ColorRange(
+ new Range<>(220f, 240f),
+ new Range<>(0.3f, 0.7f),
+ new Range<>(0.441f, 0.576f)),
+
+ // Blue Purple 260
+ new ColorRange(
+ new Range<>(260f, 280f),
+ new Range<>(0.3f, 1f), // Bigger range
+ new Range<>(0.461f, 0.553f)),
+
+ // Fuchsia
+ new ColorRange(
+ new Range<>(300f, 320f),
+ new Range<>(0.7f, 1f),
+ new Range<>(0.484f, 0.588f)),
+ new ColorRange(
+ new Range<>(300f, 320f),
+ new Range<>(0.3f, 0.7f),
+ new Range<>(0.48f, 0.592f)),
+
+ // Pink
+ new ColorRange(
+ new Range<>(320f, 340f),
+ new Range<>(0.7f, 1f),
+ new Range<>(0.466f, 0.629f)),
+
+ // Soft red
+ new ColorRange(
+ new Range<>(340f, 360f),
+ new Range<>(0.7f, 1f),
+ new Range<>(0.437f, 0.596f))
+ };
+
+ /**
+ * Representation of an HSL color range.
+ * <ul>
+ * <li>hsl[0] is Hue [0 .. 360)</li>
+ * <li>hsl[1] is Saturation [0...1]</li>
+ * <li>hsl[2] is Lightness [0...1]</li>
+ * </ul>
+ */
+ @VisibleForTesting
+ static class ColorRange {
+ private Range<Float> mHue;
+ private Range<Float> mSaturation;
+ private Range<Float> mLightness;
+
+ ColorRange(Range<Float> hue, Range<Float> saturation, Range<Float> lightness) {
+ mHue = hue;
+ mSaturation = saturation;
+ mLightness = lightness;
+ }
+
+ boolean containsColor(float h, float s, float l) {
+ if (!mHue.contains(h)) {
+ return false;
+ } else if (!mSaturation.contains(s)) {
+ return false;
+ } else if (!mLightness.contains(l)) {
+ return false;
+ }
+ return true;
+ }
+
+ @VisibleForTesting
+ float[] getCenter() {
+ return new float[] {
+ mHue.getLower() + (mHue.getUpper() - mHue.getLower()) / 2f,
+ mSaturation.getLower() + (mSaturation.getUpper() - mSaturation.getLower()) / 2f,
+ mLightness.getLower() + (mLightness.getUpper() - mLightness.getLower()) / 2f
+ };
+ }
+
+ @Override
+ public String toString() {
+ return String.format("H: %s, S: %s, L %s", mHue, mSaturation, mLightness);
+ }
+ }
+}
diff --git a/packages/SystemUI/colorextraction/tests/Android.mk b/packages/SystemUI/colorextraction/tests/Android.mk
new file mode 100644
index 0000000..a517e99
--- /dev/null
+++ b/packages/SystemUI/colorextraction/tests/Android.mk
@@ -0,0 +1,41 @@
+# 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_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_PACKAGE_NAME := ColorExtractorTests
+LOCAL_CERTIFICATE := platform
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_JAVA_LIBRARIES := android-support-test
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ mockito-target-minus-junit4 \
+ espresso-core \
+ truth-prebuilt \
+ legacy-android-test \
+ android-support-annotations \
+ android-support-v7-palette \
+ android-support-v4
+
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/packages/SystemUI/colorextraction/tests/AndroidManifest.xml b/packages/SystemUI/colorextraction/tests/AndroidManifest.xml
new file mode 100644
index 0000000..375c7f2
--- /dev/null
+++ b/packages/SystemUI/colorextraction/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.colorextraction.tests">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.systemui.colorextraction.tests"
+ android:label="Tests for ColorExtractor">
+ </instrumentation>
+</manifest>
diff --git a/packages/SystemUI/colorextraction/tests/AndroidTest.xml b/packages/SystemUI/colorextraction/tests/AndroidTest.xml
new file mode 100644
index 0000000..ee9ace4
--- /dev/null
+++ b/packages/SystemUI/colorextraction/tests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Tests for ColorExtractor.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="ColorExtractorTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="SystemUITests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.systemui.tests" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/ColorExtractorTest.java b/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/ColorExtractorTest.java
new file mode 100644
index 0000000..fd698d0
--- /dev/null
+++ b/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/ColorExtractorTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.google.android.colorextraction;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.WallpaperColors;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Color;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.google.android.colorextraction.ColorExtractor.GradientColors;
+import com.google.android.colorextraction.types.ExtractionType;
+import com.google.android.colorextraction.types.Tonal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests tonal palette generation.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ColorExtractorTest {
+
+ Context mContext;
+
+ @Before
+ public void setup() {
+ mContext = InstrumentationRegistry.getContext();
+ }
+
+ @Test
+ public void ColorExtractor_extractWhenInitialized() {
+ ExtractionType type = mock(Tonal.class);
+ new ColorExtractor(mContext, type);
+ // 1 for lock and 1 for system
+ verify(type, times(2))
+ .extractInto(any(), any(), any(), any());
+ }
+
+ @Test
+ public void getColors_usesFallbackIfFails() {
+ ExtractionType alwaysFail =
+ (inWallpaperColors, outGradientColorsNormal, outGradientColorsDark,
+ outGradientColorsExtraDark) -> false;
+ ColorExtractor extractor = new ColorExtractor(mContext, alwaysFail);
+ GradientColors colors = extractor.getColors(WallpaperManager.FLAG_SYSTEM);
+
+ assertEquals("Should be using the fallback color.",
+ colors.getMainColor(), ColorExtractor.FALLBACK_COLOR);
+ assertEquals("Should be using the fallback color.",
+ colors.getSecondaryColor(), ColorExtractor.FALLBACK_COLOR);
+ assertFalse("Dark text support should be false.", colors.supportsDarkText());
+ }
+
+ @Test
+ public void getColors_usesExtractedColors() {
+ GradientColors colorsExpectedNormal = new GradientColors();
+ colorsExpectedNormal.setMainColor(Color.RED);
+ colorsExpectedNormal.setSecondaryColor(Color.GRAY);
+
+ GradientColors colorsExpectedDark = new GradientColors();
+ colorsExpectedNormal.setMainColor(Color.BLACK);
+ colorsExpectedNormal.setSecondaryColor(Color.BLUE);
+
+ GradientColors colorsExpectedExtraDark = new GradientColors();
+ colorsExpectedNormal.setMainColor(Color.MAGENTA);
+ colorsExpectedNormal.setSecondaryColor(Color.GREEN);
+
+ ExtractionType type =
+ (inWallpaperColors, outGradientColorsNormal, outGradientColorsDark,
+ outGradientColorsExtraDark) -> {
+ outGradientColorsNormal.set(colorsExpectedNormal);
+ outGradientColorsDark.set(colorsExpectedDark);
+ outGradientColorsExtraDark.set(colorsExpectedExtraDark);
+ // Successful extraction
+ return true;
+ };
+ ColorExtractor extractor = new ColorExtractor(mContext, type);
+
+ assertEquals("Extracted colors not being used!",
+ extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_NORMAL),
+ colorsExpectedNormal);
+ assertEquals("Extracted colors not being used!",
+ extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_DARK),
+ colorsExpectedDark);
+ assertEquals("Extracted colors not being used!",
+ extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_EXTRA_DARK),
+ colorsExpectedExtraDark);
+ }
+}
diff --git a/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/types/TonalTest.java b/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/types/TonalTest.java
new file mode 100644
index 0000000..404076c
--- /dev/null
+++ b/packages/SystemUI/colorextraction/tests/src/com/google/android/colorextraction/types/TonalTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.google.android.colorextraction.types;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.WallpaperColors;
+import android.graphics.Color;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.graphics.ColorUtils;
+import android.util.Pair;
+import android.util.Range;
+
+import com.google.android.colorextraction.ColorExtractor;
+import com.google.android.colorextraction.ColorExtractor.GradientColors;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests tonal palette generation.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TonalTest {
+
+ @Test
+ public void colorRange_containsColor() {
+ Tonal.ColorRange colorRange = new Tonal.ColorRange(new Range<>(0f, 50f),
+ new Range<>(0f, 1f), new Range<>(0f, 1f));
+ float[] hsl = new float[] {25, 0, 0};
+ assertTrue("Range " + colorRange + " doesn't contain " + Arrays.toString(hsl),
+ colorRange.containsColor(hsl[0], hsl[1], hsl[2]));
+ }
+
+ @Test
+ public void colorRange_doesntContainColor() {
+ Tonal.ColorRange colorRange = new Tonal.ColorRange(new Range<>(0f, 50f),
+ new Range<>(0f, 0.5f), new Range<>(0f, 0.5f));
+ float[] hsl = new float[] {100, 0, 0};
+ assertFalse("Range " + colorRange + " shouldn't contain " + Arrays.toString(hsl),
+ colorRange.containsColor(hsl[0], hsl[1], hsl[2]));
+ hsl = new float[] {0, 0.6f, 0};
+ assertFalse("Range " + colorRange + " shouldn't contain " + Arrays.toString(hsl),
+ colorRange.containsColor(hsl[0], hsl[1], hsl[2]));
+ hsl = new float[] {0, 0, 0.6f};
+ assertFalse("Range " + colorRange + " shouldn't contain " + Arrays.toString(hsl),
+ colorRange.containsColor(hsl[0], hsl[1], hsl[2]));
+ }
+
+ @Test
+ public void colorRange_excludeBlacklistedColor() {
+ // Creating a WallpaperColors object that contains *only* blacklisted colors
+ float[] hsl = Tonal.BLACKLISTED_COLORS[0].getCenter();
+ ArrayList<Pair<Color, Integer>> blacklistedColorList = new ArrayList<>();
+ blacklistedColorList.add(new Pair<>(Color.valueOf(ColorUtils.HSLToColor(hsl)), 1));
+ WallpaperColors colors = new WallpaperColors(blacklistedColorList);
+
+ // Make sure that palette generation will fail
+ Tonal tonal = new Tonal();
+ boolean success = tonal.extractInto(colors, new GradientColors(), new GradientColors(),
+ new GradientColors());
+ assertFalse("Cannot generate a tonal palette from blacklisted colors ", success);
+ }
+}
diff --git a/packages/SystemUI/docs/demo_mode.md b/packages/SystemUI/docs/demo_mode.md
index 258c76b..6cf7060 100644
--- a/packages/SystemUI/docs/demo_mode.md
+++ b/packages/SystemUI/docs/demo_mode.md
@@ -29,6 +29,7 @@
```battery``` | | | Control the battery display
| ```level``` | | Sets the battery level (0 - 100)
| ```plugged``` | | Sets charging state (```true```, ```false```)
+ | ```powersave``` | | Sets power save mode (```true```, ```anything else```)
```network``` | | | Control the RSSI display
| ```airplane``` | | ```show``` to show icon, any other value to hide
| ```fully``` | | Sets MCS state to fully connected (```true```, ```false```)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationListenerController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationListenerController.java
new file mode 100644
index 0000000..fac9e98
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationListenerController.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.plugins.NotificationListenerController.NotificationProvider;
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+@ProvidesInterface(action = NotificationListenerController.ACTION,
+ version = NotificationListenerController.VERSION)
+@DependsOn(target = NotificationProvider.class)
+public interface NotificationListenerController extends Plugin {
+ String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_ASSISTANT";
+ int VERSION = 1;
+
+ void onListenerConnected(NotificationProvider provider);
+
+ default boolean onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+ return false;
+ }
+ default boolean onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
+ return false;
+ }
+
+ default StatusBarNotification[] getActiveNotifications(
+ StatusBarNotification[] activeNotifications) {
+ return activeNotifications;
+ }
+
+ default RankingMap getCurrentRanking(RankingMap currentRanking) {
+ return currentRanking;
+ }
+
+ @ProvidesInterface(version = NotificationProvider.VERSION)
+ interface NotificationProvider {
+ int VERSION = 1;
+
+ // Methods to get info about current notifications
+ StatusBarNotification[] getActiveNotifications();
+ RankingMap getRankingMap();
+
+ // Methods to notify sysui of changes to notification list.
+ void addNotification(StatusBarNotification sbn);
+ void removeNotification(StatusBarNotification sbn);
+ void updateRanking();
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 9ef05c5..b4b4e19 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -112,6 +112,7 @@
public boolean dualTarget = false;
public boolean isTransient = false;
public String expandedAccessibilityClassName;
+ public SlashState slash;
public boolean copyTo(State other) {
if (other == null) throw new IllegalArgumentException();
@@ -126,7 +127,8 @@
|| !Objects.equals(other.disabledByPolicy, disabledByPolicy)
|| !Objects.equals(other.state, state)
|| !Objects.equals(other.isTransient, isTransient)
- || !Objects.equals(other.dualTarget, dualTarget);
+ || !Objects.equals(other.dualTarget, dualTarget)
+ || !Objects.equals(other.slash, slash);
other.icon = icon;
other.label = label;
other.contentDescription = contentDescription;
@@ -136,6 +138,7 @@
other.state = state;
other.dualTarget = dualTarget;
other.isTransient = isTransient;
+ other.slash = slash != null ? slash.copy() : null;
return changed;
}
@@ -155,6 +158,7 @@
sb.append(",dualTarget=").append(dualTarget);
sb.append(",isTransient=").append(isTransient);
sb.append(",state=").append(state);
+ sb.append(",slash=\"").append(slash).append("\"");
return sb.append(']');
}
@@ -198,14 +202,20 @@
public static final int VERSION = 1;
public boolean activityIn;
public boolean activityOut;
+ public boolean isOverlayIconWide;
+ public int overlayIconId;
@Override
public boolean copyTo(State other) {
final SignalState o = (SignalState) other;
final boolean changed = o.activityIn != activityIn
- || o.activityOut != activityOut;
+ || o.activityOut != activityOut
+ || o.isOverlayIconWide != isOverlayIconWide
+ || o.overlayIconId != overlayIconId;
o.activityIn = activityIn;
o.activityOut = activityOut;
+ o.isOverlayIconWide = isOverlayIconWide;
+ o.overlayIconId = overlayIconId;
return super.copyTo(other) || changed;
}
@@ -246,4 +256,34 @@
}
}
+ @ProvidesInterface(version = SlashState.VERSION)
+ public static class SlashState {
+ public static final int VERSION = 2;
+
+ public boolean isSlashed;
+ public float rotation;
+
+ @Override
+ public String toString() {
+ return "isSlashed=" + isSlashed + ",rotation=" + rotation;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null) return false;
+ try {
+ return (((SlashState) o).rotation == rotation)
+ && (((SlashState) o).isSlashed == isSlashed);
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ public SlashState copy() {
+ SlashState state = new SlashState();
+ state.rotation = rotation;
+ state.isSlashed = isSlashed;
+ return state;
+ }
+ }
}
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_access_alarms_big.xml b/packages/SystemUI/res-keyguard/drawable/ic_access_alarms_big.xml
index 84ccb7c..fd385fc 100644
--- a/packages/SystemUI/res-keyguard/drawable/ic_access_alarms_big.xml
+++ b/packages/SystemUI/res-keyguard/drawable/ic_access_alarms_big.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -18,8 +18,7 @@
android:height="18dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
-
<path
- android:fillColor="@color/clock_gray"
- android:pathData="M22.0,5.7l-4.6,-3.9l-1.3,1.5l4.6,3.9L22.0,5.7zM7.9,3.4L6.6,1.9L2.0,5.7l1.3,1.5L7.9,3.4zM12.5,8.0L11.0,8.0l0.0,6.0l4.7,2.9l0.8,-1.2l-4.0,-2.4L12.5,8.0zM12.0,4.0c-5.0,0.0 -9.0,4.0 -9.0,9.0c0.0,5.0 4.0,9.0 9.0,9.0s9.0,-4.0 9.0,-9.0C21.0,8.0 17.0,4.0 12.0,4.0zM12.0,20.0c-3.9,0.0 -7.0,-3.1 -7.0,-7.0c0.0,-3.9 3.1,-7.0 7.0,-7.0c3.9,0.0 7.0,3.1 7.0,7.0C19.0,16.9 15.9,20.0 12.0,20.0z"/>
+ android:fillColor="?attr/bgProtectSecondaryTextColor"
+ android:pathData="M21.35,6.49c-0.35,0.42 -0.98,0.47 -1.4,0.12l-3.07,-2.57a1,1 0,1 1,1.29 -1.53l3.07,2.57c0.42,0.35 0.47,0.98 0.11,1.41zM7.24,2.63a1,1 0,0 0,-1.41 -0.13L2.77,5.07A0.996,0.996 0,1 0,4.05 6.6l3.06,-2.57c0.43,-0.35 0.48,-0.98 0.13,-1.4zM11.75,8c-0.41,0 -0.75,0.34 -0.75,0.75v4.68c0,0.35 0.18,0.68 0.49,0.86l3.65,2.19c0.34,0.2 0.78,0.1 0.98,-0.24a0.71,0.71 0,0 0,-0.25 -0.99l-3.37,-2v-4.5c0,-0.41 -0.34,-0.75 -0.75,-0.75zM12,5.9c-3.91,0 -7.1,3.18 -7.1,7.1s3.19,7.1 7.1,7.1 7.1,-3.18 7.1,-7.1 -3.19,-7.1 -7.1,-7.1M12,4a9,9 0,1 1,-0.001 18.001A9,9 0,0 1,12 4z" />
</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/pin_divider.xml b/packages/SystemUI/res-keyguard/drawable/pin_divider.xml
new file mode 100644
index 0000000..39104b5
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/pin_divider.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/pin_divider_color" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_esim_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_esim_area.xml
new file mode 100644
index 0000000..8cbd94b
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_esim_area.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This contains disable esim buttonas shared by sim_pin/sim_puk screens -->
+<com.android.keyguard.KeyguardEsimArea
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/keyguard_disable_esim"
+ android:visibility="gone"
+ android:text="@string/disable_carrier_button_text"
+ style="?android:attr/buttonBarButtonStyle"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/kg_status_line_font_size"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAllCaps="@bool/kg_use_all_caps" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 29c93d5..26a179a 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -54,6 +54,7 @@
android:textStyle="normal"
android:inputType="textPassword"
android:textSize="16sp"
+ android:textColor="?attr/bgProtectTextColor"
android:textAppearance="?android:attr/textAppearanceMedium"
android:imeOptions="flagForceAscii|actionDone"
android:maxLength="500"
@@ -67,6 +68,7 @@
android:contentDescription="@string/accessibility_ime_switch_button"
android:clickable="true"
android:padding="8dip"
+ android:tint="@color/background_protected"
android:layout_gravity="end|center_vertical"
android:background="?android:attr/selectableItemBackground"
android:visibility="gone"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index e75f3c15..9a97162 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -54,6 +54,7 @@
android:layout_centerHorizontal="true"
android:layout_marginRight="72dp"
androidprv:scaledTextSize="28"
+ android:textColor="?attr/bgProtectTextColor"
android:contentDescription="@string/keyguard_accessibility_pin_area"
/>
<ImageButton
@@ -65,19 +66,20 @@
android:clickable="true"
android:paddingTop="8dip"
android:paddingBottom="8dip"
- android:paddingRight="8dp"
+ android:paddingRight="0dp"
android:paddingLeft="24dp"
android:background="@drawable/ripple_drawable"
android:contentDescription="@string/keyboardview_keycode_delete"
android:layout_alignEnd="@+id/pinEntry"
android:layout_alignParentRight="true"
+ android:tint="@color/pin_delete_color"
/>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentBottom="true"
- android:background="#28FFFFFF"
+ android:background="@drawable/pin_divider"
/>
</com.android.keyguard.AlphaOptimizedRelativeLayout>
<LinearLayout
@@ -203,6 +205,7 @@
android:layout_weight="1"
android:paddingBottom="11sp"
android:src="@drawable/ic_done_wht"
+ style="@style/Keyguard.ImageButton.NumPadEnter"
android:background="@drawable/ripple_drawable"
android:contentDescription="@string/keyboardview_keycode_enter"
/>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_presentation.xml b/packages/SystemUI/res-keyguard/layout/keyguard_presentation.xml
index 920498f..87b1ee7 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_presentation.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_presentation.xml
@@ -41,7 +41,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|top"
- android:textColor="@color/clock_white"
+ android:textColor="?attr/bgProtectTextColor"
android:singleLine="true"
style="@style/widget_big_thin"
android:format12Hour="@string/keyguard_widget_12_hours_format"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
index b0a93e6..f0dec76 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
@@ -42,9 +42,16 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical"
+ android:gravity="center"
android:layout_weight="1"
android:layoutDirection="ltr"
>
+ <include layout="@layout/keyguard_esim_area"
+ android:id="@+id/keyguard_esim_area"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/eca_overlap" />
+
<RelativeLayout
android:id="@+id/row0"
android:layout_width="match_parent"
@@ -60,6 +67,7 @@
android:layout_centerHorizontal="true"
android:layout_marginRight="72dp"
androidprv:scaledTextSize="28"
+ android:textColor="?attr/bgProtectTextColor"
android:contentDescription="@string/keyguard_accessibility_sim_pin_area"
/>
<ImageButton
@@ -71,19 +79,20 @@
android:clickable="true"
android:paddingTop="8dip"
android:paddingBottom="8dip"
- android:paddingRight="8dp"
+ android:paddingRight="0dp"
android:paddingLeft="24dp"
android:background="@drawable/ripple_drawable"
android:contentDescription="@string/keyboardview_keycode_delete"
android:layout_alignEnd="@+id/pinEntry"
android:layout_alignParentRight="true"
+ android:tint="@color/pin_delete_color"
/>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentBottom="true"
- android:background="#28FFFFFF"
+ android:background="@drawable/pin_divider"
/>
</RelativeLayout>
<LinearLayout
@@ -205,6 +214,7 @@
android:layout_weight="1"
android:paddingBottom="11sp"
android:src="@drawable/ic_done_wht"
+ style="@style/Keyguard.ImageButton.NumPadEnter"
android:background="@drawable/ripple_drawable"
android:contentDescription="@string/keyboardview_keycode_enter"
/>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
index cf41bd3..119b3ee 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
@@ -43,9 +43,16 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical"
+ android:gravity="center"
android:layout_weight="1"
android:layoutDirection="ltr"
>
+ <include layout="@layout/keyguard_esim_area"
+ android:id="@+id/keyguard_esim_area"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/eca_overlap" />
+
<RelativeLayout
android:id="@+id/row0"
android:layout_width="match_parent"
@@ -61,6 +68,7 @@
android:layout_centerHorizontal="true"
android:layout_marginRight="72dp"
androidprv:scaledTextSize="28"
+ android:textColor="?attr/bgProtectTextColor"
android:contentDescription="@string/keyguard_accessibility_sim_puk_area"
/>
<ImageButton
@@ -72,19 +80,20 @@
android:clickable="true"
android:paddingTop="8dip"
android:paddingBottom="8dip"
- android:paddingRight="8dp"
+ android:paddingRight="0dp"
android:paddingLeft="24dp"
android:background="@drawable/ripple_drawable"
android:contentDescription="@string/keyboardview_keycode_delete"
android:layout_alignEnd="@+id/pinEntry"
android:layout_alignParentRight="true"
+ android:tint="@color/pin_delete_color"
/>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentBottom="true"
- android:background="#28FFFFFF"
+ android:background="@drawable/pin_divider"
/>
</RelativeLayout>
<LinearLayout
@@ -206,6 +215,7 @@
android:layout_weight="1"
android:paddingBottom="11sp"
android:src="@drawable/ic_done_wht"
+ style="@style/Keyguard.ImageButton.NumPadEnter"
android:background="@drawable/ripple_drawable"
android:contentDescription="@string/keyboardview_keycode_enter"
/>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
index 7e664d0..a0850f4 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
@@ -29,7 +29,7 @@
android:id="@+id/date_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="@color/clock_white"
+ android:textColor="?attr/bgProtectTextColor"
style="@style/widget_label"
android:letterSpacing="0.15"
android:gravity="center"
@@ -39,7 +39,7 @@
android:layout_height="wrap_content"
android:drawablePadding="6dp"
android:drawableStart="@drawable/ic_access_alarms_big"
- android:textColor="@color/clock_gray"
+ android:textColor="?attr/bgProtectSecondaryTextColor"
android:letterSpacing="0.15"
style="@style/widget_label"
android:layout_marginStart="6dp"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index 51adc1e..44fae86 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
@@ -40,7 +40,7 @@
android:layout_gravity="center_horizontal"
android:layout_centerHorizontal="true"
android:layout_alignParentTop="true"
- android:textColor="@color/clock_white"
+ android:textColor="?attr/bgProtectTextColor"
android:singleLine="true"
style="@style/widget_big_thin"
android:format12Hour="@string/keyguard_widget_12_hours_format"
@@ -73,7 +73,7 @@
android:layout_marginTop="@dimen/date_owner_info_margin"
android:layout_centerHorizontal="true"
android:layout_below="@id/keyguard_status_area"
- android:textColor="@color/clock_gray"
+ android:textColor="?attr/bgProtectSecondaryTextColor"
android:textSize="@dimen/widget_label_font_size"
android:letterSpacing="0.05"
android:ellipsize="marquee"
diff --git a/packages/SystemUI/res-keyguard/values-af/strings.xml b/packages/SystemUI/res-keyguard/values-af/strings.xml
index ac4dfc8..5af5f9b 100644
--- a/packages/SystemUI/res-keyguard/values-af/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-af/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM-PUK-area"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Volgende wekker gestel vir <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Vee uit"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Deaktiveer e-SIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Het jy die patroon vergeet?"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Verkeerde patroon"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Verkeerde PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Probeer weer oor <xliff:g id="NUMBER">%d</xliff:g> sekondes."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Teken jou patroon"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Voer SIM se PIN in"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Voer SIM se PIN vir \"<xliff:g id="CARRIER">%1$s</xliff:g>\" in"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Voer SIM se PIN in."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Voer SIM se PIN vir \"<xliff:g id="CARRIER">%1$s</xliff:g>\" in."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Deaktiveer e-SIM om toestel sonder mobiele diens te gebruik."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Voer PIN in"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Voer wagwoord in"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM is nou gedeaktiveer. Voer PUK-kode in om voort te gaan. Kontak diensverskaffer vir besonderhede."</string>
diff --git a/packages/SystemUI/res-keyguard/values-am/strings.xml b/packages/SystemUI/res-keyguard/values-am/strings.xml
index aa33845..7a937de 100644
--- a/packages/SystemUI/res-keyguard/values-am/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-am/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"የሲም ፒዩኬ አካባቢ"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"ቀጣዩ ማንቂያ ለ<xliff:g id="ALARM">%1$s</xliff:g> ተዘጋጅቷል"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"ሰርዝ"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIMን አሰናክል"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"አስገባ"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"ስርዓተ ጥለቱን እርሳ"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"የተሳሳተ ስርዓተ ጥለት"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"የተሳሳተ ፒን"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"በ<xliff:g id="NUMBER">%d</xliff:g> ሰከንዶች ውስጥ እንደገና ይሞክሩ።"</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"ስርዓተ ጥለትዎን ይሳሉ"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"የሲም ፒን ያስገቡ"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"የ«<xliff:g id="CARRIER">%1$s</xliff:g>» ሲም ፒን ያስገቡ"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"የሲም ፒን ያስገቡ።"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"የ«<xliff:g id="CARRIER">%1$s</xliff:g>» ሲም ፒን ያስገቡ።"</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"መሣሪያን ያለተንቀሳቃሽ ስልክ አገልግሎት ለመጠቀም eSIMን ያሰናክሉ።"</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"ፒን ያስገቡ"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"የይለፍ ቃል ያስገቡ"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"ሲም አሁን ተሰናክሏል። ለመቀጠል የፒዩኬ ኮድ ያስገቡ። ለዝርዝር አገልግሎት አቅራቢን ያግኙ።"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index 129b4cc..7aab70f 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"منطقة PUK لشريحة SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"تم ضبط التنبيه التالي على <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Delete"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"تعطيل شريحة eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"نسيت النقش"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"نقش خاطئ"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"رقم تعريف شخصي خاطئ"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"حاول مرة أخرى خلال <xliff:g id="NUMBER">%d</xliff:g> ثانية."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"ارسم نقشك"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"أدخل رمز رقم التعريف الشخصي لشريحة SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"أدخل رقم التعريف الشخصي لشريحة SIM التابعة للمشغّل \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"أدخل رقم التعريف الشخصي لشريحة SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"أدخل رقم التعريف الشخصي لشريحة SIM التابعة للمشغّل \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"يجب تعطيل شريحة eSIM لاستخدام الجهاز دون خدمة جوال."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"أدخل رقم التعريف الشخصي"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"أدخل كلمة المرور"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"شريحة SIM معطّلة الآن. أدخل رمز PUK للمتابعة. اتصل بمشغل شبكة الجوّال للاطلاع على التفاصيل."</string>
diff --git a/packages/SystemUI/res-keyguard/values-az/strings.xml b/packages/SystemUI/res-keyguard/values-az/strings.xml
index 08ad2b7..73887ed 100644
--- a/packages/SystemUI/res-keyguard/values-az/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-az/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM PUK sahəsi"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Növbəti zəng vaxtı: <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Silin"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSİM\'i deaktiv edin"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Daxil edin"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Modeli unutmuşam"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Yanlış Model"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Yanlış PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> saniyə sonra yenidən cəhd edin."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Modeli çəkin"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"SIM PIN kodu daxil edin"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" üçün SIM PIN daxil edin"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"SIM PIN\'ni daxil edin."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" üçün SIM PIN\'ni daxil edin."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Mobil xidmət olmadan cihazı istifadə etmək üçün eSIM\'i deaktiv edin."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"PIN daxil edin"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Parol daxil edin"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM indi deaktivdir. Davam etmək üçün PUK kod daxil edin. Ətraflı məlumat üçün operatorla əlaqə saxlayın."</string>
diff --git a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
index b28a0cc..7952b7a 100644
--- a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Oblast za PUK za SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Sledeći alarm je podešen za <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Izbriši"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Onemogući eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Zaboravio/la sam šablon"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Pogrešan šablon"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Pogrešan PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Probajte ponovo za <xliff:g id="NUMBER">%d</xliff:g> sek."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Nacrtajte šablon"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Unesite PIN za SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Unesite PIN za SIM „<xliff:g id="CARRIER">%1$s</xliff:g>“"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Unesite PIN za SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Unesite PIN za SIM „<xliff:g id="CARRIER">%1$s</xliff:g>“."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Onemogućite eSIM da biste uređaj koristili bez mobilne usluge."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Unesite PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Unesite lozinku"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM kartica je sada onemogućena. Unesite PUK kôd da biste nastavili. Detaljne informacije potražite od mobilnog operatera."</string>
diff --git a/packages/SystemUI/res-keyguard/values-be/strings.xml b/packages/SystemUI/res-keyguard/values-be/strings.xml
index 25ebbfe..3b9a9bb 100644
--- a/packages/SystemUI/res-keyguard/values-be/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-be/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Поле для PUK-кода SIM-карты"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Наступны будзільнік пастаўлены на <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Выдаліць"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Адключыць eSIM-карту"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Увесці"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Забыў(-ла) узор"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Няправільны ўзор"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Няправільны PIN-код"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Паўтарыце спробу праз <xliff:g id="NUMBER">%d</xliff:g> с."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Намалюйце ўзор"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Увядзіце PIN-код SIM-карты"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Увядзіце PIN-код SIM-карты \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Увядзіце PIN-код SIM-карты."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Увядзіце PIN-код SIM-карты для \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Адключыце eSIM-карту, каб выкарыстоўваць прыладу без сэрвісу мабільнай перадачы даных."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Увядзіце PIN-код"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Увядзіце пароль"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM-карта зараз адключана. Увядзіце PUK-код, каб працягнуць. Звяжыцеся са сваім аператарам, каб атрымаць дадатковую інфармацыю."</string>
diff --git a/packages/SystemUI/res-keyguard/values-bg/strings.xml b/packages/SystemUI/res-keyguard/values-bg/strings.xml
index 73373d5..6dd7ab9 100644
--- a/packages/SystemUI/res-keyguard/values-bg/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bg/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Област за PUK кода на SIM картата"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Следващият будилник е зададен за <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Изтриване"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Деактивиране на ел. SIM карта"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"„Enter“"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Забравена фигура"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Грешна фигура"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Грешен ПИН код"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Опитайте отново след <xliff:g id="NUMBER">%d</xliff:g> секунди."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Начертайте фигурата си"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Въведете ПИН кода за SIM картата"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Въведете ПИН кода на SIM картата за „<xliff:g id="CARRIER">%1$s</xliff:g>“"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Въведете ПИН кода за SIM картата."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Въведете ПИН кода на SIM картата за „<xliff:g id="CARRIER">%1$s</xliff:g>“."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Деактивирайте електронната SIM карта, за да използвате устройството без мобилна услуга."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Въведете ПИН кода"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Въведете паролата"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM картата вече е деактивирана. Въведете PUK кода, за да продължите. Свържете се с оператора за подробности."</string>
diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml
index 26ba5e9..25e7443 100644
--- a/packages/SystemUI/res-keyguard/values-bn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"সিম PUK অঞ্চল"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"পরবর্তী অ্যালার্ম <xliff:g id="ALARM">%1$s</xliff:g> এ সেট করা হয়েছে"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"মুছুন"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"ই-সিমটি অক্ষম করুন"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"এন্টার"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"প্যাটার্ন ভুলে গেছি"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"ভুল প্যাটার্ন"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"ভুল পিন"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> সেকেন্ডের মধ্যে আবার চেষ্টা করুন।"</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"আপনার প্যাটার্ন আঁকুন"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"সিমের পিন লিখুন"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" এর জন্য সিমের পিন লিখুন"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"সিমের পিন লিখুন।"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" এর জন্য সিমের পিন লিখুন।"</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"মোবাইল পরিষেবা ছাড়াই ডিভাইস ব্যবহার করতে ই-সিম অক্ষম করুন।"</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"পিন লিখুন"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"পাসওয়ার্ড লিখুন"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"সিমটি এখন অক্ষম করা হয়েছে। চালিয়ে যেতে PUK কোডটি লিখুন। বিশদ বিবরণের জন্য পরিষেবা প্রদানকারীর সাথে যোগাযোগ করুন।"</string>
diff --git a/packages/SystemUI/res-keyguard/values-bs/strings.xml b/packages/SystemUI/res-keyguard/values-bs/strings.xml
index 4b2fd13..0ec4ad5 100644
--- a/packages/SystemUI/res-keyguard/values-bs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bs/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Prostor za PUK kôd za SIM karticu"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Naredni alarm je podešen za <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Izbriši"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Onemogući eSIM karticu"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Zaboravili ste uzorak?"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Pogrešan uzorak"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Pogrešan PIN kôd"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Pokušajte ponovo za <xliff:g id="NUMBER">%d</xliff:g> sek."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Nacrtajte uzorak"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Unesite PIN kôd za SIM karticu"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Unesite PIN kôd za SIM karticu operatera \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Unesite PIN kôd SIM kartice."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Unesite PIN kôd SIM kartice operatera \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Onemogućite eSIM karticu za korištenje uređaja bez mobilne usluge."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Unesite PIN kôd"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Unesite lozinku"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM kartica je sada onemogućena. Unesite PUK kôd da nastavite. Za više informacija obratite se operateru."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ca/strings.xml b/packages/SystemUI/res-keyguard/values-ca/strings.xml
index 723d502..e5c8d71 100644
--- a/packages/SystemUI/res-keyguard/values-ca/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ca/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Zona del PUK de la SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"S\'ha definit la pròxima alarma per a l\'hora següent: <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Suprimeix"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Desactiva l\'eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Retorn"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"He oblidat el patró"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"El patró no és correcte"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"El PIN no és correcte"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Torna-ho a provar d\'aquí a <xliff:g id="NUMBER">%d</xliff:g> segons."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Dibuixa el patró"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Introdueix el PIN de la SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Introdueix el PIN de la SIM següent: <xliff:g id="CARRIER">%1$s</xliff:g>"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Introdueix el PIN de la SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Introdueix el PIN de la SIM de: <xliff:g id="CARRIER">%1$s</xliff:g>."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Desactiva l\'eSIM per utilitzar el dispositiu sense servei mòbil."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Introdueix el PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Introdueix la contrasenya"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"La SIM està desactivada. Introdueix el codi PUK per continuar. Contacta amb l\'operador de telefonia mòbil per obtenir més informació."</string>
diff --git a/packages/SystemUI/res-keyguard/values-cs/strings.xml b/packages/SystemUI/res-keyguard/values-cs/strings.xml
index 52b12bf..0a23d90 100644
--- a/packages/SystemUI/res-keyguard/values-cs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-cs/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Oblast kódu PUK SIM karty"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Další budík je nastaven na <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Smazat"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Deaktivovat eSIM kartu"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Zapomenuté gesto"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Nesprávné gesto"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Nesprávný kód PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Zkuste to znovu za <xliff:g id="NUMBER">%d</xliff:g> s."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Zadejte gesto"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Zadejte kód PIN SIM karty"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Zadejte kód PIN SIM karty <xliff:g id="CARRIER">%1$s</xliff:g>"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Zadejte kód PIN SIM karty."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Zadejte kód PIN SIM karty <xliff:g id="CARRIER">%1$s</xliff:g>."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"eSIM kartu deaktivujte, chcete-li telefon používat bez mobilních služeb."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Zadejte kód PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Zadejte heslo"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM karta byla zablokována. Chcete-li pokračovat, je třeba zadat kód PUK. Podrobné informace získáte od operátora."</string>
diff --git a/packages/SystemUI/res-keyguard/values-da/strings.xml b/packages/SystemUI/res-keyguard/values-da/strings.xml
index 486b46c..e55d996 100644
--- a/packages/SystemUI/res-keyguard/values-da/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-da/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Område for PUK-koden til SIM-kortet"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Næste alarm er indstillet til <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Slet"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Deaktiver eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Har du glemt mønsteret?"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Forkert mønster"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Forkert pinkode"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Prøv igen om <xliff:g id="NUMBER">%d</xliff:g> sekunder."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Tegn dit mønster"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Indtast pinkoden til SIM-kortet"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Indtast pinkoden til SIM-kortet for \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Angiv pinkoden til SIM-kortet."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Angiv pinkoden til SIM-kortet fra \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Deaktiver eSIM for at bruge enheden uden mobiltjeneste."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Indtast pinkode"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Angiv adgangskode"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM-kortet er nu deaktiveret. Indtast PUK-koden for at fortsætte. Kontakt mobilselskabet for at få flere oplysninger."</string>
diff --git a/packages/SystemUI/res-keyguard/values-de/strings.xml b/packages/SystemUI/res-keyguard/values-de/strings.xml
index 190f203..41bc9a9 100644
--- a/packages/SystemUI/res-keyguard/values-de/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-de/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM-PUK-Bereich"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Nächster Wecker gestellt für <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Löschen"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM deaktivieren"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Eingabe"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Muster vergessen"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Falsches Muster"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Falsche PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Versuche es in <xliff:g id="NUMBER">%d</xliff:g> Sekunden noch einmal."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Muster zeichnen"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"SIM-PIN eingeben"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"SIM-PIN für \"<xliff:g id="CARRIER">%1$s</xliff:g>\" eingeben"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Gib die SIM-PIN ein"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Gib die SIM-PIN für \"<xliff:g id="CARRIER">%1$s</xliff:g>\" ein."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Deaktiviere eSIM, um das Gerät ohne Mobilfunkdienst zu verwenden."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"PIN eingeben"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Passwort eingeben"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"Die SIM-Karte ist jetzt deaktiviert. Gib den PUK-Code ein, um fortzufahren. Weitere Informationen erhältst du von deinem Mobilfunkanbieter."</string>
diff --git a/packages/SystemUI/res-keyguard/values-el/strings.xml b/packages/SystemUI/res-keyguard/values-el/strings.xml
index 9707c90..d195c87 100644
--- a/packages/SystemUI/res-keyguard/values-el/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-el/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Περιοχή κωδικού PUK κάρτας SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Το επόμενο ξυπνητήρι ορίστηκε στις <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Διαγραφή"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Απενεργοποίηση eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Ξεχάσατε το μοτίβο"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Λάθος μοτίβο"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Λανθασμένος αριθμός PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Δοκιμάστε ξανά σε <xliff:g id="NUMBER">%d</xliff:g> δευτερόλεπτα."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Σχεδιάστε το μοτίβο σας"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Εισαγωγή αριθμού PIN της κάρτας SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Εισάγετε τον αριθμό PIN της SIM της εταιρείας κινητής τηλεφωνίας \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Εισαγωγή αριθμού PIN κάρτας SIM"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Εισαγάγετε τον αριθμό PIN της κάρτας SIM \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Απενεργοποιήστε την eSIM, για να χρησιμοποιήσετε τη συσκευή χωρίς υπηρεσία για κινητά."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Εισαγάγετε τον αριθμό PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Εισαγάγετε κωδικό πρόσβασης"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"Η κάρτα SIM είναι απενεργοποιημένη αυτή τη στιγμή. Εισαγάγετε τον κωδικό PUK για να συνεχίσετε. Επικοινωνήστε με την εταιρεία κινητής τηλεφωνίας σας για λεπτομέρειες."</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
index 6b02d6d..35bae2e 100644
--- a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM PUK area"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Next alarm set for <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Delete"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Disable eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Forgotten Pattern"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Wrong Pattern"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Wrong PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Try again in <xliff:g id="NUMBER">%d</xliff:g> seconds."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Draw your pattern"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Enter SIM PIN"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Enter SIM PIN for \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Enter SIM PIN."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Enter SIM PIN for \'<xliff:g id="CARRIER">%1$s</xliff:g>\'."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Disable eSIM to use device without mobile service."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Enter PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Enter Password"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM is now disabled. Enter PUK code to continue. Contact carrier for details."</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
index 6b02d6d..35bae2e 100644
--- a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM PUK area"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Next alarm set for <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Delete"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Disable eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Forgotten Pattern"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Wrong Pattern"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Wrong PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Try again in <xliff:g id="NUMBER">%d</xliff:g> seconds."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Draw your pattern"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Enter SIM PIN"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Enter SIM PIN for \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Enter SIM PIN."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Enter SIM PIN for \'<xliff:g id="CARRIER">%1$s</xliff:g>\'."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Disable eSIM to use device without mobile service."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Enter PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Enter Password"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM is now disabled. Enter PUK code to continue. Contact carrier for details."</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
index 6b02d6d..35bae2e 100644
--- a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM PUK area"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Next alarm set for <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Delete"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Disable eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Forgotten Pattern"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Wrong Pattern"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Wrong PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Try again in <xliff:g id="NUMBER">%d</xliff:g> seconds."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Draw your pattern"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Enter SIM PIN"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Enter SIM PIN for \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Enter SIM PIN."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Enter SIM PIN for \'<xliff:g id="CARRIER">%1$s</xliff:g>\'."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Disable eSIM to use device without mobile service."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Enter PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Enter Password"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM is now disabled. Enter PUK code to continue. Contact carrier for details."</string>
diff --git a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
index 761e3a1..219c813 100644
--- a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Área de PUK de la tarjeta SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Próxima alarma establecida: <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Borrar"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Inhabilitar eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Intro"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"¿Olvidaste el patrón?"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Patrón incorrecto"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN incorrecto"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Vuelve a intentarlo en <xliff:g id="NUMBER">%d</xliff:g> segundos."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Dibuja tu patrón"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Ingresa el PIN de la tarjeta SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Ingresa el PIN de la tarjeta SIM para \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Ingresa el PIN de la tarjeta SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Ingresa el PIN de la tarjeta SIM de \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Inhabilita la eSIM para usar el dispositivo sin servicio de datos móviles."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Ingresa el PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Escribe la contraseña"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"La tarjeta SIM está inhabilitada. Para continuar, ingresa el código PUK. Si quieres obtener más información, comunícate con el proveedor."</string>
diff --git a/packages/SystemUI/res-keyguard/values-es/strings.xml b/packages/SystemUI/res-keyguard/values-es/strings.xml
index 9811023..14df1e5 100644
--- a/packages/SystemUI/res-keyguard/values-es/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Área de PUK de la tarjeta SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Próxima alarma: <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Eliminar"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Inhabilita la tarjeta eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Intro"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"¿Has olvidado el patrón?"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Patrón incorrecto"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN incorrecto"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Vuelve a intentarlo en <xliff:g id="NUMBER">%d</xliff:g> segundos."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Dibuja tu patrón"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Introduce el PIN de la tarjeta SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Introduce el PIN de la tarjeta SIM \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Introduce el PIN de la tarjeta SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Introduce el PIN de la tarjeta SIM de <xliff:g id="CARRIER">%1$s</xliff:g>."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Inhabilita la tarjeta eSIM para utilizar el dispositivo sin servicio móvil."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Introduce el PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Introduce tu contraseña"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"La tarjeta SIM está inhabilitada. Para continuar, introduce el código PUK. Si quieres obtener más información, ponte en contacto con el operador."</string>
diff --git a/packages/SystemUI/res-keyguard/values-et/strings.xml b/packages/SystemUI/res-keyguard/values-et/strings.xml
index 1892ef6..a6d8e20 100644
--- a/packages/SystemUI/res-keyguard/values-et/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-et/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM-kaardi PUK-koodi ala"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Järgmine alarm on määratud ajaks <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Kustuta"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Keela eSIM-kaart"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Sisesta"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Unustasin mustri"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Vale muster"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Vale PIN-kood"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Proovige <xliff:g id="NUMBER">%d</xliff:g> sekundi pärast uuesti."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Joonistage oma muster"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Sisestage SIM-kaardi PIN-kood"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Sisestage operaatori „<xliff:g id="CARRIER">%1$s</xliff:g>” puhul SIM-kaardi PIN-kood"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Sisestage SIM-kaardi PIN-kood."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Sisestage operaatori „<xliff:g id="CARRIER">%1$s</xliff:g>” SIM-kaardi PIN-kood."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Keelake eSIM-kaart, et seadet ilma mobiilsideteenuseta kasutada."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Sisestage PIN-kood"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Sisestage parool"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM-kaart on nüüd keelatud. Jätkamiseks sisestage PUK-kood. Lisateabe saamiseks võtke ühendust operaatoriga."</string>
diff --git a/packages/SystemUI/res-keyguard/values-eu/strings.xml b/packages/SystemUI/res-keyguard/values-eu/strings.xml
index 38ff52e..4b66213c 100644
--- a/packages/SystemUI/res-keyguard/values-eu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-eu/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM txartelaren PUK kodearen eremua"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Hurrengo alarmak ordu honetan joko du: <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Ezabatu"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Desgaitu eSIM txartela"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Sartu"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Eredua ahaztu zaizu"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Eredu hori ez da zuzena"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN kode hori ez da zuzena"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Saiatu berriro <xliff:g id="NUMBER">%d</xliff:g> segundo barru."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Marraztu eredua"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Idatzi SIM txartelaren PIN kodea"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Idatzi \"<xliff:g id="CARRIER">%1$s</xliff:g>\" operadorearen SIM txartelaren PIN kodea"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Idatzi SIM txartelaren PIN kodea."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Idatzi \"<xliff:g id="CARRIER">%1$s</xliff:g>\" operadorearen SIM txartelaren PIN kodea."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Desgaitu eSIM txartela gailua zerbitzu mugikorrik gabe erabiltzeko."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Idatzi PIN kodea"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Idatzi pasahitza"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"Desgaitu egin da SIM txartela. Aurrera egiteko, idatzi PUK kodea. Xehetasunak lortzeko, jarri operadorearekin harremanetan."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml
index 3dc59ca..2fe7bfb 100644
--- a/packages/SystemUI/res-keyguard/values-fa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"قسمت PUK سیمکارت"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"زنگ ساعت بعدی برای <xliff:g id="ALARM">%1$s</xliff:g> تنظیم شد"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"حذف"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"غیرفعال کردن eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"الگو را فراموش کردهاید"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"الگوی اشتباه"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"پین اشتباه"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"پس از <xliff:g id="NUMBER">%d</xliff:g> ثانیه دوباره امتحان کنید."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"الگوی خود را رسم کنید"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"پین سیمکارت را وارد کنید"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"پین سیمکارت «<xliff:g id="CARRIER">%1$s</xliff:g>» را وارد کنید"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"پین سیمکارت را وارد کنید."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"پین سیمکارت «<xliff:g id="CARRIER">%1$s</xliff:g>» را وارد کنید."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"برای استفاده از دستگاه بدون سرویس همراه، eSIM را غیرفعال کنید."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"کد پین را وارد کنید"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"گذرواژه را وارد کنید"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"اکنون سیمکارت غیرفعال است. کد پین را برای ادامه وارد کنید. برای جزئیات با شرکت مخابراتی خود تماس بگیرید."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fi/strings.xml b/packages/SystemUI/res-keyguard/values-fi/strings.xml
index 91d8d82..23139e3 100644
--- a/packages/SystemUI/res-keyguard/values-fi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fi/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM-kortin PUK-koodin alue"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Seuraava hälytys asetettu: <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Poista"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Poista eSIM käytöstä"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Unohtunut kuvio"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Väärä kuvio"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Väärä PIN-koodi"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Yritä uudelleen <xliff:g id="NUMBER">%d</xliff:g> sekunnin kuluttua."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Piirrä kuvio"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Anna SIM-kortin PIN-koodi"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Anna operaattorin <xliff:g id="CARRIER">%1$s</xliff:g> SIM-kortin PIN-koodi."</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Anna SIM-kortin PIN-koodi."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Anna operaattorin <xliff:g id="CARRIER">%1$s</xliff:g> SIM-kortin PIN-koodi."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Poista eSIM käytöstä, jos haluat käyttää laitetta ilman mobiilipalvelua."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Anna PIN-koodi."</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Anna salasana"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM-kortti on nyt poistettu käytöstä. Jatka antamalla PUK-koodi. Saat lisätietoja ottamalla yhteyttä operaattoriin."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
index 5f2462c..1c15724 100644
--- a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Zone du code PUK de la carte SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Heure de la prochaine alarme : <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Supprimer"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Désactiver la carte eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Entrée"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"J\'ai oublié le schéma"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Schéma incorrect"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"NIP incorrect"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Veuillez réessayer dans <xliff:g id="NUMBER">%d</xliff:g> secondes."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Dessinez votre schéma"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Entrez le NIP de la carte SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Entrez le NIP de la carte SIM pour « <xliff:g id="CARRIER">%1$s</xliff:g> »"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Entrez le NIP de la carte SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Entrez le NIP de la carte SIM pour « <xliff:g id="CARRIER">%1$s</xliff:g> »."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Désactivez la carte eSIM pour utiliser l\'appareil sans service cellulaire."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Entrez le NIP"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Entrez votre mot de passe."</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"La carte SIM est maintenant désactivée. Entrez le code PUK pour continuer. Pour obtenir plus de détails, communiquez avec votre fournisseur de services."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fr/strings.xml b/packages/SystemUI/res-keyguard/values-fr/strings.xml
index ed38ae0..00ee573 100644
--- a/packages/SystemUI/res-keyguard/values-fr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Champ de la clé PUK de la carte SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Date et heure de la prochaine alarme : <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Supprimer"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Désactiver la carte eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Entrée"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"J\'ai oublié le schéma"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Schéma incorrect"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Code incorrect"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Réessayez dans <xliff:g id="NUMBER">%d</xliff:g> secondes."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Dessinez votre schéma"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Saisissez le code PIN de la carte SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Saisissez le code PIN de la carte SIM \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Saisissez le code PIN de la carte SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Saisissez le code PIN de la carte SIM \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Désactivez la carte eSIM pour utiliser l\'appareil sans service mobile."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Saisissez le code"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Saisissez le mot de passe"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"La carte SIM est maintenant désactivée. Pour continuer, saisissez la clé PUK. Contactez votre opérateur pour en savoir plus."</string>
diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml
index a615f7f..348af67 100644
--- a/packages/SystemUI/res-keyguard/values-gl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Área do PUK da tarxeta SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Próxima alarma definida para: <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Eliminar"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Desactivar eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Intro"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Esqueciches o padrón"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Padrón incorrecto"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN incorrecto"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Téntao de novo en <xliff:g id="NUMBER">%d</xliff:g> segundos."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Debuxa o teu padrón"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Introduce o PIN da SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Introduce o PIN da SIM para \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Introduce o PIN da SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Introduce o PIN da SIM para \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Desactiva a eSIM para usar o dispositivo sen servizo móbil."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Introduce o PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Insire o teu contrasinal"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"Agora a tarxeta SIM está desactivada. Introduce o código PUK para continuar. Ponte en contacto co operador para obter máis información."</string>
diff --git a/packages/SystemUI/res-keyguard/values-gu/strings.xml b/packages/SystemUI/res-keyguard/values-gu/strings.xml
index d036ef4..c795024 100644
--- a/packages/SystemUI/res-keyguard/values-gu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gu/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM PUK ક્ષેત્ર"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"<xliff:g id="ALARM">%1$s</xliff:g> માટે આગલું એલાર્મ સેટ કર્યું"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"કાઢી નાખો"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIMને અક્ષમ કરો"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"દાખલ કરો"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"પેટર્ન ભૂલી ગયાં"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"ખોટી પેટર્ન"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"ખોટો PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> સેકંડમાં ફરીથી પ્રયાસ કરો."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"તમારી પેટર્ન દોરો"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"SIM PIN દાખલ કરો"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" માટે SIM PIN દાખલ કરો"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"SIM PIN દાખલ કરો"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" માટે SIM PIN દાખલ કરો."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"મોબાઇલ સેવા વગર ઉપકરણનો ઉપયોગ કરવા eSIMને અક્ષમ કરો."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"PIN દાખલ કરો"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"પાસવર્ડ દાખલ કરો"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM હમણાં અક્ષમ કરેલ છે. ચાલુ રાખવા માટે PUK કોડ દાખલ કરો. વિગતો માટે કૅરિઅરનો સંપર્ક કરો."</string>
diff --git a/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml
index 3fb86d0..1b6fa4c 100644
--- a/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-h560dp/dimens.xml
@@ -16,5 +16,5 @@
-->
<resources>
- <dimen name="widget_big_font_size">64dp</dimen>
+ <dimen name="widget_big_font_size">72dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
index 3fb86d0..1b6fa4c 100644
--- a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
@@ -16,5 +16,5 @@
-->
<resources>
- <dimen name="widget_big_font_size">64dp</dimen>
+ <dimen name="widget_big_font_size">72dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml
index 023e433..c17218b 100644
--- a/packages/SystemUI/res-keyguard/values-hi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM PUK क्षेत्र"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"अगला अलार्म <xliff:g id="ALARM">%1$s</xliff:g> बजे के लिए सेट किया गया है"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"हटाएं"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM अक्षम करें"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"पैटर्न भूल गए हैं"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"गलत पैटर्न"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"गलत पिन"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> सेकंड में फिर से कोशिश करें."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"अपना पैटर्न बनाएं"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"SIM पिन डालें"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" का SIM पिन डालें"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"सिम पिन डालें."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" के लिए सिम पिन डालें"</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"मोबाइल सेवा के बिना डिवाइस का उपयोग करने के लिए eSIM अक्षम करें."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"पिन डालें"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"पासवर्ड डालें"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM अब अक्षम हो गया है. जारी रखने के लिए PUK कोड डालें. विवरण के लिए वाहक से संपर्क करें."</string>
diff --git a/packages/SystemUI/res-keyguard/values-hr/strings.xml b/packages/SystemUI/res-keyguard/values-hr/strings.xml
index 0cc452a..0fb5ba0 100644
--- a/packages/SystemUI/res-keyguard/values-hr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hr/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Područje PUK-a za SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Sljedeći alarm postavljen za <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Izbriši"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Onemogući eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Unos"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Zaboravili ste uzorak"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Pogrešan uzorak"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Pogrešan PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Pokušajte ponovo za <xliff:g id="NUMBER">%d</xliff:g> s."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Iscrtajte svoj uzorak"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Unesite PIN za SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Unesite PIN za SIM mobilnog operatera \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Unesite PIN za SIM"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Unesite PIN za SIM mobilnog operatera \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Onemogućite eSIM kako biste uređaj upotrebljavali bez mobilne usluge."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Unesite PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Unesite zaporku"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM je sad onemogućen. Unesite PUK kôd da biste nastavili. Obratite se mobilnom operateru za više pojedinosti."</string>
diff --git a/packages/SystemUI/res-keyguard/values-hu/strings.xml b/packages/SystemUI/res-keyguard/values-hu/strings.xml
index 32f2f47..3a2013d 100644
--- a/packages/SystemUI/res-keyguard/values-hu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hu/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"A SIM-kártyához tartozó PUK-kód mezője"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"A következő ébresztés beállított ideje: <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Törlés"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Az e-SIM-kártya letiltása"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Elfelejtettem a mintát"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Helytelen minta"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Helytelen PIN-kód"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Próbálja újra <xliff:g id="NUMBER">%d</xliff:g> másodperc múlva."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Rajzolja le a mintát"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Adja meg a SIM-kártya PIN-kódját"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Adja meg a(z) „<xliff:g id="CARRIER">%1$s</xliff:g>” SIM-kártya PIN-kódját"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Adja meg a SIM-kártya PIN-kódját."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Adja meg a(z) „<xliff:g id="CARRIER">%1$s</xliff:g>” SIM-kártya PIN-kódját."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Tiltsa le az e-SIM-kártyát az eszköz mobilszolgáltatás nélküli használatához."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Adja meg a PIN-kódot"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Írja be a jelszót"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"A SIM-kártya le van tiltva. A folytatáshoz adja meg a PUK-kódot. A részletekért vegye fel a kapcsolatot szolgáltatójával."</string>
diff --git a/packages/SystemUI/res-keyguard/values-hy/strings.xml b/packages/SystemUI/res-keyguard/values-hy/strings.xml
index 9433ae3..3fdffbb 100644
--- a/packages/SystemUI/res-keyguard/values-hy/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hy/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM քարտի PUK կոդի տարածք"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Հաջորդ զարթուցիչը դրված է <xliff:g id="ALARM">%1$s</xliff:g>-ի վրա"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Ջնջել"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Անջատել էլեկտրոնային SIM քարտը"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Մուտքի ստեղն"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Մոռացել եմ նախշը"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Նախշը սխալ է"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN կոդը սխալ է"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Փորձեք կրկին <xliff:g id="NUMBER">%d</xliff:g> վայրկյանից:"</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Մուտքագրեք նախշը"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Մուտքագրեք SIM քարտի PIN կոդը"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Մուտքագրեք SIM քարտի PIN կոդը «<xliff:g id="CARRIER">%1$s</xliff:g>»-ի համար"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Մուտքագրեք SIM քարտի PIN կոդը։"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Մուտքագրեք SIM քարտի PIN կոդը «<xliff:g id="CARRIER">%1$s</xliff:g>»-ի համար:"</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Անջատել էլեկտրոնային SIM քարտը՝ սարքն առանց բջջային ծառայությունների օգտագործելու համար:"</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Մուտքագրեք PIN-ը"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Մուտքագրեք գաղտնաբառը"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM քարտն այժմ անջատված է: Շարունակելու համար մուտքագրեք PUK կոդը: Մանրամասն տեղեկություններ ստանալու համար դիմեք օպերատորին:"</string>
diff --git a/packages/SystemUI/res-keyguard/values-in/strings.xml b/packages/SystemUI/res-keyguard/values-in/strings.xml
index fba6df2..0c615a4 100644
--- a/packages/SystemUI/res-keyguard/values-in/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-in/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Bidang PUK SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Alarm berikutnya disetel untuk <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Hapus"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Nonaktifkan eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Masukkan"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Lupa Pola?"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Pola Salah"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN Salah"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Coba lagi dalam <xliff:g id="NUMBER">%d</xliff:g> detik."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Gambar pola Anda"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Masukkan PIN SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Masukkan PIN SIM \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Masukkan PIN SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Masukkan PIN SIM \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Nonaktifkan eSIM untuk menggunakan perangkat tanpa layanan seluler."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Masukkan PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Masukkan Sandi"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM telah dinonaktifkan. Masukkan kode PUK untuk melanjutkan. Hubungi operator untuk keterangan selengkapnya."</string>
diff --git a/packages/SystemUI/res-keyguard/values-is/strings.xml b/packages/SystemUI/res-keyguard/values-is/strings.xml
index 90e378c..3340093 100644
--- a/packages/SystemUI/res-keyguard/values-is/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-is/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"PUK-svæði SIM-korts"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Næsti vekjari stilltur á <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Eyða"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Aftengja eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Færa inn"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Man ekki mynstrið"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Rangt mynstur"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Rangt PIN-númer"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Reyndu aftur eftir <xliff:g id="NUMBER">%d</xliff:g> sekúndur."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Teiknaðu mynstrið þitt"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Sláðu inn PIN-númer SIM-kortsins"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Sláðu inn PIN-númer SIM-korts fyrir „<xliff:g id="CARRIER">%1$s</xliff:g>“"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Sláðu inn PIN-númer SIM-kortsins."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Sláðu inn PIN-númer SIM-korts fyrir „<xliff:g id="CARRIER">%1$s</xliff:g>“."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Aftengdu eSIM til að nota tækið án farsímakerfisþjónustu."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Sláðu inn PIN-númer"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Sláðu inn aðgangsorð"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM-kortið hefur verið gert óvirkt. Sláðu inn PUK-númerið til að halda áfram. Hafðu samband við símafyrirtækið til að fá frekari upplýsingar."</string>
diff --git a/packages/SystemUI/res-keyguard/values-it/strings.xml b/packages/SystemUI/res-keyguard/values-it/strings.xml
index 47181ca..70fae42 100644
--- a/packages/SystemUI/res-keyguard/values-it/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-it/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Area PUK SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Prossima sveglia impostata a: <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Elimina"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Disattiva eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Invio"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Sequenza dimenticata"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Sequenza sbagliata"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN errato"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Riprova fra <xliff:g id="NUMBER">%d</xliff:g> secondi."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Inserisci la sequenza"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Inserisci il PIN della SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Inserisci il PIN della scheda SIM \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Inserisci il PIN della SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Inserisci il PIN della SIM \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Disattiva l\'eSIM per usare il dispositivo senza servizio dati mobile."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Inserisci PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Inserisci la password"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"La scheda SIM è disattivata. Inserisci il codice PUK per continuare. Contatta l\'operatore per avere informazioni dettagliate."</string>
diff --git a/packages/SystemUI/res-keyguard/values-iw/strings.xml b/packages/SystemUI/res-keyguard/values-iw/strings.xml
index 2efa1a8..6b06966 100644
--- a/packages/SystemUI/res-keyguard/values-iw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-iw/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"אזור לקוד הגישה של כרטיס ה-SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"ההתראה הבאה נקבעה ל-<xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Delete"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"השבתת ה-eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"שכחתי את קו ביטול הנעילה"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"קו ביטול הנעילה שגוי"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"קוד הגישה שגוי"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"נסה שוב בעוד <xliff:g id="NUMBER">%d</xliff:g> שניות."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"שרטט את קו ביטול הנעילה"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"הזן את קוד הגישה של כרטיס ה-SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"הזן את קוד הגישה של כרטיס ה-SIM של \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"הזן את קוד הגישה של כרטיס ה-SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"הזן את קוד הגישה של כרטיס ה-SIM של <xliff:g id="CARRIER">%1$s</xliff:g>."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"השבת את ה-eSIM כדי להשתמש במכשיר ללא שירות סלולרי."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"הזן קוד גישה"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"הזן את הסיסמה"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"כרטיס ה-SIM מושבת כעת. הזן קוד PUK כדי להמשיך. פנה אל הספק לפרטים."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ja/strings.xml b/packages/SystemUI/res-keyguard/values-ja/strings.xml
index 7ad8c74..c108d1e 100644
--- a/packages/SystemUI/res-keyguard/values-ja/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ja/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM PUK エリア"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"次のアラームを <xliff:g id="ALARM">%1$s</xliff:g> に設定しました"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"削除"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM を無効にする"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"入力"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"パターンを忘れた場合"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"パターンが正しくありません"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN が正しくありません"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> 秒後にもう一度お試しください。"</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"パターンを入力してください"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"SIM PIN を入力してください"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"「<xliff:g id="CARRIER">%1$s</xliff:g>」の SIM PIN を入力してください"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"SIM PIN を入力してください。"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"「<xliff:g id="CARRIER">%1$s</xliff:g>」の SIM PIN を入力してください。"</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"モバイル サービスなしで端末を使用するには eSIM を無効にしてください。"</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"PIN を入力してください"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"パスワードを入力してください"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM が無効になりました。続行するには PUK コードを入力してください。詳しくは携帯通信会社にお問い合わせください。"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ka/strings.xml b/packages/SystemUI/res-keyguard/values-ka/strings.xml
index 74ae02a..38eed79 100644
--- a/packages/SystemUI/res-keyguard/values-ka/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ka/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM ბარათის PUK-კოდის არე"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"შემდეგი მაღვიძარა დაყენებულია <xliff:g id="ALARM">%1$s</xliff:g>-ზე"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"წაშლა"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM-ის გათიშვა"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"შეყვანა"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"დაგავიწყდათ ნიმუში"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"ნიმუში არასწორია"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN-კოდი არასწორია"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"ცადეთ ხელახლა <xliff:g id="NUMBER">%d</xliff:g> წამში."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"დახატეთ თქვენი ნიმუში"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"შეიყვანეთ SIM ბარათის PIN-კოდი"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"შეიყვანეთ SIM ბარათის PIN-კოდი „<xliff:g id="CARRIER">%1$s</xliff:g>“-სთვის"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"შეიყვანეთ SIM ბარათის PIN-კოდი."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"შეიყვანეთ SIM ბარათის PIN-კოდი „<xliff:g id="CARRIER">%1$s</xliff:g>“-ისთვის."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"გათიშეთ eSIM, მოწყობილობის მობილური სერვისების გარეშე გამოსაყენებლად."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"შეიყვანეთ PIN-კოდი"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"შეიყვანეთ პაროლი"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM ბარათი ახლა დეაქტივირებულია. გასაგრძელებლად შეიყვანეთ PUK-კოდი. დეტალური ინფორმაციისთვის დაუკავშირდით თქვენს ოპერატორს."</string>
diff --git a/packages/SystemUI/res-keyguard/values-kk/strings.xml b/packages/SystemUI/res-keyguard/values-kk/strings.xml
index 8a088f3..137926e 100644
--- a/packages/SystemUI/res-keyguard/values-kk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kk/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM PUK аумағы"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Келесі дабыл уақыты: <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Жою"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM картасын өшіру"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Енгізу"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Өрнекті ұмытып қалдыңыз ба?"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Өрнек қате"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN коды қате"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> секундтан кейін әрекетті қайталаңыз."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Өрнекті енгізіңіз"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"SIM PIN кодын енгізіңіз"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" үшін SIM PIN кодын енгізіңіз"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"SIM PIN кодын енгізіңіз."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" үшін SIM PIN кодын енгізіңіз."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Құрылғыны мобильдік қызметсіз пайдалану үшін eSIM картасын өшіріңіз."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"PIN кодын енгізіңіз"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Кілтсөзді енгізіңіз"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM картасы өшірілді. Жалғастыру үшін PUK кодын енгізіңіз. Толығырақ ақпаратты оператордан алыңыз."</string>
diff --git a/packages/SystemUI/res-keyguard/values-km/strings.xml b/packages/SystemUI/res-keyguard/values-km/strings.xml
index b4be59e..21d1d2b 100644
--- a/packages/SystemUI/res-keyguard/values-km/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-km/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"ប្រអប់បំពេញកូដ PUK របស់ស៊ីម"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"បានកំណត់ម៉ោងរោទិ៍បន្ទាប់នៅថ្ងៃ <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"លុប"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"បិទ eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"ភ្លេចលំនាំ"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"លំនាំមិនត្រឹមត្រូវទេ"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"កូដ PIN មិនត្រឹមត្រូវទេ"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"សូមព្យាយាមម្ដងទៀតក្នុងរយៈពេល <xliff:g id="NUMBER">%d</xliff:g> វិនាទីទៀត។"</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"គូរលំនាំរបស់អ្នក"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"បញ្ចូលកូដ PIN របស់ស៊ីម"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"បញ្ចូលកូដ PIN របស់ស៊ីមសម្រាប់ \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"បញ្ចូលកូដ PIN របស់ស៊ីម។"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"បញ្ចូលកូដ PIN របស់ស៊ីមសម្រាប់ \"<xliff:g id="CARRIER">%1$s</xliff:g>\"។"</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"បិទ eSIM ដើម្បីប្រើប្រាស់ឧបករណ៍ដោយមិនចាំបាច់ប្រើសេវាកម្មទិន្នន័យចល័ត។"</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"បញ្ចូលកូដ PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"បញ្ចូលពាក្យសម្ងាត់"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"ឥឡូវនេះ ស៊ីមត្រូវបានបិទដំណើរការហើយ។ បញ្ចូលកូដ PUK ដើម្បីបន្ត។ សូមទាក់ទងទៅក្រុមហ៊ុនបម្រើសេវាទូរសព្ទរបស់អ្នក ដើម្បីទទួលបានព័ត៌មានលម្អិត។"</string>
diff --git a/packages/SystemUI/res-keyguard/values-kn/strings.xml b/packages/SystemUI/res-keyguard/values-kn/strings.xml
index b2dd79e..c16dcb9 100644
--- a/packages/SystemUI/res-keyguard/values-kn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kn/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"ಸಿಮ್ PUK ಪ್ರದೇಶ"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"<xliff:g id="ALARM">%1$s</xliff:g> ಗಂಟೆಗೆ ಮುಂದಿನ ಅಲಾರಮ್ ಹೊಂದಿಸಲಾಗಿದೆ"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"ಅಳಿಸಿ"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"ನಮೂದಿಸಿ"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"ಪ್ಯಾಟರ್ನ್ ಮರೆತಿದ್ದೀರಿ"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"ಪ್ಯಾಟರ್ನ್ ತಪ್ಪಾಗಿದೆ"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"ಪಿನ್ ತಪ್ಪಾಗಿದೆ"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> ಸೆಕೆಂಡುಗಳಲ್ಲಿ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"ನಿಮ್ಮ ಪ್ಯಾಟರ್ನ್ ಚಿತ್ರಿಸಿ"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"ಸಿಮ್ ಪಿನ್ ನಮೂದಿಸಿ"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" ಗೆ ಸಿಮ್ ಪಿನ್ ನಮೂದಿಸಿ"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"ಸಿಮ್ ಪಿನ್ ನಮೂದಿಸಿ."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" ಗಾಗಿ ಸಿಮ್ ಪಿನ್ ನಮೂದಿಸಿ."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"ಮೊಬೈಲ್ ಸೇವೆ ಇಲ್ಲದೆಯೇ ಸಾಧನವನ್ನು ಬಳಸಲು eSIM ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"ಪಿನ್ ನಮೂದಿಸಿ"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"ಪಾಸ್ವರ್ಡ್ ನಮೂದಿಸಿ"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"ಈಗ ಸಿಮ್ ನಿಷ್ಕ್ರಿಯಗೊಂಡಿದೆ. ಮುಂದುವರೆಯಲು PUK ಕೋಡ್ ನಮೂದಿಸಿ. ವಿವರಗಳಿಗಾಗಿ ವಾಹಕವನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ko/strings.xml b/packages/SystemUI/res-keyguard/values-ko/strings.xml
index 70588d3..a402a97 100644
--- a/packages/SystemUI/res-keyguard/values-ko/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ko/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM PUK 영역"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"<xliff:g id="ALARM">%1$s</xliff:g>에 다음 알람이 설정됨"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"삭제"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM 사용 중지"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter 키"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"패턴을 잊음"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"잘못된 패턴"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"잘못된 PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g>초 후에 다시 시도하세요."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"패턴 그리기"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"SIM PIN 입력"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\'<xliff:g id="CARRIER">%1$s</xliff:g>\'의 SIM PIN 입력"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"SIM PIN을 입력하세요."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\'<xliff:g id="CARRIER">%1$s</xliff:g>\'의 SIM PIN을 입력하세요."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"모바일 서비스 없이 기기를 사용하려면 eSIM을 사용 중지하세요."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"PIN 입력"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"비밀번호 입력"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM이 사용 중지되었습니다. 계속하려면 PUK 코드를 입력하세요. 자세한 내용은 이동통신사에 문의하시기 바랍니다."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml
index 8cbd4f5..d9e033c 100644
--- a/packages/SystemUI/res-keyguard/values-ky/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ky/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM-картанын PUK-кодунун аймагы"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Кийинки ойготкуч саат <xliff:g id="ALARM">%1$s</xliff:g> коюлган"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Жок кылуу"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM-картаны өчүрүү"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Киргизүү"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Графикалык ачкычты унутуп калдым"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Графикалык ачкыч туура эмес"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN-код туура эмес"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> секунддан кийин кайталаңыз."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Графикалык ачкычты тартыңыз"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"SIM-картанын PIN-кодун киргизиңиз"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" байланыш операторунун SIM-картасынын PIN-кодун киргизиңиз"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"SIM-картанын PIN-кодун киргизиңиз."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" SIM-картасынын PIN-кодун киргизиңиз."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Түзмөктү мобилдик кызматсыз колдонуу үчүн eSIM-картаны өчүрүңүз."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"PIN-кодду киргизиңиз"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Сырсөздү киргизиңиз"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM-карта азыр жарактан чыкты. Улантуу үчүн PUK-кодду киргизиңиз. Анын чоо-жайын билүү үчүн байланыш операторуна кайрылыңыз."</string>
diff --git a/packages/SystemUI/res-keyguard/values-lo/strings.xml b/packages/SystemUI/res-keyguard/values-lo/strings.xml
index d063827..f253386 100644
--- a/packages/SystemUI/res-keyguard/values-lo/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lo/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"ພື້ນທີ່ PUK ຂອງ SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"ໂມງປຸກຕໍ່ໄປຖືກຕັ້ງໄວ້ເວລາ <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"ລຶບ"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"ປິດການໃຊ້ eSIM ແລ້ວ"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"ປ້ອນເຂົ້າ"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"ລືມຮູບແບບປົດລັອກ?"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"ຮູບແບບຜິດ"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"ລະຫັດ PIN ບໍ່ຖືກຕ້ອງ"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"ລອງໃໝ່ໃນອີກ <xliff:g id="NUMBER">%d</xliff:g> ວິນາທີ."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"ແຕ້ມຮູບແບບປົດລັອກຂອງທ່ານ"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"ໃສ່ລະຫັດ PIN ຂອງຊິມ"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"ປ້ອນ SIM PIN ສຳລັບ \"<xliff:g id="CARRIER">%1$s</xliff:g>\" ເຂົ້າໄປ"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"ໃສ່ລະຫັດ PIN ຂອງຊິມ."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"ໃສ່ລະຫັດ PIN ຂອງຊິມສຳລັບ \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"ປິດການໃຊ້ eSIM ເພື່ອໃຊ້ອຸປະກອນໂດຍບໍ່ມີບໍລິການມືຖື."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"ໃສ່ລະຫັດ PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"ໃສ່ລະຫັດຜ່ານ"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"ຊິມຖືກປິດການນຳໃຊ້ແລ້ວ. ປ້ອນລະຫັດ PUK ເພື່ອດຳເນີນການຕໍ່. ຕິດຕໍ່ຜູ່ໃຫ້ບໍລິການສຳລັບລາຍລະອຽດ."</string>
diff --git a/packages/SystemUI/res-keyguard/values-lt/strings.xml b/packages/SystemUI/res-keyguard/values-lt/strings.xml
index 2dd3ed7..11de6e2 100644
--- a/packages/SystemUI/res-keyguard/values-lt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lt/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM kortelės PUK kodo sritis"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Kitas nustatytas signalas: <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Ištrinti"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Išjungti eSIM kortelę"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Pamiršau atrakinimo piešinį"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Netinkamas atrakinimo piešinys"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Netinkamas PIN kodas"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Bandykite dar kartą po <xliff:g id="NUMBER">%d</xliff:g> sek."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Nupieškite atrakinimo piešinį"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Įveskite SIM kortelės PIN kodą"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Įveskite „<xliff:g id="CARRIER">%1$s</xliff:g>“ SIM kortelės PIN kodą"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Įveskite SIM kortelės PIN kodą."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Įveskite „<xliff:g id="CARRIER">%1$s</xliff:g>“ SIM kortelės PIN kodą"</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Išjungti eSIM kortelę ir naudoti įrenginį be mobiliojo ryšio paslaugos."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Įveskite PIN kodą"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Įveskite slaptažodį"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"Dabar SIM neleidžiama. Jei norite tęsti, įveskite PUK kodą. Jei reikia išsamios informacijos, susisiekite su operatoriumi."</string>
diff --git a/packages/SystemUI/res-keyguard/values-lv/strings.xml b/packages/SystemUI/res-keyguard/values-lv/strings.xml
index 26ed490..51df25f9 100644
--- a/packages/SystemUI/res-keyguard/values-lv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lv/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM kartes PUK apgabals"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Nākamā signāla atskaņošanas laiks: <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Dzēšanas taustiņš"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Atspējot eSIM karti"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Ievadīšanas taustiņš"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Aizmirsu kombināciju"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Nepareiza kombinācija."</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Nepareizs PIN kods."</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Mēģiniet vēlreiz pēc <xliff:g id="NUMBER">%d</xliff:g> sekundes(-ēm)."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Zīmējiet savu kombināciju."</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Ievadiet SIM kartes PIN kodu"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Ievadiet SIM kartes “<xliff:g id="CARRIER">%1$s</xliff:g>” PIN kodu"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Ievadiet SIM kartes PIN kodu."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Ievadiet SIM kartes “<xliff:g id="CARRIER">%1$s</xliff:g>” PIN kodu."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Atspējojiet eSIM karti, lai ierīci varētu izmantot bez mobilā pakalpojuma."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Ievadiet PIN."</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Ievadiet paroli"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM karte ir atspējota. Lai turpinātu, ievadiet PUK kodu. Lai iegūtu detalizētu informāciju, sazinieties ar mobilo sakaru operatoru."</string>
diff --git a/packages/SystemUI/res-keyguard/values-mk/strings.xml b/packages/SystemUI/res-keyguard/values-mk/strings.xml
index e757c51..eb1eaa5 100644
--- a/packages/SystemUI/res-keyguard/values-mk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mk/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Поле за PUK на SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Следниот аларм е поставен во <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Избриши"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Оневозможи ја eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Внеси"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Ја заборавивте шемата?"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Погрешна шема"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Погрешен PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Обидете се повторно за <xliff:g id="NUMBER">%d</xliff:g> секунди."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Нацртајте ја шемата"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Внесете PIN на SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Внесете PIN на SIM за „<xliff:g id="CARRIER">%1$s</xliff:g>“"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Внесете PIN на SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Внесете PIN на SIM за „<xliff:g id="CARRIER">%1$s</xliff:g>“."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Оневозможете ја eSIM за да го користите уредот без мобилна услуга."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Внесете PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Внесете лозинка"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM-картичката сега е оневозможена. Внесете PUK-код за да продолжите. Контактирајте со операторот за детали."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml
index 5abf129..043e7c2 100644
--- a/packages/SystemUI/res-keyguard/values-ml/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ml/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"സിം PUK ഏരിയ"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"അടുത്ത അലാറം <xliff:g id="ALARM">%1$s</xliff:g>-ന് സജ്ജീകരിച്ചു"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"ഇല്ലാതാക്കുക"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM പ്രവർത്തനരഹിതമാക്കുക"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"എന്റർ"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"പാറ്റേൺ മറന്നു"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"പാറ്റേൺ തെറ്റാണ്"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"പിൻ തെറ്റാണ്"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> സെക്കന്റിനുശേഷം വീണ്ടും ശ്രമിക്കുക."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"നിങ്ങളുടെ പാറ്റേൺ വരയ്ക്കുക"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"സിം പിൻ നൽകുക"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" എന്നതിനുള്ള സിം പിൻ നൽകുക"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"സിം പിൻ നൽകുക."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" എന്ന കാരിയർക്കുള്ള സിം പിൻ നൽകുക."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"മൊബൈൽ സേവനമില്ലാതെ ഉപകരണം ഉപയോഗിക്കാൻ eSIM പ്രവർത്തനരഹിതമാക്കുക."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"പിൻ നൽകുക"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"പാസ്വേഡ് നൽകുക"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"സിം ഇപ്പോൾ പ്രവർത്തനരഹിതമാക്കി. തുടരുന്നതിന് PUK കോഡ് നൽകുക. വിശദാംശങ്ങൾക്ക് കാരിയറെ ബന്ധപ്പെടുക."</string>
diff --git a/packages/SystemUI/res-keyguard/values-mn/strings.xml b/packages/SystemUI/res-keyguard/values-mn/strings.xml
index eb045bb..360b09e 100644
--- a/packages/SystemUI/res-keyguard/values-mn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mn/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM-н PUK кодын хэсэг"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Дараагийн сэрүүлгийг <xliff:g id="ALARM">%1$s</xliff:g>-д тавьсан"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Устгах"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM-г идэвхгүй болгох"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Оруулах"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Загварыг мартсан"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Загвар буруу байна"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"ПИН код буруу байна"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> секундын дараа дахин оролдоно уу."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Загварыг оруулна уу"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"SIM-н ПИН-г оруулна уу"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\"-н SIM-н ПИН-г оруулна уу"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"SIM-н ПИН-г оруулна уу."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\"-н SIM-н ПИН-г оруулна уу."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Төхөөрөмжийг мобайл үйлчилгээгүй ашиглахын тулд eSIM-г идэвхгүй болгоно уу."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"ПИН оруулна уу"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Нууц үг оруулна уу"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM идэвхгүй байна. Үргэлжлүүлэх бол PUK кодыг оруулна уу. Дэлгэрэнгүй мэдээлэл авах бол оператор компанитайгаа холбогдоно уу."</string>
diff --git a/packages/SystemUI/res-keyguard/values-mr/strings.xml b/packages/SystemUI/res-keyguard/values-mr/strings.xml
index 946a2dd..7bb8ae5 100644
--- a/packages/SystemUI/res-keyguard/values-mr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mr/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"सिम PUK क्षेत्र"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"पुढील अलार्म <xliff:g id="ALARM">%1$s</xliff:g> साठी सेट केला"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"हटवा"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM बंद करा"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"प्रविष्ट करा"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"नमुना विसरलात"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"चुकीचा नमुना"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"चुकीचा पिन"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"आपला नमुना काढा"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"सिम पिन प्रविष्ट करा"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" साठी सिम पिन प्रविष्ट करा"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"सिम पिन एंटर करा"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" साठी सिम पिन एंटर करा"</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"मोबाइल सेवांशिवाय डिव्हाइस वापरण्यासाठी eSIM बंद करा."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"पिन प्रविष्ट करा"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"संकेतशब्द प्रविष्ट करा"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"सिम आता अक्षम केले आहे. सुरू ठेवण्यासाठी PUK कोड प्रविष्ट करा. तपशीलांसाठी वाहकाशी संपर्क साधा."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ms/strings.xml b/packages/SystemUI/res-keyguard/values-ms/strings.xml
index 197965f..144e507 100644
--- a/packages/SystemUI/res-keyguard/values-ms/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ms/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Bahagian PUK SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Penggera seterusnya ditetapkan pada <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Padam"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Lumpuhkan eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Kekunci Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Terlupa Corak"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Corak salah"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN salah"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Cuba lagi dalam <xliff:g id="NUMBER">%d</xliff:g> saat."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Lukis corak anda"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Masukkan PIN SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Masukkan PIN SIM untuk \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Masukkan PIN SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Masukkan PIN SIM untuk \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Lumpuhkan eSIM untuk menggunakan peranti tanpa perkhidmatan mudah alih."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Masukkan PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Masukkan Kata Laluan"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM kini dilumpuhkan. Masukkan kod PUK untuk meneruskan. Hubungi pembawa untuk mendapatkan butiran."</string>
diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml
index b052b21..d3e22b4 100644
--- a/packages/SystemUI/res-keyguard/values-my/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-my/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"ဆင်းမ် ပင်နံပါတ် ပြန်ဖွင့်သည့်ကုဒ် နေရာ"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"နောက်နှိုးစက်အချိန်ကို <xliff:g id="ALARM">%1$s</xliff:g> တွင် သတ်မှတ်ထားပါသည်"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"ဖျက်ရန်"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM ကို ပိတ်ရန်"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter ခလုတ်"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"ပုံစံအား မေ့သွားပါသည်"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"ပုံစံ မမှန်ကန်ပါ"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"ပင်နံပါတ် မမှန်ကန်ပါ"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> စက္ကန့်အကြာတွင် ထပ်လုပ်ကြည့်ပါ။"</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"ပုံစံကို ဆွဲပါ"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"ဆင်းမ်ကဒ် ပင်နံပါတ်ကို ထည့်ပါ"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" အတွက် ဆင်းမ်ပင်နံပါတ်ကို ထည့်ပါ"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"ဆင်းမ်ကဒ် ပင်နံပါတ်ကို ထည့်ပါ။"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" အတွက် ဆင်းမ်ကဒ်ပင်နံပါတ်ကို ထည့်ပါ။"</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"စက်ပစ္စည်းကို မိုဘိုင်းဝန်ဆောင်မှုမပါဘဲ အသုံးပြုရန် eSIM ကို ပိတ်ပါ။"</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"ပင်နံပါတ်ကို ထည့်ပါ"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"စကားဝှက်ကို ထည့်ပါ"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"ဆင်းမ်ကဒ်သည် ယခု ပိတ်သွားပါပြီ။ ရှေ့ဆက်ရန် ပင်နံပါတ် ပြန်ဖွင့်သည့် ကုဒ်ကို ထည့်ပါ။ ပိုမိုလေ့လာရန် မိုဘိုင်းဝန်ဆောင်မှုပေးသူကို ဆက်သွယ်နိုင်ပါသည်။"</string>
diff --git a/packages/SystemUI/res-keyguard/values-nb/strings.xml b/packages/SystemUI/res-keyguard/values-nb/strings.xml
index 2ba38ec..7e61e6f 100644
--- a/packages/SystemUI/res-keyguard/values-nb/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nb/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"PUK-området for SIM-kortet"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Neste alarm er stilt inn for <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Slett"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Deaktiver e-SIM-kortet"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Har du glemt mønsteret?"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Feil mønster"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Feil PIN-kode"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Prøv på nytt om <xliff:g id="NUMBER">%d</xliff:g> sekunder."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Tegn mønsteret ditt"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Skriv inn PIN-koden for SIM-kortet"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Skriv inn PIN-koden for SIM-kortet «<xliff:g id="CARRIER">%1$s</xliff:g>»"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Skriv inn PIN-koden for SIM-kortet."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Skriv inn PIN-koden for SIM-kortet «<xliff:g id="CARRIER">%1$s</xliff:g>»."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Deaktiver e-SIM-kortet for å bruke enheten uten mobiltjeneste."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Skriv inn PIN-koden"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Skriv inn passordet"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM-kortet er nå deaktivert. Skriv inn PUK-koden for å fortsette. Ta kontakt med operatøren for mer informasjon."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml
index fa5f284..4750163 100644
--- a/packages/SystemUI/res-keyguard/values-ne/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM को PUK क्षेत्र"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"अर्को अलार्म <xliff:g id="ALARM">%1$s</xliff:g> का लागि सेट गरियो"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"मेट्नुहोस्"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM लाई असक्षम पार्नुहोस्"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"प्रविष्टि गर्नुहोस्"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"ढाँचा बिर्सनुभयो"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"गलत ढाँचा"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"गलत PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> सेकेन्डमा फेरि प्रयास गर्नुहोस्।"</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"आफ्नो ढाँचा कोर्नुहोस्"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"SIM को PIN प्रविष्टि गर्नुहोस्"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"<xliff:g id="CARRIER">%1$s</xliff:g> को SIM PIN प्रविष्ट गर्नुहोस्"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"SIM को PIN प्रविष्टि गर्नुहोस्।"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" को SIM को PIN प्रविष्ट गर्नुहोस्।"</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"मोबाइल सेवा बिना यन्त्रको प्रयोग गर्न eSIM लाई असक्षम पार्नुहोस्।"</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"PIN प्रविष्टि गर्नुहोस्"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"पासवर्ड प्रविष्ट गर्नुहोस्"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM कार्ड अहिले असक्षम छ। सुचारु गर्नको लागि PUK कोड प्रविष्ट गर्नुहोस्। विवरणको लागि सेवा प्रदायकलाई सम्पर्क गर्नुहोस्।"</string>
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index 4da7398..eb27c2b 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Gebied voor pukcode van simkaart"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Volgende alarm ingesteld voor <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Delete"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Simkaart uitschakelen"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Patroon vergeten"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Onjuist patroon"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Onjuiste pincode"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Probeer het over <xliff:g id="NUMBER">%d</xliff:g> seconden opnieuw."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Teken je patroon"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Geef de pincode van de simkaart op"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Geef de pincode op voor de simkaart van \'<xliff:g id="CARRIER">%1$s</xliff:g>\'"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Geef de pincode van de simkaart op."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Geef de pincode voor de simkaart van \'<xliff:g id="CARRIER">%1$s</xliff:g>\' op."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Schakel de e-simkaart uit om het apparaat te gebruiken zonder mobiele service."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Geef je pincode op"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Geef je wachtwoord op"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"De simkaart is nu uitgeschakeld. Geef de pukcode op om door te gaan. Neem contact op met de provider voor informatie."</string>
diff --git a/packages/SystemUI/res-keyguard/values-pa/strings.xml b/packages/SystemUI/res-keyguard/values-pa/strings.xml
index 82bd400..be98391 100644
--- a/packages/SystemUI/res-keyguard/values-pa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pa/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM PUK ਖੇਤਰ"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"ਅਗਲਾ ਅਲਾਰਮ <xliff:g id="ALARM">%1$s</xliff:g> \'ਤੇ ਸੈੱਟ ਕੀਤਾ ਗਿਆ"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"ਮਿਟਾਓ"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM ਨੂੰ ਅਯੋਗ ਬਣਾਓ"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"ਦਾਖਲ ਕਰੋ"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"ਪੈਟਰਨ ਭੁੱਲ ਗਏ"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"ਗਲਤ ਪੈਟਰਨ"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"ਗਲਤ PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> ਸਕਿੰਟਾਂ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"ਆਪਣਾ ਪੈਟਰਨ ਉਲੀਕੋ"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"SIM PIN ਦਾਖਲ ਕਰੋ"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" ਲਈ SIM PIN ਦਾਖਲ ਕਰੋ"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"SIM PIN ਦਾਖਲ ਕਰੋ।"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" ਲਈ SIM PIN ਦਾਖਲ ਕਰੋ।"</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"ਮੋਬਾਈਲ ਸੇਵਾ ਤੋਂ ਬਿਨਾਂ ਡੀਵਾਈਸ ਨੂੰ ਵਰਤਣ ਲਈ eSIM ਅਯੋਗ ਬਣਾਓ।"</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"PIN ਦਾਖਲ ਕਰੋ"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"ਪਾਸਵਰਡ ਦਾਖਲ ਕਰੋ"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM ਹੁਣ ਅਯੋਗ ਬਣਾਇਆ ਗਿਆ ਹੈ। ਜਾਰੀ ਰੱਖਣ ਲਈ PUK ਕੋਡ ਦਾਖਲ ਕਰੋ। ਵੇਰਵਿਆਂ ਲਈ ਕੈਰੀਅਰ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pl/strings.xml b/packages/SystemUI/res-keyguard/values-pl/strings.xml
index a71cd8b..71a2ce6 100644
--- a/packages/SystemUI/res-keyguard/values-pl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pl/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Miejsce na kod PUK karty SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Następny alarm ustawiony na: <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Usuwanie"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Wyłącz eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Nie pamiętam wzoru"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Nieprawidłowy wzór"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Nieprawidłowy kod PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Spróbuj ponownie za <xliff:g id="NUMBER">%d</xliff:g> s."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Narysuj wzór"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Wpisz kod PIN karty SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Wpisz kod PIN karty SIM „<xliff:g id="CARRIER">%1$s</xliff:g>”"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Wpisz kod PIN karty SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Wpisz kod PIN karty SIM „<xliff:g id="CARRIER">%1$s</xliff:g>”."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Wyłącz eSIM, by używać urządzenia bez usługi sieci komórkowej."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Wpisz kod PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Wpisz hasło"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"Karta SIM została wyłączona. Wpisz kod PUK, by przejść dalej. Skontaktuj się z operatorem, by uzyskać więcej informacji."</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
index 8206b4f..0d1f9d0 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Área do PUK SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Próximo alarme definido para <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Excluir"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Desativar eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Inserir"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Esqueci o padrão"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Padrão incorreto"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN incorreto"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Tente novamente em <xliff:g id="NUMBER">%d</xliff:g> segundos."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Desenhe seu padrão"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Digite o PIN do cartão SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Informe o PIN do SIM para \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Informe o PIN do cartão SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Informe o PIN do cartão SIM para \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Desative o eSIM para usar o dispositivo sem serviço móvel."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Digite o PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Digite a senha"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"O SIM foi desativado. Insira o código PUK para continuar. Entre em contato com a operadora para mais detalhes."</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
index 7e208c2..f3fa491 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Área do PUK do cartão SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Próximo alarme definido para <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Eliminar"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Desativar eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Tecla Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Esqueceu-se do padrão"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Padrão incorreto"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN incorreto"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Tente novamente dentro de <xliff:g id="NUMBER">%d</xliff:g> segundos."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Desenhe o seu padrão"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Introduza o PIN do cartão SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Introduza o PIN do cartão SIM \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Introduza o PIN do cartão SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Introduza o PIN do cartão SIM \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Desativar eSIM para utilizar sem serviço móvel"</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Introduza o PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Introduza a palavra-passe"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"O cartão SIM está agora desativado. Introduza o código PUK para continuar. Contacte o operador para obter mais detalhes."</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt/strings.xml b/packages/SystemUI/res-keyguard/values-pt/strings.xml
index 8206b4f..0d1f9d0 100644
--- a/packages/SystemUI/res-keyguard/values-pt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Área do PUK SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Próximo alarme definido para <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Excluir"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Desativar eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Inserir"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Esqueci o padrão"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Padrão incorreto"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN incorreto"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Tente novamente em <xliff:g id="NUMBER">%d</xliff:g> segundos."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Desenhe seu padrão"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Digite o PIN do cartão SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Informe o PIN do SIM para \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Informe o PIN do cartão SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Informe o PIN do cartão SIM para \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Desative o eSIM para usar o dispositivo sem serviço móvel."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Digite o PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Digite a senha"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"O SIM foi desativado. Insira o código PUK para continuar. Entre em contato com a operadora para mais detalhes."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml
index 5e75e5b..3e57bec 100644
--- a/packages/SystemUI/res-keyguard/values-ro/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Zona codului PUK pentru cardul SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Următoarea alarmă este setată pentru <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Ștergeți"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Dezactivați cardul eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Introduceți"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Ați uitat modelul"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Model greșit"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Cod PIN greșit"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Încercați din nou peste <xliff:g id="NUMBER">%d</xliff:g> secunde."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Desenați modelul"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Introduceți codul PIN al cardului SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Introduceți codul PIN al cardului SIM pentru „<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Introduceți codul PIN al cardului SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Introduceți codul PIN al cardului SIM pentru „<xliff:g id="CARRIER">%1$s</xliff:g>”."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Dezactivați cardul eSIM pentru a folosi dispozitivul fără serviciu mobil."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Introduceți codul PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Introduceți parola"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"Cardul SIM este acum dezactivat. Pentru a continua, introduceți codul PUK. Pentru detalii, contactați operatorul."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml
index 1bdaaf4..ef637b4 100644
--- a/packages/SystemUI/res-keyguard/values-ru/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"PUK-код SIM-карты"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Время следующего сигнала будильника: <xliff:g id="ALARM">%1$s</xliff:g>."</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Удалить"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Отключить eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Клавиша ввода"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Забыли графический ключ?"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Неверный графический ключ"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Неверный PIN-код"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Повторите попытку через <xliff:g id="NUMBER">%d</xliff:g> сек."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Введите графический ключ"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Введите PIN-код SIM-карты"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Введите PIN-код SIM-карты \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Введите PIN-код SIM-карты."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Введите PIN-код SIM-карты \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Чтобы пользоваться устройством без мобильной связи, отключите eSIM."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Введите PIN-код"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Введите пароль"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM-карта отключена. Чтобы продолжить, введите PUK-код. За подробной информацией обратитесь к оператору связи."</string>
diff --git a/packages/SystemUI/res-keyguard/values-si/strings.xml b/packages/SystemUI/res-keyguard/values-si/strings.xml
index 2854853..d60b50e 100644
--- a/packages/SystemUI/res-keyguard/values-si/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-si/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM PUK කොටස"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"<xliff:g id="ALARM">%1$s</xliff:g>ට ඊළඟ එලාමය සකසා ඇත"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"මකන්න"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM අබල කරන්න"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"ඇතුල් කරන්න"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"රටාව අමතකයි"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"වැරදි රටාවකි"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN එක වැරදියි"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"තත්පර <xliff:g id="NUMBER">%d</xliff:g> කින් නැවත උත්සහ කරන්න."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"ඔබගේ රටාව අඳින්න"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"SIM PIN ඇතුලු කරන්න"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" සඳහා SIM PIN ඇතුළත් කරන්න"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"SIM PIN ඇතුළු කරන්න"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" සඳහා SIM PIN ඇතුළු කරන්න"</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"ජංගම සේවාවෙන් තොරව උපාංගය භාවිත කිරීමට eSIM අබල කරන්න."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"PIN එක ඇතුළු කරන්න"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"මුරපදය ඇතුළු කරන්න"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"දැන් SIM එක අබල කර ඇත. ඉදිරියට යාමට PUK කේතය යොදන්න. විස්තර සඳහා වාහකයා අමතන්න."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sk/strings.xml b/packages/SystemUI/res-keyguard/values-sk/strings.xml
index f88055d..4b3a7f7 100644
--- a/packages/SystemUI/res-keyguard/values-sk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sk/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Oblasť kódu PUK SIM karty"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Nasledujúci budík je nastavený na <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Odstrániť"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Zakázať eSIM kartu"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Nepamätám si vzor"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Nesprávny vzor"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Nesprávny kód PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Skúste to znova o <xliff:g id="NUMBER">%d</xliff:g> s."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Nakreslite svoj vzor"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Zadajte PIN SIM karty"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Zadajte kód PIN pre SIM kartu operátora <xliff:g id="CARRIER">%1$s</xliff:g>"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Zadajte PIN pre SIM kartu"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Zadajte kód PIN pre SIM kartu operátora <xliff:g id="CARRIER">%1$s</xliff:g>."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Zakážte eSIM kartu a používajte zariadenie bez mobilnej služby."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Zadajte kód PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Zadajte heslo"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM karta je teraz zakázaná. Ak chcete pokračovať, zadajte kód PUK. Podrobné informácie získate od operátora."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sl/strings.xml b/packages/SystemUI/res-keyguard/values-sl/strings.xml
index 882ba22..a523625 100644
--- a/packages/SystemUI/res-keyguard/values-sl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sl/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Območje za kodo PUK kartice SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Naslednji alarm je nastavljen za <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Izbris"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Onemogoči kartico e-SIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Tipka Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Pozabljen vzorec"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Napačen vzorec"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Napačna koda PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Poskusite znova čez <xliff:g id="NUMBER">%d</xliff:g> s."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Narišite vzorec"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Vnesite kodo PIN kartice SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Vnesite kodo PIN kartice SIM operaterja »<xliff:g id="CARRIER">%1$s</xliff:g>«"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Vnesite kodo PIN kartice SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Vnesite kodo PIN kartice SIM operaterja »<xliff:g id="CARRIER">%1$s</xliff:g>«."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Onemogočite kartico e-SIM, če želite napravo uporabljati brez mobilne storitve."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Vnesite kodo PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Vnesite geslo"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"Kartica SIM je onemogočena. Če želite nadaljevati, vnesite kodo PUK. Za dodatne informacije se obrnite na operaterja."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sq/strings.xml b/packages/SystemUI/res-keyguard/values-sq/strings.xml
index d3f1f3a..d65a800 100644
--- a/packages/SystemUI/res-keyguard/values-sq/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sq/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Zona e kodit PUK të kartës SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Alarmi tjetër i caktuar: <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Fshi"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Çaktivizo kartën eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Dërgo"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Harrova motivin"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Motivi është i gabuar"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Kod PIN i gabuar"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Provo përsëri për <xliff:g id="NUMBER">%d</xliff:g> sekonda."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Vizato motivin tënd"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Fut kodin PIN të kartës SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Fut kodin PIN të kartës SIM të \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Fut kodin PIN të kartës SIM"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Fut kodin PIN të kartës SIM për \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Çaktivizo kartën eSIM për ta përdorur pajisjen pa shërbimin celular."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Fut kodin PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Fut fjalëkalimin"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"Karta SIM tani është e çaktivizuar. Fut kodin PUK për të vazhduar. Kontakto me operatorin për detaje."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sr/strings.xml b/packages/SystemUI/res-keyguard/values-sr/strings.xml
index 683eef6..bd610c5 100644
--- a/packages/SystemUI/res-keyguard/values-sr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sr/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Област за PUK за SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Следећи аларм је подешен за <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Избриши"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Онемогући eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Заборавио/ла сам шаблон"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Погрешан шаблон"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Погрешан PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Пробајте поново за <xliff:g id="NUMBER">%d</xliff:g> сек."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Нацртајте шаблон"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Унесите PIN за SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Унесите PIN за SIM „<xliff:g id="CARRIER">%1$s</xliff:g>“"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Унесите PIN за SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Унесите PIN за SIM „<xliff:g id="CARRIER">%1$s</xliff:g>“."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Онемогућите eSIM да бисте уређај користили без мобилне услуге."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Унесите PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Унесите лозинку"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM картица је сада онемогућена. Унесите PUK кôд да бисте наставили. Детаљне информације потражите од мобилног оператера."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sv/strings.xml b/packages/SystemUI/res-keyguard/values-sv/strings.xml
index 47334f8..f7eec5c 100644
--- a/packages/SystemUI/res-keyguard/values-sv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sv/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"PUK-kodsområde för SIM-kort"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Nästa alarm är inställt på <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Radera"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Inaktivera eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Retur"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Har du glömt ditt grafiska lösenord?"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Fel grafiskt lösenord"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Fel pinkod"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Försök igen om <xliff:g id="NUMBER">%d</xliff:g> sekunder."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Rita ditt grafiska lösenord"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Ange pinkod för SIM-kortet"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Ange pinkod för SIM-kortet för <xliff:g id="CARRIER">%1$s</xliff:g>"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Ange pinkod för SIM-kortet."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Ange pinkod för SIM-kortet för <xliff:g id="CARRIER">%1$s</xliff:g>."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Inaktivera eSIM om du vill använda enheten utan mobiltjänst."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Ange pinkod"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Ange lösenord"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM-kortet har inaktiverats. Du måste ange en PUK-kod innan du kan fortsätta. Kontakta operatören för mer information."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sw/strings.xml b/packages/SystemUI/res-keyguard/values-sw/strings.xml
index b2991d2..9a10266 100644
--- a/packages/SystemUI/res-keyguard/values-sw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sw/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Eneo la PUK ya SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Kengele inayofuata italia saa <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Futa"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Zima eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Weka"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Umesahau Mchoro"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Mchoro si Sahihi"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Nambari ya PIN si sahihi"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Jaribu tena baada ya sekunde <xliff:g id="NUMBER">%d</xliff:g>."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Chora mchoro wako"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Weka PIN ya SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Weka PIN ya SIM ya \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Weka PIN ya SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Weka PIN ya SIM ya \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Zima eSIM ili utumie kifaa bila huduma ya vifaa vya mkononi."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Weka PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Weka Nenosiri"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM sasa imefungwa. Weka nambari ya PUK ili uendelee. Wasiliana na mtoa huduma za mtandao kwa maelezo."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml
index 436d000..5f2db2c 100644
--- a/packages/SystemUI/res-keyguard/values-ta/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"சிம் PUKக்கான பகுதி"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"அடுத்த அலாரம் <xliff:g id="ALARM">%1$s</xliff:g>க்கு அமைக்கப்பட்டுள்ளது"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"நீக்கும் பொத்தான்"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"இ-சிம்மை முடக்கும்"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"என்டர் பொத்தான்"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"பேட்டர்ன் நினைவில்லையா"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"தவறான பேட்டர்ன்"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"தவறான பின்"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> வினாடிகளில் மீண்டும் முயலவும்."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"பேட்டர்னை வரையவும்"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"சிம் பின்னை உள்ளிடவும்"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\"க்கான சிம் பின்னை உள்ளிடவும்"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"சிம் பின்னை உள்ளிடவும்."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\"க்கான சிம் பின்னை உள்ளிடவும்."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"மொபைல் சேவை இல்லாமல் சாதனத்தைப் பயன்படுத்த, இ-சிம்மை முடக்கவும்."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"பின்னை உள்ளிடவும்"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"கடவுச்சொல்லை உள்ளிடவும்"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"சிம் தற்போது முடக்கப்பட்டுள்ளது. தொடர, PUK குறியீட்டை உள்ளிடவும். விவரங்களுக்கு, தொலைத்தொடர்பு நிறுவனத்தைத் தொடர்புகொள்ளவும்."</string>
diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml
index 1b635659..3973bce 100644
--- a/packages/SystemUI/res-keyguard/values-te/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-te/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM PUK ప్రాంతం"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"తదుపరి అలారం <xliff:g id="ALARM">%1$s</xliff:g>కి సెట్ చేయబడింది"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"తొలగించు"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIMని నిలిపివేయండి"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"నమూనాను మర్చిపోయాను"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"నమూనా తప్పు"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"పిన్ తప్పు"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> సెకన్లలో మళ్లీ ప్రయత్నించండి."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"మీ నమూనాను గీయండి"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"SIM పిన్ను నమోదు చేయండి"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" కోసం SIM పిన్ నమోదు చేయండి"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"SIM పిన్ని నమోదు చేయండి."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" కోసం SIM పిన్ని నమోదు చేయండి."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"మొబైల్ సేవ లేకుండా డివైజ్ని ఉపయోగించడం కోసం eSIMని నిలిపివేయండి."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"పిన్ను నమోదు చేయండి"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"పాస్వర్డ్ని నమోదు చేయండి"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"ఇప్పుడు SIM నిలిపివేయబడింది. కొనసాగించాలంటే, PUK కోడ్ను నమోదు చేయండి. వివరాల కోసం క్యారియర్ను సంప్రదించండి."</string>
diff --git a/packages/SystemUI/res-keyguard/values-th/strings.xml b/packages/SystemUI/res-keyguard/values-th/strings.xml
index 0472c16..8d69e61 100644
--- a/packages/SystemUI/res-keyguard/values-th/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-th/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"พื้นที่ PUK ของซิม"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"ตั้งเวลาปลุกครั้งถัดไปไว้ที่ <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"ลบ"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"ปิดใช้ซิมอิเล็กทรอนิกส์"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"ลืมรูปแบบ"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"รูปแบบไม่ถูกต้อง"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN ไม่ถูกต้อง"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"ลองอีกครั้งในอีก <xliff:g id="NUMBER">%d</xliff:g> วินาที"</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"วาดรูปแบบของคุณ"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"ป้อน PIN ของซิม"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"ป้อน PIN ของซิมสำหรับ \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"ป้อน PIN ของซิม"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"ป้อน PIN ของซิมสำหรับ \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"ปิดใช้ซิมอิเล็กทรอนิกส์เพื่อใช้อุปกรณ์โดยไม่มีบริการมือถือ"</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"ป้อน PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"ป้อนรหัสผ่าน"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"ซิมการ์ดถูกปิดใช้แล้ว ป้อนรหัส PUK เพื่อดำเนินการต่อ โปรดสอบถามรายละเอียดจากผู้ให้บริการ"</string>
diff --git a/packages/SystemUI/res-keyguard/values-tl/strings.xml b/packages/SystemUI/res-keyguard/values-tl/strings.xml
index 0b7aa13..f34718e 100644
--- a/packages/SystemUI/res-keyguard/values-tl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tl/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Lugar ng PUK ng SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Nakatakda ang susunod na alarm sa <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"I-delete"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"I-disable ang eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Nakalimutan ang Pattern"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Mali ang Pattern"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Mali ang PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Subukang muli sa loob ng <xliff:g id="NUMBER">%d</xliff:g> (na) segundo."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Iguhit ang iyong pattern"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Ilagay ang PIN ng SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Ilagay ang PIN ng SIM para sa \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Ilagay ang PIN ng SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Ilagay ang PIN ng SIM para sa \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"I-disable ang eSIM upang magamit ang device nang walang serbisyo sa mobile."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Ilagay ang PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Ilagay ang Password"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"Naka-disable na ngayon ang SIM. Ilagay ang PUK code upang magpatuloy. Makipag-ugnayan sa carrier para sa mga detalye."</string>
diff --git a/packages/SystemUI/res-keyguard/values-tr/strings.xml b/packages/SystemUI/res-keyguard/values-tr/strings.xml
index f108095..183af79 100644
--- a/packages/SystemUI/res-keyguard/values-tr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tr/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM PUK alanı"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Sonraki alarm <xliff:g id="ALARM">%1$s</xliff:g> olarak ayarlandı"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Delete"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM\'i devre dışı bırak"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Deseni unuttunuz mu?"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Yanlış Desen"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Yanlış PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> saniye içinde yeniden deneyin."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Deseninizi çizin"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"SIM PIN kodunu girin"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" için SIM PIN\'ini kodunu girin"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"SIM PIN kodunu girin."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" için SIM PIN kodunu girin."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Cihazı mobil hizmet olmadan kullanmak için eSIM\'i devre dışı bırakın."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"PIN\'i girin"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Şifreyi Girin"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM kart artık devre dışı bırakıldı. Devam etmek için PUK kodunu girin. Ayrıntılı bilgi için operatörle bağlantı kurun."</string>
diff --git a/packages/SystemUI/res-keyguard/values-uk/strings.xml b/packages/SystemUI/res-keyguard/values-uk/strings.xml
index fc52ce3..a2f6743 100644
--- a/packages/SystemUI/res-keyguard/values-uk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uk/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"PUK-код SIM-карти"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Наступний сигнал: <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Видалити"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Вимкнути eSIM-карту"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Ввести"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Не пам’ятаю ключ"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Неправильний ключ"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Неправильний PIN-код"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Повторіть спробу через <xliff:g id="NUMBER">%d</xliff:g> с."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Намалюйте ключ"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Введіть PIN-код SIM-карти"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Введіть PIN-код SIM-карти для оператора \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Введіть PIN-код SIM-карти."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Введіть PIN-код SIM-карти для оператора \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Вимкнути eSIM-карту, щоб використовувати пристрій без мобільного зв’язку."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Введіть PIN-код"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Введіть пароль"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"Зараз SIM-карту вимкнено. Введіть PUK-код, щоб продовжити. Зв’яжіться з оператором, щоб дізнатися більше."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml
index 1258547..1b20118 100644
--- a/packages/SystemUI/res-keyguard/values-ur/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM PUK کا علاقہ"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"اگلا الارم <xliff:g id="ALARM">%1$s</xliff:g> کیلئے سیٹ ہے"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"حذف کریں"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIM غیر فعال کریں"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"درج کریں"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"پیٹرن بھول گئے"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"غلط پیٹرن"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"غلط PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> سیکنڈ میں دوبارہ کوشش کریں۔"</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"اپنا پیٹرن ڈرا کریں"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"SIM PIN درج کریں"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" کیلئے SIM PIN درج کریں"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"SIM PIN درج کریں۔"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"\"<xliff:g id="CARRIER">%1$s</xliff:g>\" کیلئے SIM PIN درج کریں۔"</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"موبائل سروس کے بغیر آلہ کا استعمال کرنے کیلئے eSIM غیر فعال کریں۔"</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"PIN درج کریں"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"پاسورڈ درج کریں"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM اب غیر فعال ہوگیا ہے۔ جاری رکھنے کیلئے PUK کوڈ درج کریں۔ تفصیلات کیلئے کیریئر سے رابطہ کریں۔"</string>
diff --git a/packages/SystemUI/res-keyguard/values-uz/strings.xml b/packages/SystemUI/res-keyguard/values-uz/strings.xml
index e822382..0011c26 100644
--- a/packages/SystemUI/res-keyguard/values-uz/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uz/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM karta PUK kodi maydoni"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Signal <xliff:g id="ALARM">%1$s</xliff:g> da chalinadi."</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"O‘chirib tashlash"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"eSIMni faolsizlantirish"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter tugmasi"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Grafik kalit esimdan chiqdi"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Grafik kalit xato"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN kod xato"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"<xliff:g id="NUMBER">%d</xliff:g> soniyadan keyin qaytadan urining."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Grafik kalit chizing"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"SIM kartaning PIN kodini kiriting"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"“<xliff:g id="CARRIER">%1$s</xliff:g>” SIM kartasi uchun PIN kodni kiriting"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"SIM karta PIN kodini kiriting."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"“<xliff:g id="CARRIER">%1$s</xliff:g>” SIM kartasi PIN kodini kiriting."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Qurilmadan mobil xizmatlarsiz foydalanish uchun eSIMni faolsizlantiring."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"PIN kodni kiriting"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Parol kiriting"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM karta hozir o‘chirilgan. Davom etish uchun PUK kodni kiriting. Batafsil axborot olish uchun tarmoq operatori bilan bog‘laning."</string>
diff --git a/packages/SystemUI/res-keyguard/values-vi/strings.xml b/packages/SystemUI/res-keyguard/values-vi/strings.xml
index 59e5c9c..4fdddb2 100644
--- a/packages/SystemUI/res-keyguard/values-vi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-vi/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Khu vực mã PUK của SIM"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Báo thức tiếp theo được đặt cho <xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Xóa"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Vô hiệu hóa eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Nhập"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Đã quên hình mở khóa"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Hình mở khóa sai"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Mã PIN sai"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Thử lại sau <xliff:g id="NUMBER">%d</xliff:g> giây."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Vẽ hình mở khóa của bạn"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Nhập mã PIN của SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Nhập mã PIN của SIM dành cho \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Nhập mã PIN của SIM."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Nhập mã PIN của SIM dành cho \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Vô hiệu hóa eSIM để sử dụng thiết bị khi không có dịch vụ di động."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Nhập mã PIN"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Nhập mật khẩu"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM hiện bị vô hiệu hóa. Hãy nhập mã PUK để tiếp tục. Liên hệ với nhà cung cấp dịch vụ để biết chi tiết."</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
index a5eca34..ad92979 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM 卡 PUK 码区域"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"下一个闹钟时间已设置为<xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"删除"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"停用 eSIM 卡"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"输入"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"忘记了图案"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"图案错误"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN 码错误"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"请在 <xliff:g id="NUMBER">%d</xliff:g> 秒后重试。"</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"绘制您的图案"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"请输入 SIM 卡 PIN 码"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"请输入“<xliff:g id="CARRIER">%1$s</xliff:g>”的 SIM 卡 PIN 码"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"请输入 SIM 卡 PIN 码。"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"请输入“<xliff:g id="CARRIER">%1$s</xliff:g>”的 SIM 卡 PIN 码。"</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"停用 eSIM 卡即可在没有移动服务的情况下使用设备。"</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"请输入 PIN 码"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"请输入密码"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM 卡现已停用,需要输入 PUK 码才能继续使用。要了解详情,请联系您的运营商。"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
index 8059a04..5cf4ac5 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM 卡 PUK 區域"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"已經將下一個鬧鐘時間設做<xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Delete 鍵 (刪除)"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"停用 eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter 鍵 (輸入)"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"忘記上鎖圖案"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"上鎖圖案錯誤"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN 碼錯誤"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"請在 <xliff:g id="NUMBER">%d</xliff:g> 秒後再試一次。"</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"畫出上鎖圖案"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"輸入 SIM 卡 PIN 碼"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"輸入「<xliff:g id="CARRIER">%1$s</xliff:g>」的 SIM 卡 PIN 碼"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"輸入 SIM 卡的 PIN 碼。"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"輸入「<xliff:g id="CARRIER">%1$s</xliff:g>」SIM 卡的 PIN 碼。"</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"停用 eSIM,即可在沒有流動服務的情況下使用裝置。"</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"輸入 PIN 碼"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"輸入密碼"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM 卡現已停用,請輸入 PUK 碼以繼續。詳情請與流動網絡供應商聯絡。"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
index a03635b..6b7435a 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM 卡 PUK 區"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"已設定下一個鬧鐘時間:<xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"刪除"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"停用 eSIM 卡"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Enter 鍵"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"忘記解鎖圖案"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"解鎖圖案錯誤"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"PIN 碼錯誤"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"請在 <xliff:g id="NUMBER">%d</xliff:g> 秒後再試一次。"</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"畫出解鎖圖案"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"輸入 SIM 卡 PIN 碼"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"輸入「<xliff:g id="CARRIER">%1$s</xliff:g>」的 SIM 卡 PIN 碼"</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"輸入 SIM 卡的 PIN 碼。"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"輸入「<xliff:g id="CARRIER">%1$s</xliff:g>」SIM 卡的 PIN 碼。"</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"停用 eSIM 卡即可在沒有行動服務的情況下使用裝置。"</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"輸入 PIN 碼"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"輸入密碼"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM 卡已遭停用,輸入 PUK 碼即可繼續使用。如需瞭解詳情,請與電信業者聯絡。"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zu/strings.xml b/packages/SystemUI/res-keyguard/values-zu/strings.xml
index eaa5ecb..8008ce4 100644
--- a/packages/SystemUI/res-keyguard/values-zu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zu/strings.xml
@@ -51,6 +51,7 @@
<string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Indawo ye-SIM PUK"</string>
<string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"I-alamu elandelayo esethelwe i-<xliff:g id="ALARM">%1$s</xliff:g>"</string>
<string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Susa"</string>
+ <string name="disable_carrier_button_text" msgid="6914341927421916114">"Khubaza i-eSIM"</string>
<string name="keyboardview_keycode_enter" msgid="4505833604411016668">"Faka"</string>
<string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Ukhohlwe iphethini?"</string>
<string name="kg_wrong_pattern" msgid="7620081431514773802">"Iphatheni engalungile"</string>
@@ -58,8 +59,9 @@
<string name="kg_wrong_pin" msgid="4785660766909463466">"Iphinikhodi engalungile"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="527455490371878356">"Zama futhi emasekhondini angu-<xliff:g id="NUMBER">%d</xliff:g>."</string>
<string name="kg_pattern_instructions" msgid="5547646893001491340">"Dweba iphethini yakho"</string>
- <string name="kg_sim_pin_instructions" msgid="1795013002231184046">"Faka i-PIN ye-SIM"</string>
- <string name="kg_sim_pin_instructions_multi" msgid="1586316574649150223">"Faka i-PIN ye-SIM ye-\"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Faka i-PIN ye-SIM"</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Faka i-PIN ye-SIM ye-\"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+ <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Khubaza i-eSIM ukusebenzisa idivayisi ngaphandle kwesevisi yeselula."</string>
<string name="kg_pin_instructions" msgid="4069609316644030034">"Faka iphinikhodi"</string>
<string name="kg_password_instructions" msgid="136952397352976538">"Faka iphasiwedi"</string>
<string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"I-SIM manje ikhutshaziwe. Faka ikhodi ye-PUK ukuze uqhubeke. Xhumana nenkampani yenethiwekhi ngemininingwane."</string>
diff --git a/packages/SystemUI/res-keyguard/values/attrs.xml b/packages/SystemUI/res-keyguard/values/attrs.xml
index 7cfe631..d3d60a1 100644
--- a/packages/SystemUI/res-keyguard/values/attrs.xml
+++ b/packages/SystemUI/res-keyguard/values/attrs.xml
@@ -35,9 +35,13 @@
<attr name="android:gravity" />
<attr name="dotSize" format="dimension" />
<attr name="charPadding" format="dimension" />
+ <attr name="android:textColor" format="color" />
</declare-styleable>
<declare-styleable name="CarrierText">
<attr name="allCaps" format="boolean" />
</declare-styleable>
+
+ <attr name="pinDividerColor" format="color" />
+ <attr name="pinDeleteColor" format="color" />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/colors.xml b/packages/SystemUI/res-keyguard/values/colors.xml
deleted file mode 100644
index 3998c5b..0000000
--- a/packages/SystemUI/res-keyguard/values/colors.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 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>
-
- <!-- Clock -->
- <color name="clock_white">#ffffffff</color>
- <color name="clock_gray">@*android:color/secondary_text_default_material_dark</color>
-</resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 3ca6e69..41c723e 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -41,7 +41,7 @@
<!-- Default clock parameters -->
<dimen name="bottom_text_spacing_digital">-1dp</dimen>
<dimen name="widget_label_font_size">14sp</dimen>
- <dimen name="widget_big_font_size">64dp</dimen>
+ <dimen name="widget_big_font_size">72dp</dimen>
<!-- The y translation to apply at the start in appear animations. -->
<dimen name="appear_y_translation_start">32dp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 1efa3a8..b951c4c 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -119,6 +119,8 @@
<!-- KeyguardPinView - accessibility support --><skip />
<!-- Description of the Delete button in a KeyboardView. [CHAR LIMIT=NONE] -->
<string name="keyboardview_keycode_delete">Delete</string>
+ <!-- Description of the button used to disable current carrier when the device supported embedded SIM. [CHAR LIMIT=30] -->
+ <string name="disable_carrier_button_text">Disable eSIM</string>
<!-- Description of the Enter button in a KeyboardView. [CHAR LIMIT=NONE] -->
<string name="keyboardview_keycode_enter">Enter</string>
@@ -135,9 +137,11 @@
<!-- Instructions for using the pattern unlock screen -->
<string name="kg_pattern_instructions">Draw your pattern</string>
<!-- Instructions for using the SIM PIN unlock screen -->
- <string name="kg_sim_pin_instructions">Enter SIM PIN</string>
+ <string name="kg_sim_pin_instructions">Enter SIM PIN.</string>
<!-- Instructions for using the SIM PIN unlock screen when there's more than one SIM -->
- <string name="kg_sim_pin_instructions_multi">Enter SIM PIN for \"<xliff:g id="carrier" example="CARD 1">%1$s</xliff:g>\"</string>
+ <string name="kg_sim_pin_instructions_multi">Enter SIM PIN for \"<xliff:g id="carrier" example="CARD 1">%1$s</xliff:g>\".</string>
+ <!-- Instructions for disabling eSIM carrier to unlock the phone with embedded SIM -->
+ <string name="kg_sim_lock_instructions_esim">Disable eSIM to use device without mobile service.</string>
<!-- Instructions for using the PIN unlock screen -->
<string name="kg_pin_instructions">Enter PIN</string>
<!-- Instructions for using the password unlock screen -->
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 53a559f..d7ff349 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -20,11 +20,11 @@
<resources>
<!-- Keyguard PIN pad styles -->
<style name="Keyguard.TextView" parent="@android:style/Widget.DeviceDefault.TextView">
- <item name="android:textColor">@*android:color/primary_device_default_light</item>
+ <item name="android:textColor">?attr/bgProtectSecondaryTextColor</item>
<item name="android:textSize">@dimen/kg_status_line_font_size</item>
</style>
<style name="Keyguard.TextView.EmergencyButton" parent="@android:style/DeviceDefault.ButtonBar">
- <item name="android:textColor">@*android:color/primary_device_default_light</item>
+ <item name="android:textColor">?attr/bgProtectSecondaryTextColor</item>
<item name="android:textSize">@dimen/kg_status_line_font_size</item>
<item name="android:background">@null</item>
</style>
@@ -34,13 +34,16 @@
<item name="android:background">@null</item>
<item name="android:textSize">36sp</item>
<item name="android:fontFamily">sans-serif-light</item>
- <item name="android:textColor">#ffffffff</item>
+ <item name="android:textColor">?attr/bgProtectTextColor</item>
<item name="android:paddingBottom">-16dp</item>
</style>
+ <style name="Keyguard.ImageButton.NumPadEnter" parent="@android:style/Widget.ImageButton">
+ <item name="android:tint">?attr/bgProtectTextColor</item>
+ </style>
<style name="Widget.TextView.NumPadKey.Klondike" parent="Widget.TextView.NumPadKey">
<item name="android:textSize">12sp</item>
<item name="android:fontFamily">sans-serif</item>
- <item name="android:textColor">#80ffffff</item>
+ <item name="android:textColor">?attr/bgProtectSecondaryTextColor</item>
<item name="android:paddingBottom">0dp</item>
</style>
@@ -58,8 +61,9 @@
</style>
<style name="PasswordTheme" parent="@android:style/Theme.DeviceDefault">
- <item name="android:colorControlNormal">#80ffffff</item>
- <item name="android:colorControlActivated">#80ffffff</item>
+ <item name="android:textColor">?attr/bgProtectTextColor</item>
+ <item name="android:colorControlNormal">?attr/bgProtectTextColor</item>
+ <item name="android:colorControlActivated">?attr/bgProtectTextColor</item>
</style>
<style name="keyguard_presentation_theme" parent="@android:style/Theme.Material.NoActionBar.Fullscreen">
diff --git a/packages/SystemUI/res/anim/ic_dnd_disable_bar01_0_animation.xml b/packages/SystemUI/res/anim/ic_dnd_disable_bar01_0_animation.xml
deleted file mode 100644
index a914687c..0000000
--- a/packages/SystemUI/res/anim/ic_dnd_disable_bar01_0_animation.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-<set
- xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 0.680404663086,3.53039550781 c 0.0,0.0 -0.01416015625,0.00492858886719 -0.01416015625,0.00492858886719 c 0.0,0.0 1.41400146484,-1.41400146484 1.41400146484,-1.41400146484 c 0.0,0.0 0.0151519775391,-0.00492858886719 0.0151519775391,-0.00492858886719 c 0.0,0.0 -1.41499328613,1.41400146484 -1.41499328613,1.41400146484 Z"
- android:valueTo="M 0.680404663086,3.53039550781 c 0.0,0.0 20.0110015869,20.0110015869 20.0110015869,20.0110015869 c 0.0,0.0 1.41400146484,-1.41400146484 1.41400146484,-1.41400146484 c 0.0,0.0 -20.0100097656,-20.0110015869 -20.0100097656,-20.0110015869 c 0.0,0.0 -1.41499328613,1.41400146484 -1.41499328613,1.41400146484 Z"
- android:valueType="pathType"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_dnd_disable_mask_1_animation.xml b/packages/SystemUI/res/anim/ic_dnd_disable_mask_1_animation.xml
deleted file mode 100644
index 3c60c01..0000000
--- a/packages/SystemUI/res/anim/ic_dnd_disable_mask_1_animation.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-<set
- xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M -9.67768859863,-9.82725524902 c 0.0,0.0 -7.8091583252,7.18409729004 -7.8091583252,7.18409729004 c 0.0,0.0 20.9320983887,20.9321136475 20.9320983887,20.9321136475 c 0.0,0.0 15.041595459,-15.0416717529 15.041595459,-15.0416717529 c 0.0,0.0 -20.5361938477,-20.536239624 -20.5361938477,-20.536239624 c 0.0,0.0 -6.20835876465,6.05171203613 -6.20835876465,6.05171203613 c 0.0,0.0 0.219390869141,0.219299316406 0.219390869141,0.219299316406 c 0.0,0.0 -1.41998291016,1.40998840332 -1.41998291016,1.40998840332 c 0.0,0.0 -0.219390869141,-0.219299316406 -0.219390869141,-0.219299316406 Z"
- android:valueTo="M -9.67768859863,-9.82725524902 c 0.0,0.0 -7.8091583252,7.18409729004 -7.8091583252,7.18409729004 c 0.0,0.0 20.9320983887,20.9321136475 20.9320983887,20.9321136475 c 0.0,0.0 15.041595459,-15.0416717529 15.041595459,-15.0416717529 c 0.0,0.0 -20.5361938477,-20.536239624 -20.5361938477,-20.536239624 c 0.0,0.0 -6.20835876465,6.05171203613 -6.20835876465,6.05171203613 c 0.0,0.0 20.4531555176,20.4530792236 20.4531555176,20.4530792236 c 0.0,0.0 -1.41999816895,1.40995788574 -1.41999816895,1.40995788574 c 0.0,0.0 -20.4531402588,-20.4530487061 -20.4531402588,-20.4530487061 Z"
- android:valueType="pathType"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_dnd_total_silence_disable_mask_1_animation.xml b/packages/SystemUI/res/anim/ic_dnd_total_silence_disable_mask_1_animation.xml
deleted file mode 100644
index 8d3296e..0000000
--- a/packages/SystemUI/res/anim/ic_dnd_total_silence_disable_mask_1_animation.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-<set
- xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M -9.67768859863,-9.82725524902 c 0.0,0.0 -7.8091583252,7.18409729004 -7.8091583252,7.18409729004 c 0.0,0.0 20.9320983887,20.9321136475 20.9320983887,20.9321136475 c 0.0,0.0 15.041595459,-15.0416717529 15.041595459,-15.0416717529 c 0.0,0.0 -20.5361938477,-20.536239624 -20.5361938477,-20.536239624 c 0.0,0.0 -6.20835876465,6.05171203613 -6.20835876465,6.05171203613 c 0.0,0.0 0.219390869141,0.219299316406 0.219390869141,0.219299316406 c 0.0,0.0 -1.41998291016,1.40998840332 -1.41998291016,1.40998840332 c 0.0,0.0 -0.219390869141,-0.219299316406 -0.219390869141,-0.219299316406 Z"
- android:valueTo="M -9.67768859863,-9.82725524902 c 0.0,0.0 -7.8091583252,7.18409729004 -7.8091583252,7.18409729004 c 0.0,0.0 20.9320983887,20.9321136475 20.9320983887,20.9321136475 c 0.0,0.0 15.041595459,-15.0416717529 15.041595459,-15.0416717529 c 0.0,0.0 -20.5361938477,-20.536239624 -20.5361938477,-20.536239624 c 0.0,0.0 -6.20835876465,6.05171203613 -6.20835876465,6.05171203613 c 0.0,0.0 19.8804626465,19.8695220947 19.8804626465,19.8695220947 c 0.0,0.0 -1.41999816895,1.40995788574 -1.41999816895,1.40995788574 c 0.0,0.0 -19.8804473877,-19.8694915771 -19.8804473877,-19.8694915771 Z"
- android:valueType="pathType"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_dnd_total_silence_disable_outer_ring_merged_animation.xml b/packages/SystemUI/res/anim/ic_dnd_total_silence_disable_outer_ring_merged_animation.xml
deleted file mode 100644
index 2626499..0000000
--- a/packages/SystemUI/res/anim/ic_dnd_total_silence_disable_outer_ring_merged_animation.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-<set
- xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 0.0,-10.0 c -5.5,0.0 -10.0,4.5 -10.0,10.0 c 0.0,5.5 4.5,10.0 10.0,10.0 c 5.5,0.0 10.0,-4.5 10.0,-10.0 c 0.0,-5.5 -4.5,-10.0 -10.0,-10.0 Z M 0.0,8.5 c -4.69999694824,0.0 -8.5,-3.80000305176 -8.5,-8.5 c 0.0,-4.69999694824 3.80000305176,-8.5 8.5,-8.5 c 4.69999694824,0.0 8.5,3.80000305176 8.5,8.5 c 0.0,4.69999694824 -3.80000305176,8.5 -8.5,8.5 Z M -11.3195953369,-8.46960449219 c 0.0,0.0 -0.0141754150391,0.00492858886719 -0.014175415039,0.00492858886719 c 0.0,0.0 1.41400146484,-1.41400146484 1.41400146484,-1.41400146484 c 0.0,0.0 0.0151672363281,-0.00492858886719 0.0151672363281,-0.00492858886719 c 0.0,0.0 -1.41499328613,1.41400146484 -1.41499328613,1.41400146484 Z M 3.0,1.0 c 0.0,0.0 -6.0,0.0 -6.0,0.0 c 0.0,0.0 0.0,-2.0 0.0,-2.0 c 0.0,0.0 6.0,0.0 6.0,0.0 c 0.0,0.0 0.0,2.0 0.0,2.0 Z M 0.0,-6.0 c -3.30000305176,0.0 -6.0,2.69999694824 -6.0,6.0 c 0.0,3.30000305176 2.69999694824,6.0 6.0,6.0 c 3.30000305176,0.0 6.0,-2.69999694824 6.0,-6.0 c 0.0,-3.30000305176 -2.60000610352,-6.0 -6.0,-6.0 Z"
- android:valueTo="M 0.0,-10.0 c -5.5,0.0 -10.0,4.5 -10.0,10.0 c 0.0,5.5 4.5,10.0 10.0,10.0 c 5.5,0.0 10.0,-4.5 10.0,-10.0 c 0.0,-5.5 -4.5,-10.0 -10.0,-10.0 Z M 0.0,8.5 c -4.69999694824,0.0 -8.5,-3.80000305176 -8.5,-8.5 c 0.0,-4.69999694824 3.80000305176,-8.5 8.5,-8.5 c 4.69999694824,0.0 8.5,3.80000305176 8.5,8.5 c 0.0,4.69999694824 -3.80000305176,8.5 -8.5,8.5 Z M -11.3195953369,-8.46960449219 c 0.0,0.0 20.0109863281,20.0110015869 20.0109863281,20.0110015869 c 0.0,0.0 1.41400146484,-1.41400146484 1.41400146484,-1.41400146484 c 0.0,0.0 -20.0099945068,-20.0110015869 -20.0099945068,-20.0110015869 c 0.0,0.0 -1.41499328613,1.41400146484 -1.41499328613,1.41400146484 Z M 5.0,1.0 c 0.0,0.0 -10.0,0.0 -10.0,0.0 c 0.0,0.0 0.0,-2.0 0.0,-2.0 c 0.0,0.0 10.0,0.0 10.0,0.0 c 0.0,0.0 0.0,2.0 0.0,2.0 Z M 0.0,-10.0 c -5.52000427246,0.0 -10.0,4.47999572754 -10.0,10.0 c 0.0,5.52000427246 4.47999572754,10.0 10.0,10.0 c 5.52000427246,0.0 10.0,-4.47999572754 10.0,-10.0 c 0.0,-5.52000427246 -4.47999572754,-10.0 -10.0,-10.0 Z"
- android:valueType="pathType"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_hotspot_disable_animation_cross_1.xml b/packages/SystemUI/res/anim/ic_hotspot_disable_animation_cross_1.xml
deleted file mode 100644
index ad06d8e..0000000
--- a/packages/SystemUI/res/anim/ic_hotspot_disable_animation_cross_1.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 4.44044494629,2.24310302734 c 0.0,0.0 0.0875396728516,0.112457275391 0.0875396728516,0.112457275391 "
- android:valueTo="M 4.44044494629,2.24310302734 c 0.0,0.0 35.4000396729,35.3999633789 35.4000396729,35.3999633789 "
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_hotspot_disable_cross_1_pathdata_interpolator" />
- <objectAnimator
- android:duration="17"
- android:propertyName="strokeAlpha"
- android:valueFrom="0"
- android:valueTo="1"
- android:interpolator="@android:interpolator/linear" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_hotspot_disable_animation_mask.xml b/packages/SystemUI/res/anim/ic_hotspot_disable_animation_mask.xml
deleted file mode 100644
index 4cd8ce9..0000000
--- a/packages/SystemUI/res/anim/ic_hotspot_disable_animation_mask.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <set
- android:ordering="sequentially" >
- <objectAnimator
- android:duration="117"
- android:propertyName="pathData"
- android:valueFrom="M 38.8337860107,-40.3974914551 c 0.0,0.0 -38.4077911377,30.8523712158 -38.4077911377,30.8523712158 c 0.0,0.0 6.97125244141,7.33258056641 6.97125244141,7.33258056641 c 0.0,0.0 -2.4169921875,2.57838439941 -2.4169921875,2.57838439941 c 0.0,0.0 -6.77128601074,-6.82850646973 -6.77128601074,-6.82850646973 c 0.0,0.0 -32.6199798584,25.1699066162 -32.6199798584,25.1699066162 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 27.6589050293,-22.6579437256 27.6589050293,-22.6579437256 c 0.0,0.0 -30.8645172119,-34.00390625 -30.8645172119,-34.00390625 c 0.0,0.0 2.70756530762,-1.99278259277 2.70756530762,-1.99278259277 c 0.0,0.0 1.53030395508,-0.876571655273 1.53030395508,-0.876571655274 c 0.0,0.0 2.85780334473,-3.12069702148 2.85780334473,-3.12069702148 c 0.0,0.0 0.659332275391,0.664688110352 0.659332275391,0.664688110351 c 0.0,0.0 -3.13299560547,2.82977294922 -3.13299560547,2.82977294922 c 0.0,0.0 29.0108337402,34.4080963135 29.0108337402,34.4080963135 c 0.0,0.0 42.8175811768,-34.3554534912 42.8175811768,-34.3554534912 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueTo="M 38.8337860107,-40.3974914551 c 0.0,0.0 -38.4077911377,30.8523712158 -38.4077911377,30.8523712158 c 0.0,0.0 29.8566131592,30.1964874268 29.8566131592,30.1964874268 c 0.0,0.0 -2.4169921875,2.57838439941 -2.4169921875,2.57838439941 c 0.0,0.0 -29.6566467285,-29.6924133301 -29.6566467285,-29.6924133301 c 0.0,0.0 -32.6199798584,25.1699066162 -32.6199798584,25.1699066162 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 27.6589050293,-22.6579437256 27.6589050293,-22.6579437256 c 0.0,0.0 -30.8645172119,-34.00390625 -30.8645172119,-34.00390625 c 0.0,0.0 2.70756530762,-1.99278259277 2.70756530762,-1.99278259277 c 0.0,0.0 1.53030395508,-0.876571655273 1.53030395508,-0.876571655274 c 0.0,0.0 2.85780334473,-3.12069702148 2.85780334473,-3.12069702148 c 0.0,0.0 0.905746459961,0.856552124023 0.905746459961,0.856552124023 c 0.0,0.0 -3.13299560547,2.82975769043 -3.13299560547,2.82975769043 c 0.0,0.0 28.7644195557,34.2162475586 28.7644195557,34.2162475586 c 0.0,0.0 42.8175811768,-34.3554534912 42.8175811768,-34.3554534912 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_hotspot_disable_mask_pathdata_interpolator_1" />
- <objectAnimator
- android:duration="233"
- android:propertyName="pathData"
- android:valueFrom="M 38.8337860107,-40.3974914551 c 0.0,0.0 -38.4077911377,30.8523712158 -38.4077911377,30.8523712158 c 0.0,0.0 29.8566131592,30.1964874268 29.8566131592,30.1964874268 c 0.0,0.0 -2.4169921875,2.57838439941 -2.4169921875,2.57838439941 c 0.0,0.0 -29.6566467285,-29.6924133301 -29.6566467285,-29.6924133301 c 0.0,0.0 -32.6199798584,25.1699066162 -32.6199798584,25.1699066162 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 27.6589050293,-22.6579437256 27.6589050293,-22.6579437256 c 0.0,0.0 -30.8645172119,-34.00390625 -30.8645172119,-34.00390625 c 0.0,0.0 2.70756530762,-1.99278259277 2.70756530762,-1.99278259277 c 0.0,0.0 1.53030395508,-0.876571655273 1.53030395508,-0.876571655274 c 0.0,0.0 2.85780334473,-3.12069702148 2.85780334473,-3.12069702148 c 0.0,0.0 0.905746459961,0.856552124023 0.905746459961,0.856552124023 c 0.0,0.0 -3.13299560547,2.82975769043 -3.13299560547,2.82975769043 c 0.0,0.0 28.7644195557,34.2162475586 28.7644195557,34.2162475586 c 0.0,0.0 42.8175811768,-34.3554534912 42.8175811768,-34.3554534912 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueTo="M 38.8337860107,-40.3974914551 c 0.0,0.0 -38.4077911377,30.8523712158 -38.4077911377,30.8523712158 c 0.0,0.0 43.1884765625,43.515335083 43.1884765625,43.515335083 c 0.0,0.0 -2.4169921875,2.57838439941 -2.4169921875,2.57838439941 c 0.0,0.0 -42.9885101318,-43.0112609863 -42.9885101318,-43.0112609863 c 0.0,0.0 -32.6199798584,25.1699066162 -32.6199798584,25.1699066162 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 27.6589050293,-22.6579437256 27.6589050293,-22.6579437256 c 0.0,0.0 -30.8645172119,-34.00390625 -30.8645172119,-34.00390625 c 0.0,0.0 2.70756530762,-1.99278259277 2.70756530762,-1.99278259277 c 0.0,0.0 1.53030395508,-0.876571655273 1.53030395508,-0.876571655274 c 0.0,0.0 2.85780334473,-3.12069702148 2.85780334473,-3.12069702148 c 0.0,0.0 13.0984039307,13.025604248 13.0984039307,13.025604248 c 0.0,0.0 -3.13299560547,2.82977294922 -3.13299560547,2.82977294922 c 0.0,0.0 16.571762085,22.0471801758 16.571762085,22.0471801758 c 0.0,0.0 42.8175811768,-34.3554534912 42.8175811768,-34.3554534912 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_hotspot_disable_mask_pathdata_interpolator_2" />
- </set>
-</set>
diff --git a/packages/SystemUI/res/anim/ic_hotspot_enable_animation_cross_1.xml b/packages/SystemUI/res/anim/ic_hotspot_enable_animation_cross_1.xml
deleted file mode 100644
index 523e53a..0000000
--- a/packages/SystemUI/res/anim/ic_hotspot_enable_animation_cross_1.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 4.44044494629,2.24310302734 c 0.0,0.0 35.4000396729,35.3999633789 35.4000396729,35.3999633789 "
- android:valueTo="M 4.44044494629,2.24310302734 c 0.0,0.0 0.0875396728516,0.112457275391 0.0875396728516,0.112457275391 "
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_hotspot_enable_cross_1_pathdata_interpolator" />
- <set
- android:ordering="sequentially" >
- <objectAnimator
- android:duration="333"
- android:propertyName="strokeAlpha"
- android:valueFrom="1"
- android:valueTo="1"
- android:interpolator="@android:interpolator/linear" />
- <objectAnimator
- android:duration="17"
- android:propertyName="strokeAlpha"
- android:valueFrom="1"
- android:valueTo="0"
- android:interpolator="@android:interpolator/linear" />
- </set>
-</set>
diff --git a/packages/SystemUI/res/anim/ic_hotspot_enable_animation_mask.xml b/packages/SystemUI/res/anim/ic_hotspot_enable_animation_mask.xml
deleted file mode 100644
index 7562c9b..0000000
--- a/packages/SystemUI/res/anim/ic_hotspot_enable_animation_mask.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <set
- android:ordering="sequentially" >
- <objectAnimator
- android:duration="233"
- android:propertyName="pathData"
- android:valueFrom="M 38.8337860107,-40.3974914551 c 0.0,0.0 -38.4077911377,30.8523712158 -38.4077911377,30.8523712158 c 0.0,0.0 43.1884765625,43.515335083 43.1884765625,43.515335083 c 0.0,0.0 -2.4169921875,2.57838439941 -2.4169921875,2.57838439941 c 0.0,0.0 -42.9885101318,-43.0112609863 -42.9885101318,-43.0112609863 c 0.0,0.0 -32.6199798584,25.1699066162 -32.6199798584,25.1699066162 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 27.6589050293,-22.6579437256 27.6589050293,-22.6579437256 c 0.0,0.0 -30.8645172119,-34.00390625 -30.8645172119,-34.00390625 c 0.0,0.0 2.70756530762,-1.99278259277 2.70756530762,-1.99278259277 c 0.0,0.0 1.53030395508,-0.876571655273 1.53030395508,-0.876571655274 c 0.0,0.0 2.85780334473,-3.12069702148 2.85780334473,-3.12069702148 c 0.0,0.0 13.0984039307,13.025604248 13.0984039307,13.025604248 c 0.0,0.0 -3.13299560547,2.82977294922 -3.13299560547,2.82977294922 c 0.0,0.0 16.571762085,22.0471801758 16.571762085,22.0471801758 c 0.0,0.0 42.8175811768,-34.3554534912 42.8175811768,-34.3554534912 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueTo="M 38.8337860107,-40.3974914551 c 0.0,0.0 -38.4077911377,30.8523712158 -38.4077911377,30.8523712158 c 0.0,0.0 29.8566131592,30.1964874268 29.8566131592,30.1964874268 c 0.0,0.0 -2.4169921875,2.57838439941 -2.4169921875,2.57838439941 c 0.0,0.0 -29.6566467285,-29.6924133301 -29.6566467285,-29.6924133301 c 0.0,0.0 -32.6199798584,25.1699066162 -32.6199798584,25.1699066162 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 27.6589050293,-22.6579437256 27.6589050293,-22.6579437256 c 0.0,0.0 -30.8645172119,-34.00390625 -30.8645172119,-34.00390625 c 0.0,0.0 2.70756530762,-1.99278259277 2.70756530762,-1.99278259277 c 0.0,0.0 1.53030395508,-0.876571655273 1.53030395508,-0.876571655274 c 0.0,0.0 2.85780334473,-3.12069702148 2.85780334473,-3.12069702148 c 0.0,0.0 0.905746459961,0.856552124023 0.905746459961,0.856552124023 c 0.0,0.0 -3.13299560547,2.82975769043 -3.13299560547,2.82975769043 c 0.0,0.0 28.7644195557,34.2162475586 28.7644195557,34.2162475586 c 0.0,0.0 42.8175811768,-34.3554534912 42.8175811768,-34.3554534912 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_hotspot_enable_mask_pathdata_interpolator_1" />
- <objectAnimator
- android:duration="117"
- android:propertyName="pathData"
- android:valueFrom="M 38.8337860107,-40.3974914551 c 0.0,0.0 -38.4077911377,30.8523712158 -38.4077911377,30.8523712158 c 0.0,0.0 29.8566131592,30.1964874268 29.8566131592,30.1964874268 c 0.0,0.0 -2.4169921875,2.57838439941 -2.4169921875,2.57838439941 c 0.0,0.0 -29.6566467285,-29.6924133301 -29.6566467285,-29.6924133301 c 0.0,0.0 -32.6199798584,25.1699066162 -32.6199798584,25.1699066162 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 27.6589050293,-22.6579437256 27.6589050293,-22.6579437256 c 0.0,0.0 -30.8645172119,-34.00390625 -30.8645172119,-34.00390625 c 0.0,0.0 2.70756530762,-1.99278259277 2.70756530762,-1.99278259277 c 0.0,0.0 1.53030395508,-0.876571655273 1.53030395508,-0.876571655274 c 0.0,0.0 2.85780334473,-3.12069702148 2.85780334473,-3.12069702148 c 0.0,0.0 0.905746459961,0.856552124023 0.905746459961,0.856552124023 c 0.0,0.0 -3.13299560547,2.82975769043 -3.13299560547,2.82975769043 c 0.0,0.0 28.7644195557,34.2162475586 28.7644195557,34.2162475586 c 0.0,0.0 42.8175811768,-34.3554534912 42.8175811768,-34.3554534912 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueTo="M 38.8337860107,-40.3974914551 c 0.0,0.0 -38.4077911377,30.8523712158 -38.4077911377,30.8523712158 c 0.0,0.0 6.97125244141,7.33258056641 6.97125244141,7.33258056641 c 0.0,0.0 -2.4169921875,2.57838439941 -2.4169921875,2.57838439941 c 0.0,0.0 -6.77128601074,-6.82850646973 -6.77128601074,-6.82850646973 c 0.0,0.0 -32.6199798584,25.1699066162 -32.6199798584,25.1699066162 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 27.6589050293,-22.6579437256 27.6589050293,-22.6579437256 c 0.0,0.0 -30.8645172119,-34.00390625 -30.8645172119,-34.00390625 c 0.0,0.0 2.70756530762,-1.99278259277 2.70756530762,-1.99278259277 c 0.0,0.0 1.53030395508,-0.876571655273 1.53030395508,-0.876571655274 c 0.0,0.0 2.85780334473,-3.12069702148 2.85780334473,-3.12069702148 c 0.0,0.0 0.659332275391,0.664688110352 0.659332275391,0.664688110351 c 0.0,0.0 -3.13299560547,2.82977294922 -3.13299560547,2.82977294922 c 0.0,0.0 29.0108337402,34.4080963135 29.0108337402,34.4080963135 c 0.0,0.0 42.8175811768,-34.3554534912 42.8175811768,-34.3554534912 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_hotspot_enable_mask_pathdata_interpolator_2" />
- </set>
-</set>
diff --git a/packages/SystemUI/res/anim/ic_invert_colors_disable_animation_cross_1.xml b/packages/SystemUI/res/anim/ic_invert_colors_disable_animation_cross_1.xml
deleted file mode 100644
index 5e78f4c..0000000
--- a/packages/SystemUI/res/anim/ic_invert_colors_disable_animation_cross_1.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 7.54049682617,3.9430847168 c 0.0,0.0 0.324981689453,0.399978637695 0.324981689453,0.399978637695 "
- android:valueTo="M 7.54049682617,3.9430847168 c 0.0,0.0 31.5749816895,31.4499664307 31.5749816895,31.4499664307 "
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_invert_colors_disable_cross_1_pathdata_interpolator" />
- <objectAnimator
- android:duration="17"
- android:propertyName="strokeAlpha"
- android:valueFrom="0"
- android:valueTo="1"
- android:interpolator="@android:interpolator/linear" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_invert_colors_disable_animation_icon.xml b/packages/SystemUI/res/anim/ic_invert_colors_disable_animation_icon.xml
deleted file mode 100644
index 9cc3b8e..0000000
--- a/packages/SystemUI/res/anim/ic_invert_colors_disable_animation_icon.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <set
- android:ordering="sequentially" >
- <objectAnimator
- android:duration="200"
- android:propertyName="alpha"
- android:valueFrom="1"
- android:valueTo="1"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="1"
- android:valueTo="0.5"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- </set>
-</set>
diff --git a/packages/SystemUI/res/anim/ic_invert_colors_disable_animation_mask.xml b/packages/SystemUI/res/anim/ic_invert_colors_disable_animation_mask.xml
deleted file mode 100644
index 7f77372..0000000
--- a/packages/SystemUI/res/anim/ic_invert_colors_disable_animation_mask.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 37.8337860107,-40.4599914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 9.55097961426,9.55285644531 9.55097961426,9.55285644531 c 0.0,0.0 -2.61698913574,2.09387207031 -2.61698913574,2.09387207031 c 0.0,0.0 -9.75096130371,-9.56428527832 -9.75096130371,-9.56428527832 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueTo="M 37.8337860107,-40.3974914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 40.9884796143,40.9278411865 40.9884796143,40.9278411865 c 0.0,0.0 -2.61700439453,2.0938873291 -2.61700439453,2.0938873291 c 0.0,0.0 -41.1884460449,-40.9392852783 -41.1884460449,-40.9392852783 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueType="pathType"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_invert_colors_enable_animation_cross_1.xml b/packages/SystemUI/res/anim/ic_invert_colors_enable_animation_cross_1.xml
deleted file mode 100644
index eacd248..0000000
--- a/packages/SystemUI/res/anim/ic_invert_colors_enable_animation_cross_1.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 7.54049682617,3.9430847168 c 0.0,0.0 31.5749816895,31.4499664307 31.5749816895,31.4499664307 "
- android:valueTo="M 7.54049682617,3.9430847168 c 0.0,0.0 0.324981689453,0.399978637695 0.324981689453,0.399978637695 "
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_invert_colors_enable_cross_1_pathdata_interpolator" />
- <set
- android:ordering="sequentially" >
- <objectAnimator
- android:duration="333"
- android:propertyName="strokeAlpha"
- android:valueFrom="1"
- android:valueTo="1"
- android:interpolator="@android:interpolator/linear" />
- <objectAnimator
- android:duration="17"
- android:propertyName="strokeAlpha"
- android:valueFrom="1"
- android:valueTo="0"
- android:interpolator="@android:interpolator/linear" />
- </set>
-</set>
diff --git a/packages/SystemUI/res/anim/ic_invert_colors_enable_animation_icon.xml b/packages/SystemUI/res/anim/ic_invert_colors_enable_animation_icon.xml
deleted file mode 100644
index 50a1af6..0000000
--- a/packages/SystemUI/res/anim/ic_invert_colors_enable_animation_icon.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <set
- android:ordering="sequentially" >
- <objectAnimator
- android:duration="200"
- android:propertyName="alpha"
- android:valueFrom="0.54"
- android:valueTo="0.54"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="0.54"
- android:valueTo="1"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- </set>
-</set>
diff --git a/packages/SystemUI/res/anim/ic_invert_colors_enable_animation_mask.xml b/packages/SystemUI/res/anim/ic_invert_colors_enable_animation_mask.xml
deleted file mode 100644
index a2126a0..0000000
--- a/packages/SystemUI/res/anim/ic_invert_colors_enable_animation_mask.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 37.8337860107,-40.3974914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 40.9884796143,40.9278411865 40.9884796143,40.9278411865 c 0.0,0.0 -2.61700439453,2.0938873291 -2.61700439453,2.0938873291 c 0.0,0.0 -41.1884460449,-40.9392852783 -41.1884460449,-40.9392852783 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueTo="M 37.8337860107,-40.4599914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 9.55097961426,9.55285644531 9.55097961426,9.55285644531 c 0.0,0.0 -2.61698913574,2.09387207031 -2.61698913574,2.09387207031 c 0.0,0.0 -9.75096130371,-9.56428527832 -9.75096130371,-9.56428527832 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_invert_colors_enable_mask_pathdata_interpolator" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_airplane_disable_animation_cross_1.xml b/packages/SystemUI/res/anim/ic_signal_airplane_disable_animation_cross_1.xml
deleted file mode 100644
index 39e4aec..0000000
--- a/packages/SystemUI/res/anim/ic_signal_airplane_disable_animation_cross_1.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 7.54049682617,3.9430847168 c 0.0,0.0 0.324981689453,0.399978637695 0.324981689453,0.399978637695 "
- android:valueTo="M 7.54049682617,3.9430847168 c 0.0,0.0 31.5749816895,31.4499664307 31.5749816895,31.4499664307 "
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_signal_airplane_disable_cross_1_pathdata_interpolator" />
- <objectAnimator
- android:duration="17"
- android:propertyName="strokeAlpha"
- android:valueFrom="0"
- android:valueTo="1"
- android:interpolator="@android:interpolator/linear" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_airplane_disable_animation_ic_signal_airplane.xml b/packages/SystemUI/res/anim/ic_signal_airplane_disable_animation_ic_signal_airplane.xml
deleted file mode 100644
index 9cc3b8e..0000000
--- a/packages/SystemUI/res/anim/ic_signal_airplane_disable_animation_ic_signal_airplane.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <set
- android:ordering="sequentially" >
- <objectAnimator
- android:duration="200"
- android:propertyName="alpha"
- android:valueFrom="1"
- android:valueTo="1"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="1"
- android:valueTo="0.5"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- </set>
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_airplane_disable_animation_mask.xml b/packages/SystemUI/res/anim/ic_signal_airplane_disable_animation_mask.xml
deleted file mode 100644
index 7f77372..0000000
--- a/packages/SystemUI/res/anim/ic_signal_airplane_disable_animation_mask.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 37.8337860107,-40.4599914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 9.55097961426,9.55285644531 9.55097961426,9.55285644531 c 0.0,0.0 -2.61698913574,2.09387207031 -2.61698913574,2.09387207031 c 0.0,0.0 -9.75096130371,-9.56428527832 -9.75096130371,-9.56428527832 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueTo="M 37.8337860107,-40.3974914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 40.9884796143,40.9278411865 40.9884796143,40.9278411865 c 0.0,0.0 -2.61700439453,2.0938873291 -2.61700439453,2.0938873291 c 0.0,0.0 -41.1884460449,-40.9392852783 -41.1884460449,-40.9392852783 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueType="pathType"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_airplane_enable_animation_cross_1.xml b/packages/SystemUI/res/anim/ic_signal_airplane_enable_animation_cross_1.xml
deleted file mode 100644
index 1b73cc0..0000000
--- a/packages/SystemUI/res/anim/ic_signal_airplane_enable_animation_cross_1.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 7.54049682617,3.9430847168 c 0.0,0.0 31.5749816895,31.4499664307 31.5749816895,31.4499664307 "
- android:valueTo="M 7.54049682617,3.9430847168 c 0.0,0.0 0.324981689453,0.399978637695 0.324981689453,0.399978637695 "
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_signal_airplane_enable_cross_1_pathdata_interpolator" />
- <set
- android:ordering="sequentially" >
- <objectAnimator
- android:duration="333"
- android:propertyName="strokeAlpha"
- android:valueFrom="1"
- android:valueTo="1"
- android:interpolator="@android:interpolator/linear" />
- <objectAnimator
- android:duration="17"
- android:propertyName="strokeAlpha"
- android:valueFrom="1"
- android:valueTo="0"
- android:interpolator="@android:interpolator/linear" />
- </set>
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_airplane_enable_animation_ic_signal_airplane.xml b/packages/SystemUI/res/anim/ic_signal_airplane_enable_animation_ic_signal_airplane.xml
deleted file mode 100644
index 5fdb2a0..0000000
--- a/packages/SystemUI/res/anim/ic_signal_airplane_enable_animation_ic_signal_airplane.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <set
- android:ordering="sequentially" >
- <objectAnimator
- android:duration="200"
- android:propertyName="alpha"
- android:valueFrom="0.5"
- android:valueTo="0.5"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="0.5"
- android:valueTo="1"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- </set>
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_airplane_enable_animation_mask.xml b/packages/SystemUI/res/anim/ic_signal_airplane_enable_animation_mask.xml
deleted file mode 100644
index c45426c..0000000
--- a/packages/SystemUI/res/anim/ic_signal_airplane_enable_animation_mask.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 37.8337860107,-40.3974914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 40.9884796143,40.9278411865 40.9884796143,40.9278411865 c 0.0,0.0 -2.61700439453,2.0938873291 -2.61700439453,2.0938873291 c 0.0,0.0 -41.1884460449,-40.9392852783 -41.1884460449,-40.9392852783 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueTo="M 37.8337860107,-40.4599914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 9.55097961426,9.55285644531 9.55097961426,9.55285644531 c 0.0,0.0 -2.61698913574,2.09387207031 -2.61698913574,2.09387207031 c 0.0,0.0 -9.75096130371,-9.56428527832 -9.75096130371,-9.56428527832 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_signal_airplane_enable_mask_pathdata_interpolator" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_flashlight_disable_animation_cross_1.xml b/packages/SystemUI/res/anim/ic_signal_flashlight_disable_animation_cross_1.xml
deleted file mode 100644
index b9b19f5..0000000
--- a/packages/SystemUI/res/anim/ic_signal_flashlight_disable_animation_cross_1.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 8.34049987793,5.6930847168 c 0.0,0.0 0.274993896484,0.29997253418 0.274993896484,0.29997253418 "
- android:valueTo="M 8.34049987793,5.6930847168 c 0.0,0.0 29.7749786377,29.7999725342 29.7749786377,29.7999725342 "
- android:valueType="pathType"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- <objectAnimator
- android:duration="17"
- android:propertyName="strokeAlpha"
- android:valueFrom="0"
- android:valueTo="1"
- android:interpolator="@android:interpolator/linear" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_flashlight_disable_animation_ic_signal_flashlight.xml b/packages/SystemUI/res/anim/ic_signal_flashlight_disable_animation_ic_signal_flashlight.xml
deleted file mode 100644
index 9cc3b8e..0000000
--- a/packages/SystemUI/res/anim/ic_signal_flashlight_disable_animation_ic_signal_flashlight.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <set
- android:ordering="sequentially" >
- <objectAnimator
- android:duration="200"
- android:propertyName="alpha"
- android:valueFrom="1"
- android:valueTo="1"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="1"
- android:valueTo="0.5"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- </set>
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_flashlight_disable_animation_mask.xml b/packages/SystemUI/res/anim/ic_signal_flashlight_disable_animation_mask.xml
deleted file mode 100644
index 321a965..0000000
--- a/packages/SystemUI/res/anim/ic_signal_flashlight_disable_animation_mask.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 37.8337860107,-39.2849731445 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 9.55097961426,9.55285644531 9.55097961426,9.55285644531 c 0.0,0.0 -2.61698913574,2.09387207031 -2.61698913574,2.09387207031 c 0.0,0.0 -9.75096130371,-9.56428527832 -9.75096130371,-9.56428527832 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueTo="M 37.8337860107,-39.2975769043 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 40.9884796143,40.9278411865 40.9884796143,40.9278411865 c 0.0,0.0 -2.61700439453,2.0938873291 -2.61700439453,2.0938873291 c 0.0,0.0 -41.1884460449,-40.9392852783 -41.1884460449,-40.9392852783 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueType="pathType"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_flashlight_enable_animation_cross_1.xml b/packages/SystemUI/res/anim/ic_signal_flashlight_enable_animation_cross_1.xml
deleted file mode 100644
index 011a96f..0000000
--- a/packages/SystemUI/res/anim/ic_signal_flashlight_enable_animation_cross_1.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 8.34049987793,5.6930847168 c 0.0,0.0 29.7749786377,29.7999725342 29.7749786377,29.7999725342 "
- android:valueTo="M 8.34049987793,5.6930847168 c 0.0,0.0 0.274993896484,0.29997253418 0.274993896484,0.29997253418 "
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_signal_flashlight_enable_cross_1_pathdata_interpolator" />
- <set
- android:ordering="sequentially" >
- <objectAnimator
- android:duration="333"
- android:propertyName="strokeAlpha"
- android:valueFrom="1"
- android:valueTo="1"
- android:interpolator="@android:interpolator/linear" />
- <objectAnimator
- android:duration="17"
- android:propertyName="strokeAlpha"
- android:valueFrom="1"
- android:valueTo="0"
- android:interpolator="@android:interpolator/linear" />
- </set>
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_flashlight_enable_animation_ic_signal_flashlight.xml b/packages/SystemUI/res/anim/ic_signal_flashlight_enable_animation_ic_signal_flashlight.xml
deleted file mode 100644
index 5fdb2a0..0000000
--- a/packages/SystemUI/res/anim/ic_signal_flashlight_enable_animation_ic_signal_flashlight.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <set
- android:ordering="sequentially" >
- <objectAnimator
- android:duration="200"
- android:propertyName="alpha"
- android:valueFrom="0.5"
- android:valueTo="0.5"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="0.5"
- android:valueTo="1"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- </set>
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_flashlight_enable_animation_mask.xml b/packages/SystemUI/res/anim/ic_signal_flashlight_enable_animation_mask.xml
deleted file mode 100644
index 14bab3a..0000000
--- a/packages/SystemUI/res/anim/ic_signal_flashlight_enable_animation_mask.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 37.8337860107,-39.2975769043 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 40.9884796143,40.9278411865 40.9884796143,40.9278411865 c 0.0,0.0 -2.61700439453,2.0938873291 -2.61700439453,2.0938873291 c 0.0,0.0 -41.1884460449,-40.9392852783 -41.1884460449,-40.9392852783 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueTo="M 37.8337860107,-39.2849731445 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 9.55097961426,9.55285644531 9.55097961426,9.55285644531 c 0.0,0.0 -2.61698913574,2.09387207031 -2.61698913574,2.09387207031 c 0.0,0.0 -9.75096130371,-9.56428527832 -9.75096130371,-9.56428527832 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_signal_flashlight_enable_mask_pathdata_interpolator" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_location_disable_animation_cross_1.xml b/packages/SystemUI/res/anim/ic_signal_location_disable_animation_cross_1.xml
deleted file mode 100644
index 0c57530..0000000
--- a/packages/SystemUI/res/anim/ic_signal_location_disable_animation_cross_1.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 6.44050598145,1.9430847168 c 0.0,0.0 0.374984741211,0.399978637695 0.374984741211,0.399978637695 "
- android:valueTo="M 6.44050598145,1.9430847168 c 0.0,0.0 33.5749816895,33.4499664307 33.5749816895,33.4499664307 "
- android:valueType="pathType"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- <objectAnimator
- android:duration="17"
- android:propertyName="strokeAlpha"
- android:valueFrom="0"
- android:valueTo="1"
- android:interpolator="@android:interpolator/linear" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_location_disable_animation_ic_signal_location.xml b/packages/SystemUI/res/anim/ic_signal_location_disable_animation_ic_signal_location.xml
deleted file mode 100644
index 9cc3b8e..0000000
--- a/packages/SystemUI/res/anim/ic_signal_location_disable_animation_ic_signal_location.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <set
- android:ordering="sequentially" >
- <objectAnimator
- android:duration="200"
- android:propertyName="alpha"
- android:valueFrom="1"
- android:valueTo="1"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="1"
- android:valueTo="0.5"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- </set>
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_location_disable_animation_mask.xml b/packages/SystemUI/res/anim/ic_signal_location_disable_animation_mask.xml
deleted file mode 100644
index 501d68b..0000000
--- a/packages/SystemUI/res/anim/ic_signal_location_disable_animation_mask.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 38.8337860107,-40.4599914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 7.3759765625,7.55284118652 7.3759765625,7.55284118652 c 0.0,0.0 -2.61698913574,2.0938873291 -2.61698913574,2.0938873291 c 0.0,0.0 -7.57595825195,-7.56428527832 -7.57595825195,-7.56428527832 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueTo="M 38.8337860107,-40.3974914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 40.9884796143,41.1153411865 40.9884796143,41.1153411865 c 0.0,0.0 -2.61700439453,2.0938873291 -2.61700439453,2.0938873291 c 0.0,0.0 -41.1884460449,-41.1267852783 -41.1884460449,-41.1267852783 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueType="pathType"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_location_enable_animation_cross_1.xml b/packages/SystemUI/res/anim/ic_signal_location_enable_animation_cross_1.xml
deleted file mode 100644
index 1b5445d..0000000
--- a/packages/SystemUI/res/anim/ic_signal_location_enable_animation_cross_1.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 6.44050598145,1.9430847168 c 0.0,0.0 33.5749816895,33.4499664307 33.5749816895,33.4499664307 "
- android:valueTo="M 6.44050598145,1.9430847168 c 0.0,0.0 0.374984741211,0.399978637695 0.374984741211,0.399978637695 "
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_signal_location_enable_cross_1_pathdata_interpolator" />
- <set
- android:ordering="sequentially" >
- <objectAnimator
- android:duration="333"
- android:propertyName="strokeAlpha"
- android:valueFrom="1"
- android:valueTo="1"
- android:interpolator="@android:interpolator/linear" />
- <objectAnimator
- android:duration="17"
- android:propertyName="strokeAlpha"
- android:valueFrom="1"
- android:valueTo="0"
- android:interpolator="@android:interpolator/linear" />
- </set>
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_location_enable_animation_ic_signal_location.xml b/packages/SystemUI/res/anim/ic_signal_location_enable_animation_ic_signal_location.xml
deleted file mode 100644
index 5fdb2a0..0000000
--- a/packages/SystemUI/res/anim/ic_signal_location_enable_animation_ic_signal_location.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <set
- android:ordering="sequentially" >
- <objectAnimator
- android:duration="200"
- android:propertyName="alpha"
- android:valueFrom="0.5"
- android:valueTo="0.5"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="0.5"
- android:valueTo="1"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- </set>
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_location_enable_animation_mask.xml b/packages/SystemUI/res/anim/ic_signal_location_enable_animation_mask.xml
deleted file mode 100644
index 9ca12da..0000000
--- a/packages/SystemUI/res/anim/ic_signal_location_enable_animation_mask.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 38.8337860107,-40.3974914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 40.9884796143,41.1153411865 40.9884796143,41.1153411865 c 0.0,0.0 -2.61700439453,2.0938873291 -2.61700439453,2.0938873291 c 0.0,0.0 -41.1884460449,-41.1267852783 -41.1884460449,-41.1267852783 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueTo="M 38.8337860107,-40.4599914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 7.3759765625,7.55284118652 7.3759765625,7.55284118652 c 0.0,0.0 -2.61698913574,2.0938873291 -2.61698913574,2.0938873291 c 0.0,0.0 -7.57595825195,-7.56428527832 -7.57595825195,-7.56428527832 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_signal_location_enable_mask_pathdata_interpolator" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_workmode_disable_animation_cross_1.xml b/packages/SystemUI/res/anim/ic_signal_workmode_disable_animation_cross_1.xml
deleted file mode 100644
index 661ab08..0000000
--- a/packages/SystemUI/res/anim/ic_signal_workmode_disable_animation_cross_1.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 7.54049682617,3.9430847168 c 0.0,0.0 0.324981689453,0.399978637695 0.324981689453,0.399978637695 "
- android:valueTo="M 7.54049682617,3.9430847168 c 0.0,0.0 31.5749816895,31.4499664307 31.5749816895,31.4499664307 "
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_signal_workmode_disable_cross_1_pathdata_interpolator" />
- <objectAnimator
- android:duration="17"
- android:propertyName="strokeAlpha"
- android:valueFrom="0"
- android:valueTo="1"
- android:interpolator="@android:interpolator/linear" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_workmode_disable_animation_mask.xml b/packages/SystemUI/res/anim/ic_signal_workmode_disable_animation_mask.xml
deleted file mode 100644
index 1199442..0000000
--- a/packages/SystemUI/res/anim/ic_signal_workmode_disable_animation_mask.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 37.8337860107,-40.4599914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 9.55097961426,9.55285644531 9.55097961426,9.55285644531 c 0.0,0.0 -2.61698913574,2.09387207031 -2.61698913574,2.09387207031 c 0.0,0.0 -9.75096130371,-9.56428527832 -9.75096130371,-9.56428527832 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueTo="M 37.8337860107,-40.3974914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 40.9884796143,40.9278411865 40.9884796143,40.9278411865 c 0.0,0.0 -2.61700439453,2.0938873291 -2.61700439453,2.0938873291 c 0.0,0.0 -41.1884460449,-40.9392852783 -41.1884460449,-40.9392852783 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueType="pathType"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_cross_1.xml b/packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_cross_1.xml
deleted file mode 100644
index 6c7e751..0000000
--- a/packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_cross_1.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 7.54049682617,3.9430847168 c 0.0,0.0 31.5749816895,31.4499664307 31.5749816895,31.4499664307 "
- android:valueTo="M 7.54049682617,3.9430847168 c 0.0,0.0 0.324981689453,0.399978637695 0.324981689453,0.399978637695 "
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_signal_workmode_enable_cross_1_pathdata_interpolator" />
- <set
- android:ordering="sequentially" >
- <objectAnimator
- android:duration="333"
- android:propertyName="strokeAlpha"
- android:valueFrom="1"
- android:valueTo="1"
- android:interpolator="@android:interpolator/linear" />
- <objectAnimator
- android:duration="17"
- android:propertyName="strokeAlpha"
- android:valueFrom="1"
- android:valueTo="0"
- android:interpolator="@android:interpolator/linear" />
- </set>
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_ic_signal_briefcase.xml b/packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_ic_signal_briefcase.xml
deleted file mode 100644
index c699fe2..0000000
--- a/packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_ic_signal_briefcase.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <set
- android:ordering="sequentially" >
- <objectAnimator
- android:duration="200"
- android:propertyName="alpha"
- android:valueFrom="0.5"
- android:valueTo="0.5"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- <objectAnimator
- android:duration="350"
- android:propertyName="alpha"
- android:valueFrom="0.5"
- android:valueTo="1"
- android:interpolator="@android:interpolator/fast_out_slow_in" />
- </set>
-</set>
diff --git a/packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_mask.xml b/packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_mask.xml
deleted file mode 100644
index 5595e5c..0000000
--- a/packages/SystemUI/res/anim/ic_signal_workmode_enable_animation_mask.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:duration="350"
- android:propertyName="pathData"
- android:valueFrom="M 37.8337860107,-40.3974914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 40.9884796143,40.9278411865 40.9884796143,40.9278411865 c 0.0,0.0 -2.61700439453,2.0938873291 -2.61700439453,2.0938873291 c 0.0,0.0 -41.1884460449,-40.9392852783 -41.1884460449,-40.9392852783 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueTo="M 37.8337860107,-40.4599914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 9.55097961426,9.55285644531 9.55097961426,9.55285644531 c 0.0,0.0 -2.61698913574,2.09387207031 -2.61698913574,2.09387207031 c 0.0,0.0 -9.75096130371,-9.56428527832 -9.75096130371,-9.56428527832 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z"
- android:valueType="pathType"
- android:interpolator="@interpolator/ic_signal_workmode_enable_mask_pathdata_interpolator" />
-</set>
diff --git a/packages/SystemUI/res/color/background_protect_secondary.xml b/packages/SystemUI/res/color/background_protect_secondary.xml
new file mode 100644
index 0000000..26dc151
--- /dev/null
+++ b/packages/SystemUI/res/color/background_protect_secondary.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="?attr/bgProtectSecondaryTextColor" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/background_protected.xml b/packages/SystemUI/res/color/background_protected.xml
new file mode 100644
index 0000000..ab8ed87
--- /dev/null
+++ b/packages/SystemUI/res/color/background_protected.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="?attr/bgProtectTextColor" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/pin_delete_color.xml b/packages/SystemUI/res/color/pin_delete_color.xml
new file mode 100644
index 0000000..bb8b8e3
--- /dev/null
+++ b/packages/SystemUI/res/color/pin_delete_color.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:alpha="0.61" android:color="?attr/bgProtectTextColor" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/pin_divider_color.xml b/packages/SystemUI/res/color/pin_divider_color.xml
new file mode 100644
index 0000000..ca5f431
--- /dev/null
+++ b/packages/SystemUI/res/color/pin_divider_color.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:alpha="0.45" android:color="?attr/bgProtectSecondaryTextColor" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/qs_background_dark.xml b/packages/SystemUI/res/color/qs_background_dark.xml
new file mode 100644
index 0000000..1aa732f
--- /dev/null
+++ b/packages/SystemUI/res/color/qs_background_dark.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:alpha="0.93"
+ android:color="?android:attr/colorPrimaryDark"/>
+</selector>
diff --git a/packages/SystemUI/res/drawable/car_progress_bar.xml b/packages/SystemUI/res/drawable/car_progress_bar.xml
new file mode 100644
index 0000000..742fca7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/car_progress_bar.xml
@@ -0,0 +1,30 @@
+<!--
+ 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
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@android:id/background">
+ <shape>
+ <solid android:color="@color/car_user_switcher_progress_bgcolor" />
+ </shape>
+ </item>
+
+ <item android:id="@android:id/progress">
+ <clip>
+ <shape>
+ <solid android:color="@color/car_user_switcher_progress_fgcolor" />
+ </shape>
+ </clip>
+ </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/car_round_button.xml b/packages/SystemUI/res/drawable/car_round_button.xml
new file mode 100644
index 0000000..5f4deb3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/car_round_button.xml
@@ -0,0 +1,21 @@
+<!--
+ 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
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+
+ <solid android:color="@color/car_start_driving_background" />
+ <corners android:radius="@dimen/car_start_driving_corner_radius" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/car_stat_sys_data_bluetooth_indicator.xml b/packages/SystemUI/res/drawable/car_stat_sys_data_bluetooth_indicator.xml
index b131c38..99ee3b1 100644
--- a/packages/SystemUI/res/drawable/car_stat_sys_data_bluetooth_indicator.xml
+++ b/packages/SystemUI/res/drawable/car_stat_sys_data_bluetooth_indicator.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2016 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.
@@ -16,10 +16,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="17dp"
android:height="17dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0">
-
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M14.0,24.0l-4.0,-4.0l-4.0,4.0l4.0,4.0L14.0,24.0zM35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6zM38.0,20.0l-4.0,4.0l4.0,4.0l4.0,-4.0L38.0,20.0z"/>
+ android:viewportWidth="18.0"
+ android:viewportHeight="18.0">
+ <group
+ android:translateY="0.5"
+ android:translateX="0.5" >
+ <path
+ android:pathData="M9.57,8.5l2.79,-2.78c0.3,-0.3 0.3,-0.8 0,-1.1L9.04,1.29L9.02,1.27C8.7,0.98 8.21,1 7.91,1.31C7.78,1.45 7.71,1.64 7.71,1.84v4.79L4.69,3.61c-0.3,-0.3 -0.79,-0.3 -1.09,0s-0.3,0.79 0,1.09L7.39,8.5L3.6,12.29c-0.3,0.3 -0.3,0.79 0,1.09s0.79,0.3 1.09,0l3.01,-3.01v4.8c0,0.42 0.35,0.77 0.77,0.77c0.19,0 0.39,-0.07 0.53,-0.21l0.04,-0.04l3.32,-3.32c0.3,-0.3 0.3,-0.8 0,-1.1L9.57,8.5zM9.19,6.77v-3.2l1.6,1.6L9.19,6.77zM9.19,13.42v-3.2l1.6,1.6L9.19,13.42zM4.03,9.29c-0.44,0.44 -1.15,0.44 -1.58,0C2.02,8.86 2.02,8.16 2.45,7.72l0.01,-0.01C2.89,7.27 3.59,7.27 4.02,7.7l0.01,0.01C4.47,8.15 4.47,8.85 4.03,9.29zM14.44,7.71c0.44,0.44 0.44,1.15 0,1.58c-0.44,0.44 -1.15,0.44 -1.58,0c-0.44,-0.43 -0.44,-1.13 -0.01,-1.57l0.01,-0.01C13.3,7.28 14,7.27 14.43,7.7C14.44,7.7 14.44,7.71 14.44,7.71z"
+ android:fillColor="#FFFFFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_access_alarms_small.xml b/packages/SystemUI/res/drawable/ic_access_alarms_small.xml
index b94cc8e..f6e5ceb 100644
--- a/packages/SystemUI/res/drawable/ic_access_alarms_small.xml
+++ b/packages/SystemUI/res/drawable/ic_access_alarms_small.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -17,10 +17,8 @@
android:width="16dp"
android:height="16dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/textColorTertiary">
-
+ android:viewportHeight="24.0">
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M22.0,5.7l-4.6,-3.9l-1.3,1.5l4.6,3.9L22.0,5.7zM7.9,3.4L6.6,1.9L2.0,5.7l1.3,1.5L7.9,3.4zM12.5,8.0L11.0,8.0l0.0,6.0l4.7,2.9l0.8,-1.2l-4.0,-2.4L12.5,8.0zM12.0,4.0c-5.0,0.0 -9.0,4.0 -9.0,9.0c0.0,5.0 4.0,9.0 9.0,9.0s9.0,-4.0 9.0,-9.0C21.0,8.0 17.0,4.0 12.0,4.0zM12.0,20.0c-3.9,0.0 -7.0,-3.1 -7.0,-7.0c0.0,-3.9 3.1,-7.0 7.0,-7.0c3.9,0.0 7.0,3.1 7.0,7.0C19.0,16.9 15.9,20.0 12.0,20.0z"/>
+ android:pathData="M21.35,6.49c-0.35,0.42 -0.98,0.47 -1.4,0.12l-3.07,-2.57a1,1 0,1 1,1.29 -1.53l3.07,2.57c0.42,0.35 0.47,0.98 0.11,1.41zM7.24,2.63a1,1 0,0 0,-1.41 -0.13L2.77,5.07A0.996,0.996 0,1 0,4.05 6.6l3.06,-2.57c0.43,-0.35 0.48,-0.98 0.13,-1.4zM11.75,8c-0.41,0 -0.75,0.34 -0.75,0.75v4.68c0,0.35 0.18,0.68 0.49,0.86l3.65,2.19c0.34,0.2 0.78,0.1 0.98,-0.24a0.71,0.71 0,0 0,-0.25 -0.99l-3.37,-2v-4.5c0,-0.41 -0.34,-0.75 -0.75,-0.75zM12,5.9c-3.91,0 -7.1,3.18 -7.1,7.1s3.19,7.1 7.1,7.1 7.1,-3.18 7.1,-7.1 -3.19,-7.1 -7.1,-7.1M12,4a9,9 0,1 1,-0.001 18.001A9,9 0,0 1,12 4z"
+ android:fillColor="#FFFFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_data_saver.xml b/packages/SystemUI/res/drawable/ic_data_saver.xml
index 64bbff0..0f027ee 100644
--- a/packages/SystemUI/res/drawable/ic_data_saver.xml
+++ b/packages/SystemUI/res/drawable/ic_data_saver.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2016 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.
@@ -14,18 +14,15 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24.0dp"
- android:height="24.0dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M12.0,19.0c-3.9,0.0 -7.0,-3.1 -7.0,-7.0c0.0,-3.5 2.6,-6.4 6.0,-6.9L11.0,2.0C5.9,2.5 2.0,6.8 2.0,12.0c0.0,5.5 4.5,10.0 10.0,10.0c3.3,0.0 6.2,-1.6 8.1,-4.1l-2.6,-1.5C16.2,18.0 14.2,19.0 12.0,19.0z"/>
+ android:pathData="M16,12c0,0.55 -0.45,1 -1,1h-2v2c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1v-2L9,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h2L11,9c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v2h2c0.55,0 1,0.45 1,1zM17.69,16.87a7.437,7.437 0,0 1,-5.93 2.63c-3.82,-0.12 -7.03,-3.25 -7.25,-7.07 -0.21,-3.84 2.48,-7.1 6.07,-7.79 0.24,-0.04 0.42,-0.24 0.42,-0.48L11,2.62c0,-0.3 -0.27,-0.55 -0.57,-0.5A10.02,10.02 0,0 0,2.09 13.4c0.59,4.4 4.16,7.94 8.56,8.52a9.99,9.99 0,0 0,9.14 -3.65,0.5 0.5,0 0,0 -0.15,-0.74l-1.32,-0.76a0.469,0.469 0,0 0,-0.63 0.1z"
+ android:fillColor="#FFFFFFFF"/>
<path
- android:fillColor="#54FFFFFF"
- android:pathData="M13.0,2.0l0.0,3.0c3.4,0.5 6.0,3.4 6.0,6.9c0.0,0.9 -0.2,1.8 -0.5,2.5l2.6,1.5c0.6,-1.2 0.9,-2.6 0.9,-4.1C22.0,6.8 18.0,2.6 13.0,2.0z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M16.0,11.0l0.0,2.0 -3.0,0.0 0.0,3.0 -2.0,0.0 0.0,-3.0 -3.0,0.0 0.0,-2.0 3.0,0.0 0.0,-3.0 2.0,0.0 0.0,3.0z"/>
+ android:pathData="M13.41,4.64a0.493,0.493 0,0 1,-0.41 -0.48L13,2.62c0,-0.3 0.27,-0.55 0.57,-0.5C18.35,2.88 22,7.01 22,12c0,1.23 -0.23,2.41 -0.64,3.5 -0.11,0.28 -0.45,0.4 -0.72,0.24l-1.33,-0.77a0.484,0.484 0,0 1,-0.21 -0.59c0.25,-0.75 0.39,-1.55 0.39,-2.38 0.01,-3.65 -2.62,-6.69 -6.08,-7.36z"
+ android:fillColor="#54FFFFFF" />
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_data_saver_off.xml b/packages/SystemUI/res/drawable/ic_data_saver_off.xml
index 3001ad9..29e6c91 100644
--- a/packages/SystemUI/res/drawable/ic_data_saver_off.xml
+++ b/packages/SystemUI/res/drawable/ic_data_saver_off.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2016 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.
@@ -21,8 +21,5 @@
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
- android:pathData="M12.0,19.0c-3.9,0.0 -7.0,-3.1 -7.0,-7.0c0.0,-3.5 2.6,-6.4 6.0,-6.9L11.0,2.0C5.9,2.5 2.0,6.8 2.0,12.0c0.0,5.5 4.5,10.0 10.0,10.0c3.3,0.0 6.2,-1.6 8.1,-4.1l-2.6,-1.5C16.2,18.0 14.2,19.0 12.0,19.0z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M13.0,2.0l0.0,3.0c3.4,0.5 6.0,3.4 6.0,6.9c0.0,0.9 -0.2,1.8 -0.5,2.5l2.6,1.5c0.6,-1.2 0.9,-2.6 0.9,-4.1C22.0,6.8 18.0,2.6 13.0,2.0z"/>
+ android:pathData="M18.32,16.75l1.32,0.76c0.26,0.15 0.34,0.51 0.15,0.74 -2.09,2.6 -5.44,4.14 -9.14,3.65 -4.4,-0.58 -7.96,-4.12 -8.56,-8.52C1.34,7.8 5.21,2.95 10.43,2.12c0.3,-0.05 0.57,0.2 0.57,0.5v1.53c0,0.24 -0.18,0.44 -0.41,0.49 -3.6,0.69 -6.29,3.95 -6.07,7.79 0.21,3.82 3.43,6.95 7.25,7.07 2.37,0.08 4.51,-0.96 5.93,-2.63a0.48,0.48 0,0 1,0.62 -0.12zM19.5,12c0,0.83 -0.14,1.63 -0.39,2.38 -0.08,0.23 0.01,0.47 0.21,0.59l1.33,0.77c0.26,0.15 0.61,0.04 0.72,-0.24 0.4,-1.09 0.63,-2.27 0.63,-3.5 0,-4.99 -3.65,-9.12 -8.43,-9.88 -0.3,-0.04 -0.57,0.2 -0.57,0.5v1.53c0,0.24 0.18,0.44 0.41,0.48 3.46,0.68 6.09,3.72 6.09,7.37z" />
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_dnd_disable_animation.xml b/packages/SystemUI/res/drawable/ic_dnd_disable_animation.xml
deleted file mode 100644
index d755481..0000000
--- a/packages/SystemUI/res/drawable/ic_dnd_disable_animation.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-<animated-vector
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/ic_dnd_disable" >
- <target
- android:name="mask_1"
- android:animation="@anim/ic_dnd_disable_mask_1_animation" />
- <target
- android:name="bar01_0"
- android:animation="@anim/ic_dnd_disable_bar01_0_animation" />
-</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_dnd_total_silence_disable_animation.xml b/packages/SystemUI/res/drawable/ic_dnd_total_silence_disable_animation.xml
deleted file mode 100644
index f796d62..0000000
--- a/packages/SystemUI/res/drawable/ic_dnd_total_silence_disable_animation.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-<animated-vector
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/ic_dnd_total_silence_disable" >
- <target
- android:name="mask_1"
- android:animation="@anim/ic_dnd_total_silence_disable_mask_1_animation" />
- <target
- android:name="outer_ring_merged"
- android:animation="@anim/ic_dnd_total_silence_disable_outer_ring_merged_animation" />
-</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_hotspot.xml b/packages/SystemUI/res/drawable/ic_hotspot.xml
new file mode 100644
index 0000000..8450bf6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_hotspot.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <group
+ android:translateY="-0.32">
+ <path
+ android:pathData="M12,11c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,13a6,6 0,0 0,-6.75 -5.95c-2.62,0.32 -4.78,2.41 -5.18,5.02 -0.32,2.14 0.49,4.11 1.92,5.39 0.48,0.43 1.24,0.33 1.56,-0.23 0.24,-0.42 0.14,-0.94 -0.22,-1.26a3.99,3.99 0,0 1,-1.22 -3.94,3.954 3.954,0 0,1 2.9,-2.91A4.007,4.007 0,0 1,16 13c0,1.18 -0.51,2.23 -1.33,2.96 -0.36,0.33 -0.47,0.85 -0.23,1.27 0.31,0.54 1.04,0.69 1.5,0.28A5.97,5.97 0,0 0,18 13zM10.83,3.07c-4.62,0.52 -8.35,4.33 -8.78,8.96a9.966,9.966 0,0 0,4.02 9.01c0.48,0.35 1.16,0.2 1.46,-0.31 0.25,-0.43 0.14,-0.99 -0.26,-1.29 -2.28,-1.69 -3.65,-4.55 -3.16,-7.7 0.54,-3.5 3.46,-6.29 6.98,-6.68C15.91,4.51 20,8.28 20,13c0,2.65 -1.29,4.98 -3.27,6.44 -0.4,0.3 -0.51,0.85 -0.26,1.29 0.3,0.52 0.98,0.66 1.46,0.31A9.96,9.96 0,0 0,22 13c0,-5.91 -5.13,-10.62 -11.17,-9.93z"
+ android:fillColor="#FFFFFFFF"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_hotspot_disable.xml b/packages/SystemUI/res/drawable/ic_hotspot_disable.xml
deleted file mode 100644
index 2570483..0000000
--- a/packages/SystemUI/res/drawable/ic_hotspot_disable.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="root"
- android:alpha="1.0"
- android:height="48dp"
- android:width="48dp"
- android:viewportHeight="48"
- android:viewportWidth="48"
- android:tint="?android:attr/colorControlNormal" >
- <group
- android:name="ic_hotspot"
- android:translateX="23.9778"
- android:translateY="24.26443" >
- <group
- android:name="ic_hotspot_pivot"
- android:translateX="-23.21545"
- android:translateY="-18.86649" >
- <clip-path
- android:name="mask"
- android:pathData="M 38.8337860107,-40.3974914551 c 0.0,0.0 -38.4077911377,30.8523712158 -38.4077911377,30.8523712158 c 0.0,0.0 6.97125244141,7.33258056641 6.97125244141,7.33258056641 c 0.0,0.0 -2.4169921875,2.57838439941 -2.4169921875,2.57838439941 c 0.0,0.0 -6.77128601074,-6.82850646973 -6.77128601074,-6.82850646973 c 0.0,0.0 -32.6199798584,25.1699066162 -32.6199798584,25.1699066162 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 27.6589050293,-22.6579437256 27.6589050293,-22.6579437256 c 0.0,0.0 -30.8645172119,-34.00390625 -30.8645172119,-34.00390625 c 0.0,0.0 2.70756530762,-1.99278259277 2.70756530762,-1.99278259277 c 0.0,0.0 1.53030395508,-0.876571655273 1.53030395508,-0.876571655274 c 0.0,0.0 2.85780334473,-3.12069702148 2.85780334473,-3.12069702148 c 0.0,0.0 0.659332275391,0.664688110352 0.659332275391,0.664688110351 c 0.0,0.0 -3.13299560547,2.82977294922 -3.13299560547,2.82977294922 c 0.0,0.0 29.0108337402,34.4080963135 29.0108337402,34.4080963135 c 0.0,0.0 42.8175811768,-34.3554534912 42.8175811768,-34.3554534912 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z" />
- <group
- android:name="cross" >
- <path
- android:name="cross_1"
- android:pathData="M 4.44044494629,2.24310302734 c 0.0,0.0 0.0875396728516,0.112457275391 0.0875396728516,0.112457275391 "
- android:strokeColor="#FFFFFFFF"
- android:strokeAlpha="0"
- android:strokeWidth="3.5"
- android:fillColor="#00000000" />
- </group>
- <group
- android:name="hotspot"
- android:translateX="23.481"
- android:translateY="18.71151" >
- <group
- android:name="circles"
- android:translateX="-0.23909"
- android:translateY="-0.10807" >
- <path
- android:name="path_3"
- android:pathData="M -0.0042724609375,-2.64895629883 c -2.20922851562,0.0 -4.0,1.791015625 -4.0,4.0 c 0.0,2.20922851562 1.79077148438,4.0 4.0,4.0 c 2.208984375,0.0 4.0,-1.79077148438 4.0,-4.0 c 0.0,-2.208984375 -1.791015625,-4.0 -4.0,-4.0 Z M 11.9957275391,1.35104370117 c 0.0,-6.626953125 -5.373046875,-12.0 -12.0,-12.0 c -6.62719726562,0.0 -12.0,5.373046875 -12.0,12.0 c 0.0,4.43603515625 2.41381835938,8.30004882812 5.99194335938,10.3771972656 c 0.0,0.0 2.01586914062,-3.48217773438 2.01586914062,-3.48217773438 c -2.38500976562,-1.38500976562 -4.0078125,-3.93798828125 -4.0078125,-6.89501953125 c 0.0,-4.41796875 3.58178710938,-8.0 8.0,-8.0 c 4.41796875,0.0 8.0,3.58203125 8.0,8.0 c 0.0,2.95703125 -1.623046875,5.51000976562 -4.00805664062,6.89501953125 c 0.0,0.0 2.01586914062,3.48217773438 2.01586914062,3.48217773438 c 3.578125,-2.0771484375 5.9921875,-5.94116210938 5.9921875,-10.3771972656 Z M -0.0042724609375,-18.6489562988 c -11.0451660156,0.0 -20.0,8.9541015625 -20.0,20.0 c 0.0,7.39306640625 4.02099609375,13.8330078125 9.98779296875,17.2951660156 c 0.0,0.0 2.00219726562,-3.458984375 2.00219726562,-3.458984375 c -4.77319335938,-2.77001953125 -7.98999023438,-7.92211914062 -7.98999023438,-13.8361816406 c 0.0,-8.8369140625 7.16381835938,-16.0 16.0,-16.0 c 8.83595275879,0.0 16.0000152588,7.1630859375 16.0000152588,16.0 c 0.0,5.9140625 -3.21704101562,11.0661621094 -7.990234375,13.8361816406 c 0.0,0.0 2.00219726562,3.458984375 2.00219726563,3.458984375 c 5.966796875,-3.46215820312 9.98803710937,-9.90209960938 9.98803710938,-17.2951660156 c 0.0,-11.0458984375 -8.955078125,-20.0 -20.0000152588,-20.0 Z"
- android:fillColor="#FFFFFFFF"
- android:fillAlpha="1" />
- </group>
- </group>
- </group>
- </group>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_hotspot_disable_animation.xml b/packages/SystemUI/res/drawable/ic_hotspot_disable_animation.xml
deleted file mode 100644
index e446a32..0000000
--- a/packages/SystemUI/res/drawable/ic_hotspot_disable_animation.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/ic_hotspot_disable" >
- <target
- android:name="mask"
- android:animation="@anim/ic_hotspot_disable_animation_mask" />
- <target
- android:name="cross_1"
- android:animation="@anim/ic_hotspot_disable_animation_cross_1" />
-</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_hotspot_enable.xml b/packages/SystemUI/res/drawable/ic_hotspot_enable.xml
deleted file mode 100644
index 7a99630..0000000
--- a/packages/SystemUI/res/drawable/ic_hotspot_enable.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="root"
- android:height="48dp"
- android:width="48dp"
- android:viewportHeight="48"
- android:viewportWidth="48" >
- <group
- android:name="ic_hotspot"
- android:translateX="23.97354"
- android:translateY="24.26306" >
- <group
- android:name="ic_hotspot_pivot"
- android:translateX="-23.21545"
- android:translateY="-18.86649" >
- <clip-path
- android:name="mask"
- android:pathData="M 38.8337860107,-40.3974914551 c 0.0,0.0 -38.4077911377,30.8523712158 -38.4077911377,30.8523712158 c 0.0,0.0 43.1884765625,43.515335083 43.1884765625,43.515335083 c 0.0,0.0 -2.4169921875,2.57838439941 -2.4169921875,2.57838439941 c 0.0,0.0 -42.9885101318,-43.0112609863 -42.9885101318,-43.0112609863 c 0.0,0.0 -32.6199798584,25.1699066162 -32.6199798584,25.1699066162 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 27.6589050293,-22.6579437256 27.6589050293,-22.6579437256 c 0.0,0.0 -30.8645172119,-34.00390625 -30.8645172119,-34.00390625 c 0.0,0.0 2.70756530762,-1.99278259277 2.70756530762,-1.99278259277 c 0.0,0.0 1.53030395508,-0.876571655273 1.53030395508,-0.876571655274 c 0.0,0.0 2.85780334473,-3.12069702148 2.85780334473,-3.12069702148 c 0.0,0.0 13.0984039307,13.025604248 13.0984039307,13.025604248 c 0.0,0.0 -3.13299560547,2.82977294922 -3.13299560547,2.82977294922 c 0.0,0.0 16.571762085,22.0471801758 16.571762085,22.0471801758 c 0.0,0.0 42.8175811768,-34.3554534912 42.8175811768,-34.3554534912 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z" />
- <group
- android:name="cross" >
- <path
- android:name="cross_1"
- android:pathData="M 4.44044494629,2.24310302734 c 0.0,0.0 35.4000396729,35.3999633789 35.4000396729,35.3999633789 "
- android:strokeColor="#FFFFFFFF"
- android:strokeAlpha="1"
- android:strokeWidth="3.5"
- android:fillColor="#00000000" />
- </group>
- <group
- android:name="hotspot"
- android:translateX="23.481"
- android:translateY="18.71151" >
- <group
- android:name="circles"
- android:translateX="-0.23909"
- android:translateY="-0.10807" >
- <path
- android:name="circle_3"
- android:pathData="M 0.0843505859375,-2.93901062012 c -2.30102539062,0.0 -4.16702270508,1.86602783203 -4.16702270508,4.16702270508 c 0.0,2.29898071289 1.86599731445,4.16598510742 4.16702270508,4.16598510742 c 2.29998779297,0.0 4.16598510742,-1.86700439453 4.16598510742,-4.16598510742 c 0.0,-2.30099487305 -1.86599731445,-4.16702270508 -4.16598510742,-4.16702270508 Z M 11.1185302734,5.83390808105 c 0.0,0.0 0.0009765625,0.00100708007812 0.0009765625,0.00100708007812 c 0.14501953125,-0.356994628906 0.27099609375,-0.725006103516 0.382995605469,-1.09799194336 c 0.0570068359375,-0.195007324219 0.101013183594,-0.394989013672 0.149017333984,-0.595001220703 c 0.0690002441406,-0.281005859375 0.126983642578,-0.563995361328 0.175994873047,-0.851989746094 c 0.0270080566406,-0.169006347656 0.0559997558594,-0.337005615234 0.0759887695313,-0.509002685547 c 0.0580139160156,-0.468017578125 0.0970153808594,-0.942993164062 0.0970153808593,-1.4280090332 c 0.0,0.0 0.0,-0.00100708007812 0.0,-0.00100708007812 c 0.0,-5.03900146484 -3.11099243164,-9.3450012207 -7.5119934082,-11.1229858398 c -0.00601196289062,-0.00299072265625 -0.0130004882812,-0.0050048828125 -0.0190124511719,-0.00701904296875 c -0.686004638672,-0.275970458984 -1.39999389648,-0.492980957031 -2.14099121094,-0.638977050781 c -0.072998046875,-0.0150146484375 -0.149017333984,-0.02099609375 -0.222991943359,-0.0339965820313 c -0.302001953125,-0.0540161132812 -0.605010986328,-0.106018066406 -0.916015625,-0.136016845703 c -0.389984130859,-0.0390014648438 -0.786987304688,-0.0599975585938 -1.18899536133,-0.0599975585937 c -0.402008056641,0.0 -0.799011230469,0.02099609375 -1.19000244141,0.0599975585937 c -0.304992675781,0.0299987792969 -0.602996826172,0.0809936523438 -0.901000976563,0.132995605469 c -0.0790100097656,0.0150146484375 -0.160003662109,0.02099609375 -0.238006591797,0.0370178222656 c -0.368988037109,0.0719909667969 -0.730987548828,0.164001464844 -1.08700561523,0.269989013672 c -0.00299072265625,0.00100708007812 -0.0059814453125,0.00201416015625 -0.00900268554687,0.0020141601562 c -0.351989746094,0.10498046875 -0.694000244141,0.226989746094 -1.0309753418,0.361999511719 c -0.0110168457031,0.00399780273438 -0.0220031738281,0.00698852539062 -0.0320129394531,0.0119934082031 c -4.40200805664,1.77798461914 -7.51098632812,6.083984375 -7.5119934082,11.1229858398 c 0.0,0.00799560546875 0.00198364257812,0.0160217285156 0.0019836425781,0.0240173339844 c 0.00100708007812,0.475006103516 0.0380249023438,0.940002441406 0.0950012207032,1.39898681641 c 0.02001953125,0.175994873047 0.0490112304688,0.348999023438 0.0780029296875,0.523010253906 c 0.0469970703125,0.281982421875 0.105010986328,0.557983398438 0.171997070312,0.833984375 c 0.0480041503906,0.204010009766 0.093017578125,0.410003662109 0.152008056641,0.610015869141 c 0.110992431641,0.372009277344 0.238006591797,0.736999511719 0.382019042969,1.09298706055 c 0.0,0.0 0.0009765625,0.0 0.0009765625,0.0 c 1.00701904297,2.48400878906 2.81301879883,4.56100463867 5.11001586914,5.89501953125 c 0.0,0.0 2.01599121094,-3.48300170898 2.01599121094,-3.48300170898 c -2.03900146484,-1.18402099609 -3.5119934082,-3.22500610352 -3.89898681641,-5.63900756836 c 0.0,0.0 0.0009765625,-0.00100708007812 0.0009765625,-0.00100708007812 c -0.0220031738281,-0.130981445312 -0.0369873046875,-0.265991210938 -0.052978515625,-0.399993896484 c -0.0290222167969,-0.274993896484 -0.0570068359375,-0.552001953125 -0.0570068359375,-0.834991455078 c 0.0,0.0 0.0,-0.0190124511719 0.0,-0.0190124511719 c 0.0,-3.98999023438 2.92498779297,-7.28900146484 6.74398803711,-7.89199829102 c 0.0,0.0 0.0180053710938,0.0169982910156 0.0180053710938,0.0169982910156 c 0.404998779297,-0.0639953613281 0.81298828125,-0.125 1.23599243164,-0.125 c 0.0,0.0 0.00201416015625,0.0 0.00201416015624,0.0 c 0.0,0.0 0.00299072265625,0.0 0.00299072265626,0.0 c 0.423004150391,0.0 0.830017089844,0.0610046386719 1.23501586914,0.125 c 0.0,0.0 0.0169982910156,-0.0180053710938 0.0169982910156,-0.0180053710938 c 3.81997680664,0.60400390625 6.74499511719,3.90301513672 6.74499511719,7.89199829102 c 0.0,0.292999267578 -0.0280151367188,0.578002929688 -0.0589904785156,0.861999511719 c -0.0150146484375,0.132019042969 -0.0290222167969,0.264007568359 -0.051025390625,0.393005371094 c -0.385986328125,2.41500854492 -1.85897827148,4.45599365234 -3.89797973633,5.64001464844 c 0.0,0.0 2.01599121094,3.48300170898 2.01599121094,3.48300170898 c 2.29699707031,-1.33401489258 4.10299682617,-3.41101074219 5.11001586914,-5.89602661133 Z M 19.9300231934,2.95698547363 c 0.0059814453125,-0.0659790039062 0.0159912109375,-0.130981445312 0.02099609375,-0.196990966797 c 0.031982421875,-0.462005615234 0.0479736328125,-0.928009033203 0.0489807128906,-1.39700317383 c 0,0.0 0,-0.00997924804688 0,-0.00997924804687 c 0,0.0 0,-0.00100708007812 0,-0.00100708007813 c 0,-7.22500610352 -3.84399414062,-13.5360107422 -9.58599853516,-17.0500183105 c -1.06500244141,-0.652984619141 -2.19299316406,-1.20599365234 -3.37799072266,-1.65197753906 c -0.157989501953,-0.0599975585938 -0.317016601562,-0.118011474609 -0.476989746094,-0.174011230469 c -0.317016601562,-0.110992431641 -0.634002685547,-0.218994140625 -0.9580078125,-0.31298828125 c -0.470001220703,-0.139007568359 -0.944000244141,-0.264007568359 -1.4280090332,-0.368011474609 c -0.186004638672,-0.0390014648438 -0.376983642578,-0.0669860839844 -0.565002441406,-0.101013183594 c -0.414001464844,-0.0759887695312 -0.832000732422,-0.140991210938 -1.25500488281,-0.190979003906 c -0.184997558594,-0.0220031738281 -0.369995117188,-0.0429992675781 -0.556976318359,-0.0599975585937 c -0.592010498047,-0.0530090332031 -1.18801879883,-0.0899963378906 -1.79602050781,-0.0899963378907 c -0.605987548828,0.0 -1.20300292969,0.0369873046875 -1.79598999023,0.0899963378907 c -0.186004638672,0.0169982910156 -0.371002197266,0.0379943847656 -0.555999755859,0.0599975585937 c -0.423004150391,0.0499877929688 -0.842010498047,0.114990234375 -1.25601196289,0.190979003906 c -0.18798828125,0.0350036621094 -0.377990722656,0.06201171875 -0.563995361328,0.101013183594 c -0.483001708984,0.10400390625 -0.959991455078,0.22900390625 -1.42999267578,0.368011474609 c -0.321990966797,0.093994140625 -0.638000488281,0.201995849609 -0.953002929688,0.311981201172 c -0.162994384766,0.0570068359375 -0.324005126953,0.115997314453 -0.484985351562,0.177001953125 c -1.18099975586,0.445007324219 -2.30599975586,0.997009277344 -3.36801147461,1.64700317383 c -0.00201416015625,0.00100708007812 -0.00399780273438,0.00201416015625 -0.0060119628907,0.0029907226562 c -5.74099731445,3.51400756836 -9.58499145508,9.82501220703 -9.58599853516,17.0500183105 c 0,0.0 0,0.00100708007812 0,0.00100708007813 c 0,0.0059814453125 0.00100708007812,0.0130004882812 0.0010070800781,0.0199890136719 c 0.0,0.466003417969 0.0169982910156,0.928009033203 0.0490112304688,1.38598632812 c 0.0050048828125,0.0690002441406 0.0159912109375,0.136016845703 0.02099609375,0.206024169922 c 0.031982421875,0.401000976562 0.0719909667969,0.799987792969 0.127990722656,1.19400024414 c 0.00201416015625,0.0189819335938 0.00701904296875,0.0369873046875 0.010009765625,0.0569763183594 c 0.888000488281,6.17202758789 4.59799194336,11.4250183105 9.7799987793,14.4309997559 c 0.0,0.0 2.00198364258,-3.458984375 2.00198364258,-3.458984375 c -2.58599853516,-1.5 -4.708984375,-3.70401000977 -6.11697387695,-6.34399414063 c 0.0,0.0 0.0169982910156,-0.0180053710938 0.0169982910156,-0.0180053710938 c -0.890014648438,-1.67098999023 -1.50601196289,-3.5110168457 -1.76000976562,-5.46499633789 c -0.00698852539062,-0.0500183105469 -0.010009765625,-0.102020263672 -0.0159912109375,-0.152008056641 c -0.0330200195312,-0.273010253906 -0.0610046386719,-0.545989990234 -0.0800170898437,-0.821990966797 c -0.0220031738281,-0.343017578125 -0.0350036621094,-0.68701171875 -0.0350036621094,-1.03500366211 c 0,-6.53701782227 3.92599487305,-12.1480102539 9.54299926758,-14.6310119629 c 0.157012939453,-0.0700073242188 0.313995361328,-0.135986328125 0.472015380859,-0.199981689453 c 0.373992919922,-0.151000976562 0.751983642578,-0.294006347656 1.13900756836,-0.417022705078 c 0.108978271484,-0.0350036621094 0.221984863281,-0.0619812011719 0.332000732422,-0.0950012207031 c 0.349975585938,-0.102996826172 0.705993652344,-0.194976806641 1.06597900391,-0.273986816406 c 0.114013671875,-0.0249938964844 0.227996826172,-0.052001953125 0.342010498047,-0.0750122070313 c 0.440002441406,-0.0869750976562 0.885986328125,-0.154998779297 1.33700561523,-0.203979492188 c 0.10400390625,-0.0120239257812 0.209991455078,-0.02001953125 0.315002441406,-0.0299987792969 c 0.47998046875,-0.0429992675781 0.963989257812,-0.072998046875 1.45397949219,-0.072998046875 c 0.492004394531,0.0 0.975006103516,0.0299987792969 1.45401000977,0.072998046875 c 0.105987548828,0.00997924804688 0.212005615234,0.0179748535156 0.316986083984,0.0299987792969 c 0.450012207031,0.0489807128906 0.89501953125,0.117004394531 1.33502197266,0.203002929688 c 0.115997314453,0.0239868164062 0.22998046875,0.0509948730469 0.345001220703,0.0769958496094 c 0.358001708984,0.0780029296875 0.710998535156,0.169982910156 1.06097412109,0.272003173828 c 0.111022949219,0.0329895019531 0.226013183594,0.0609741210938 0.336029052734,0.0969848632813 c 0.385986328125,0.123016357422 0.761993408203,0.265014648438 1.13497924805,0.415008544922 c 0.160003662109,0.0650024414062 0.319000244141,0.131988525391 0.477020263672,0.201995849609 c 5.61599731445,2.48400878906 9.53997802734,8.09399414062 9.53997802734,14.6310119629 c 0,0.346984863281 -0.0130004882812,0.690979003906 -0.0350036621094,1.03399658203 c -0.0179748535156,0.274993896484 -0.0469970703125,0.548004150391 -0.0789794921875,0.819000244141 c -0.00601196289062,0.052001953125 -0.010009765625,0.10498046875 -0.0160217285156,0.154998779297 c -0.252990722656,1.95498657227 -0.871002197266,3.79400634766 -1.75997924805,5.46499633789 c 0.0,0.0 0.0169982910156,0.0180053710938 0.0169982910156,0.0180053710938 c -1.40802001953,2.63998413086 -3.53100585938,4.84399414062 -6.11700439453,6.34399414063 c 0.0,0.0 2.00198364258,3.458984375 2.00198364258,3.458984375 c 5.18402099609,-3.00698852539 8.89501953125,-8.26300048828 9.78100585938,-14.4379882813 c 0.00201416015625,-0.0169982910156 0.00601196289062,-0.0320129394531 0.0079956054688,-0.0490112304688 c 0.0570068359375,-0.39697265625 0.0970153808594,-0.798980712891 0.129028320312,-1.20300292969 Z"
- android:fillColor="#FFFFFFFF"
- android:fillAlpha="1" />
- </group>
- </group>
- </group>
- </group>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_hotspot_enable_animation.xml b/packages/SystemUI/res/drawable/ic_hotspot_enable_animation.xml
deleted file mode 100644
index 945e42f..0000000
--- a/packages/SystemUI/res/drawable/ic_hotspot_enable_animation.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/ic_hotspot_enable" >
- <target
- android:name="mask"
- android:animation="@anim/ic_hotspot_enable_animation_mask" />
- <target
- android:name="cross_1"
- android:animation="@anim/ic_hotspot_enable_animation_cross_1" />
-</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_invert_colors.xml b/packages/SystemUI/res/drawable/ic_invert_colors.xml
new file mode 100644
index 0000000..f7a86a4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_invert_colors.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <group
+ android:translateX="-0.61">
+ <path
+ android:pathData="M17.44,7.71L12.7,2.97a0.996,0.996 0,0 0,-1.41 0L6.56,7.71c-3,3 -3.4,7.89 -0.62,11.1 1.6,1.85 3.83,2.77 6.06,2.77s4.46,-0.92 6.06,-2.77c2.78,-3.21 2.38,-8.1 -0.62,-11.1zM12,19.59c-1.6,0 -3.11,-0.62 -4.24,-1.76C6.62,16.69 6,15.19 6,13.59s0.62,-3.11 1.76,-4.24L12,5.1v14.49z"
+ android:fillColor="#FFFFFFFF"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_invert_colors_disable.xml b/packages/SystemUI/res/drawable/ic_invert_colors_disable.xml
deleted file mode 100644
index 49ee48b..0000000
--- a/packages/SystemUI/res/drawable/ic_invert_colors_disable.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="root"
- android:alpha="1.0"
- android:height="48dp"
- android:width="48dp"
- android:viewportHeight="48"
- android:viewportWidth="48"
- android:tint="?android:attr/colorControlNormal" >
- <group
- android:name="icon"
- android:translateX="21.9995"
- android:translateY="25.73401" >
- <group
- android:name="icon_pivot"
- android:translateX="-23.21545"
- android:translateY="-18.86649" >
- <clip-path
- android:name="mask"
- android:pathData="M 37.8337860107,-40.4599914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 9.55097961426,9.55285644531 9.55097961426,9.55285644531 c 0.0,0.0 -2.61698913574,2.09387207031 -2.61698913574,2.09387207031 c 0.0,0.0 -9.75096130371,-9.56428527832 -9.75096130371,-9.56428527832 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z" />
- <group
- android:name="cross" >
- <path
- android:name="cross_1"
- android:pathData="M 7.54049682617,3.9430847168 c 0.0,0.0 0.324981689453,0.399978637695 0.324981689453,0.399978637695 "
- android:strokeColor="#FFFFFFFF"
- android:strokeAlpha="0"
- android:strokeWidth="3.5"
- android:fillColor="#00000000" />
- </group>
- <group
- android:name="ink_drop"
- android:translateY="-6.8" >
- <path
- android:name="outer_drop"
- android:pathData="M 35.3000030518,15.8999938965 c 0.0,0.0 -11.3000030518,-11.3999938965 -11.3000030518,-11.3999938965 c 0,0.0 -11.3000030518,11.3999938965 -11.3000030518,11.3999938965 c -6.19999694824,6.20001220703 -6.19999694824,16.4000091553 0.0,22.6000061035 c 3.10000610352,3.10000610352 7.19999694824,4.69999694824 11.3000030518,4.69999694824 c 4.10000610352,0.0 8.19999694824,-1.59999084473 11.3000030518,-4.69999694824 c 6.30000305176,-6.30000305176 6.30000305176,-16.3999938965 0.0,-22.6000061035 Z M 24,39.1999969482 c 0,0.0 0,0.0 0,0.0 c -3.19999694824,0.0 -6.19999694824,-1.19999694824 -8.5,-3.5 c -2.30000305176,-2.29998779297 -3.5,-5.30000305176 -3.5,-8.5 c 0,-3.19999694824 1.19999694824,-6.19999694824 3.5,-8.5 c 0.0,0.0 8.5,-8.5 8.5,-8.5 c 0,0.0 0,29.0 0,29.0 Z"
- android:fillColor="#FFFFFFFF"
- android:fillAlpha="1" />
- </group>
- </group>
- </group>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_invert_colors_disable_animation.xml b/packages/SystemUI/res/drawable/ic_invert_colors_disable_animation.xml
deleted file mode 100644
index aeda0a5..0000000
--- a/packages/SystemUI/res/drawable/ic_invert_colors_disable_animation.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/ic_invert_colors_disable" >
- <target
- android:name="mask"
- android:animation="@anim/ic_invert_colors_disable_animation_mask" />
- <target
- android:name="cross_1"
- android:animation="@anim/ic_invert_colors_disable_animation_cross_1" />
-</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_invert_colors_enable.xml b/packages/SystemUI/res/drawable/ic_invert_colors_enable.xml
deleted file mode 100644
index 169cc71..0000000
--- a/packages/SystemUI/res/drawable/ic_invert_colors_enable.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="root"
- android:height="48dp"
- android:width="48dp"
- android:viewportHeight="48"
- android:viewportWidth="48"
- android:tint="?android:attr/colorControlNormal" >
- <group
- android:name="icon"
- android:translateX="21.9995"
- android:translateY="25.73401" >
- <group
- android:name="icon_pivot"
- android:translateX="-23.21545"
- android:translateY="-18.86649" >
- <clip-path
- android:name="mask"
- android:pathData="M 37.8337860107,-40.3974914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 40.9884796143,40.9278411865 40.9884796143,40.9278411865 c 0.0,0.0 -2.61700439453,2.0938873291 -2.61700439453,2.0938873291 c 0.0,0.0 -41.1884460449,-40.9392852783 -41.1884460449,-40.9392852783 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z" />
- <group
- android:name="cross" >
- <path
- android:name="cross_1"
- android:pathData="M 7.54049682617,3.9430847168 c 0.0,0.0 31.5749816895,31.4499664307 31.5749816895,31.4499664307 "
- android:strokeColor="#FFFFFFFF"
- android:strokeAlpha="1"
- android:strokeWidth="3.5"
- android:fillColor="#00000000" />
- </group>
- <group
- android:name="ink_drop"
- android:translateY="-6.8" >
- <path
- android:name="outer_drop"
- android:pathData="M 35.3000030518,15.8999938965 c 0.0,0.0 -11.3000030518,-11.3999938965 -11.3000030518,-11.3999938965 c 0,0.0 -11.3000030518,11.3999938965 -11.3000030518,11.3999938965 c -6.19999694824,6.20001220703 -6.19999694824,16.4000091553 0.0,22.6000061035 c 3.10000610352,3.10000610352 7.19999694824,4.69999694824 11.3000030518,4.69999694824 c 4.10000610352,0.0 8.19999694824,-1.59999084473 11.3000030518,-4.69999694824 c 6.30000305176,-6.30000305176 6.30000305176,-16.3999938965 0.0,-22.6000061035 Z M 24,39.1999969482 c 0,0.0 0,0.0 0,0.0 c -3.19999694824,0.0 -6.19999694824,-1.19999694824 -8.5,-3.5 c -2.30000305176,-2.29998779297 -3.5,-5.30000305176 -3.5,-8.5 c 0,-3.19999694824 1.19999694824,-6.19999694824 3.5,-8.5 c 0.0,0.0 8.5,-8.5 8.5,-8.5 c 0,0.0 0,29.0 0,29.0 Z"
- android:fillColor="#FFFFFFFF"
- android:fillAlpha="1" />
- </group>
- </group>
- </group>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_invert_colors_enable_animation.xml b/packages/SystemUI/res/drawable/ic_invert_colors_enable_animation.xml
deleted file mode 100644
index 85928ac..0000000
--- a/packages/SystemUI/res/drawable/ic_invert_colors_enable_animation.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/ic_invert_colors_enable" >
- <target
- android:name="mask"
- android:animation="@anim/ic_invert_colors_enable_animation_mask" />
- <target
- android:name="cross_1"
- android:animation="@anim/ic_invert_colors_enable_animation_cross_1" />
-</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_mode_edit.xml b/packages/SystemUI/res/drawable/ic_mode_edit.xml
index f32da59..2ad3119 100644
--- a/packages/SystemUI/res/drawable/ic_mode_edit.xml
+++ b/packages/SystemUI/res/drawable/ic_mode_edit.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2016 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.
@@ -21,5 +21,5 @@
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FFFFFF"
- android:pathData="M3.0,17.25L3.0,21.0l3.75,0.0L17.81,9.94l-3.75,-3.75L3.0,17.25zM20.71,7.04c0.39,-0.3 0.39,-1.02 0.0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0.0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
+ android:pathData="M3,18.08v2.42c0,0.28 0.22,0.5 0.5,0.5h2.42c0.53,0 1.04,-0.21 1.41,-0.59L17.81,9.94l-3.75,-3.75L3.59,16.66c-0.38,0.38 -0.59,0.89 -0.59,1.42zM20.71,7.04a0.996,0.996 0,0 0,0 -1.41l-2.34,-2.34a0.996,0.996 0,0 0,-1.41 0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml
index 8a3e975..3eb81f8 100644
--- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -14,13 +14,12 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="64dp"
- android:height="64dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0"
- android:tint="?android:attr/colorControlNormal">
-
+ android:width="64dp"
+ android:height="64dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal" >
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M14.0,24.0l-4.0,-4.0l-4.0,4.0l4.0,4.0L14.0,24.0zM35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6zM38.0,20.0l-4.0,4.0l4.0,4.0l4.0,-4.0L38.0,20.0z"/>
+ android:pathData="M13.51,12l3.75,-3.74c0.41,-0.41 0.41,-1.07 0,-1.48l-4.47,-4.47 -0.03,-0.03a1.046,1.046 0,0 0,-1.76 0.76v6.44L6.95,5.43c-0.41,-0.41 -1.06,-0.41 -1.47,0s-0.41,1.06 0,1.47l5.09,5.1 -5.09,5.09c-0.41,0.41 -0.41,1.06 0,1.47s1.06,0.41 1.47,0L11,14.51v6.45a1.04,1.04 0,0 0,1.75 0.76l0.05,-0.05 4.46,-4.46c0.41,-0.41 0.41,-1.07 0,-1.48L13.51,12zM12.99,9.67v-4.3l2.15,2.15 -2.15,2.15zM12.99,18.62v-4.3l2.15,2.15 -2.15,2.15zM6.06,13.06c-0.59,0.59 -1.54,0.59 -2.12,0a1.49,1.49 0,0 1,0 -2.12,1.49 1.49,0 0,1 2.12,0c0.59,0.59 0.59,1.53 0,2.12zM20.06,10.94c0.59,0.59 0.59,1.54 0,2.12 -0.59,0.59 -1.54,0.59 -2.12,0a1.49,1.49 0,0 1,0 -2.12,1.49 1.49,0 0,1 2.12,0z"
+ android:fillColor="#FFFFFFFF" />
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_connecting.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connecting.xml
index 1dadc05..fbd92de 100644
--- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_connecting.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connecting.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -14,13 +14,12 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="64dp"
- android:height="64dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0"
- android:tint="?android:attr/colorControlNormal">
-
+ android:width="64dp"
+ android:height="64dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal" >
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M28.5,24.0l4.6,4.6c0.6,-1.4 0.9,-3.0 0.9,-4.7c0.0,-1.6 -0.3,-3.2 -0.9,-4.6L28.5,24.0zM39.1,13.4L36.5,16.0c1.3,2.4 2.0,5.1 2.0,8.0s-0.7,5.6 -2.0,8.0l2.4,2.4c1.9,-3.1 3.1,-6.7 3.1,-10.6C42.0,20.0 40.9,16.5 39.1,13.4zM31.4,15.4L20.0,4.0l-2.0,0.0l0.0,15.2L8.8,10.0L6.0,12.8L17.2,24.0L6.0,35.2L8.8,38.0l9.2,-9.2L18.0,44.0l2.0,0.0l11.4,-11.4L22.8,24.0L31.4,15.4zM22.0,11.7l3.8,3.8L22.0,19.2L22.0,11.7zM25.8,32.6L22.0,36.3l0.0,-7.5L25.8,32.6z"/>
+ android:pathData="M14.95,11.3l1.62,-1.61C16.84,10.41 17,11.18 17,12s-0.16,1.61 -0.44,2.33l-1.61,-1.61C14.56,12.33 14.56,11.69 14.95,11.3zM18.69,7.54c-0.26,0.26 -0.32,0.65 -0.17,0.98c0.47,1.07 0.72,2.24 0.72,3.47c0,1.24 -0.26,2.43 -0.73,3.49c-0.14,0.32 -0.09,0.69 0.16,0.94c0.4,0.4 1.09,0.29 1.34,-0.23c0.63,-1.3 0.98,-2.76 0.98,-4.3c-0.01,-1.46 -0.33,-2.86 -0.91,-4.12C19.84,7.23 19.12,7.11 18.69,7.54zM15.26,15.74c0.41,0.41 0.41,1.07 0,1.48l-4.51,4.51C10.57,21.89 10.32,22 10.04,22C9.47,22 9,21.53 9,20.96v-6.45l-4.05,4.05c-0.41,0.41 -1.06,0.41 -1.47,0s-0.41,-1.06 0,-1.47L8.57,12L3.48,6.9c-0.41,-0.41 -0.41,-1.06 0,-1.47s1.06,-0.41 1.47,0L9,9.49V3.04C9,2.47 9.47,2 10.04,2c0.28,0 0.53,0.11 0.72,0.29l4.5,4.5c0.41,0.41 0.41,1.07 0,1.48L11.51,12L15.26,15.74zM13.14,7.53l-2.15,-2.15v4.3C10.99,9.68 13.14,7.53 13.14,7.53zM13.14,16.47l-2.15,-2.15v4.3C10.99,18.62 13.14,16.47 13.14,16.47z"
+ android:fillColor="#FFFFFFFF" />
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.xml
index 33a4047..e6d6f2c 100644
--- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -16,12 +16,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
android:alpha="0.14"
android:tint="?android:attr/colorForeground">
<path
android:fillColor="#FFFFFF"
- android:pathData="M35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6z"/>
+ android:pathData="M13.5,12l3.8,-3.7c0.4,-0.4 0.4,-1.1 0,-1.5l-4.5,-4.5c-0.4,-0.4 -1.1,-0.4 -1.5,0.1C11.1,2.5 11,2.8 11,3v6.4L6.9,5.4C6.5,5 5.9,5 5.5,5.4s-0.4,1.1 0,1.5l5.1,5.1l-5.1,5.1c-0.4,0.4 -0.4,1.1 0,1.5s1.1,0.4 1.5,0l4.1,-4V21c0,0.6 0.5,1 1,1c0.3,0 0.5,-0.1 0.7,-0.3l0.1,0l4.5,-4.5c0.4,-0.4 0.4,-1.1 0,-1.5L13.5,12zM13,9.7V5.4l2.1,2.2L13,9.7zM13,18.6v-4.3l2.1,2.2L13,18.6z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml
deleted file mode 100644
index 3148a2c..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="64dp"
- android:height="64dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0"
- android:tint="?android:attr/colorControlNormal">
-
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M26.0,11.8l3.8,3.8l-3.2,3.2l2.8,2.8l6.0,-6.0L24.0,4.2l-2.0,0.0l0.0,10.1l4.0,4.0L26.0,11.8zM10.8,8.2L8.0,11.0l13.2,13.2L10.0,35.3l2.8,2.8L22.0,29.0l0.0,15.2l2.0,0.0l8.6,-8.6l4.6,4.6l2.8,-2.8L10.8,8.2zM26.0,36.5L26.0,29.0l3.8,3.8L26.0,36.5z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml
index 2e9e741..8cc6caa 100644
--- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -14,13 +14,12 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="64dp"
- android:height="64dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0"
- android:tint="?android:attr/colorControlNormal">
-
+ android:width="64dp"
+ android:height="64dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
- android:pathData="M35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6z"/>
+ android:pathData="M13.5,12l3.8,-3.7c0.4,-0.4 0.4,-1.1 0,-1.5l-4.5,-4.5c-0.4,-0.4 -1.1,-0.4 -1.5,0.1C11.1,2.5 11,2.8 11,3v6.4L6.9,5.4C6.5,5 5.9,5 5.5,5.4s-0.4,1.1 0,1.5l5.1,5.1l-5.1,5.1c-0.4,0.4 -0.4,1.1 0,1.5s1.1,0.4 1.5,0l4.1,-4V21c0,0.6 0.5,1 1,1c0.3,0 0.5,-0.1 0.7,-0.3l0.1,0l4.5,-4.5c0.4,-0.4 0.4,-1.1 0,-1.5L13.5,12zM13,9.7V5.4l2.1,2.2L13,9.7zM13,18.6v-4.3l2.1,2.2L13,18.6z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_brightness_auto_off.xml b/packages/SystemUI/res/drawable/ic_qs_brightness_auto_off.xml
index b7fe5ab..aaebc77 100644
--- a/packages/SystemUI/res/drawable/ic_qs_brightness_auto_off.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_brightness_auto_off.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2014 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.
diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_detail_empty.xml b/packages/SystemUI/res/drawable/ic_qs_cast_detail_empty.xml
index fc831f4..ffde286 100644
--- a/packages/SystemUI/res/drawable/ic_qs_cast_detail_empty.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_cast_detail_empty.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -23,5 +23,5 @@
<path
android:fillColor="#FFFFFF"
- android:pathData="M42.0,6.0L6.0,6.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,6.0l4.0,0.0l0.0,-6.0l36.0,0.0l0.0,28.0L28.0,38.0l0.0,4.0l14.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,10.0C46.0,7.8 44.2,6.0 42.0,6.0zM2.0,36.0l0.0,6.0l6.0,0.0C8.0,38.7 5.3,36.0 2.0,36.0zM2.0,28.0l0.0,4.0c5.5,0.0 10.0,4.5 10.0,10.0l4.0,0.0C16.0,34.3 9.7,28.0 2.0,28.0zM2.0,20.0l0.0,4.0c9.9,0.0 18.0,8.1 18.0,18.0l4.0,0.0C24.0,29.8 14.1,20.0 2.0,20.0z"/>
+ android:pathData="M1,18v2c0,0.55 0.45,1 1,1h2c0,-1.66 -1.34,-3 -3,-3zM0.97,15.06c-0.01,0.51 0.35,0.93 0.85,1.02 2.08,0.36 3.74,2 4.1,4.08 0.09,0.48 0.5,0.84 0.99,0.84 0.61,0 1.09,-0.54 1,-1.14a6.996,6.996 0,0 0,-5.8 -5.78c-0.59,-0.09 -1.12,0.38 -1.14,0.98zM0.97,11.03c-0.01,0.52 0.37,0.96 0.88,1.01 4.26,0.43 7.68,3.82 8.1,8.08 0.05,0.5 0.48,0.88 0.99,0.88 0.59,0 1.06,-0.51 1,-1.1 -0.52,-5.21 -4.66,-9.34 -9.87,-9.85 -0.57,-0.05 -1.08,0.4 -1.1,0.98zM21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2z" />
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_off.xml b/packages/SystemUI/res/drawable/ic_qs_cast_off.xml
index 06a0886..9e57577 100644
--- a/packages/SystemUI/res/drawable/ic_qs_cast_off.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_cast_off.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -14,12 +14,11 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="64dp"
- android:height="64dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0">
-
+ android:width="64dp"
+ android:height="64dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M42.0,6.0L6.0,6.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,6.0l4.0,0.0l0.0,-6.0l36.0,0.0l0.0,28.0L28.0,38.0l0.0,4.0l14.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,10.0C46.0,7.8 44.2,6.0 42.0,6.0zM2.0,36.0l0.0,6.0l6.0,0.0C8.0,38.7 5.3,36.0 2.0,36.0zM2.0,28.0l0.0,4.0c5.5,0.0 10.0,4.5 10.0,10.0l4.0,0.0C16.0,34.3 9.7,28.0 2.0,28.0zM2.0,20.0l0.0,4.0c9.9,0.0 18.0,8.1 18.0,18.0l4.0,0.0C24.0,29.8 14.1,20.0 2.0,20.0z"/>
+ android:pathData="M1,18v2c0,0.55 0.45,1 1,1h2c0,-1.66 -1.34,-3 -3,-3zM0.97,15.06c-0.01,0.51 0.35,0.93 0.85,1.02 2.08,0.36 3.74,2 4.1,4.08 0.09,0.48 0.5,0.84 0.99,0.84 0.61,0 1.09,-0.54 1,-1.14a6.996,6.996 0,0 0,-5.8 -5.78c-0.59,-0.09 -1.12,0.38 -1.14,0.98zM0.97,11.03c-0.01,0.52 0.37,0.96 0.88,1.01 4.26,0.43 7.68,3.82 8.1,8.08 0.05,0.5 0.48,0.88 0.99,0.88 0.59,0 1.06,-0.51 1,-1.1 -0.52,-5.21 -4.66,-9.34 -9.87,-9.85 -0.57,-0.05 -1.08,0.4 -1.1,0.98zM21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2z"
+ android:fillColor="#FFFFFFFF" />
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_on.xml b/packages/SystemUI/res/drawable/ic_qs_cast_on.xml
index 794eb9e..3dda87c 100644
--- a/packages/SystemUI/res/drawable/ic_qs_cast_on.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_cast_on.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -16,10 +16,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0">
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
- android:pathData="M2.0,36.0l0.0,6.0l6.0,0.0C8.0,38.7 5.3,36.0 2.0,36.0zM2.0,28.0l0.0,4.0c5.5,0.0 10.0,4.5 10.0,10.0l4.0,0.0C16.0,34.3 9.7,28.0 2.0,28.0zM38.0,14.0L10.0,14.0l0.0,3.3c7.9,2.6 14.2,8.8 16.7,16.7L38.0,34.0L38.0,14.0zM2.0,20.0l0.0,4.0c9.9,0.0 18.0,8.1 18.0,18.0l4.0,0.0C24.0,29.8 14.1,20.0 2.0,20.0zM42.0,6.0L6.0,6.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,6.0l4.0,0.0l0.0,-6.0l36.0,0.0l0.0,28.0L28.0,38.0l0.0,4.0l14.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,10.0C46.0,7.8 44.2,6.0 42.0,6.0z"/>
+ android:pathData="M1,18v2c0,0.55 0.45,1 1,1h2c0,-1.66 -1.34,-3 -3,-3zM0.97,15.06c-0.01,0.51 0.35,0.93 0.85,1.02 2.08,0.36 3.74,2 4.1,4.08 0.09,0.48 0.5,0.84 0.99,0.84 0.61,0 1.09,-0.54 1,-1.14a6.996,6.996 0,0 0,-5.8 -5.78c-0.59,-0.09 -1.12,0.38 -1.14,0.98zM19,7L5,7v1.63c3.96,1.28 7.09,4.41 8.37,8.37L19,17L19,7zM0.97,11.03c-0.01,0.52 0.37,0.96 0.88,1.01 4.26,0.43 7.68,3.82 8.1,8.08 0.05,0.5 0.48,0.88 0.99,0.88 0.59,0 1.06,-0.51 1,-1.1 -0.52,-5.21 -4.66,-9.34 -9.87,-9.85 -0.57,-0.05 -1.08,0.4 -1.1,0.98zM21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2z" />
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml b/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml
index 6264484..8b5e4b0 100644
--- a/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml
@@ -14,10 +14,11 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="17dp"
- android:height="17.0dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:width="24dp"
+ android:height="24.0dp"
+ android:viewportWidth="40.0"
+ android:viewportHeight="40.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19.0,6.41L17.59,5.0 12.0,10.59 6.41,5.0 5.0,6.41 10.59,12.0 5.0,17.59 6.41,19.0 12.0,13.41 17.59,19.0 19.0,17.59 13.41,12.0z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_dnd_detail_empty.xml b/packages/SystemUI/res/drawable/ic_qs_dnd_detail_empty.xml
new file mode 100644
index 0000000..d71018b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_dnd_detail_empty.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="56dp"
+ android:height="56dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:alpha="0.14"
+ android:tint="?android:attr/colorForeground">
+ <path
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM16,13L8,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h8c0.55,0 1,0.45 1,1s-0.45,1 -1,1z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_dnd_off.xml b/packages/SystemUI/res/drawable/ic_qs_dnd_off.xml
deleted file mode 100644
index 9168dbc..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_dnd_off.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:viewportHeight="24.0"
- android:viewportWidth="24.0"
- android:width="64dp" >
-
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M13.8,11.1L17.0,11.1l0.0,2.0l-1.2,0.0l4.5,4.5c1.1,-1.6 1.7,-3.5 1.7,-5.5c0.0,-5.5 -4.5,-10.0 -10.0,-10.0c-2.0,0.0 -3.9,0.6 -5.5,1.7L13.8,11.1z" />
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M13.0,13.1L7.0,13.1l0.0,-2.0l4.0,0.0L4.9,5.0C3.1,6.8 2.0,9.3 2.0,12.1c0.0,5.5 4.5,10.0 10.0,10.0c2.8,0.0 5.3,-1.1 7.1,-2.9L13.0,13.1z" />
-
- <group
- android:pivotX="12.0"
- android:pivotY="12.0"
- android:rotation="45.0"
- android:translateX="0.5"
- android:translateY="0.5" >
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M-2.8,11.8l28.3,0.0l0.0,2.0l-28.3,0.0z" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_qs_dnd_on.xml b/packages/SystemUI/res/drawable/ic_qs_dnd_on.xml
index f4c20a9..233b9b9 100644
--- a/packages/SystemUI/res/drawable/ic_qs_dnd_on.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_dnd_on.xml
@@ -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.
@@ -16,9 +16,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0">
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
<path
- android:fillColor="#FFFFFFFF"
- android:pathData="M24.0,4.0C12.95,4.0 4.0,12.95 4.0,24.0s8.95,20.0 20.0,20.0 20.0,-8.95 20.0,-20.0S35.05,4.0 24.0,4.0zm10.0,22.0L14.0,26.0l0.0,-4.0l20.0,0.0l0.0,4.0z"/>
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM16,13L8,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h8c0.55,0 1,0.45 1,1s-0.45,1 -1,1z"
+ android:fillColor="#FFFFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_dnd_on_total_silence.xml b/packages/SystemUI/res/drawable/ic_qs_dnd_on_total_silence.xml
index fb26c09..5012aa4 100644
--- a/packages/SystemUI/res/drawable/ic_qs_dnd_on_total_silence.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_dnd_on_total_silence.xml
@@ -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.
@@ -18,12 +18,7 @@
android:height="64dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
-
<path
android:fillColor="#FFFFFFFF"
- android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0s10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM12.0,20.5c-4.7,0.0 -8.5,-3.8 -8.5,-8.5S7.3,3.5 12.0,3.5s8.5,3.8 8.5,8.5S16.7,20.5 12.0,20.5z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M12.0,6.0c-3.3,0.0 -6.0,2.7 -6.0,6.0c0.0,3.3 2.7,6.0 6.0,6.0s6.0,-2.7 6.0,-6.0C18.0,8.7 15.4,6.0 12.0,6.0zM15.0,13.0L9.0,13.0l0.0,-2.0l6.0,0.0L15.0,13.0z"/>
-
+ android:pathData="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10 10,-4.5 10,-10S17.5,2 12,2zM12,20.5c-4.7,0 -8.5,-3.8 -8.5,-8.5S7.3,3.5 12,3.5s8.5,3.8 8.5,8.5 -3.8,8.5 -8.5,8.5zM12,6c-3.3,0 -6,2.7 -6,6s2.7,6 6,6 6,-2.7 6,-6 -2.6,-6 -6,-6zM14,13h-4c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h4c0.55,0 1,0.45 1,1s-0.45,1 -1,1z" />
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_night_display_off.xml b/packages/SystemUI/res/drawable/ic_qs_night_display_off.xml
deleted file mode 100644
index aaca663..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_night_display_off.xml
+++ /dev/null
@@ -1,26 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="64dp"
- android:height="64dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
-
- <path
- android:fillColor="#FFF"
- android:pathData="M6,12c0,5.5,4.5,10,10,10c1,0,2-0.2,3-0.5c-4.1-1.3-7-5.1-7-9.5s2.9-8.3,7-9.5C18.1,2.2,17.1,2,16,2C10.5,2,6,6.5,6,12z" />
-
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_night_display_on.xml b/packages/SystemUI/res/drawable/ic_qs_night_display_on.xml
index aaca663..8801f5d 100644
--- a/packages/SystemUI/res/drawable/ic_qs_night_display_on.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_night_display_on.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2016 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.
@@ -16,11 +16,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
-
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
<path
- android:fillColor="#FFF"
- android:pathData="M6,12c0,5.5,4.5,10,10,10c1,0,2-0.2,3-0.5c-4.1-1.3-7-5.1-7-9.5s2.9-8.3,7-9.5C18.1,2.2,17.1,2,16,2C10.5,2,6,6.5,6,12z" />
-
-</vector>
+ android:pathData="M13,12c0,-3.57 2.2,-6.62 5.31,-7.87 0.89,-0.36 0.75,-1.69 -0.19,-1.9 -1.1,-0.24 -2.27,-0.3 -3.48,-0.14 -4.51,0.6 -8.12,4.31 -8.59,8.83C5.43,16.93 10.12,22 16,22c0.73,0 1.43,-0.08 2.12,-0.23 0.95,-0.21 1.1,-1.53 0.2,-1.9A8.471,8.471 0,0 1,13 12z"
+ android:fillColor="#FFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml b/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml
new file mode 100644
index 0000000..15d521f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M3.500000,11.000000L1.800000,11.000000L1.800000,4.400000L0.200000,5.100000L0.200000,3.700000l3.100000,-1.300000l0.200000,0.000000L3.500000,11.000000z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M8.600000,5.500000l1.200000,-3.000000l1.900000,0.000000L9.700000,6.700000l2.200000,4.300000L9.900000,11.000000L8.700000,7.900000L7.400000,11.000000L5.500000,11.000000l2.100000,-4.300000L5.600000,2.500000l1.900000,0.000000L8.600000,5.500000z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml b/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml
new file mode 100644
index 0000000..ce37331
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M2.000000,6.000000l0.800000,0.000000c0.300000,0.000000 0.500000,-0.100000 0.700000,-0.300000s0.200000,-0.500000 0.200000,-0.900000c0.000000,-0.300000 -0.100000,-0.600000 -0.200000,-0.800000S3.200000,3.700000 2.900000,3.700000C2.700000,3.700000 2.500000,3.800000 2.300000,4.000000S2.100000,4.400000 2.100000,4.700000L0.500000,4.700000C0.500000,4.000000 0.700000,3.400000 1.100000,3.000000s1.000000,-0.600000 1.700000,-0.600000c0.800000,0.000000 1.400000,0.200000 1.900000,0.600000s0.700000,1.000000 0.700000,1.800000c0.000000,0.400000 -0.100000,0.700000 -0.300000,1.100000S4.600000,6.500000 4.300000,6.600000C4.700000,6.800000 5.000000,7.100000 5.200000,7.400000s0.300000,0.700000 0.300000,1.200000c0.000000,0.800000 -0.200000,1.400000 -0.700000,1.800000s-1.100000,0.700000 -1.900000,0.700000c-0.700000,0.000000 -1.300000,-0.200000 -1.800000,-0.600000s-0.700000,-1.000000 -0.700000,-1.800000L2.000000,8.700000C2.000000,9.000000 2.100000,9.300000 2.300000,9.500000s0.400000,0.300000 0.600000,0.300000c0.300000,0.000000 0.500000,-0.100000 0.700000,-0.300000S3.900000,9.000000 3.900000,8.600000c0.000000,-0.500000 -0.100000,-0.800000 -0.300000,-1.000000S3.200000,7.300000 2.800000,7.300000L2.000000,7.300000L2.000000,6.000000z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M12.500000,9.900000c-0.200000,0.400000 -0.600000,0.700000 -1.000000,0.900000s-1.000000,0.400000 -1.800000,0.400000c-0.900000,0.000000 -1.700000,-0.300000 -2.200000,-0.800000S6.700000,9.000000 6.700000,7.900000L6.700000,5.600000c0.000000,-1.100000 0.300000,-1.900000 0.800000,-2.400000s1.200000,-0.800000 2.100000,-0.800000c1.000000,0.000000 1.700000,0.200000 2.100000,0.700000s0.700000,1.200000 0.700000,2.100000l-1.600000,0.000000c0.000000,-0.500000 -0.100000,-0.900000 -0.200000,-1.100000s-0.500000,-0.300000 -0.900000,-0.300000c-0.400000,0.000000 -0.700000,0.200000 -0.900000,0.500000S8.400000,5.000000 8.400000,5.600000l0.000000,2.300000c0.000000,0.700000 0.100000,1.100000 0.300000,1.400000s0.600000,0.500000 1.000000,0.500000c0.300000,0.000000 0.600000,0.000000 0.700000,-0.100000s0.300000,-0.200000 0.400000,-0.300000L10.799999,7.800000L9.600000,7.800000L9.600000,6.600000l2.900000,0.000000L12.500000,9.900000z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml b/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml
new file mode 100644
index 0000000..4a8d0ab
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M4.600000,7.800000l0.700000,0.000000l0.000000,1.300000L4.600000,9.100000L4.600000,11.000000L3.000000,11.000000L3.000000,9.200000L0.100000,9.200000L0.000000,8.100000L3.000000,2.500000l1.700000,0.000000L4.700000,7.800000zM1.600000,7.800000L3.000000,7.800000l0.000000,-3.000000L2.900000,5.000000L1.600000,7.800000z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M11.900000,9.900000c-0.200000,0.400000 -0.600000,0.700000 -1.000000,0.900000s-1.000000,0.400000 -1.800000,0.400000c-0.900000,0.000000 -1.700000,-0.300000 -2.200000,-0.800000S6.100000,9.000000 6.100000,7.900000L6.100000,5.600000c0.000000,-1.100000 0.300000,-1.900000 0.800000,-2.400000S8.100000,2.400000 9.000000,2.400000c1.000000,0.000000 1.700000,0.200000 2.100000,0.700000s0.700000,1.200000 0.700000,2.100000l-1.600000,0.000000c0.000000,-0.500000 -0.100000,-0.900000 -0.200000,-1.100000S9.500000,3.700000 9.000000,3.700000c-0.400000,0.000000 -0.700000,0.200000 -0.900000,0.500000S7.700000,5.000000 7.700000,5.600000l0.000000,2.300000c0.000000,0.700000 0.100000,1.100000 0.300000,1.400000s0.600000,0.500000 1.000000,0.500000c0.300000,0.000000 0.600000,0.000000 0.700000,-0.100000s0.300000,-0.200000 0.400000,-0.300000L10.099999,7.800000L9.000000,7.800000L9.000000,6.600000l2.900000,0.000000L11.900000,9.900000z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml b/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml
new file mode 100644
index 0000000..e0c6b68
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml
@@ -0,0 +1,31 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M4.6,7.8l0.7,0.0l0.0,1.3L4.6,9.1L4.6,11.0L3.0,11.0L3.0,9.2L0.1,9.2L0.0,8.2l3.0,-5.7l1.7,0.0L4.6,7.8L4.6,7.8zM1.7,7.8L3.0,7.8l0.0,-3.0L2.9,5.0L1.7,7.8z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M11.9,9.9c-0.2,0.4 -0.6,0.7 -1.0,0.9s-1.0,0.4 -1.8,0.4c-0.9,0.0 -1.7,-0.3 -2.2,-0.8S6.1,9.0 6.1,7.9L6.1,5.6c0.0,-1.1 0.3,-1.9 0.8,-2.4S8.2,2.4 9.0,2.4c1.0,0.0 1.7,0.2 2.1,0.7s0.7,1.2 0.7,2.1l-1.6,0.0c0.0,-0.5 -0.1,-0.9 -0.2,-1.1S9.5,3.7 9.0,3.7c-0.4,0.0 -0.7,0.2 -0.9,0.5S7.8,5.0 7.8,5.6l0.0,2.3c0.0,0.7 0.1,1.1 0.3,1.4c0.2,0.3 0.6,0.5 1.0,0.5c0.3,0.0 0.6,0.0 0.7,-0.1s0.3,-0.2 0.4,-0.3L10.2,7.8L9.0,7.8L9.0,6.6l2.9,0.0L11.9,9.9z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M17.7,4.4l-1.900001,0.0 0.0,-1.9 -1.2,0.0 0.0,1.9 -1.900001,0.0 0.0,1.2 1.900001,0.0 0.0,1.9 1.2,0.0 0.0,-1.9 1.900001,0.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_disabled.xml b/packages/SystemUI/res/drawable/ic_qs_signal_disabled.xml
new file mode 100644
index 0000000..c841a66
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_disabled.xml
@@ -0,0 +1,29 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="true"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M21.799999,22.299999l-1.199999,-1.299999 0.000000,0.000000 -9.600000,-10.000000 0.000000,0.000000 -6.400000,-6.700000 -1.300000,1.300000 6.400000,6.700000 -8.700000,8.700000 16.900000,0.000000 2.600000,2.700001z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M21.000000,1.000000l-8.600000,8.600000 8.600000,9.100000z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_e.xml b/packages/SystemUI/res/drawable/ic_qs_signal_e.xml
new file mode 100644
index 0000000..5525508
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_e.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <group
+ android:translateX="3.5" >
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M4.400000,7.300000L1.700000,7.300000l0.000000,2.400000l3.300000,0.000000L5.000000,11.000000L0.000000,11.000000L0.000000,2.500000l4.900000,0.000000l0.000000,1.300000L1.700000,3.800000l0.000000,2.100000l2.800000,0.000000L4.500000,7.300000z"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_g.xml b/packages/SystemUI/res/drawable/ic_qs_signal_g.xml
new file mode 100644
index 0000000..f499fe7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_g.xml
@@ -0,0 +1,27 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <group android:translateX="3.5" >
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M6.500000,9.900000c-0.200000,0.400000 -0.600000,0.700000 -1.000000,0.900000s-1.000000,0.400000 -1.800000,0.400000c-0.900000,0.000000 -1.700000,-0.300000 -2.200000,-0.800000S0.700000,9.000000 0.700000,7.900000L0.700000,5.600000c0.000000,-1.100000 0.300000,-1.900000 0.800000,-2.400000s1.200000,-0.800000 2.100000,-0.800000c1.000000,0.000000 1.700000,0.200000 2.100000,0.700000s0.700000,1.200000 0.700000,2.100000L4.700000,5.200000c0.000000,-0.500000 -0.100000,-0.900000 -0.200000,-1.100000S4.000000,3.700000 3.600000,3.700000c-0.400000,0.000000 -0.700000,0.200000 -0.900000,0.500000S2.300000,5.000000 2.300000,5.600000l0.000000,2.300000c0.000000,0.700000 0.100000,1.100000 0.300000,1.400000s0.600000,0.500000 1.000000,0.500000c0.300000,0.000000 0.600000,0.000000 0.700000,-0.100000s0.300000,-0.200000 0.400000,-0.300000L4.700000,7.800000L3.500000,7.800000L3.500000,6.600000l2.900000,0.000000L6.400000,9.900000z"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_h.xml b/packages/SystemUI/res/drawable/ic_qs_signal_h.xml
new file mode 100644
index 0000000..2e6ea23
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_h.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <group
+ android:translateX="3.5" >
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M6.000000,11.000000L4.400000,11.000000L4.400000,7.500000L1.700000,7.500000L1.700000,11.000000L0.000000,11.000000L0.000000,2.500000l1.700000,0.000000l0.000000,3.700000l2.700000,0.000000L4.400000,2.500000L6.000000,2.500000L6.000000,11.000000z"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml b/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml
new file mode 100644
index 0000000..af9c446
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml
@@ -0,0 +1,31 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M2.000000,9.700000l2.000000,0.000000L4.000000,11.000000L0.300000,11.000000L0.300000,2.500000L2.000000,2.500000L2.000000,9.700000z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M8.300000,3.800000L7.000000,3.800000L7.000000,11.000000L5.300000,11.000000L5.300000,3.800000L4.000000,3.800000L4.000000,2.500000l4.300000,0.000000L8.300000,3.800000z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M12.400000,7.300000l-1.700000,0.000000l0.000000,2.400000l2.100000,0.000000L12.799999,11.000000L9.000000,11.000000L9.000000,2.500000l3.700000,0.000000l0.000000,1.300000l-2.100000,0.000000l0.000000,2.100000l1.700000,0.000000L12.300000,7.300000z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml b/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml
new file mode 100644
index 0000000..5ff7d85
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml
@@ -0,0 +1,34 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M2.0,9.7l2.0,0.0L4.0,11.0L0.4,11.0L0.4,2.5L2.0,2.5L2.0,9.7z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M8.3,3.8L7.0,3.8L7.0,11.0L5.4,11.0L5.4,3.8L4.0,3.8L4.0,2.5l4.3,0.0L8.3,3.8z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M12.4,7.3l-1.7,0.0l0.0,2.4l2.1,0.0L12.799999,11.0L9.0,11.0L9.0,2.5l3.7,0.0l0.0,1.3l-2.1,0.0l0.0,2.1l1.7,0.0L12.4,7.3L12.4,7.3z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M18.4,4.4l-1.9,0.0 0.0,-1.9 -1.2,0.0 0.0,1.9 -1.900001,0.0 0.0,1.2 1.900001,0.0 0.0,1.9 1.2,0.0 0.0,-1.9 1.9,0.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_0.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_0.xml
index e6f9292..7cbc26f 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_0.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_0.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2016 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.
@@ -14,15 +14,19 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24.0dp"
- android:height="24.0dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
- android:fillAlpha="0.3"
- android:fillColor="#FFFFFF"/>
- <path
- android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.799999,-1.800001z"
- android:fillColor="#FFFFFF"/>
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="25.50"
+ android:viewportHeight="25.50">
+ <group
+ android:translateX="0.77"
+ android:translateY="0.23" >
+ <path
+ android:pathData="M14,12h6.54l3.12,-3.89c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3C6.44,3 2.33,5.36 0.56,6.57C0.05,6.92 -0.05,7.63 0.33,8.11L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L14,20.13V12z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
+ <path
+ android:pathData="M22.71,15.67l-1.83,1.83l1.83,1.83c0.38,0.38 0.38,1 0,1.38v0c-0.38,0.38 -1,0.39 -1.38,0l-1.83,-1.83l-1.83,1.83c-0.38,0.38 -1,0.38 -1.38,0l-0.01,-0.01c-0.38,-0.38 -0.38,-1 0,-1.38l1.83,-1.83l-1.82,-1.82c-0.38,-0.38 -0.38,-1 0,-1.38l0.01,-0.01c0.38,-0.38 1,-0.38 1.38,0l1.82,1.82l1.82,-1.82c0.38,-0.38 1,-0.38 1.38,0l0,0C23.09,14.67 23.09,15.29 22.71,15.67z"
+ android:fillColor="#FFFFFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_1.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_1.xml
index d423ccb..675dfc9 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_1.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_1.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2016 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.
@@ -14,18 +14,19 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24.0dp"
- android:height="24.0dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M13.8,13.2c-0.1,0.0 -0.3,-0.1 -0.4,-0.1c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,0.0 -0.6,-0.1 -0.9,-0.1c0.0,0.0 0.0,0.0 -0.1,0.0c0.0,0.0 0.0,0.0 0.0,0.0s0.0,0.0 0.0,0.0c0.0,0.0 0.0,0.0 -0.1,0.0c-0.3,0.0 -0.6,0.0 -0.9,0.1c-0.1,0.0 -0.3,0.0 -0.4,0.1c-0.2,0.0 -0.3,0.1 -0.5,0.1c-0.2,0.0 -0.3,0.1 -0.5,0.1c-0.1,0.0 -0.1,0.0 -0.2,0.1c-1.6,0.5 -2.7,1.3 -2.8,1.5l5.3,6.6l0.0,0.0l0.0,0.0l0.0,0.0l0.0,0.0l1.8,-2.2L13.700002,13.2z"
- android:fillColor="#FFFFFF"/>
- <path
- android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
- android:fillAlpha="0.3"
- android:fillColor="#FFFFFF"/>
- <path
- android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.799999,-1.800001z"
- android:fillColor="#FFFFFF"/>
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="25.50"
+ android:viewportHeight="25.50">
+ <group
+ android:translateX="0.77"
+ android:translateY="0.23" >
+ <path
+ android:pathData="M14,12h6.54l3.12,-3.89c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3C6.44,3 2.33,5.36 0.56,6.57C0.05,6.92 -0.05,7.63 0.33,8.11L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L14,20.13V12z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
+ <path
+ android:pathData="M14,20.13l-1.18,1.47c-0.43,0.53 -1.23,0.53 -1.66,0l-5.1,-6.35C7.65,13.85 9.72,13 12,13c0.69,0 1.36,0.08 2,0.23V20.13zM22.71,14.29L22.71,14.29c-0.38,-0.38 -1,-0.39 -1.38,0l-1.82,1.82l-1.82,-1.82c-0.38,-0.38 -1,-0.38 -1.38,0l-0.01,0.01c-0.38,0.38 -0.38,1 0,1.38l1.82,1.82l-1.83,1.83c-0.38,0.38 -0.38,1 0,1.38l0.01,0.01c0.38,0.38 1,0.38 1.38,0l1.83,-1.83l1.83,1.83c0.38,0.38 1,0.38 1.38,0v0c0.38,-0.38 0.38,-1 0,-1.38l-1.83,-1.83l1.83,-1.83C23.09,15.29 23.09,14.67 22.71,14.29z"
+ android:fillColor="#FFFFFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_2.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_2.xml
index 1982130..c9a7eb8 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_2.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_2.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2016 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.
@@ -14,18 +14,19 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24.0dp"
- android:height="24.0dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M13.8,12.2l4.9,0.0c-1.0,-0.7 -3.4,-2.2 -6.7,-2.2c-4.1,0.0 -6.9,2.2 -7.2,2.5l7.2,9.0l0.0,0.0l0.0,0.0l1.8,-2.2L13.800001,12.2z"
- android:fillColor="#FFFFFF"/>
- <path
- android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
- android:fillAlpha="0.3"
- android:fillColor="#FFFFFF"/>
- <path
- android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.800001,1.9 -1.800001,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
- android:fillColor="#FFFFFF"/>
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="25.50"
+ android:viewportHeight="25.50">
+ <group
+ android:translateX="0.77"
+ android:translateY="0.23" >
+ <path
+ android:pathData="M14,12h6.54l3.12,-3.89c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3C6.44,3 2.33,5.36 0.56,6.57C0.05,6.92 -0.05,7.63 0.33,8.11L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L14,20.13V12z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
+ <path
+ android:pathData="M14,20.13l-1.18,1.47c-0.43,0.53 -1.23,0.53 -1.66,0l-6.98,-8.7C6.28,11.1 9.01,10 12,10c2.45,0 4.72,0.74 6.62,2H14V20.13zM22.71,14.29L22.71,14.29c-0.38,-0.38 -1,-0.39 -1.38,0l-1.82,1.82l-1.82,-1.82c-0.38,-0.38 -1,-0.38 -1.38,0l-0.01,0.01c-0.38,0.38 -0.38,1 0,1.38l1.82,1.82l-1.83,1.83c-0.38,0.38 -0.38,1 0,1.38l0.01,0.01c0.38,0.38 1,0.38 1.38,0l1.83,-1.83l1.83,1.83c0.38,0.38 1,0.38 1.38,0v0c0.38,-0.38 0.38,-1 0,-1.38l-1.83,-1.83l1.83,-1.83C23.09,15.29 23.09,14.67 22.71,14.29z"
+ android:fillColor="#FFFFFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_3.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_3.xml
index b350111..a6facae 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_3.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_3.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2016 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.
@@ -14,18 +14,19 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24.0dp"
- android:height="24.0dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M13.8,12.2l5.7,0.0l1.0,-1.2C20.0,10.6 16.8,8.0 12.0,8.0s-8.0,2.6 -8.5,3.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
- android:fillColor="#FFFFFF"/>
- <path
- android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
- android:fillAlpha="0.3"
- android:fillColor="#FFFFFF"/>
- <path
- android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
- android:fillColor="#FFFFFF"/>
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="25.50"
+ android:viewportHeight="25.50">
+ <group
+ android:translateX="0.77"
+ android:translateY="0.23" >
+ <path
+ android:pathData="M14,12h6.54l3.12,-3.89c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3C6.44,3 2.33,5.36 0.56,6.57C0.05,6.92 -0.05,7.63 0.33,8.11L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L14,20.13V12z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFF"/>
+ <path
+ android:pathData="M14,20.13l-1.18,1.47c-0.43,0.53 -1.23,0.53 -1.66,0L2.93,11.35C5.37,9.26 8.54,8 12,8c3.46,0 6.62,1.26 9.07,3.34L20.54,12H14V20.13zM22.71,14.29L22.71,14.29c-0.38,-0.38 -1,-0.39 -1.38,0l-1.82,1.82l-1.82,-1.82c-0.38,-0.38 -1,-0.38 -1.38,0l-0.01,0.01c-0.38,0.38 -0.38,1 0,1.38l1.82,1.82l-1.83,1.83c-0.38,0.38 -0.38,1 0,1.38l0.01,0.01c0.38,0.38 1,0.38 1.38,0l1.83,-1.83l1.83,1.83c0.38,0.38 1,0.38 1.38,0v0c0.38,-0.38 0.38,-1 0,-1.38l-1.83,-1.83l1.83,-1.83C23.09,15.29 23.09,14.67 22.71,14.29z"
+ android:fillColor="#FFFFFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_4.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_4.xml
index 136a004..2eae8f5 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_4.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_4.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2016 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.
@@ -14,14 +14,15 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24.0dp"
- android:height="24.0dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
- android:fillColor="#FFFFFF"/>
- <path
- android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
- android:fillColor="#FFFFFF"/>
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="25.50"
+ android:viewportHeight="25.50">
+ <group
+ android:translateX="0.77"
+ android:translateY="0.23" >
+ <path
+ android:pathData="M22.71,15.67l-1.83,1.83l1.83,1.83c0.38,0.38 0.38,1 0,1.38v0c-0.38,0.38 -1,0.39 -1.38,0l-1.83,-1.83l-1.83,1.83c-0.38,0.38 -1,0.38 -1.38,0l-0.01,-0.01c-0.38,-0.38 -0.38,-1 0,-1.38l1.83,-1.83l-1.82,-1.82c-0.38,-0.38 -0.38,-1 0,-1.38l0.01,-0.01c0.38,-0.38 1,-0.38 1.38,0l1.82,1.82l1.82,-1.82c0.38,-0.38 1,-0.38 1.38,0l0,0C23.09,14.67 23.09,15.29 22.71,15.67zM14,12h6.54l3.12,-3.89c0.39,-0.48 0.28,-1.19 -0.23,-1.54C21.66,5.36 17.55,3 12,3C6.44,3 2.33,5.36 0.56,6.57C0.05,6.92 -0.05,7.63 0.33,8.11L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L14,20.13V12z"
+ android:fillColor="#FFFFFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.xml
index 7993c80..95345b8 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -14,14 +14,17 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="56dp"
- android:height="56dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0"
- android:alpha="0.14"
- android:tint="?android:attr/colorForeground">
-
- <path
- android:pathData="M24.0,4.0C15.0,4.0 6.7,7.0 0.0,12.0l24.0,32.0l24.0,-32.0C41.3,7.0 33.0,4.0 24.0,4.0z"
- android:fillColor="#FFFFFF" />
+ android:width="56dp"
+ android:height="56dp"
+ android:viewportWidth="25.0"
+ android:viewportHeight="23.5"
+ android:alpha="0.14"
+ android:tint="?android:attr/colorForeground" >
+ <group
+ android:translateX="0.51"
+ android:translateY="-1.1">
+ <path
+ android:pathData="M23.66,8.11c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3 6.44,3 2.33,5.36 0.56,6.57c-0.51,0.35 -0.61,1.06 -0.23,1.54L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L23.66,8.11z"
+ android:fillColor="#FFFFFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_disabled.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_disabled.xml
index 70e4780..ea02ba7 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_disabled.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_disabled.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -14,16 +14,15 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="29.5dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M17.500000,16.500000L5.800000,3.400000c0.000000,0.000000 0.000000,0.000000 0.000000,0.000000l-2.700000,-3.000000L1.600000,1.800000l2.200000,2.500000c-2.000000,1.000000 -3.200000,2.000000 -3.400000,2.200000L13.000000,22.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l0.000000,0.000000l3.200000,-3.900000l2.400000,2.700000l1.500000,-1.400000L17.500000,16.500000L17.500000,16.500000z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000c-1.900000,0.000000 -3.600000,0.300000 -5.200000,0.700000L18.700001,15.000000L25.600000,6.500000z"
- android:fillAlpha=".3"/>
+ android:width="32dp"
+ android:height="29.5dp"
+ android:viewportWidth="25.6"
+ android:viewportHeight="23.6">
+ <group
+ android:translateX="0.8"
+ android:translateY="1.1">
+ <path
+ android:pathData="M23.66,8.11c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3 6.44,3 2.33,5.36 0.56,6.57c-0.51,0.35 -0.61,1.06 -0.23,1.54L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L23.66,8.11z"
+ android:fillColor="#FFFFFFFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_disconnected.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_disconnected.xml
index 2dcdb71..cb87cae 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_disconnected.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_disconnected.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2016 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.
@@ -14,29 +14,19 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="26.0dp"
- android:height="24.0dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.0,8.5
- c0.85,0.0 1.6,0.23 2.3,0.62l2.24,-2.79
- C25.1,5.96 20.26,2.0 13.0,2.0
- S0.9,5.9 0.42,6.32
- l12.57,15.6 4.21,-5.17
- c-0.76,-0.87 -1.22,-2.0 -1.22,-3.25
- c0.0,-2.76 2.24,-5.0 5.0,-5.0z"
- android:fillAlpha=".3"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M21.0,10.0
- c-1.93,0.0 -3.5,1.57 -3.5,3.5l1.75,0.0
- c0.0,-0.9 0.78,-1.75 1.75,-1.75s1.7,0.78 1.75,1.75
- c0.0,0.48 -0.2,0.92 -0.51,1.24l-1.09,1.1
- c-0.6,0.63 -1.02,1.51 -1.02,2.47l0.0,0.44l1.75,0.0
- c0.0,-1.3 0.39,-1.84 1.03,-2.47l0.78,-0.8
- c0.5,-0.5 0.82,-1.2 0.82,-1.97
- C24.5,11.57 22.93,10.0 21.0,10.0z
- m-0.95,11.95l1.9,0.0l0.0,-1.9l-1.9,0.0l0.0,1.9z"/>
+ android:width="26dp"
+ android:height="24dp"
+ android:viewportWidth="25.6"
+ android:viewportHeight="23.7">
+ <group
+ android:translateX="0.82"
+ android:translateY="-1">
+ <path
+ android:pathData="M18.9,20.85c0,-0.61 0.49,-1.1 1.1,-1.1 0.61,0 1.1,0.49 1.1,1.1 0,0.61 -0.49,1.1 -1.1,1.1 -0.61,0 -1.1,-0.49 -1.1,-1.1zM20,10c-1.53,0 -2.84,0.99 -3.31,2.36 -0.19,0.56 0.23,1.14 0.81,1.14 0.36,0 0.72,-0.21 0.84,-0.55 0.23,-0.7 0.89,-1.2 1.66,-1.2 0.97,0 1.75,0.78 1.75,1.75 0,0.48 -0.2,0.92 -0.51,1.24l-1.09,1.1c-0.69,0.69 -0.92,1.38 -0.99,2.2 -0.04,0.51 0.36,0.96 0.88,0.96 0.44,0 0.83,-0.33 0.87,-0.77 0.07,-0.79 0.31,-1.27 1,-1.95l0.78,-0.8c0.5,-0.5 0.82,-1.2 0.82,-1.97C23.5,11.57 21.93,10 20,10z"
+ android:fillColor="#FFFFFFFF"/>
+ <path
+ android:pathData="M14.73,12.88c0,-2.7 2.19,-4.88 4.88,-4.88 1.22,0 2.32,0.46 3.18,1.2l0.88,-1.09c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3 6.44,3 2.33,5.36 0.56,6.57c-0.51,0.35 -0.61,1.06 -0.23,1.54L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0l3.88,-4.82a4.862,4.862 0,0 1,-1.97 -3.9z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFFFFFFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_full_0.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_full_0.xml
index 1bc7438..53e4efc 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_full_0.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_full_0.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -14,11 +14,15 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="29.5dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#4DFFFFFF"
- android:pathData="M13.000000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
+ android:width="32.0dp"
+ android:height="29.5dp"
+ android:viewportWidth="25.6"
+ android:viewportHeight="23.6">
+ <group
+ android:translateX="0.8"
+ android:translateY="-0.9">
+ <path
+ android:pathData="M23.66,8.11c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3 6.44,3 2.33,5.36 0.56,6.57c-0.51,0.35 -0.61,1.06 -0.23,1.54L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L23.66,8.11z"
+ android:fillColor="#4DFFFFFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_full_1.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_full_1.xml
index 5856115..8294183 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_full_1.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_full_1.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -14,14 +14,18 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="29.5dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#4DFFFFFF"
- android:pathData="M13.100000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.500000,6.500000L13.100000,22.000000L13.100000,22.000000L13.100000,22.000000L13.100000,22.000000L13.100000,22.000000z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M13.100000,22.000000l5.500000,-6.800000c-0.200000,-0.200000 -2.300000,-1.900000 -5.500000,-1.900000s-5.300000,1.800000 -5.500000,1.900000L13.100000,22.000000L13.100000,22.000000L13.100000,22.000000L13.100000,22.000000L13.100000,22.000000z"/>
+ android:width="32dp"
+ android:height="29.5dp"
+ android:viewportWidth="25.6"
+ android:viewportHeight="23.6">
+ <group
+ android:translateX="0.8"
+ android:translateY="-0.9">
+ <path
+ android:pathData="M23.66,8.11c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3 6.44,3 2.33,5.36 0.56,6.57c-0.51,0.35 -0.61,1.06 -0.23,1.54L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L23.66,8.11z"
+ android:fillColor="#4DFFFFFF"/>
+ <path
+ android:pathData="M12.82,21.6l5.11,-6.36A8.942,8.942 0,0 0,12 13c-2.28,0 -4.35,0.85 -5.94,2.25l5.1,6.35c0.43,0.53 1.23,0.53 1.66,0z"
+ android:fillColor="#FFFFFFFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_full_2.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_full_2.xml
index 4a5e1f8..3d59cf2 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_full_2.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_full_2.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -14,14 +14,18 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="29.5dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#4DFFFFFF"
- android:pathData="M13.000000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M13.000000,22.000000l7.600000,-9.400000C20.299999,12.400000 17.400000,10.000000 13.000000,10.000000s-7.300000,2.400000 -7.600000,2.700000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
+ android:width="32dp"
+ android:height="29.5dp"
+ android:viewportWidth="25.6"
+ android:viewportHeight="23.6">
+ <group
+ android:translateX="0.8"
+ android:translateY="-0.9">
+ <path
+ android:pathData="M23.66,8.11c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3 6.44,3 2.33,5.36 0.56,6.57c-0.51,0.35 -0.61,1.06 -0.23,1.54L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L23.66,8.11z"
+ android:fillColor="#4DFFFFFF"/>
+ <path
+ android:pathData="M12.82,21.6l6.99,-8.7C17.71,11.1 14.99,10 12,10c-2.99,0 -5.72,1.1 -7.82,2.91l6.98,8.7c0.43,0.52 1.23,0.52 1.66,-0.01z"
+ android:fillColor="#FFFFFFFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_full_3.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_full_3.xml
index 965442d..21313b8 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_full_3.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_full_3.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -14,14 +14,18 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="29.5dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#4DFFFFFF"
- android:pathData="M13.000000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M13.000000,22.000000l9.200000,-11.400000c-0.400000,-0.300000 -3.900000,-3.200000 -9.200000,-3.200000s-8.900000,3.000000 -9.200000,3.200000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
+ android:width="32dp"
+ android:height="29.5dp"
+ android:viewportWidth="25.6"
+ android:viewportHeight="23.6">
+ <group
+ android:translateX="0.8"
+ android:translateY="-0.9">
+ <path
+ android:pathData="M23.66,8.11c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3 6.44,3 2.33,5.36 0.56,6.57c-0.51,0.35 -0.61,1.06 -0.23,1.54L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L23.66,8.11z"
+ android:fillColor="#4DFFFFFF"/>
+ <path
+ android:pathData="M12.82,21.6l8.25,-10.26A13.961,13.961 0,0 0,12 8c-3.46,0 -6.63,1.26 -9.07,3.35l8.23,10.26c0.43,0.52 1.23,0.52 1.66,-0.01z"
+ android:fillColor="#FFFFFFFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_full_4.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_full_4.xml
index b29d3f9..fd763ff 100644
--- a/packages/SystemUI/res/drawable/ic_qs_wifi_full_4.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_full_4.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -14,11 +14,15 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="29.5dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M13.000000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
+ android:width="32dp"
+ android:height="29.5dp"
+ android:viewportWidth="25.6"
+ android:viewportHeight="23.6">
+ <group
+ android:translateX="0.8"
+ android:translateY="-0.9">
+ <path
+ android:pathData="M23.66,8.11c0.39,-0.48 0.29,-1.19 -0.22,-1.54C21.67,5.36 17.55,3 12,3 6.44,3 2.33,5.36 0.56,6.57c-0.51,0.35 -0.61,1.06 -0.23,1.54L11.16,21.6c0.42,0.53 1.23,0.53 1.66,0L23.66,8.11z"
+ android:fillColor="#FFFFFFFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_settings.xml b/packages/SystemUI/res/drawable/ic_settings.xml
index 9c78742..6d24c7e 100644
--- a/packages/SystemUI/res/drawable/ic_settings.xml
+++ b/packages/SystemUI/res/drawable/ic_settings.xml
@@ -1,4 +1,4 @@
-<!-- Copyright (C) 2014 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.
You may obtain a copy of the License at"+
@@ -13,14 +13,11 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
-android:width="24dp"
-android:height="24dp" android:viewportWidth="24.0"
- android:viewportHeight="24.0">
-
-
-<path
- android:pathData="M19.4,13.0c0.0,-0.3 0.1,-0.6 0.1,-1.0s0.0,-0.7 -0.1,-1.0l2.1,-1.7c0.2,-0.2 0.2,-0.4 0.1,-0.6l-2.0,-3.5C19.5,5.1 19.3,5.0 19.0,5.1l-2.5,1.0c-0.5,-0.4 -1.1,-0.7 -1.7,-1.0l-0.4,-2.6C14.5,2.2 14.2,2.0 14.0,2.0l-4.0,0.0C9.8,2.0 9.5,2.2 9.5,2.4L9.1,5.1C8.5,5.3 8.0,5.7 7.4,6.1L5.0,5.1C4.7,5.0 4.5,5.1 4.3,5.3l-2.0,3.5C2.2,8.9 2.3,9.2 2.5,9.4L4.6,11.0c0.0,0.3 -0.1,0.6 -0.1,1.0s0.0,0.7 0.1,1.0l-2.1,1.7c-0.2,0.2 -0.2,0.4 -0.1,0.6l2.0,3.5C4.5,18.9 4.7,19.0 5.0,18.9l2.5,-1.0c0.5,0.4 1.1,0.7 1.7,1.0l0.4,2.6c0.0,0.2 0.2,0.4 0.5,0.4l4.0,0.0c0.2,0.0 0.5,-0.2 0.5,-0.4l0.4,-2.6c0.6,-0.3 1.2,-0.6 1.7,-1.0l2.5,1.0c0.2,0.1 0.5,0.0 0.6,-0.2l2.0,-3.5c0.1,-0.2 0.1,-0.5 -0.1,-0.6L19.4,13.0zM12.0,15.5c-1.9,0.0 -3.5,-1.6 -3.5,-3.5s1.6,-3.5 3.5,-3.5s3.5,1.6 3.5,3.5S13.9,15.5 12.0,15.5z"
- android:fillColor="#ffffffff"
- />
-
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M21.4,14.2l-1.94,-1.45c0.03,-0.25 0.04,-0.5 0.04,-0.76s-0.01,-0.51 -0.04,-0.76L21.4,9.8c0.42,-0.31 0.52,-0.94 0.24,-1.41l-1.6,-2.76c-0.28,-0.48 -0.88,-0.7 -1.36,-0.5l-2.14,0.91c-0.48,-0.37 -1.01,-0.68 -1.57,-0.92l-0.27,-2.2c-0.06,-0.52 -0.56,-0.92 -1.11,-0.92h-3.18c-0.55,0 -1.05,0.4 -1.11,0.92l-0.26,2.19c-0.57,0.24 -1.1,0.55 -1.58,0.92l-2.14,-0.91c-0.48,-0.2 -1.08,0.02 -1.36,0.5l-1.6,2.76c-0.28,0.48 -0.18,1.1 0.24,1.42l1.94,1.45c-0.03,0.24 -0.04,0.49 -0.04,0.75s0.01,0.51 0.04,0.76L2.6,14.2c-0.42,0.31 -0.52,0.94 -0.24,1.41l1.6,2.76c0.28,0.48 0.88,0.7 1.36,0.5l2.14,-0.91c0.48,0.37 1.01,0.68 1.57,0.92l0.27,2.19c0.06,0.53 0.56,0.93 1.11,0.93h3.18c0.55,0 1.04,-0.4 1.11,-0.92l0.27,-2.19c0.56,-0.24 1.09,-0.55 1.57,-0.92l2.14,0.91c0.48,0.2 1.08,-0.02 1.36,-0.5l1.6,-2.76c0.28,-0.48 0.18,-1.1 -0.24,-1.42zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"
+ android:fillColor="#FFFFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_settings_16dp.xml b/packages/SystemUI/res/drawable/ic_settings_16dp.xml
index c21b60c..e3ed229 100644
--- a/packages/SystemUI/res/drawable/ic_settings_16dp.xml
+++ b/packages/SystemUI/res/drawable/ic_settings_16dp.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2016 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.
@@ -19,6 +19,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:pathData="M19.4,13.0c0.0,-0.3 0.1,-0.6 0.1,-1.0s0.0,-0.7 -0.1,-1.0l2.1,-1.7c0.2,-0.2 0.2,-0.4 0.1,-0.6l-2.0,-3.5C19.5,5.1 19.3,5.0 19.0,5.1l-2.5,1.0c-0.5,-0.4 -1.1,-0.7 -1.7,-1.0l-0.4,-2.6C14.5,2.2 14.2,2.0 14.0,2.0l-4.0,0.0C9.8,2.0 9.5,2.2 9.5,2.4L9.1,5.1C8.5,5.3 8.0,5.7 7.4,6.1L5.0,5.1C4.7,5.0 4.5,5.1 4.3,5.3l-2.0,3.5C2.2,8.9 2.3,9.2 2.5,9.4L4.6,11.0c0.0,0.3 -0.1,0.6 -0.1,1.0s0.0,0.7 0.1,1.0l-2.1,1.7c-0.2,0.2 -0.2,0.4 -0.1,0.6l2.0,3.5C4.5,18.9 4.7,19.0 5.0,18.9l2.5,-1.0c0.5,0.4 1.1,0.7 1.7,1.0l0.4,2.6c0.0,0.2 0.2,0.4 0.5,0.4l4.0,0.0c0.2,0.0 0.5,-0.2 0.5,-0.4l0.4,-2.6c0.6,-0.3 1.2,-0.6 1.7,-1.0l2.5,1.0c0.2,0.1 0.5,0.0 0.6,-0.2l2.0,-3.5c0.1,-0.2 0.1,-0.5 -0.1,-0.6L19.4,13.0zM12.0,15.5c-1.9,0.0 -3.5,-1.6 -3.5,-3.5s1.6,-3.5 3.5,-3.5s3.5,1.6 3.5,3.5S13.9,15.5 12.0,15.5z"
- android:fillColor="#ffffffff" />
+ android:pathData="M21.4,14.2l-1.94,-1.45c0.03,-0.25 0.04,-0.5 0.04,-0.76s-0.01,-0.51 -0.04,-0.76L21.4,9.8c0.42,-0.31 0.52,-0.94 0.24,-1.41l-1.6,-2.76c-0.28,-0.48 -0.88,-0.7 -1.36,-0.5l-2.14,0.91c-0.48,-0.37 -1.01,-0.68 -1.57,-0.92l-0.27,-2.2c-0.06,-0.52 -0.56,-0.92 -1.11,-0.92h-3.18c-0.55,0 -1.05,0.4 -1.11,0.92l-0.26,2.19c-0.57,0.24 -1.1,0.55 -1.58,0.92l-2.14,-0.91c-0.48,-0.2 -1.08,0.02 -1.36,0.5l-1.6,2.76c-0.28,0.48 -0.18,1.1 0.24,1.42l1.94,1.45c-0.03,0.24 -0.04,0.49 -0.04,0.75s0.01,0.51 0.04,0.76L2.6,14.2c-0.42,0.31 -0.52,0.94 -0.24,1.41l1.6,2.76c0.28,0.48 0.88,0.7 1.36,0.5l2.14,-0.91c0.48,0.37 1.01,0.68 1.57,0.92l0.27,2.19c0.06,0.53 0.56,0.93 1.11,0.93h3.18c0.55,0 1.04,-0.4 1.11,-0.92l0.27,-2.19c0.56,-0.24 1.09,-0.55 1.57,-0.92l2.14,0.91c0.48,0.2 1.08,-0.02 1.36,-0.5l1.6,-2.76c0.28,-0.48 0.18,-1.1 -0.24,-1.42zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"
+ android:fillColor="#FFFFFFFF"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_airplane.xml b/packages/SystemUI/res/drawable/ic_signal_airplane.xml
new file mode 100644
index 0000000..50dd436
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_signal_airplane.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="20.5"
+ android:viewportHeight="20.5">
+ <group
+ android:translateX="0.985"
+ android:translateY="1.10">
+ <path
+ android:pathData="M16.01,9.87l-6.24,-3.9v-4.7C9.77,0.57 9.21,0 8.5,0S7.23,0.57 7.23,1.28v4.7L0.99,9.88c-0.37,0.23 -0.6,0.64 -0.6,1.08v0.41c0,0.29 0.29,0.5 0.55,0.41l6.27,-1.97v4.7l-1.37,1.02c-0.21,0.16 -0.34,0.41 -0.34,0.68v0.57c0,0.15 0.12,0.23 0.27,0.2 1.67,-0.47 1.12,-0.31 2.73,-0.78 1.03,0.3 1.7,0.49 2.72,0.78 0.15,0.03 0.27,-0.06 0.27,-0.2v-0.57c0,-0.27 -0.13,-0.52 -0.34,-0.68l-1.37,-1.02v-4.7l6.27,1.97c0.28,0.09 0.55,-0.12 0.55,-0.41v-0.41c0.01,-0.45 -0.23,-0.87 -0.59,-1.09z"
+ android:fillColor="#FF0"/>
+ </group>
+</vector>
+
diff --git a/packages/SystemUI/res/drawable/ic_signal_airplane_disable.xml b/packages/SystemUI/res/drawable/ic_signal_airplane_disable.xml
deleted file mode 100644
index 09a67e1..0000000
--- a/packages/SystemUI/res/drawable/ic_signal_airplane_disable.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="root"
- android:alpha="1.0"
- android:height="48dp"
- android:width="48dp"
- android:viewportHeight="48"
- android:viewportWidth="48" >
- <group
- android:name="ic_signal_airplane"
- android:translateX="21.9995"
- android:translateY="25.73401" >
- <group
- android:name="ic_signal_airplane_pivot"
- android:translateX="-23.21545"
- android:translateY="-18.86649" >
- <clip-path
- android:name="mask"
- android:pathData="M 37.8337860107,-40.4599914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 9.55097961426,9.55285644531 9.55097961426,9.55285644531 c 0.0,0.0 -2.61698913574,2.09387207031 -2.61698913574,2.09387207031 c 0.0,0.0 -9.75096130371,-9.56428527832 -9.75096130371,-9.56428527832 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z" />
- <group
- android:name="cross" >
- <path
- android:name="cross_1"
- android:pathData="M 7.54049682617,3.9430847168 c 0.0,0.0 0.324981689453,0.399978637695 0.324981689453,0.399978637695 "
- android:strokeColor="#FFFFFFFF"
- android:strokeAlpha="0"
- android:strokeWidth="3.5"
- android:fillColor="#00000000" />
- </group>
- <group
- android:name="plane"
- android:translateX="23.481"
- android:translateY="18.71151" >
- <path
- android:name="plane_1"
- android:pathData="M 18.9439849854,7.98849487305 c 0.0,0.0 0.0,-4.0 0.0,-4.0 c 0.0,0.0 -16.0,-10.0 -16.0,-10.0 c 0.0,0.0 0.0,-11.0 0.0,-11.0 c 0.0,-1.70001220703 -1.30000305176,-3.0 -3.0,-3.0 c -1.69999694824,0.0 -3.0,1.29998779297 -3.0,3.0 c 0.0,0.0 0.0,11.0 0.0,11.0 c 0.0,0.0 -16.0,10.0 -16.0,10.0 c 0.0,0.0 0.0,4.0 0.0,4.0 c 0.0,0.0 16.0,-5.0 16.0,-5.0 c 0.0,0.0 0.0,11.0 0.0,11.0 c 0.0,0.0 -4.0,3.0 -4.0,3.0 c 0.0,0.0 0.0,3.0 0.0,3.0 c 0.0,0.0 7.0,-2.0 7.0,-2.0 c 0.0,0.0 7.0,2.0 7.0,2.0 c 0.0,0.0 0.0,-3.0 0.0,-3.0 c 0.0,0.0 -4.0,-3.0 -4.0,-3.0 c 0.0,0.0 0.0,-11.0 0.0,-11.0 c 0.0,0.0 16.0,5.0 16.0,5.0 Z"
- android:fillColor="#FFFFFFFF"
- android:fillAlpha="1" />
- </group>
- </group>
- </group>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_airplane_disable_animation.xml b/packages/SystemUI/res/drawable/ic_signal_airplane_disable_animation.xml
deleted file mode 100644
index 56ae4aa..0000000
--- a/packages/SystemUI/res/drawable/ic_signal_airplane_disable_animation.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/ic_signal_airplane_disable" >
- <target
- android:name="mask"
- android:animation="@anim/ic_signal_airplane_disable_animation_mask" />
- <target
- android:name="cross_1"
- android:animation="@anim/ic_signal_airplane_disable_animation_cross_1" />
-</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_airplane_enable.xml b/packages/SystemUI/res/drawable/ic_signal_airplane_enable.xml
deleted file mode 100644
index b7ac0134..0000000
--- a/packages/SystemUI/res/drawable/ic_signal_airplane_enable.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="root"
- android:height="48dp"
- android:width="48dp"
- android:viewportHeight="48"
- android:viewportWidth="48" >
- <group
- android:name="ic_signal_airplane"
- android:translateX="21.9995"
- android:translateY="25.73401" >
- <group
- android:name="ic_signal_airplane_pivot"
- android:translateX="-23.21545"
- android:translateY="-18.86649" >
- <clip-path
- android:name="mask"
- android:pathData="M 37.8337860107,-40.3974914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 40.9884796143,40.9278411865 40.9884796143,40.9278411865 c 0.0,0.0 -2.61700439453,2.0938873291 -2.61700439453,2.0938873291 c 0.0,0.0 -41.1884460449,-40.9392852783 -41.1884460449,-40.9392852783 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z" />
- <group
- android:name="cross" >
- <path
- android:name="cross_1"
- android:pathData="M 7.54049682617,3.9430847168 c 0.0,0.0 31.5749816895,31.4499664307 31.5749816895,31.4499664307 "
- android:strokeColor="#FFFFFFFF"
- android:strokeAlpha="1"
- android:strokeWidth="3.5"
- android:fillColor="#00000000" />
- </group>
- <group
- android:name="plane"
- android:translateX="23.481"
- android:translateY="18.71151" >
- <path
- android:name="plane_1"
- android:pathData="M 18.9439849854,7.98849487305 c 0.0,0.0 0.0,-4.0 0.0,-4.0 c 0.0,0.0 -16.0,-10.0 -16.0,-10.0 c 0.0,0.0 0.0,-11.0 0.0,-11.0 c 0.0,-1.70001220703 -1.30000305176,-3.0 -3.0,-3.0 c -1.69999694824,0.0 -3.0,1.29998779297 -3.0,3.0 c 0.0,0.0 0.0,11.0 0.0,11.0 c 0.0,0.0 -16.0,10.0 -16.0,10.0 c 0.0,0.0 0.0,4.0 0.0,4.0 c 0.0,0.0 16.0,-5.0 16.0,-5.0 c 0.0,0.0 0.0,11.0 0.0,11.0 c 0.0,0.0 -4.0,3.0 -4.0,3.0 c 0.0,0.0 0.0,3.0 0.0,3.0 c 0.0,0.0 7.0,-2.0 7.0,-2.0 c 0.0,0.0 7.0,2.0 7.0,2.0 c 0.0,0.0 0.0,-3.0 0.0,-3.0 c 0.0,0.0 -4.0,-3.0 -4.0,-3.0 c 0.0,0.0 0.0,-11.0 0.0,-11.0 c 0.0,0.0 16.0,5.0 16.0,5.0 Z"
- android:fillColor="#FFFFFFFF"
- android:fillAlpha="1" />
- </group>
- </group>
- </group>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_airplane_enable_animation.xml b/packages/SystemUI/res/drawable/ic_signal_airplane_enable_animation.xml
deleted file mode 100644
index 87dfba9..0000000
--- a/packages/SystemUI/res/drawable/ic_signal_airplane_enable_animation.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/ic_signal_airplane_enable" >
- <target
- android:name="ic_signal_airplane"
- android:animation="@anim/ic_signal_airplane_enable_animation_ic_signal_airplane" />
- <target
- android:name="mask"
- android:animation="@anim/ic_signal_airplane_enable_animation_mask" />
- <target
- android:name="cross_1"
- android:animation="@anim/ic_signal_airplane_enable_animation_cross_1" />
-</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_flashlight.xml b/packages/SystemUI/res/drawable/ic_signal_flashlight.xml
new file mode 100644
index 0000000..38979a8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_signal_flashlight.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <group
+ android:translateX="-0.05"
+ android:translateY="0.85" >
+ <path
+ android:pathData="M8.28,2h7.43c0.55,0 1,0.45 1,1v0.96L7.28,3.96L7.28,3c0,-0.55 0.45,-1 1,-1zM14.8,9.76L14.8,21c0,0.55 -0.45,1 -1,1h-3.61c-0.55,0 -1,-0.45 -1,-1L9.19,9.78c-2.2,-1.17 -1.91,-3.76 -1.91,-3.76L7.28,5.7h9.44v0.32s0.26,2.57 -1.92,3.74zM13.38,12.47c0,-0.76 -0.62,-1.38 -1.38,-1.38s-1.38,0.62 -1.38,1.38 0.62,1.38 1.38,1.38 1.38,-0.62 1.38,-1.38z"
+ android:fillColor="#FFFFFFFF"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_flashlight_disable.xml b/packages/SystemUI/res/drawable/ic_signal_flashlight_disable.xml
deleted file mode 100644
index 35844b7..0000000
--- a/packages/SystemUI/res/drawable/ic_signal_flashlight_disable.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="root"
- android:alpha="1.0"
- android:height="48dp"
- android:width="48dp"
- android:viewportHeight="48"
- android:viewportWidth="48" >
- <group
- android:name="ic_signal_flashlight"
- android:translateX="21.9995"
- android:translateY="25.73401" >
- <group
- android:name="ic_signal_flashlight_pivot"
- android:translateX="-23.21545"
- android:translateY="-18.86649" >
- <clip-path
- android:name="mask"
- android:pathData="M 37.8337860107,-39.2849731445 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 9.55097961426,9.55285644531 9.55097961426,9.55285644531 c 0.0,0.0 -2.61698913574,2.09387207031 -2.61698913574,2.09387207031 c 0.0,0.0 -9.75096130371,-9.56428527832 -9.75096130371,-9.56428527832 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z" />
- <group
- android:name="cross" >
- <path
- android:name="cross_1"
- android:pathData="M 8.34049987793,5.6930847168 c 0.0,0.0 0.274993896484,0.29997253418 0.274993896484,0.29997253418 "
- android:strokeColor="#FFFFFFFF"
- android:strokeAlpha="0"
- android:strokeWidth="3.5"
- android:fillColor="#00000000" />
- </group>
- <group
- android:name="flashlight"
- android:translateX="25.06235"
- android:translateY="22.48294" >
- <path
- android:name="light"
- android:pathData="M -9.40809631348,-23.6970062256 c 0.0,0.0 18.8699951172,0.0 18.8699951172,0.0 c 0.0,0.0 0.0,3.91700744629 0.0,3.91700744629 c 0.0,0.0 -18.8699951172,0.0 -18.8699951172,0.0 c 0.0,0.0 0.0,-3.91700744629 0.0,-3.91700744629 Z M 9.4615020752,-15.6629943848 c 0.0,0.0 0.0,-0.639999389648 0.0,-0.639999389649 c 0.0,0.0 -18.8699951172,0.0 -18.8699951172,0.0 c 0.0,0.0 0.0,0.639999389648 0.0,0.639999389649 c 0.0,0.0 -0.581008911133,5.18899536133 3.82598876953,7.52299499512 c 0.0,0.0 0.0,24.4429931641 0.0,24.4429931641 c 0.0,0.0 11.2129974365,0.0 11.2129974365,0.0 c 0.0,0.0 0.0,-24.4769897461 0.0,-24.4769897461 c 4.35900878906,-2.35301208496 3.83100891113,-7.48899841309 3.83100891113,-7.48899841309 Z M 0.0234985351562,0 c -1.52299499512,0 -2.75700378418,-1.23399353027 -2.75700378418,-2.75700378418 c 0.0,-1.52299499512 1.23400878906,-2.75700378418 2.75700378418,-2.75700378418 c 1.52299499512,0.0 2.75700378418,1.23400878906 2.75700378418,2.75700378418 c 0.0,1.52200317383 -1.23400878906,2.75700378418 -2.75700378418,2.75700378418 Z"
- android:fillColor="#FFFFFFFF"
- android:fillAlpha="1" />
- </group>
- </group>
- </group>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_flashlight_disable_animation.xml b/packages/SystemUI/res/drawable/ic_signal_flashlight_disable_animation.xml
deleted file mode 100644
index e228b7c..0000000
--- a/packages/SystemUI/res/drawable/ic_signal_flashlight_disable_animation.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/ic_signal_flashlight_disable" >
- <target
- android:name="mask"
- android:animation="@anim/ic_signal_flashlight_disable_animation_mask" />
- <target
- android:name="cross_1"
- android:animation="@anim/ic_signal_flashlight_disable_animation_cross_1" />
-</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_flashlight_enable.xml b/packages/SystemUI/res/drawable/ic_signal_flashlight_enable.xml
deleted file mode 100644
index c2215f1..0000000
--- a/packages/SystemUI/res/drawable/ic_signal_flashlight_enable.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="root"
- android:height="48dp"
- android:width="48dp"
- android:viewportHeight="48"
- android:viewportWidth="48" >
- <group
- android:name="ic_signal_flashlight"
- android:translateX="21.9995"
- android:translateY="25.73401" >
- <group
- android:name="ic_signal_flashlight_pivot"
- android:translateX="-23.21545"
- android:translateY="-18.86649" >
- <clip-path
- android:name="mask"
- android:pathData="M 37.8337860107,-39.2975769043 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 40.9884796143,40.9278411865 40.9884796143,40.9278411865 c 0.0,0.0 -2.61700439453,2.0938873291 -2.61700439453,2.0938873291 c 0.0,0.0 -41.1884460449,-40.9392852783 -41.1884460449,-40.9392852783 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z" />
- <group
- android:name="cross" >
- <path
- android:name="cross_1"
- android:pathData="M 8.34049987793,5.6930847168 c 0.0,0.0 29.7749786377,29.7999725342 29.7749786377,29.7999725342 "
- android:strokeColor="#FFFFFFFF"
- android:strokeAlpha="1"
- android:strokeWidth="3.5"
- android:fillColor="#00000000" />
- </group>
- <group
- android:name="flashlight"
- android:translateX="25.06235"
- android:translateY="22.48294" >
- <path
- android:name="light"
- android:pathData="M -9.40809631348,-23.6970062256 c 0.0,0.0 18.8699951172,0.0 18.8699951172,0.0 c 0.0,0.0 0.0,3.91700744629 0.0,3.91700744629 c 0.0,0.0 -18.8699951172,0.0 -18.8699951172,0.0 c 0.0,0.0 0.0,-3.91700744629 0.0,-3.91700744629 Z M 9.4615020752,-15.6629943848 c 0.0,0.0 0.0,-0.639999389648 0.0,-0.639999389649 c 0.0,0.0 -18.8699951172,0.0 -18.8699951172,0.0 c 0.0,0.0 0.0,0.639999389648 0.0,0.639999389649 c 0.0,0.0 -0.581008911133,5.18899536133 3.82598876953,7.52299499512 c 0.0,0.0 0.0,24.4429931641 0.0,24.4429931641 c 0.0,0.0 11.2129974365,0.0 11.2129974365,0.0 c 0.0,0.0 0.0,-24.4769897461 0.0,-24.4769897461 c 4.35900878906,-2.35301208496 3.83100891113,-7.48899841309 3.83100891113,-7.48899841309 Z M 0.0234985351562,0 c -1.52299499512,0 -2.75700378418,-1.23399353027 -2.75700378418,-2.75700378418 c 0.0,-1.52299499512 1.23400878906,-2.75700378418 2.75700378418,-2.75700378418 c 1.52299499512,0.0 2.75700378418,1.23400878906 2.75700378418,2.75700378418 c 0.0,1.52200317383 -1.23400878906,2.75700378418 -2.75700378418,2.75700378418 Z"
- android:fillColor="#FFFFFFFF"
- android:fillAlpha="1" />
- </group>
- </group>
- </group>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_flashlight_enable_animation.xml b/packages/SystemUI/res/drawable/ic_signal_flashlight_enable_animation.xml
deleted file mode 100644
index 220c65e..0000000
--- a/packages/SystemUI/res/drawable/ic_signal_flashlight_enable_animation.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/ic_signal_flashlight_enable" >
- <target
- android:name="mask"
- android:animation="@anim/ic_signal_flashlight_enable_animation_mask" />
- <target
- android:name="cross_1"
- android:animation="@anim/ic_signal_flashlight_enable_animation_cross_1" />
-</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_location.xml b/packages/SystemUI/res/drawable/ic_signal_location.xml
new file mode 100644
index 0000000..2f60580
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_signal_location.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <group
+ android:translateX="0.02"
+ android:translateY="0.82">
+ <path
+ android:pathData="M12,2C8.13,2 5,5.13 5,9c0,4.17 4.42,9.92 6.24,12.11 0.4,0.48 1.13,0.48 1.53,0C14.58,18.92 19,13.17 19,9c0,-3.87 -3.13,-7 -7,-7zM12,11.5a2.5,2.5 0,0 1,0 -5,2.5 2.5,0 0,1 0,5z"
+ android:fillColor="#FFFFFFFF"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_location_disable.xml b/packages/SystemUI/res/drawable/ic_signal_location_disable.xml
deleted file mode 100644
index 439851d..0000000
--- a/packages/SystemUI/res/drawable/ic_signal_location_disable.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="root"
- android:alpha="1.0"
- android:height="48dp"
- android:width="48dp"
- android:viewportHeight="48"
- android:viewportWidth="48" >
- <group
- android:name="ic_signal_location"
- android:translateX="21.9995"
- android:translateY="25.73401" >
- <group
- android:name="ic_signal_location_pivot"
- android:translateX="-23.21545"
- android:translateY="-18.86649" >
- <clip-path
- android:name="mask"
- android:pathData="M 38.8337860107,-40.4599914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 7.3759765625,7.55284118652 7.3759765625,7.55284118652 c 0.0,0.0 -2.61698913574,2.0938873291 -2.61698913574,2.0938873291 c 0.0,0.0 -7.57595825195,-7.56428527832 -7.57595825195,-7.56428527832 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z" />
- <group
- android:name="cross" >
- <path
- android:name="cross_1"
- android:pathData="M 6.44050598145,1.9430847168 c 0.0,0.0 0.374984741211,0.399978637695 0.374984741211,0.399978637695 "
- android:strokeColor="#FFFFFFFF"
- android:strokeAlpha="0"
- android:strokeWidth="3.5"
- android:fillColor="#00000000" />
- </group>
- <group
- android:name="location"
- android:translateX="23.481"
- android:translateY="18.71151" >
- <path
- android:name="pin"
- android:pathData="M 1.76899719238,-20.011505127 c -7.69999694824,0.0 -14.0,6.30000305176 -14.0,14.0 c 0.0,10.5 14.0,26.0 14.0,26.0 c 0.0,0.0 14.0,-15.5 14.0,-26.0 c 0.0,-7.69999694824 -6.30000305176,-14.0 -14.0,-14.0 Z M 1.76899719238,-1.01150512695 c -2.80000305176,0.0 -5.0,-2.19999694824 -5.0,-5.0 c 0.0,-2.80000305176 2.19999694824,-5.0 5.0,-5.0 c 2.80000305176,0.0 5.0,2.19999694824 5.0,5.0 c 0.0,2.80000305176 -2.19999694824,5.0 -5.0,5.0 Z"
- android:fillColor="#FFFFFFFF"
- android:fillAlpha="1" />
- </group>
- </group>
- </group>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_location_disable_animation.xml b/packages/SystemUI/res/drawable/ic_signal_location_disable_animation.xml
deleted file mode 100644
index 0e9d1cb..0000000
--- a/packages/SystemUI/res/drawable/ic_signal_location_disable_animation.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/ic_signal_location_disable" >
- <target
- android:name="mask"
- android:animation="@anim/ic_signal_location_disable_animation_mask" />
- <target
- android:name="cross_1"
- android:animation="@anim/ic_signal_location_disable_animation_cross_1" />
-</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_location_enable.xml b/packages/SystemUI/res/drawable/ic_signal_location_enable.xml
deleted file mode 100644
index d4b8673..0000000
--- a/packages/SystemUI/res/drawable/ic_signal_location_enable.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="root"
- android:height="48dp"
- android:width="48dp"
- android:viewportHeight="48"
- android:viewportWidth="48" >
- <group
- android:name="ic_signal_location"
- android:translateX="21.9995"
- android:translateY="25.73401" >
- <group
- android:name="ic_signal_location_pivot"
- android:translateX="-23.21545"
- android:translateY="-18.86649" >
- <clip-path
- android:name="mask"
- android:pathData="M 38.8337860107,-40.3974914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 40.9884796143,41.1153411865 40.9884796143,41.1153411865 c 0.0,0.0 -2.61700439453,2.0938873291 -2.61700439453,2.0938873291 c 0.0,0.0 -41.1884460449,-41.1267852783 -41.1884460449,-41.1267852783 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z" />
- <group
- android:name="cross" >
- <path
- android:name="cross_1"
- android:pathData="M 6.44050598145,1.9430847168 c 0.0,0.0 33.5749816895,33.4499664307 33.5749816895,33.4499664307 "
- android:strokeColor="#FFFFFFFF"
- android:strokeAlpha="1"
- android:strokeWidth="3.5"
- android:fillColor="#00000000" />
- </group>
- <group
- android:name="location"
- android:translateX="23.481"
- android:translateY="18.71151" >
- <path
- android:name="pin"
- android:pathData="M 1.76899719238,-20.011505127 c -7.69999694824,0.0 -14.0,6.30000305176 -14.0,14.0 c 0.0,10.5 14.0,26.0 14.0,26.0 c 0.0,0.0 14.0,-15.5 14.0,-26.0 c 0.0,-7.69999694824 -6.30000305176,-14.0 -14.0,-14.0 Z M 1.76899719238,-1.01150512695 c -2.80000305176,0.0 -5.0,-2.19999694824 -5.0,-5.0 c 0.0,-2.80000305176 2.19999694824,-5.0 5.0,-5.0 c 2.80000305176,0.0 5.0,2.19999694824 5.0,5.0 c 0.0,2.80000305176 -2.19999694824,5.0 -5.0,5.0 Z"
- android:fillColor="#FFFFFFFF"
- android:fillAlpha="1" />
- </group>
- </group>
- </group>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_location_enable_animation.xml b/packages/SystemUI/res/drawable/ic_signal_location_enable_animation.xml
deleted file mode 100644
index 9f1d917..0000000
--- a/packages/SystemUI/res/drawable/ic_signal_location_enable_animation.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/ic_signal_location_enable" >
- <target
- android:name="mask"
- android:animation="@anim/ic_signal_location_enable_animation_mask" />
- <target
- android:name="cross_1"
- android:animation="@anim/ic_signal_location_enable_animation_cross_1" />
-</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_workmode_disable_animation.xml b/packages/SystemUI/res/drawable/ic_signal_workmode_disable_animation.xml
deleted file mode 100644
index 9c23126..0000000
--- a/packages/SystemUI/res/drawable/ic_signal_workmode_disable_animation.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/ic_signal_workmode_disable" >
- <target
- android:name="mask"
- android:animation="@anim/ic_signal_workmode_disable_animation_mask" />
- <target
- android:name="cross_1"
- android:animation="@anim/ic_signal_workmode_disable_animation_cross_1" />
-</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_workmode_enable_animation.xml b/packages/SystemUI/res/drawable/ic_signal_workmode_enable_animation.xml
deleted file mode 100644
index 04ddfad..0000000
--- a/packages/SystemUI/res/drawable/ic_signal_workmode_enable_animation.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/ic_signal_workmode_enable" >
- <target
- android:name="ic_signal_briefcase"
- android:animation="@anim/ic_signal_workmode_enable_animation_ic_signal_briefcase" />
- <target
- android:name="mask"
- android:animation="@anim/ic_signal_workmode_enable_animation_mask" />
- <target
- android:name="cross_1"
- android:animation="@anim/ic_signal_workmode_enable_animation_cross_1" />
-</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_background_primary.xml b/packages/SystemUI/res/drawable/qs_background_primary.xml
index 8ea9e06..4165830 100644
--- a/packages/SystemUI/res/drawable/qs_background_primary.xml
+++ b/packages/SystemUI/res/drawable/qs_background_primary.xml
@@ -15,6 +15,6 @@
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android">
<shape>
- <solid android:color="?android:attr/colorPrimary"/>
+ <solid android:color="?android:attr/colorBackgroundFloating"/>
</shape>
</inset>
diff --git a/packages/SystemUI/res/drawable/rounded.xml b/packages/SystemUI/res/drawable/rounded.xml
new file mode 100644
index 0000000..ef7aea7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/rounded.xml
@@ -0,0 +1,24 @@
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="8dp"
+ android:height="8dp"
+ android:viewportWidth="8"
+ android:viewportHeight="8">
+
+ <path
+ android:fillColor="#000000"
+ android:pathData="M8,0H0v8C0,3.6,3.6,0,8,0z" />
+
+</vector>
diff --git a/packages/SystemUI/res/drawable/rounded_bg.xml b/packages/SystemUI/res/drawable/rounded_bg.xml
new file mode 100644
index 0000000..56aa9fe
--- /dev/null
+++ b/packages/SystemUI/res/drawable/rounded_bg.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#ffffffff" />
+ <corners
+ android:bottomLeftRadius="@dimen/corner_size"
+ android:topLeftRadius="@dimen/corner_size"
+ android:bottomRightRadius="0dp"
+ android:topRightRadius="0dp"
+ />
+</shape>
diff --git a/packages/SystemUI/res/drawable/rounded_bg_bottom.xml b/packages/SystemUI/res/drawable/rounded_bg_bottom.xml
new file mode 100644
index 0000000..0201f28
--- /dev/null
+++ b/packages/SystemUI/res/drawable/rounded_bg_bottom.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#ffeeeeee" />
+ <corners
+ android:bottomLeftRadius="@dimen/corner_size"
+ android:topLeftRadius="0dp"
+ android:bottomRightRadius="0dp"
+ android:topRightRadius="0dp"
+ />
+</shape>
diff --git a/packages/SystemUI/res/drawable/rounded_bg_full.xml b/packages/SystemUI/res/drawable/rounded_bg_full.xml
new file mode 100644
index 0000000..65d00de
--- /dev/null
+++ b/packages/SystemUI/res/drawable/rounded_bg_full.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#ffffffff" />
+ <corners
+ android:bottomLeftRadius="@dimen/corner_size"
+ android:topLeftRadius="@dimen/corner_size"
+ android:bottomRightRadius="@dimen/corner_size"
+ android:topRightRadius="@dimen/corner_size"
+ />
+</shape>
diff --git a/packages/SystemUI/res/drawable/rounded_full_bg_bottom.xml b/packages/SystemUI/res/drawable/rounded_full_bg_bottom.xml
new file mode 100644
index 0000000..f6ee558
--- /dev/null
+++ b/packages/SystemUI/res/drawable/rounded_full_bg_bottom.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#ffeeeeee" />
+ <corners
+ android:bottomLeftRadius="@dimen/corner_size"
+ android:topLeftRadius="0dp"
+ android:bottomRightRadius="@dimen/corner_size"
+ android:topRightRadius="0dp"
+ />
+</shape>
diff --git a/packages/SystemUI/res/drawable/stat_sys_airplane_mode.xml b/packages/SystemUI/res/drawable/stat_sys_airplane_mode.xml
index 433da5f..2655bcd 100644
--- a/packages/SystemUI/res/drawable/stat_sys_airplane_mode.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_airplane_mode.xml
@@ -1,28 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2014 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
- -->
+**
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="17.25dp"
- android:height="18dp"
- android:viewportWidth="46.0"
- android:viewportHeight="48.0">
-
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M20.4,18.0"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M42.0,32.0l0.0,-4.0L26.0,18.0L26.0,7.0c0.0,-1.7 -1.3,-3.0 -3.0,-3.0c-1.7,0.0 -3.0,1.3 -3.0,3.0l0.0,11.0L4.0,28.0l0.0,4.0l16.0,-5.0l0.0,11.0l-4.0,3.0l0.0,3.0l7.0,-2.0l7.0,2.0l0.0,-3.0l-4.0,-3.0L26.0,27.0L42.0,32.0z"/>
+ android:width="17.25dp"
+ android:height="18dp"
+ android:viewportWidth="19.65"
+ android:viewportHeight="20.5">
+ <group
+ android:translateX="1.3"
+ android:translateY="1.7">
+ <path
+ android:pathData="M16.01,9.87l-6.24,-3.9v-4.7C9.77,0.57 9.21,0 8.5,0S7.23,0.57 7.23,1.28v4.7L0.99,9.88c-0.37,0.23 -0.6,0.64 -0.6,1.08v0.41c0,0.29 0.29,0.5 0.55,0.41l6.27,-1.97v4.7l-1.37,1.02c-0.21,0.16 -0.34,0.41 -0.34,0.68v0.57c0,0.15 0.12,0.23 0.27,0.2 1.67,-0.47 1.12,-0.31 2.73,-0.78 1.03,0.3 1.7,0.49 2.72,0.78 0.15,0.03 0.27,-0.06 0.27,-0.2v-0.57c0,-0.27 -0.13,-0.52 -0.34,-0.68l-1.37,-1.02v-4.7l6.27,1.97c0.28,0.09 0.55,-0.12 0.55,-0.41v-0.41c0.01,-0.45 -0.23,-0.87 -0.59,-1.09z"
+ android:fillColor="#FFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_alarm.xml b/packages/SystemUI/res/drawable/stat_sys_alarm.xml
index 48c2222..0e9319c 100644
--- a/packages/SystemUI/res/drawable/stat_sys_alarm.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_alarm.xml
@@ -1,29 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2014 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
- -->
+**
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="2.5dp"
android:insetRight="2.5dp">
- <vector xmlns:android="http://schemas.android.com/apk/res/android"
+ <vector
android:width="17dp"
android:height="17dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
-
- <path
- android:fillColor="#ffffffff"
- android:pathData="M22.0,5.7l-4.6,-3.9l-1.3,1.5l4.6,3.9L22.0,5.7zM7.9,3.4L6.6,1.9L2.0,5.7l1.3,1.5L7.9,3.4zM12.5,8.0L11.0,8.0l0.0,6.0l4.7,2.9l0.8,-1.2l-4.0,-2.4L12.5,8.0zM12.0,4.0c-5.0,0.0 -9.0,4.0 -9.0,9.0c0.0,5.0 4.0,9.0 9.0,9.0s9.0,-4.0 9.0,-9.0C21.0,8.0 17.0,4.0 12.0,4.0zM12.0,20.0c-3.9,0.0 -7.0,-3.1 -7.0,-7.0c0.0,-3.9 3.1,-7.0 7.0,-7.0c3.9,0.0 7.0,3.1 7.0,7.0C19.0,16.9 15.9,20.0 12.0,20.0z"/>
+ android:viewportWidth="19.3"
+ android:viewportHeight="19.3">
+ <group
+ android:translateX="1.2"
+ android:translateY="1.2">
+ <path
+ android:pathData="M0.97,3.97c-0.32,-0.33 -0.24,-0.81 0.08,-1.13L3.47,0.74C3.79,0.42 4.28,0.5 4.6,0.82s0.24,0.89 -0.08,1.13L2.1,4.05c-0.4,0.32 -0.89,0.24 -1.13,-0.08zM15.97,2.84l-2.5,-2.1c-0.32,-0.32 -0.8,-0.25 -1.13,0.08 -0.32,0.32 -0.24,0.81 0.08,1.13l2.5,2.1c0.33,0.3 0.81,0.24 1.13,-0.08 0.27,-0.31 0.25,-0.89 -0.08,-1.13zM15.73,9.21c0,4.03 -3.23,7.26 -7.26,7.26s-7.26,-3.23 -7.26,-7.26 3.23,-7.26 7.26,-7.26 7.26,3.23 7.26,7.26zM14.2,9.21c0,-3.15 -2.58,-5.73 -5.73,-5.73S2.74,6.06 2.74,9.21s2.58,5.73 5.73,5.73 5.73,-2.59 5.73,-5.73zM8.27,5.18c-0.33,0 -0.6,0.27 -0.6,0.6v4.23l3.34,2c0.27,0.17 0.62,0.08 0.79,-0.19s0.07,-0.64 -0.2,-0.8L8.87,9.41L8.87,5.78c0,-0.33 -0.27,-0.6 -0.6,-0.6z"
+ android:fillColor="#FFF"/>
+ </group>
</vector>
-</inset>
\ No newline at end of file
+</inset>
diff --git a/packages/SystemUI/res/drawable/stat_sys_cast.xml b/packages/SystemUI/res/drawable/stat_sys_cast.xml
index 4a7cbb3..5228085 100644
--- a/packages/SystemUI/res/drawable/stat_sys_cast.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_cast.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -18,11 +18,11 @@
android:insetRight="2.5dp">
<vector android:width="17dp"
android:height="17dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0">
+ android:viewportWidth="17.0"
+ android:viewportHeight="17.0">
<path
android:fillColor="#FFFFFFFF"
- android:pathData="M2.0,36.0l0.0,6.0l6.0,0.0C8.0,38.7 5.3,36.0 2.0,36.0zM2.0,28.0l0.0,4.0c5.5,0.0 10.0,4.5 10.0,10.0l4.0,0.0C16.0,34.3 9.7,28.0 2.0,28.0zM38.0,14.0L10.0,14.0l0.0,3.3c7.9,2.6 14.2,8.8 16.7,16.7L38.0,34.0L38.0,14.0zM2.0,20.0l0.0,4.0c9.9,0.0 18.0,8.1 18.0,18.0l4.0,0.0C24.0,29.8 14.1,20.0 2.0,20.0zM42.0,6.0L6.0,6.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,6.0l4.0,0.0l0.0,-6.0l36.0,0.0l0.0,28.0L28.0,38.0l0.0,4.0l14.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,10.0C46.0,7.8 44.2,6.0 42.0,6.0z"/>
+ android:pathData="M0.71,12.75v1.42c0,0.39 0.32,0.71 0.71,0.71h1.42C2.83,13.7 1.88,12.75 0.71,12.75zM0.69,10.67c-0.01,0.36 0.25,0.66 0.6,0.72c1.47,0.26 2.65,1.42 2.9,2.89c0.06,0.34 0.35,0.6 0.7,0.6c0.43,0 0.77,-0.38 0.71,-0.81c-0.34,-2.11 -2,-3.76 -4.11,-4.09C1.08,9.91 0.7,10.24 0.69,10.67zM13.46,4.96H3.54v1.15c2.81,0.91 5.02,3.12 5.93,5.93h3.99V4.96zM0.69,7.81C0.68,8.18 0.95,8.49 1.31,8.53c3.02,0.3 5.44,2.71 5.74,5.72c0.04,0.35 0.34,0.62 0.7,0.62c0.42,0 0.75,-0.36 0.71,-0.78c-0.37,-3.69 -3.3,-6.62 -6.99,-6.98C1.06,7.08 0.7,7.4 0.69,7.81zM14.88,2.12H2.12c-0.78,0 -1.42,0.64 -1.42,1.42v2.12h1.42V3.54h12.75v9.92H9.92v1.42h4.96c0.78,0 1.42,-0.64 1.42,-1.42V3.54C16.29,2.76 15.65,2.12 14.88,2.12z" />
</vector>
</inset>
diff --git a/packages/SystemUI/res/drawable/stat_sys_data_bluetooth.xml b/packages/SystemUI/res/drawable/stat_sys_data_bluetooth.xml
index e2d3539..4dc0e4b 100644
--- a/packages/SystemUI/res/drawable/stat_sys_data_bluetooth.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_data_bluetooth.xml
@@ -1,25 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
-Copyright (C) 2014 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.
+**
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="17dp"
android:height="17dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0">
-
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6z"/>
+ android:viewportWidth="17.0"
+ android:viewportHeight="17.0">
+ <group
+ android:translateY="0.5"
+ android:translateX="0.5" >
+ <path
+ android:pathData="M8.84,8l2.62,-2.62c0.29,-0.29 0.29,-0.75 0,-1.04L8.33,1.22L8.31,1.2c-0.3,-0.28 -0.76,-0.26 -1.03,0.04c-0.13,0.13 -0.2,0.31 -0.2,0.5v4.51L4.24,3.4c-0.29,-0.29 -0.74,-0.29 -1.03,0s-0.29,0.74 0,1.03L6.78,8l-3.56,3.56c-0.29,0.29 -0.29,0.74 0,1.03s0.74,0.29 1.03,0l2.83,-2.83v4.51c0,0.4 0.33,0.73 0.73,0.73c0.18,0 0.36,-0.07 0.5,-0.2l0.03,-0.03l3.12,-3.12c0.29,-0.29 0.29,-0.75 0,-1.04L8.84,8zM8.47,6.37V3.36l1.5,1.5L8.47,6.37zM8.47,12.63V9.62l1.5,1.5L8.47,12.63z"
+ android:fillColor="#FFFFFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_data_bluetooth_connected.xml b/packages/SystemUI/res/drawable/stat_sys_data_bluetooth_connected.xml
index 94c40ba..c8a4440 100644
--- a/packages/SystemUI/res/drawable/stat_sys_data_bluetooth_connected.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_data_bluetooth_connected.xml
@@ -1,25 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
-Copyright (C) 2014 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.
+**
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="17dp"
android:height="17dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0">
-
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M14.0,24.0l-4.0,-4.0l-4.0,4.0l4.0,4.0L14.0,24.0zM35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6zM38.0,20.0l-4.0,4.0l4.0,4.0l4.0,-4.0L38.0,20.0z"/>
+ android:viewportWidth="18.0"
+ android:viewportHeight="18.0">
+ <group
+ android:translateY="0.5"
+ android:translateX="0.5" >
+ <path
+ android:pathData="M9.57,8.5l2.79,-2.78c0.3,-0.3 0.3,-0.8 0,-1.1L9.04,1.29L9.02,1.27C8.7,0.98 8.21,1 7.91,1.31C7.78,1.45 7.71,1.64 7.71,1.84v4.79L4.69,3.61c-0.3,-0.3 -0.79,-0.3 -1.09,0s-0.3,0.79 0,1.09L7.39,8.5L3.6,12.29c-0.3,0.3 -0.3,0.79 0,1.09s0.79,0.3 1.09,0l3.01,-3.01v4.8c0,0.42 0.35,0.77 0.77,0.77c0.19,0 0.39,-0.07 0.53,-0.21l0.04,-0.04l3.32,-3.32c0.3,-0.3 0.3,-0.8 0,-1.1L9.57,8.5zM9.19,6.77v-3.2l1.6,1.6L9.19,6.77zM9.19,13.42v-3.2l1.6,1.6L9.19,13.42zM4.03,9.29c-0.44,0.44 -1.15,0.44 -1.58,0C2.02,8.86 2.02,8.16 2.45,7.72l0.01,-0.01C2.89,7.27 3.59,7.27 4.02,7.7l0.01,0.01C4.47,8.15 4.47,8.85 4.03,9.29zM14.44,7.71c0.44,0.44 0.44,1.15 0,1.58c-0.44,0.44 -1.15,0.44 -1.58,0c-0.44,-0.43 -0.44,-1.13 -0.01,-1.57l0.01,-0.01C13.3,7.28 14,7.27 14.43,7.7C14.44,7.7 14.44,7.71 14.44,7.71z"
+ android:fillColor="#FFFFFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_data_saver.xml b/packages/SystemUI/res/drawable/stat_sys_data_saver.xml
index c36678d..fed7cae 100644
--- a/packages/SystemUI/res/drawable/stat_sys_data_saver.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_data_saver.xml
@@ -1,34 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- 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.
+**
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="2.5dp"
- android:insetRight="2.5dp">
+ android:insetRight="2.5dp" >
<vector
- android:width="17.0dp"
- android:height="17.0dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M12.0,19.0c-3.9,0.0 -7.0,-3.1 -7.0,-7.0c0.0,-3.5 2.6,-6.4 6.0,-6.9L11.0,2.0C5.9,2.5 2.0,6.8 2.0,12.0c0.0,5.5 4.5,10.0 10.0,10.0c3.3,0.0 6.2,-1.6 8.1,-4.1l-2.6,-1.5C16.2,18.0 14.2,19.0 12.0,19.0z"/>
- <path
- android:fillColor="#4DFFFFFF"
- android:pathData="M13.0,2.0l0.0,3.0c3.4,0.5 6.0,3.4 6.0,6.9c0.0,0.9 -0.2,1.8 -0.5,2.5l2.6,1.5c0.6,-1.2 0.9,-2.6 0.9,-4.1C22.0,6.8 18.0,2.6 13.0,2.0z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M16.0,11.0l0.0,2.0 -3.0,0.0 0.0,3.0 -2.0,0.0 0.0,-3.0 -3.0,0.0 0.0,-2.0 3.0,0.0 0.0,-3.0 2.0,0.0 0.0,3.0z"/>
+ android:width="18dp"
+ android:height="18dp"
+ android:viewportWidth="19.0"
+ android:viewportHeight="19.0">
+ <group
+ android:translateX="1.0"
+ android:translateY="1.0">
+ <path
+ android:pathData="M11.5,8.5c0,0.41 -0.34,0.74 -0.74,0.74L9.24,9.24v1.5c0,0.41 -0.34,0.74 -0.74,0.74s-0.74,-0.34 -0.74,-0.74v-1.5h-1.5c-0.41,0 -0.74,-0.34 -0.74,-0.74s0.34,-0.74 0.74,-0.74h1.5v-1.5c0,-0.41 0.34,-0.74 0.74,-0.74s0.74,0.34 0.74,0.74v1.5h1.5c0.42,-0.01 0.76,0.33 0.76,0.74z"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M13.23,12.05l0.99,0.57c0.19,0.12 0.25,0.38 0.12,0.55A7.452,7.452 0,0 1,7.5 15.9c-3.29,-0.44 -5.96,-3.08 -6.41,-6.38 -0.57,-4.17 2.33,-7.8 6.23,-8.42 0.23,-0.04 0.44,0.15 0.44,0.37v1.15c0,0.18 -0.14,0.33 -0.31,0.37 -2.7,0.52 -4.71,2.96 -4.55,5.83 0.16,2.86 2.57,5.21 5.43,5.29 1.77,0.06 3.38,-0.72 4.44,-1.97 0.11,-0.14 0.31,-0.18 0.46,-0.09z"
+ android:fillColor="#FFF" />
+ <path
+ android:pathData="M14.11,8.5c0,0.62 -0.11,1.22 -0.29,1.78 -0.06,0.17 0.01,0.35 0.16,0.45l1,0.57c0.19,0.12 0.46,0.03 0.54,-0.18 0.3,-0.82 0.47,-1.7 0.47,-2.62 0,-3.73 -2.73,-6.82 -6.31,-7.39a0.377,0.377 0,0 0,-0.44 0.37v1.15c0,0.18 0.14,0.33 0.31,0.36 2.59,0.51 4.56,2.78 4.56,5.51z"
+ android:fillAlpha="0.3"
+ android:fillColor="#FFF" />
+
+ </group>
</vector>
</inset>
diff --git a/packages/SystemUI/res/drawable/stat_sys_dnd.xml b/packages/SystemUI/res/drawable/stat_sys_dnd.xml
index 3bf5e98..bd4cb0a 100644
--- a/packages/SystemUI/res/drawable/stat_sys_dnd.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_dnd.xml
@@ -1,28 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2015 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.
+**
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="2.5dp"
- android:insetRight="2.5dp">
+ android:insetRight="2.5dp" >
<vector
- android:width="17dp"
- android:height="17dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0">
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M24.0,4.0C12.95,4.0 4.0,12.95 4.0,24.0s8.95,20.0 20.0,20.0 20.0,-8.95 20.0,-20.0S35.05,4.0 24.0,4.0zm10.0,22.0L14.0,26.0l0.0,-4.0l20.0,0.0l0.0,4.0z"/>
+ android:width="17dp"
+ android:height="17dp"
+ android:viewportWidth="16.6"
+ android:viewportHeight="16.6">
+ <group
+ android:translateX="-0.2"
+ android:translateY="-0.2">
+ <path
+ android:pathData="M8.5,1.59c-3.81,0 -6.91,3.09 -6.91,6.91s3.09,6.91 6.91,6.91 6.91,-3.09 6.91,-6.91 -3.1,-6.91 -6.91,-6.91zM11.16,9.56L5.84,9.56c-0.59,0 -1.06,-0.48 -1.06,-1.06s0.48,-1.06 1.06,-1.06h5.31a1.06,1.06 0,0 1,0.01 2.12z"
+ android:fillColor="#FFF"/>
+ </group>
</vector>
</inset>
diff --git a/packages/SystemUI/res/drawable/stat_sys_dnd_total_silence.xml b/packages/SystemUI/res/drawable/stat_sys_dnd_total_silence.xml
index 82e25ca..11a3bdd 100644
--- a/packages/SystemUI/res/drawable/stat_sys_dnd_total_silence.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_dnd_total_silence.xml
@@ -1,31 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2015 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.
+**
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="2.5dp"
- android:insetRight="2.5dp">
+ android:insetRight="2.5dp" >
<vector
- android:width="17dp"
- android:height="17dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0s10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM12.0,20.5c-4.7,0.0 -8.5,-3.8 -8.5,-8.5S7.3,3.5 12.0,3.5s8.5,3.8 8.5,8.5S16.7,20.5 12.0,20.5z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M12.0,6.0c-3.3,0.0 -6.0,2.7 -6.0,6.0c0.0,3.3 2.7,6.0 6.0,6.0s6.0,-2.7 6.0,-6.0C18.0,8.7 15.4,6.0 12.0,6.0zM15.0,13.0L9.0,13.0l0.0,-2.0l6.0,0.0L15.0,13.0z"/>
+ android:width="16dp"
+ android:height="16dp"
+ android:viewportWidth="16.6"
+ android:viewportHeight="16.6">
+ <group
+ android:translateX="-0.2"
+ android:translateY="-0.2">
+ <path
+ android:pathData="M8.5,3.72c-2.62,0 -4.78,2.16 -4.78,4.78s2.16,4.78 4.78,4.78 4.78,-2.16 4.78,-4.78 -2.07,-4.78 -4.78,-4.78zM10.09,9.56L6.91,9.56c-0.59,0 -1.06,-0.48 -1.06,-1.06s0.48,-1.06 1.06,-1.06h3.19c0.59,0 1.06,0.48 1.06,1.06s-0.48,1.06 -1.07,1.06z"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M8.5,0.53C4.11,0.53 0.53,4.11 0.53,8.5s3.58,7.97 7.97,7.97 7.97,-3.58 7.97,-7.97S12.89,0.53 8.5,0.53zM8.5,15.28c-3.75,0 -6.78,-3.03 -6.78,-6.78S4.75,1.72 8.5,1.72s6.78,3.03 6.78,6.78 -3.03,6.78 -6.78,6.78z"
+ android:fillColor="#FFF"/>
+ </group>
</vector>
</inset>
diff --git a/packages/SystemUI/res/drawable/stat_sys_hotspot.xml b/packages/SystemUI/res/drawable/stat_sys_hotspot.xml
index 01e8888..4f52777 100644
--- a/packages/SystemUI/res/drawable/stat_sys_hotspot.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_hotspot.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -17,13 +17,16 @@
android:insetLeft="2.5dp"
android:insetRight="2.5dp">
<vector
- android:width="17.0dp"
- android:height="17.0dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0">
-
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M24.000000,22.000000c-2.200000,0.000000 -4.000000,1.800000 -4.000000,4.000000c0.000000,2.200000 1.800000,4.000000 4.000000,4.000000c2.200000,0.000000 4.000000,-1.800000 4.000000,-4.000000C28.000000,23.799999 26.200001,22.000000 24.000000,22.000000zM36.000000,26.000000c0.000000,-6.600000 -5.400000,-12.000000 -12.000000,-12.000000c-6.600000,0.000000 -12.000000,5.400000 -12.000000,12.000000c0.000000,4.400000 2.400000,8.300000 6.000000,10.400000l2.000000,-3.500000c-2.400000,-1.400000 -4.000000,-3.900000 -4.000000,-6.900000c0.000000,-4.400000 3.600000,-8.000000 8.000000,-8.000000s8.000000,3.600000 8.000000,8.000000c0.000000,3.000000 -1.600000,5.500000 -4.000000,6.900000l2.000000,3.500000C33.599998,34.299999 36.000000,30.400000 36.000000,26.000000zM24.000000,6.000000C13.000000,6.000000 4.000000,15.000000 4.000000,26.000000c0.000000,7.400000 4.000000,13.800000 10.000000,17.299999l2.000000,-3.500000c-4.800000,-2.800000 -8.000000,-7.900000 -8.000000,-13.800000c0.000000,-8.800000 7.200000,-16.000000 16.000000,-16.000000s16.000000,7.200000 16.000000,16.000000c0.000000,5.900000 -3.200000,11.100000 -8.000000,13.800000l2.000000,3.500000c6.000000,-3.500000 10.000000,-9.900000 10.000000,-17.299999C44.000000,15.000000 35.000000,6.000000 24.000000,6.000000z"/>
+ android:width="18.0dp"
+ android:height="18.0dp"
+ android:viewportWidth="18.0"
+ android:viewportHeight="18.0">
+ <group
+ android:translateX="0.5"
+ android:translateY="0.5" >
+ <path
+ android:pathData="M8.5,7.79c-0.78,0 -1.42,0.64 -1.42,1.42c0,0.78 0.64,1.42 1.42,1.42s1.42,-0.64 1.42,-1.42C9.92,8.43 9.28,7.79 8.5,7.79zM12.75,9.21c0,-2.35 -1.9,-4.25 -4.25,-4.25c-0.18,0 -0.35,0.01 -0.53,0.03C6.11,5.22 4.58,6.7 4.3,8.55c-0.23,1.52 0.35,2.91 1.36,3.82C6,12.67 6.54,12.6 6.76,12.2c0.17,-0.3 0.1,-0.67 -0.16,-0.89c-0.78,-0.7 -1.12,-1.77 -0.86,-2.79C5.99,7.5 6.78,6.71 7.8,6.46C9.32,6.08 10.86,7 11.25,8.52c0.06,0.23 0.09,0.46 0.09,0.69c0,0.84 -0.36,1.58 -0.94,2.1c-0.25,0.23 -0.33,0.6 -0.16,0.9c0.22,0.38 0.74,0.49 1.06,0.2C12.22,11.6 12.75,10.43 12.75,9.21zM7.63,1.85C4.19,2.24 1.42,5.07 1.1,8.52c-0.26,2.61 0.88,5.15 2.99,6.7c0.36,0.26 0.86,0.15 1.09,-0.23c0.19,-0.32 0.1,-0.74 -0.19,-0.96c-1.7,-1.26 -2.71,-3.38 -2.35,-5.73c0.4,-2.6 2.57,-4.68 5.19,-4.97c3.59,-0.41 6.63,2.4 6.63,5.91c0,1.97 -0.96,3.7 -2.43,4.79c-0.3,0.22 -0.38,0.63 -0.19,0.96c0.22,0.39 0.73,0.49 1.09,0.23c1.9,-1.4 3.03,-3.62 3.03,-5.98C15.94,4.84 12.12,1.34 7.63,1.85z"
+ android:fillColor="#FFFFFFFF"/>
+ </group>
</vector>
</inset>
diff --git a/packages/SystemUI/res/drawable/stat_sys_no_sims.xml b/packages/SystemUI/res/drawable/stat_sys_no_sims.xml
index 2229c99..faea7d1 100644
--- a/packages/SystemUI/res/drawable/stat_sys_no_sims.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_no_sims.xml
@@ -1,25 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
-Copyright (C) 2014 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.
+**
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="17dp"
- android:height="17dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
-
- <path
- android:fillColor="?attr/backgroundColor"
- android:pathData="M19.0,5.0c0.0,-1.1 -0.9,-2.0 -2.0,-2.0l-7.0,0.0L7.7,5.3L19.0,16.7L19.0,5.0zM3.7,3.9L2.4,5.2L5.0,7.8L5.0,19.0c0.0,1.1 0.9,2.0 2.0,2.0l10.0,0.0c0.4,0.0 0.7,-0.1 1.0,-0.3l1.9,1.9l1.3,-1.3L3.7,3.9z"/>
+ android:width="17dp"
+ android:height="17dp"
+ android:viewportWidth="18.4"
+ android:viewportHeight="18.4">
+ <group
+ android:translateX="0.7"
+ android:translateY="1.0">
+ <path
+ android:pathData="M13.91,11.84L5.14,3.08l1.81,-1.81h5.41c0.85,0 1.54,0.69 1.54,1.54l0.01,9.03zM15.06,14.95L2.54,2.44c-0.28,-0.28 -0.71,-0.28 -0.99,0s-0.28,0.71 0,0.98l1.53,1.53v8.67c0,0.85 0.69,1.54 1.54,1.54h7.74c0.27,0 0.52,-0.07 0.74,-0.2l0.96,0.96c0.28,0.28 0.71,0.28 0.99,0 0.28,-0.26 0.28,-0.69 0.01,-0.97z"
+ android:fillColor="?attr/backgroundColor"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_ringer_vibrate.xml b/packages/SystemUI/res/drawable/stat_sys_ringer_vibrate.xml
index eb7b4fc..e525fec 100644
--- a/packages/SystemUI/res/drawable/stat_sys_ringer_vibrate.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_ringer_vibrate.xml
@@ -1,28 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
-Copyright (C) 2014 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.
+**
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetLeft="3dp"
- android:insetRight="3dp">
- <vector android:width="18dp"
- android:height="18dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
-
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M0.0,15.0l2.0,0.0L2.0,9.0L0.0,9.0L0.0,15.0zM3.0,17.0l2.0,0.0L5.0,7.0L3.0,7.0L3.0,17.0zM22.0,9.0l0.0,6.0l2.0,0.0L24.0,9.0L22.0,9.0zM19.0,17.0l2.0,0.0L21.0,7.0l-2.0,0.0L19.0,17.0zM16.5,3.0l-9.0,0.0C6.7,3.0 6.0,3.7 6.0,4.5l0.0,15.0C6.0,20.3 6.7,21.0 7.5,21.0l9.0,0.0c0.8,0.0 1.5,-0.7 1.5,-1.5l0.0,-15.0C18.0,3.7 17.3,3.0 16.5,3.0zM16.0,19.0L8.0,19.0L8.0,5.0l8.0,0.0L16.0,19.0z"/>
+ android:insetLeft="2.5dp"
+ android:insetRight="2.5dp">
+ <vector
+ android:width="19dp"
+ android:height="17dp"
+ android:viewportWidth="21.0"
+ android:viewportHeight="19.0">
+ <group
+ android:translateX="1.0"
+ android:translateY="1.0">
+ <path
+ android:pathData="M13.28,0.53L5.84,0.53c-0.88,0 -1.59,0.71 -1.59,1.59v12.75c0,0.88 0.71,1.59 1.59,1.59h7.44c0.88,0 1.59,-0.71 1.59,-1.59L14.87,2.12c0.01,-0.88 -0.71,-1.59 -1.59,-1.59zM13.28,14.88L5.84,14.88L5.84,2.12h7.44v12.76zM2.66,13.81a0.52,0.52 0,0 1,-0.53 -0.53L2.13,3.72c0,-0.3 0.23,-0.53 0.53,-0.53 0.3,0 0.53,0.23 0.53,0.53v9.56c0,0.3 -0.24,0.53 -0.53,0.53zM0.53,11.69a0.52,0.52 0,0 1,-0.53 -0.53L0,5.84c0,-0.3 0.23,-0.53 0.53,-0.53 0.3,0 0.53,0.23 0.53,0.53v5.31c0,0.3 -0.23,0.54 -0.53,0.54zM16.47,13.81c0.3,0 0.53,-0.23 0.53,-0.53L17,3.72c0,-0.3 -0.23,-0.53 -0.53,-0.53 -0.3,0 -0.53,0.23 -0.53,0.53v9.56c0,0.3 0.23,0.53 0.53,0.53zM18.59,11.69c0.3,0 0.53,-0.23 0.53,-0.53L19.12,5.84c0,-0.3 -0.23,-0.53 -0.53,-0.53 -0.3,0 -0.53,0.23 -0.53,0.53v5.31c0,0.3 0.24,0.54 0.53,0.54z"
+ android:fillColor="#FFF"/>
+ </group>
</vector>
-</inset>
\ No newline at end of file
+</inset>
diff --git a/packages/SystemUI/res/drawable/stat_sys_vpn_ic.xml b/packages/SystemUI/res/drawable/stat_sys_vpn_ic.xml
index 7ca8c40..66f4fbb 100644
--- a/packages/SystemUI/res/drawable/stat_sys_vpn_ic.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_vpn_ic.xml
@@ -1,24 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
-Copyright (C) 2014 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.
+**
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="17.0dp"
- android:height="17.0dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M12.700000,10.000000c-0.800000,-2.300000 -3.000000,-4.000000 -5.700000,-4.000000c-3.300000,0.000000 -6.000000,2.700000 -6.000000,6.000000s2.700000,6.000000 6.000000,6.000000c2.600000,0.000000 4.800000,-1.700000 5.700000,-4.000000L17.000000,14.000000l0.000000,4.000000l4.000000,0.000000l0.000000,-4.000000l2.000000,0.000000l0.000000,-4.000000L12.700000,10.000000zM7.000000,14.000000c-1.100000,0.000000 -2.000000,-0.900000 -2.000000,-2.000000c0.000000,-1.100000 0.900000,-2.000000 2.000000,-2.000000s2.000000,0.900000 2.000000,2.000000C9.000000,13.100000 8.100000,14.000000 7.000000,14.000000z"/>
+ android:width="17dp"
+ android:height="17dp"
+ android:viewportWidth="19.0"
+ android:viewportHeight="19.0">
+ <group
+ android:translateX="1.0"
+ android:translateY="1.0">
+ <path
+ android:pathData="M15.46,6.96H9a4.637,4.637 0,0 0,-4.37 -3.09C2.07,3.87 0,5.94 0,8.5s2.07,4.63 4.63,4.63c2.02,0 3.73,-1.29 4.37,-3.09h2.2v1.54a1.54,1.54 0,0 0,3.08 0v-1.54h1.16c0.87,0 1.56,-0.69 1.56,-1.54s-0.69,-1.54 -1.54,-1.54zM4.63,10.04a1.54,1.54 0,1 1,0.001 -3.081,1.54 1.54,0 0,1 -0.001,3.081z"
+ android:fillColor="#FFF"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_0.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_0.xml
index 2de2e36..b3cd455 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_0.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_0.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2016 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.
@@ -16,12 +16,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18.41dp"
android:height="18.41dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
- android:fillColor="?attr/backgroundColor"/>
- <path
- android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.799999,-1.800001z"
- android:fillColor="?attr/fillColor"/>
+ android:viewportWidth="21.2"
+ android:viewportHeight="21.2">
+ <group
+ android:translateX="0.5"
+ android:translateY="2.0">
+ <path
+ android:pathData="M18.79,9.79c-0.32,-0.32 -0.83,-0.32 -1.15,0L16.43,11l-1.21,-1.21c-0.32,-0.32 -0.83,-0.32 -1.15,0L14.06,9.8l0,0c-0.32,0.32 -0.32,0.83 0,1.15l1.21,1.21l-1.21,1.21l0,0c-0.32,0.32 -0.32,0.83 0,1.15l0.01,0.01c0.32,0.32 0.83,0.32 1.15,0l1.21,-1.21l1.21,1.21c0.32,0.32 0.83,0.32 1.15,0c0.32,-0.32 0.32,-0.83 0,-1.15l-1.21,-1.21l1.21,-1.21C19.1,10.64 19.1,10.13 18.79,9.79z"
+ android:fillColor="?attr/fillColor"/>
+ <path
+ android:pathData="M11.69,7.44h6.27L19.95,5c0.4,-0.49 0.3,-1.22 -0.23,-1.56c-1.6,-1.05 -5.04,-2.9 -9.62,-2.9c-4.59,0 -8.03,1.85 -9.62,2.9C-0.05,3.78 -0.16,4.51 0.24,5l9.02,11.08c0.42,0.52 1.22,0.52 1.64,0l0.78,-0.96V7.44z"
+ android:fillColor="?attr/backgroundColor"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_0_fully.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_0_fully.xml
index 60f7eb6..6c0fad8 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_0_fully.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_0_fully.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -14,11 +14,15 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="18.41dp"
- android:height="17dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="?attr/backgroundColor"
- android:pathData="M13.000000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
+ android:width="18.41dp"
+ android:height="17dp"
+ android:viewportWidth="21.66"
+ android:viewportHeight="20">
+ <group
+ android:translateX="0.74"
+ android:translateY="1.2">
+ <path
+ android:pathData="M19.95,5c0.4,-0.49 0.3,-1.22 -0.23,-1.56 -1.6,-1.05 -5.04,-2.9 -9.62,-2.9 -4.59,0 -8.03,1.85 -9.62,2.9C-0.05,3.78 -0.16,4.51 0.24,5l9.02,11.08c0.42,0.52 1.22,0.52 1.64,0L19.95,5z"
+ android:fillColor="?attr/backgroundColor"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_1.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_1.xml
index 144a7c1..2ebbaf2 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_1.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_1.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2016 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.
@@ -14,17 +14,21 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="18.41dp"
- android:height="18.41dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M13.8,13.2c-0.1,0.0 -0.3,-0.1 -0.4,-0.1c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,0.0 -0.6,-0.1 -0.9,-0.1c0.0,0.0 0.0,0.0 -0.1,0.0c0.0,0.0 0.0,0.0 0.0,0.0s0.0,0.0 0.0,0.0c0.0,0.0 0.0,0.0 -0.1,0.0c-0.3,0.0 -0.6,0.0 -0.9,0.1c-0.1,0.0 -0.3,0.0 -0.4,0.1c-0.2,0.0 -0.3,0.1 -0.5,0.1c-0.2,0.0 -0.3,0.1 -0.5,0.1c-0.1,0.0 -0.1,0.0 -0.2,0.1c-1.6,0.5 -2.7,1.3 -2.8,1.5l5.3,6.6l0.0,0.0l0.0,0.0l0.0,0.0l0.0,0.0l1.8,-2.2L13.700002,13.2z"
- android:fillColor="?attr/fillColor"/>
- <path
- android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
- android:fillColor="?attr/backgroundColor"/>
- <path
- android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.799999,-1.800001z"
- android:fillColor="?attr/fillColor"/>
+ android:width="18.41dp"
+ android:height="18.41dp"
+ android:viewportWidth="21.7"
+ android:viewportHeight="21.7">
+ <group
+ android:translateY="2.2"
+ android:translateX="0.75">
+ <path
+ android:pathData="M11.69,7.44h6.27L19.95,5c0.4,-0.49 0.3,-1.22 -0.23,-1.56c-1.6,-1.05 -5.04,-2.9 -9.62,-2.9c-4.59,0 -8.03,1.85 -9.62,2.9C-0.05,3.78 -0.16,4.51 0.24,5l9.02,11.08c0.42,0.52 1.22,0.52 1.64,0l0.78,-0.96V7.44z"
+ android:fillColor="?attr/backgroundColor" />
+ <path
+ android:pathData="M5.02,10.86l4.25,5.21c0.42,0.52 1.22,0.52 1.64,0l0.78,-0.96V9.14c-0.51,-0.11 -1.05,-0.17 -1.59,-0.17C8.15,8.97 6.37,9.69 5.02,10.86z"
+ android:fillColor="?attr/fillColor" />
+ <path
+ android:pathData="M17.57,12.17l1.21,-1.21c0.32,-0.32 0.32,-0.83 0,-1.17c-0.32,-0.32 -0.83,-0.32 -1.15,0L16.43,11l-1.21,-1.21c-0.32,-0.32 -0.83,-0.32 -1.15,0L14.06,9.8c-0.32,0.32 -0.32,0.83 0,1.15l1.21,1.21l-1.21,1.21c-0.32,0.32 -0.32,0.83 0,1.15l0.01,0.01c0.32,0.32 0.83,0.32 1.15,0l1.21,-1.21l1.21,1.21c0.32,0.32 0.83,0.32 1.15,0c0.32,-0.32 0.32,-0.83 0,-1.15L17.57,12.17z"
+ android:fillColor="?attr/fillColor" />
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_1_fully.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_1_fully.xml
index 554350d..eaead9b 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_1_fully.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_1_fully.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -14,14 +14,18 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="18.41dp"
- android:height="17dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="?attr/backgroundColor"
- android:pathData="M13.100000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.500000,6.500000L13.100000,22.000000L13.100000,22.000000L13.100000,22.000000L13.100000,22.000000L13.100000,22.000000z"/>
- <path
- android:fillColor="?attr/fillColor"
- android:pathData="M13.100000,22.000000l5.500000,-6.800000c-0.200000,-0.200000 -2.300000,-1.900000 -5.500000,-1.900000s-5.300000,1.800000 -5.500000,1.900000L13.100000,22.000000L13.100000,22.000000L13.100000,22.000000L13.100000,22.000000L13.100000,22.000000z"/>
+ android:width="18.41dp"
+ android:height="17dp"
+ android:viewportWidth="21.66"
+ android:viewportHeight="20.0">
+ <group
+ android:translateX="0.79"
+ android:translateY="1.2">
+ <path
+ android:pathData="M19.95,5c0.4,-0.49 0.3,-1.22 -0.23,-1.56 -1.6,-1.05 -5.04,-2.9 -9.62,-2.9 -4.59,0 -8.03,1.85 -9.62,2.9C-0.05,3.78 -0.16,4.51 0.24,5l9.02,11.08c0.42,0.52 1.22,0.52 1.64,0L19.95,5z"
+ android:fillColor="?attr/backgroundColor"/>
+ <path
+ android:pathData="M10.1,8.97c-1.95,0 -3.72,0.72 -5.08,1.9l4.25,5.21c0.42,0.52 1.22,0.52 1.64,0l4.26,-5.22a7.702,7.702 0,0 0,-5.07 -1.89z"
+ android:fillColor="?attr/fillColor"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_2.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_2.xml
index 6b7f712..809147c 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_2.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_2.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2016 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.
@@ -14,17 +14,21 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="18.41dp"
- android:height="18.41dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M13.8,12.2l4.9,0.0c-1.0,-0.7 -3.4,-2.2 -6.7,-2.2c-4.1,0.0 -6.9,2.2 -7.2,2.5l7.2,9.0l0.0,0.0l0.0,0.0l1.8,-2.2L13.800001,12.2z"
- android:fillColor="?attr/fillColor"/>
- <path
- android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
- android:fillColor="?attr/backgroundColor"/>
- <path
- android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.800001,1.9 -1.800001,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
- android:fillColor="?attr/fillColor"/>
+ android:width="18.41dp"
+ android:height="18.41dp"
+ android:viewportWidth="21.7"
+ android:viewportHeight="21.7">
+ <group
+ android:translateX="0.75"
+ android:translateY="2.2" >
+ <path
+ android:pathData="M11.69,7.44h6.27L19.95,5c0.4,-0.49 0.3,-1.22 -0.23,-1.56c-1.6,-1.05 -5.04,-2.9 -9.62,-2.9c-4.59,0 -8.03,1.85 -9.62,2.9C-0.05,3.78 -0.16,4.51 0.24,5l9.02,11.08c0.42,0.52 1.22,0.52 1.64,0l0.78,-0.96V7.44z"
+ android:fillColor="?attr/backgroundColor"/>
+ <path
+ android:pathData="M10.09,6.44c-2.55,0 -4.88,0.93 -6.68,2.45l5.85,7.18c0.42,0.52 1.22,0.52 1.64,0l0.78,-0.96V7.44h2.84C13.18,6.8 11.68,6.44 10.09,6.44z"
+ android:fillColor="?attr/fillColor"/>
+ <path
+ android:pathData="M17.57,12.17l1.21,-1.21c0.32,-0.32 0.32,-0.83 0,-1.17c-0.32,-0.32 -0.83,-0.32 -1.15,0L16.43,11l-1.21,-1.21c-0.32,-0.32 -0.83,-0.32 -1.15,0L14.06,9.8c-0.32,0.32 -0.32,0.83 0,1.15l1.21,1.21l-1.21,1.21c-0.32,0.32 -0.32,0.83 0,1.15l0.01,0.01c0.32,0.32 0.83,0.32 1.15,0l1.21,-1.21l1.21,1.21c0.32,0.32 0.83,0.32 1.15,0c0.32,-0.32 0.32,-0.83 0,-1.15L17.57,12.17z"
+ android:fillColor="?attr/fillColor"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_2_fully.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_2_fully.xml
index 2c2465a..6d4fb77 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_2_fully.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_2_fully.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -14,14 +14,18 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="18.41dp"
- android:height="17dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="?attr/backgroundColor"
- android:pathData="M13.000000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
- <path
- android:fillColor="?attr/fillColor"
- android:pathData="M13.000000,22.000000l7.600000,-9.400000C20.299999,12.400000 17.400000,10.000000 13.000000,10.000000s-7.300000,2.400000 -7.600000,2.700000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
+android:width="18.41dp"
+android:height="17dp"
+android:viewportWidth="21.86"
+android:viewportHeight="20.19">
+ <group
+ android:translateX="0.85"
+ android:translateY="1.4" >
+ <path
+ android:pathData="M19.95,5c0.4,-0.49 0.3,-1.22 -0.23,-1.56 -1.6,-1.05 -5.04,-2.9 -9.62,-2.9 -4.59,0 -8.03,1.85 -9.62,2.9C-0.05,3.78 -0.16,4.51 0.24,5l9.02,11.08c0.42,0.52 1.22,0.52 1.64,0L19.95,5z"
+ android:fillColor="?attr/backgroundColor" />
+ <path
+ android:pathData="M10.09,6.44c-2.55,0 -4.88,0.93 -6.68,2.45l5.85,7.18c0.42,0.52 1.22,0.52 1.64,0l5.86,-7.19a10.284,10.284 0,0 0,-6.67 -2.44z"
+ android:fillColor="?attr/fillColor" />
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_3.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_3.xml
index d34b4de..4c479c0 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_3.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_3.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2016 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.
@@ -16,15 +16,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18.41dp"
android:height="18.41dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M13.8,12.2l5.7,0.0l1.0,-1.2C20.0,10.6 16.8,8.0 12.0,8.0s-8.0,2.6 -8.5,3.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
- android:fillColor="?attr/fillColor"/>
- <path
- android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
- android:fillColor="?attr/backgroundColor"/>
- <path
- android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
- android:fillColor="?attr/fillColor"/>
+ android:viewportWidth="21.7"
+ android:viewportHeight="21.7">
+ <group
+ android:translateX="0.75"
+ android:translateY="2.3" >
+ <path
+ android:pathData="M11.69,7.44h6.27L19.95,5c0.4,-0.49 0.3,-1.22 -0.23,-1.56c-1.6,-1.05 -5.04,-2.9 -9.62,-2.9c-4.59,0 -8.03,1.85 -9.62,2.9C-0.05,3.78 -0.16,4.51 0.24,5l9.02,11.08c0.42,0.52 1.22,0.52 1.64,0l0.78,-0.96V7.44z"
+ android:fillColor="?attr/backgroundColor"/>
+ <path
+ android:pathData="M10.08,4.75c-2.96,0 -5.66,1.06 -7.74,2.82l6.93,8.5c0.21,0.26 0.51,0.39 0.82,0.39c0.22,0 0.44,-0.07 0.63,-0.2c0.07,-0.05 0.14,-0.11 0.2,-0.19l0.78,-0.96V7.44h5.98C15.6,5.77 12.96,4.75 10.08,4.75z"
+ android:fillColor="?attr/fillColor"/>
+ <path
+ android:pathData="M17.57,12.17l1.21,-1.21c0.32,-0.32 0.32,-0.83 0,-1.17c-0.32,-0.32 -0.83,-0.32 -1.15,0L16.43,11l-1.21,-1.21c-0.32,-0.32 -0.83,-0.32 -1.15,0L14.06,9.8c-0.32,0.32 -0.32,0.83 0,1.15l1.21,1.21l-1.21,1.21c-0.32,0.32 -0.32,0.83 0,1.15l0.01,0.01c0.32,0.32 0.83,0.32 1.15,0l1.21,-1.21l1.21,1.21c0.32,0.32 0.83,0.32 1.15,0c0.32,-0.32 0.32,-0.83 0,-1.15L17.57,12.17z"
+ android:fillColor="?attr/fillColor"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_3_fully.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_3_fully.xml
index 7d0f756..6c96300 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_3_fully.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_3_fully.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -14,14 +14,18 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="18.41dp"
- android:height="17dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="?attr/backgroundColor"
- android:pathData="M13.000000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
- <path
- android:fillColor="?attr/fillColor"
- android:pathData="M13.000000,22.000000l9.200000,-11.400000c-0.400000,-0.300000 -3.900000,-3.200000 -9.200000,-3.200000s-8.900000,3.000000 -9.200000,3.200000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
+ android:width="18.41dp"
+ android:height="17dp"
+ android:viewportWidth="21.80"
+ android:viewportHeight="20.1">
+ <group
+ android:translateX="0.81"
+ android:translateY="1.29" >
+ <path
+ android:pathData="M19.95,5c0.4,-0.49 0.3,-1.22 -0.23,-1.56 -1.6,-1.05 -5.04,-2.9 -9.62,-2.9 -4.59,0 -8.03,1.85 -9.62,2.9C-0.05,3.78 -0.16,4.51 0.24,5l9.02,11.08c0.42,0.52 1.22,0.52 1.64,0L19.95,5z"
+ android:fillColor="?attr/backgroundColor"/>
+ <path
+ android:pathData="M10.08,4.75c-2.96,0 -5.66,1.06 -7.74,2.82l6.93,8.5c0.21,0.26 0.51,0.39 0.82,0.39 0.23,0 0.46,-0.07 0.65,-0.22 -0.02,0.02 -0.04,0.03 -0.07,0.05 0.09,-0.06 0.17,-0.13 0.24,-0.22l6.93,-8.5c-2.09,-1.74 -4.8,-2.82 -7.76,-2.82z"
+ android:fillColor="?attr/fillColor"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_4.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_4.xml
index 7d1bfe1..8fb7a7b 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_4.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_4.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2016 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.
@@ -14,14 +14,16 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="18.41dp"
- android:height="18.41dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M13.8,12.2l5.7,0.0L23.6,7.0C23.2,6.7 18.7,3.0 12.0,3.0C5.3,3.0 0.8,6.7 0.4,7.0L12.0,21.5l0.0,0.0l0.0,0.0l1.8,-2.2L13.8,12.2z"
- android:fillColor="?attr/singleToneColor"/>
- <path
- android:pathData="M21.9,15.4l-1.1,-1.2 -1.9,1.900001 -1.9,-1.900001 -1.1,1.2 1.9,1.9 -1.9,1.800001 1.1,1.199999 1.9,-1.9 1.9,1.9 1.1,-1.199999 -1.9,-1.800001z"
- android:fillColor="?attr/singleToneColor"/>
+ android:width="18.41dp"
+ android:height="18.41dp"
+ android:viewportWidth="21.95"
+ android:viewportHeight="21.65">
+ <group
+ android:translateX="0.90"
+ android:translateY="2.25"
+ >
+ <path
+ android:pathData="M11.69,15.12l-0.78,0.96c-0.43,0.52 -1.22,0.52 -1.64,0L0.24,5c-0.4,-0.49 -0.29,-1.22 0.23,-1.56c1.59,-1.05 5.03,-2.9 9.62,-2.9c4.59,0 8.02,1.85 9.62,2.9c0.53,0.35 0.63,1.08 0.23,1.56l-1.99,2.44h-6.27V15.12zM18.79,9.79c-0.32,-0.32 -0.83,-0.32 -1.15,0L16.43,11l-1.21,-1.21c-0.32,-0.32 -0.83,-0.32 -1.15,0L14.06,9.8l0,0c-0.32,0.32 -0.32,0.83 0,1.15l1.21,1.21l-1.21,1.21l0,0c-0.32,0.32 -0.32,0.83 0,1.15l0.01,0.01c0.32,0.32 0.83,0.32 1.15,0l1.21,-1.21l1.21,1.21c0.32,0.32 0.83,0.32 1.15,0c0.32,-0.32 0.32,-0.83 0,-1.15l-1.21,-1.21l1.21,-1.21C19.1,10.64 19.1,10.13 18.79,9.79z"
+ android:fillColor="?attr/singleToneColor"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_4_fully.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_4_fully.xml
index a7e213f..bc3fdb5 100644
--- a/packages/SystemUI/res/drawable/stat_sys_wifi_signal_4_fully.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_signal_4_fully.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2014 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.
@@ -14,11 +14,15 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="18.41dp"
- android:height="17dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="?attr/singleToneColor"
- android:pathData="M13.000000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
+ android:width="18.41dp"
+ android:height="17dp"
+ android:viewportWidth="21.75"
+ android:viewportHeight="20.0">
+ <group
+ android:translateX="0.80"
+ android:translateY="1.25">
+ <path
+ android:pathData="M19.95,5c0.4,-0.49 0.3,-1.22 -0.23,-1.56 -1.6,-1.05 -5.04,-2.9 -9.62,-2.9 -4.59,0 -8.03,1.85 -9.62,2.9C-0.05,3.78 -0.16,4.51 0.24,5l9.02,11.08c0.42,0.52 1.22,0.52 1.64,0L19.95,5z"
+ android:fillColor="?attr/singleToneColor"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/interpolator/ic_hotspot_disable_cross_1_pathdata_interpolator.xml b/packages/SystemUI/res/interpolator/ic_hotspot_disable_cross_1_pathdata_interpolator.xml
deleted file mode 100644
index bc0442f..0000000
--- a/packages/SystemUI/res/interpolator/ic_hotspot_disable_cross_1_pathdata_interpolator.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.166666667,0 0.2,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_hotspot_disable_ic_hotspot_alpha_interpolator.xml b/packages/SystemUI/res/interpolator/ic_hotspot_disable_ic_hotspot_alpha_interpolator.xml
deleted file mode 100644
index f7072f2..0000000
--- a/packages/SystemUI/res/interpolator/ic_hotspot_disable_ic_hotspot_alpha_interpolator.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.8,0 0.6,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_hotspot_disable_mask_pathdata_interpolator_1.xml b/packages/SystemUI/res/interpolator/ic_hotspot_disable_mask_pathdata_interpolator_1.xml
deleted file mode 100644
index 0cc9c02..0000000
--- a/packages/SystemUI/res/interpolator/ic_hotspot_disable_mask_pathdata_interpolator_1.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.166666667,0 0.833333333,0.833333333 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_hotspot_disable_mask_pathdata_interpolator_2.xml b/packages/SystemUI/res/interpolator/ic_hotspot_disable_mask_pathdata_interpolator_2.xml
deleted file mode 100644
index 44c755e..0000000
--- a/packages/SystemUI/res/interpolator/ic_hotspot_disable_mask_pathdata_interpolator_2.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.166666667,0.166666667 0.2,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_hotspot_enable_cross_1_pathdata_interpolator.xml b/packages/SystemUI/res/interpolator/ic_hotspot_enable_cross_1_pathdata_interpolator.xml
deleted file mode 100644
index bc90d28..0000000
--- a/packages/SystemUI/res/interpolator/ic_hotspot_enable_cross_1_pathdata_interpolator.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.8,0 0.833333333,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_hotspot_enable_mask_pathdata_interpolator_1.xml b/packages/SystemUI/res/interpolator/ic_hotspot_enable_mask_pathdata_interpolator_1.xml
deleted file mode 100644
index e361d9c..0000000
--- a/packages/SystemUI/res/interpolator/ic_hotspot_enable_mask_pathdata_interpolator_1.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.8,0 0.833333333,0.833333333 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_hotspot_enable_mask_pathdata_interpolator_2.xml b/packages/SystemUI/res/interpolator/ic_hotspot_enable_mask_pathdata_interpolator_2.xml
deleted file mode 100644
index 25ba970..0000000
--- a/packages/SystemUI/res/interpolator/ic_hotspot_enable_mask_pathdata_interpolator_2.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.166666667,0.166666667 0.833333333,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_invert_colors_disable_cross_1_pathdata_interpolator.xml b/packages/SystemUI/res/interpolator/ic_invert_colors_disable_cross_1_pathdata_interpolator.xml
deleted file mode 100644
index bc0442f..0000000
--- a/packages/SystemUI/res/interpolator/ic_invert_colors_disable_cross_1_pathdata_interpolator.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.166666667,0 0.2,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_invert_colors_enable_cross_1_pathdata_interpolator.xml b/packages/SystemUI/res/interpolator/ic_invert_colors_enable_cross_1_pathdata_interpolator.xml
deleted file mode 100644
index bc90d28..0000000
--- a/packages/SystemUI/res/interpolator/ic_invert_colors_enable_cross_1_pathdata_interpolator.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.8,0 0.833333333,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_invert_colors_enable_mask_pathdata_interpolator.xml b/packages/SystemUI/res/interpolator/ic_invert_colors_enable_mask_pathdata_interpolator.xml
deleted file mode 100644
index f7072f2..0000000
--- a/packages/SystemUI/res/interpolator/ic_invert_colors_enable_mask_pathdata_interpolator.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.8,0 0.6,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_signal_airplane_disable_cross_1_pathdata_interpolator.xml b/packages/SystemUI/res/interpolator/ic_signal_airplane_disable_cross_1_pathdata_interpolator.xml
deleted file mode 100644
index bc0442f..0000000
--- a/packages/SystemUI/res/interpolator/ic_signal_airplane_disable_cross_1_pathdata_interpolator.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.166666667,0 0.2,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_signal_airplane_enable_cross_1_pathdata_interpolator.xml b/packages/SystemUI/res/interpolator/ic_signal_airplane_enable_cross_1_pathdata_interpolator.xml
deleted file mode 100644
index bc90d28..0000000
--- a/packages/SystemUI/res/interpolator/ic_signal_airplane_enable_cross_1_pathdata_interpolator.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.8,0 0.833333333,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_signal_airplane_enable_mask_pathdata_interpolator.xml b/packages/SystemUI/res/interpolator/ic_signal_airplane_enable_mask_pathdata_interpolator.xml
deleted file mode 100644
index f7072f2..0000000
--- a/packages/SystemUI/res/interpolator/ic_signal_airplane_enable_mask_pathdata_interpolator.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.8,0 0.6,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_signal_flashlight_enable_cross_1_pathdata_interpolator.xml b/packages/SystemUI/res/interpolator/ic_signal_flashlight_enable_cross_1_pathdata_interpolator.xml
deleted file mode 100644
index f7072f2..0000000
--- a/packages/SystemUI/res/interpolator/ic_signal_flashlight_enable_cross_1_pathdata_interpolator.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.8,0 0.6,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_signal_flashlight_enable_mask_pathdata_interpolator.xml b/packages/SystemUI/res/interpolator/ic_signal_flashlight_enable_mask_pathdata_interpolator.xml
deleted file mode 100644
index f7072f2..0000000
--- a/packages/SystemUI/res/interpolator/ic_signal_flashlight_enable_mask_pathdata_interpolator.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.8,0 0.6,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_signal_location_enable_cross_1_pathdata_interpolator.xml b/packages/SystemUI/res/interpolator/ic_signal_location_enable_cross_1_pathdata_interpolator.xml
deleted file mode 100644
index f7072f2..0000000
--- a/packages/SystemUI/res/interpolator/ic_signal_location_enable_cross_1_pathdata_interpolator.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.8,0 0.6,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_signal_location_enable_mask_pathdata_interpolator.xml b/packages/SystemUI/res/interpolator/ic_signal_location_enable_mask_pathdata_interpolator.xml
deleted file mode 100644
index f7072f2..0000000
--- a/packages/SystemUI/res/interpolator/ic_signal_location_enable_mask_pathdata_interpolator.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.8,0 0.6,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_signal_workmode_disable_cross_1_pathdata_interpolator.xml b/packages/SystemUI/res/interpolator/ic_signal_workmode_disable_cross_1_pathdata_interpolator.xml
deleted file mode 100644
index 66cfaff..0000000
--- a/packages/SystemUI/res/interpolator/ic_signal_workmode_disable_cross_1_pathdata_interpolator.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.166666667,0 0.2,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_signal_workmode_enable_cross_1_pathdata_interpolator.xml b/packages/SystemUI/res/interpolator/ic_signal_workmode_enable_cross_1_pathdata_interpolator.xml
deleted file mode 100644
index a0118d70..0000000
--- a/packages/SystemUI/res/interpolator/ic_signal_workmode_enable_cross_1_pathdata_interpolator.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.8,0 0.833333333,1 1,1" />
diff --git a/packages/SystemUI/res/interpolator/ic_signal_workmode_enable_mask_pathdata_interpolator.xml b/packages/SystemUI/res/interpolator/ic_signal_workmode_enable_mask_pathdata_interpolator.xml
deleted file mode 100644
index 1820bab..0000000
--- a/packages/SystemUI/res/interpolator/ic_signal_workmode_enable_mask_pathdata_interpolator.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 c 0.8,0 0.6,1 1,1" />
diff --git a/packages/SystemUI/res/layout/ambient_indication.xml b/packages/SystemUI/res/layout/ambient_indication.xml
new file mode 100644
index 0000000..37ba35f
--- /dev/null
+++ b/packages/SystemUI/res/layout/ambient_indication.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- empty stub -->
+<merge />
diff --git a/packages/SystemUI/res/layout/battery_detail.xml b/packages/SystemUI/res/layout/battery_detail.xml
deleted file mode 100644
index 6162d65..0000000
--- a/packages/SystemUI/res/layout/battery_detail.xml
+++ /dev/null
@@ -1,107 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <TextView
- android:id="@+id/charge_and_estimation"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingStart="16dp"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?android:attr/colorAccent" />
-
- <com.android.systemui.ResizingSpace
- android:layout_width="match_parent"
- android:layout_height="@dimen/battery_detail_graph_space_top" />
-
- <com.android.settingslib.graph.UsageView
- android:id="@+id/battery_usage"
- android:layout_width="match_parent"
- android:layout_height="141dp"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="24dp"
- systemui:sideLabels="@array/battery_labels"
- android:colorAccent="?android:attr/colorAccent"
- systemui:textColor="?android:attr/textColorSecondary" />
-
- <com.android.systemui.ResizingSpace
- android:layout_width="match_parent"
- android:layout_height="@dimen/battery_detail_graph_space_bottom" />
-
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?android:attr/listDivider"
- android:layout_marginBottom="8dp" />
-
- <RelativeLayout
- android:id="@+id/switch_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="16dp"
- android:paddingBottom="16dp"
- android:background="?android:attr/selectableItemBackground"
- android:clickable="true">
-
- <ImageView
- android:id="@android:id/icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:scaleType="fitCenter"
- android:adjustViewBounds="true"
- android:layout_alignParentTop="true"
- android:layout_alignParentStart="true"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="32dp" />
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toStartOf="@android:id/toggle"
- android:layout_toEndOf="@android:id/icon"
- android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary"
- android:text="@string/battery_detail_switch_title" />
-
- <TextView
- android:id="@android:id/summary"
- android:visibility="gone"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@android:id/title"
- android:layout_toStartOf="@android:id/toggle"
- android:layout_toEndOf="@android:id/icon"
- android:textAppearance="@style/TextAppearance.QS.DetailItemSecondary"
- android:text="@string/battery_detail_switch_summary" />
-
- <Switch
- android:id="@android:id/toggle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_alignParentTop="true"
- android:layout_marginEnd="16dp"
- android:clickable="false"
- android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
-
- </RelativeLayout>
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/brightness_mirror.xml b/packages/SystemUI/res/layout/brightness_mirror.xml
index bbaff6a..d6e7507 100644
--- a/packages/SystemUI/res/layout/brightness_mirror.xml
+++ b/packages/SystemUI/res/layout/brightness_mirror.xml
@@ -16,7 +16,7 @@
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/brightness_mirror"
- android:layout_width="@dimen/notification_panel_width"
+ android:layout_width="@dimen/qs_panel_width"
android:layout_height="wrap_content"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:visibility="invisible">
diff --git a/packages/SystemUI/res/layout/car_fullscreen_user_pod.xml b/packages/SystemUI/res/layout/car_fullscreen_user_pod.xml
index b7e666f..bc29012 100644
--- a/packages/SystemUI/res/layout/car_fullscreen_user_pod.xml
+++ b/packages/SystemUI/res/layout/car_fullscreen_user_pod.xml
@@ -15,24 +15,26 @@
limitations under the License.
-->
-<!-- LinearLayout -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
- android:gravity="top|center_horizontal"
- android:layout_width="180dp"
- android:layout_height="wrap_content">
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/car_fullscreen_user_pod_margin_side"
+ android:layout_marginRight="@dimen/car_fullscreen_user_pod_margin_side"
+ android:gravity="center"
+ android:layout_weight="1" >
<ImageView android:id="@+id/user_avatar"
- android:padding="10dp"
- android:layout_gravity="center"
- android:layout_width="@dimen/car_fullscreen_user_pod_image_avatar_width"
- android:layout_height="@dimen/car_fullscreen_user_pod_image_avatar_height" />
+ android:layout_gravity="center"
+ android:layout_width="@dimen/car_fullscreen_user_pod_image_avatar_width"
+ android:layout_height="@dimen/car_fullscreen_user_pod_image_avatar_height" />
<TextView android:id="@+id/user_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="@dimen/car_fullscreen_user_pod_text_size"
- android:textColor="@color/qs_user_detail_name"
- android:gravity="center_horizontal" />
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/car_fullscreen_user_pod_margin_above_text"
+ android:textSize="@dimen/car_fullscreen_user_pod_text_size"
+ android:textColor="@color/qs_user_detail_name"
+ android:layout_gravity="center_horizontal" />
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/car_fullscreen_user_pod_container.xml b/packages/SystemUI/res/layout/car_fullscreen_user_pod_container.xml
new file mode 100644
index 0000000..8c55680
--- /dev/null
+++ b/packages/SystemUI/res/layout/car_fullscreen_user_pod_container.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center">
+
+ <!-- car_fullscreen_user_pods will be dynamically added here. -->
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/car_fullscreen_user_switcher.xml b/packages/SystemUI/res/layout/car_fullscreen_user_switcher.xml
index 6e4b213..c82eddc 100644
--- a/packages/SystemUI/res/layout/car_fullscreen_user_switcher.xml
+++ b/packages/SystemUI/res/layout/car_fullscreen_user_switcher.xml
@@ -14,38 +14,58 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:fitsSystemWindows="true"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone">
- <LinearLayout
+ android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical">
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/car_lockscreen_disclaimer_title"
- android:textSize="@dimen/car_lockscreen_disclaimer_title_size"
- android:paddingStart="@dimen/car_lockscreen_disclaimer_title_padding_start"
- android:paddingTop="@dimen/car_lockscreen_disclaimer_title_padding_top" />
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:text="@string/car_lockscreen_disclaimer_text"
- android:textSize="@dimen/car_lockscreen_disclaimer_text_size"
- android:paddingStart="@dimen/car_lockscreen_disclaimer_text_padding_start"
- android:paddingEnd="@dimen/car_lockscreen_disclaimer_text_padding_end"
- android:paddingTop="@dimen/car_lockscreen_disclaimer_text_padding_top" />
+ android:background="@android:color/black"
+ android:visibility="gone">
+
+ <!-- This progressbar is activated while we're switching users. -->
+ <ProgressBar
+ android:id="@+id/switching_users"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:indeterminate="true"
+ android:visibility="gone"
+ android:layout_gravity="center" />
+
+ <RelativeLayout
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- This progress bar is the countdown timer. -->
+ <ProgressBar
+ android:id="@+id/countdown_progress"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_user_switcher_progress_bar_height"
+ style="@style/CarUserSwitcher.ProgressBar"
+ android:layout_marginTop="@dimen/car_user_switcher_progress_bar_margin_top"
+ android:layout_alignParentTop="true"/>
+
<com.android.systemui.statusbar.car.UserGridView
android:id="@+id/user_grid"
- android:layout_gravity="center"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:stretchMode="columnWidth">
- </com.android.systemui.statusbar.car.UserGridView>
- </LinearLayout>
+ android:layout_marginLeft="@dimen/car_margin"
+ android:layout_marginRight="@dimen/car_margin"
+ android:layout_centerInParent="true" />
+
+ <com.android.systemui.statusbar.car.PageIndicator
+ android:id="@+id/user_switcher_page_indicator"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_page_indicator_dot_diameter"
+ android:layout_marginTop="@dimen/car_page_indicator_margin_top"
+ android:layout_below="@+id/user_grid" />
+
+ <Button
+ android:id="@+id/start_driving"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/car_start_driving_height"
+ android:text="@string/start_driving"
+ style="@style/CarUserSwitcher.StartDrivingButton"
+ android:layout_alignParentBottom="true"
+ android:layout_centerHorizontal="true" />
+ </RelativeLayout>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_item.xml b/packages/SystemUI/res/layout/global_actions_item.xml
new file mode 100644
index 0000000..358833c
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_item.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<!-- RelativeLayouts have an issue enforcing minimum heights, so just
+ work around this for now with LinearLayouts. -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/global_actions_panel_width"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:orientation="vertical"
+ android:paddingBottom="24dip"
+ android:paddingEnd="8dip"
+ android:paddingStart="8dip"
+ android:paddingTop="24dip">
+
+ <ImageView
+ android:id="@*android:id/icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_gravity="center"
+ android:scaleType="center"/>
+
+ <TextView
+ android:id="@*android:id/message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|center_horizontal"
+ android:gravity="center"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ />
+
+ <TextView
+ android:id="@*android:id/status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|center_horizontal"
+ android:gravity="center"
+ android:minHeight="26dp"
+ android:textColor="?android:attr/textColorTertiary"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_wrapped.xml b/packages/SystemUI/res/layout/global_actions_wrapped.xml
new file mode 100644
index 0000000..f409c03
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_wrapped.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.systemui.HardwareUiLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginBottom="0dp"
+ android:paddingTop="@dimen/global_actions_top_padding"
+ android:clipToPadding="false"
+ android:clipChildren="false">
+
+ <LinearLayout
+ android:id="@android:id/list"
+ android:layout_width="@dimen/global_actions_panel_width"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|end"
+ android:layout_marginBottom="@dimen/volume_dialog_margin_bottom"
+ android:orientation="vertical"
+ android:paddingTop="@dimen/volume_dialog_padding_top"
+ android:divider="@null"
+ android:dividerHeight="0dp"
+ android:translationZ="8dp">
+
+ </LinearLayout>
+
+</com.android.systemui.HardwareUiLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index e3063c5..ef9cf4a 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -39,7 +39,7 @@
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textStyle="italic"
- android:textColor="#ffffff"
+ android:textColor="?attr/bgProtectSecondaryTextColor"
android:textAppearance="?android:attr/textAppearanceSmall"
android:visibility="gone" />
@@ -49,7 +49,7 @@
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textStyle="italic"
- android:textColor="#ffffff"
+ android:textColor="?attr/bgProtectSecondaryTextColor"
android:textAppearance="?android:attr/textAppearanceSmall"
android:accessibilityLiveRegion="polite" />
@@ -68,7 +68,8 @@
android:layout_gravity="bottom|end"
android:src="@drawable/ic_camera_alt_24dp"
android:scaleType="center"
- android:contentDescription="@string/accessibility_camera_button" />
+ android:contentDescription="@string/accessibility_camera_button"
+ android:tint="?attr/bgProtectTextColor" />
<com.android.systemui.statusbar.KeyguardAffordanceView
android:id="@+id/left_button"
@@ -77,7 +78,8 @@
android:layout_gravity="bottom|start"
android:src="@drawable/ic_phone_24dp"
android:scaleType="center"
- android:contentDescription="@string/accessibility_phone_button" />
+ android:contentDescription="@string/accessibility_phone_button"
+ android:tint="?attr/bgProtectTextColor" />
<com.android.systemui.statusbar.phone.LockIcon
android:id="@+id/lock_icon"
@@ -86,6 +88,9 @@
android:layout_gravity="bottom|center_horizontal"
android:src="@drawable/ic_lock_24dp"
android:contentDescription="@string/accessibility_unlock_button"
- android:scaleType="center" />
+ android:scaleType="center"
+ android:tint="?attr/bgProtectTextColor" />
+
+ <include layout="@layout/keyguard_bottom_area_overlay" />
</com.android.systemui.statusbar.phone.KeyguardBottomAreaView>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area_overlay.xml b/packages/SystemUI/res/layout/keyguard_bottom_area_overlay.xml
new file mode 100644
index 0000000..37ba35f
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area_overlay.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- empty stub -->
+<merge />
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index c5f2f4a..71a681c 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -63,7 +63,7 @@
android:gravity="center_vertical"
android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="#ffffff"
+ android:textColor="?attr/bgProtectSecondaryTextColor"
android:singleLine="true" />
</com.android.systemui.statusbar.phone.KeyguardStatusBarView>
diff --git a/packages/SystemUI/res/layout/notification_children_divider.xml b/packages/SystemUI/res/layout/notification_children_divider.xml
index 76315b8..eb74306 100644
--- a/packages/SystemUI/res/layout/notification_children_divider.xml
+++ b/packages/SystemUI/res/layout/notification_children_divider.xml
@@ -20,4 +20,4 @@
android:id="@+id/notification_more_divider"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_divider_height"
- android:background="#FF616161" />
+ android:background="@color/notification_divider_color" />
diff --git a/packages/SystemUI/res/layout/qs_customize_panel.xml b/packages/SystemUI/res/layout/qs_customize_panel.xml
index cbc2575..9ab8ac6 100644
--- a/packages/SystemUI/res/layout/qs_customize_panel.xml
+++ b/packages/SystemUI/res/layout/qs_customize_panel.xml
@@ -21,7 +21,6 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical"
- android:elevation="4dp"
android:background="@drawable/qs_customizer_background"
android:gravity="center_horizontal">
diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
index 34e43ce..63f80ae 100644
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ b/packages/SystemUI/res/layout/qs_detail.xml
@@ -20,7 +20,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/qs_detail_background"
- android:elevation="4dp"
android:clickable="true"
android:orientation="vertical"
android:paddingBottom="8dp"
diff --git a/packages/SystemUI/res/layout/qs_footer.xml b/packages/SystemUI/res/layout/qs_footer.xml
index 871d1f3..db39905 100644
--- a/packages/SystemUI/res/layout/qs_footer.xml
+++ b/packages/SystemUI/res/layout/qs_footer.xml
@@ -21,13 +21,11 @@
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="48dp"
- android:elevation="4dp"
android:baselineAligned="false"
android:clickable="false"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="0dp"
- android:background="#00000000"
android:gravity="center_vertical"
android:orientation="horizontal">
@@ -116,9 +114,4 @@
android:padding="14dp" />
</LinearLayout>
- <include layout="@layout/qs_divider"
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:layout_gravity="bottom" />
-
</com.android.systemui.qs.QSFooter>
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index fb4ac04..87101e4 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -18,24 +18,16 @@
android:id="@+id/quick_settings_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?android:attr/colorPrimaryDark"
+ android:background="@drawable/qs_background_primary"
+ android:elevation="4dp"
android:clipToPadding="false"
android:clipChildren="false">
- <View
- android:id="@+id/qs_background"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@drawable/qs_background_primary"
- android:elevation="4dp" />
-
<com.android.systemui.qs.QSPanel
android:id="@+id/quick_settings_panel"
android:layout_marginTop="28dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:elevation="4dp"
- android:background="#00000000"
android:layout_marginBottom="48dp" />
<include layout="@layout/quick_status_bar_expanded_header" />
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 97376c3..2ff626a 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -24,8 +24,6 @@
android:layout_height="@dimen/status_bar_header_height"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:baselineAligned="false"
- android:background="#00000000"
- android:elevation="4dp"
android:clickable="false"
android:clipChildren="false"
android:clipToPadding="false"
@@ -56,15 +54,10 @@
android:textColor="?android:attr/textColorPrimary"
android:singleLine="true" />
- <FrameLayout
- android:id="@+id/system_icons_container"
- android:layout_width="wrap_content"
+ <com.android.systemui.BatteryMeterView android:id="@+id/battery"
android:layout_height="match_parent"
- android:layout_gravity="center_vertical">
-
- <include layout="@layout/system_icons" />
-
- </FrameLayout>
+ android:layout_width="wrap_content"
+ />
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
diff --git a/packages/SystemUI/res/layout/rounded_corners.xml b/packages/SystemUI/res/layout/rounded_corners.xml
new file mode 100644
index 0000000..8d391e0
--- /dev/null
+++ b/packages/SystemUI/res/layout/rounded_corners.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** Copyright 2012, 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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <ImageView
+ android:id="@+id/left"
+ android:layout_width="12dp"
+ android:layout_height="12dp"
+ android:tint="#ff000000"
+ android:src="@drawable/rounded" />
+ <ImageView
+ android:id="@+id/right"
+ android:layout_width="12dp"
+ android:layout_height="12dp"
+ android:tint="#ff000000"
+ android:layout_gravity="end"
+ android:src="@drawable/rounded" />
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/signal_cluster_view.xml b/packages/SystemUI/res/layout/signal_cluster_view.xml
index 2e22943..25c87d3 100644
--- a/packages/SystemUI/res/layout/signal_cluster_view.xml
+++ b/packages/SystemUI/res/layout/signal_cluster_view.xml
@@ -32,6 +32,7 @@
android:layout_width="wrap_content"
android:paddingEnd="6dp"
android:src="@drawable/stat_sys_vpn_ic"
+ android:tint="@color/background_protect_secondary"
android:contentDescription="@string/accessibility_vpn_on"
/>
<FrameLayout
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 0852020..bef0830 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -27,7 +27,6 @@
<include
layout="@layout/keyguard_status_view"
- android:layout_height="wrap_content"
android:visibility="gone" />
<com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
@@ -41,7 +40,7 @@
<FrameLayout
android:id="@+id/qs_frame"
android:layout="@layout/qs_panel"
- android:layout_width="@dimen/notification_panel_width"
+ android:layout_width="@dimen/qs_panel_width"
android:layout_height="match_parent"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:clipToPadding="false"
@@ -50,11 +49,15 @@
<com.android.systemui.statusbar.stack.NotificationStackScrollLayout
android:id="@+id/notification_stack_scroller"
+ android:layout_marginTop="@dimen/notification_panel_margin_top"
android:layout_width="@dimen/notification_panel_width"
android:layout_height="match_parent"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:layout_marginBottom="@dimen/close_handle_underlap" />
+ <include layout="@layout/ambient_indication"
+ android:id="@+id/ambient_indication_container" />
+
<ViewStub
android:id="@+id/keyguard_user_switcher"
android:layout="@layout/keyguard_user_switcher"
diff --git a/packages/SystemUI/res/layout/status_bar_no_notifications.xml b/packages/SystemUI/res/layout/status_bar_no_notifications.xml
index 6f87184..ed4bc67 100644
--- a/packages/SystemUI/res/layout/status_bar_no_notifications.xml
+++ b/packages/SystemUI/res/layout/status_bar_no_notifications.xml
@@ -27,7 +27,7 @@
android:layout_height="64dp"
android:paddingTop="28dp"
android:gravity="top|center_horizontal"
- android:textColor="#e6ffffff"
+ android:textColor="?attr/bgProtectTextColor"
android:textSize="16sp"
android:text="@string/empty_shade_text"/>
</com.android.systemui.statusbar.EmptyShadeView>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml b/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml
index efb273f..eefa9bc 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_dismiss_all.xml
@@ -30,5 +30,6 @@
android:focusable="true"
android:contentDescription="@string/accessibility_clear_all"
android:text="@string/clear_all_notifications_text"
+ android:textColor="?attr/bgProtectTextColor"
android:textAllCaps="true"/>
</com.android.systemui.statusbar.DismissView>
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index 51eefb6..029d16e 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -48,7 +48,6 @@
android:layout_height="match_parent"
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
- sysui:scrimColor="@color/scrim_behind_color"
/>
<com.android.systemui.statusbar.AlphaOptimizedView
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 18ffd0f..9076199 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -16,12 +16,12 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/volume_dialog"
- android:layout_width="match_parent"
+ android:layout_width="@dimen/volume_dialog_panel_width"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/volume_dialog_margin_bottom"
- android:background="@drawable/volume_dialog_background"
+ android:layout_gravity="center_vertical|end"
android:paddingTop="@dimen/volume_dialog_padding_top"
- android:translationZ="4dp" >
+ android:translationZ="8dp" >
<LinearLayout
android:id="@+id/volume_dialog_content"
@@ -57,6 +57,7 @@
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
+ android:visibility="gone"
android:textAppearance="@style/TextAppearance.Volume.Header" />
<com.android.keyguard.AlphaOptimizedImageButton
xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/packages/SystemUI/res/layout/volume_dialog_wrapped.xml b/packages/SystemUI/res/layout/volume_dialog_wrapped.xml
new file mode 100644
index 0000000..57489fd
--- /dev/null
+++ b/packages/SystemUI/res/layout/volume_dialog_wrapped.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.systemui.HardwareUiLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/top_padding"
+ android:layout_marginBottom="@dimen/bottom_padding">
+
+ <include layout="@layout/volume_dialog"/>
+
+</com.android.systemui.HardwareUiLayout>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 32f3ca5..db8edff 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Soek"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Gly op vir <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Gly links vir <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Jy sal nie deur geluide en vibrasies gepla word nie, behalwe deur wekkers, herinneringe, geleenthede en bellers wat jy spesifiseer. Jy sal steeds enigiets hoor wat jy kies om te speel, insluitend musiek, video\'s en speletjies."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Jy sal nie deur geluide en vibrasies gepla word nie, behalwe deur wekkers. Jy sal steeds enigiets hoor wat jy kies om te speel, insluitend musiek, video\'s en speletjies."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Jy sal nie deur klanke en vibrasies gesteur word nie, afgesien van wekkers, onthounotas, gebeurtenisse en bellers wat jy spesifiseer."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Pasmaak"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Dit blokkeer ALLE klanke en vibrasies, insluitend van wekkers, musiek, video\'s en speletjies af. Jy sal steeds foonoproepe kan maak."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Dit blokkeer ALLE klanke en vibrasies, insluitend van wekkers, musiek, video\'s en speletjies af."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Jy is gekoppel aan <xliff:g id="APPLICATION">%1$s</xliff:g>, wat jou persoonlike netwerkaktiwiteit, insluitend e-posse, programme en webwerwe, kan monitor."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> bestuur jou werkprofiel. Die profiel is gekoppel aan <xliff:g id="APPLICATION">%2$s</xliff:g>, wat jou netwerkaktiwiteit, insluitend e-posse, programme en webwerwe, kan monitor.\n\nKontak jou administrateur vir meer inligting."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> bestuur jou werkprofiel. Die profiel is gekoppel aan <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, wat jou netwerkaktiwiteit, insluitend e-posse, programme en webwerwe, kan monitor.\n\nJy is ook gekoppel aan <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, wat jou persoonlike netwerkaktiwiteit kan monitor."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Ontsluit vir <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> loop tans"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Toestel sal gesluit bly totdat jy dit handmatig ontsluit"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Kry kennisgewings vinniger"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Sien hulle voordat jy ontsluit"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Nee dankie"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Stel op"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Skakel nou af"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Beëindig nou"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Vou uit"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Vou in"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Skerm is vasgespeld"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Af"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Met kragkennisgewingkontroles kan jy \'n belangrikheidvlak van 0 tot 5 vir \'n program se kennisgewings stel. \n\n"<b>"Vlak 5"</b>" \n- Wys aan die bokant van die kennisgewinglys \n- Laat volskermonderbreking toe \n- Wys altyd opspringkennisgewings \n\n"<b>"Vlak 4"</b>" \n- Verhoed volskermonderbreking \n- Wys altyd opspringkennisgewings \n\n"<b>"Vlak 3"</b>" \n- Verhoed volskermonderbreking \n- Verhoed opspringkennisgewings \n\n"<b>"Vlak 2"</b>" \n- Verhoed volskermonderbreking \n- Verhoed opspringkennisgewings \n- Moet nooit \'n klank maak of vibreer nie \n\n"<b>"Vlak 1"</b>" \n- Verhoed volskermonderbreking \n- Verhoed opspringkennisgewings \n- Moet nooit \'n klank maak of vibreer nie \n- Versteek van sluitskerm en statusbalk \n- Wys aan die onderkant van die kennisgewinglys \n\n"<b>"Vlak 0"</b>" \n- Blokkeer alle kennisgewings van die program af"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Kennisgewings"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Jy sal nie meer hierdie kennisgewings kry nie."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Jy sal nie meer hierdie kennisgewings kry nie"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> kennisgewingkategorieë"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Hierdie program het nie kennisgewingkategorieë nie"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 uit <xliff:g id="NUMBER_1">%d</xliff:g> kennisgewingkategorieë van hierdie program</item>
<item quantity="one">1 uit <xliff:g id="NUMBER_0">%d</xliff:g> kennisgewingkategorie van hierdie program</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"kennisgewingkontroles"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"kennisgewing-sluimeropsies"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minute"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minute"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 uur"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 uur"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ONTDOEN"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Sluimer vir <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Batterygebruik"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Batterybespaarder is nie beskikbaar wanneer gelaai word nie"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Batterybespaarder"</string>
diff --git a/packages/SystemUI/res/values-af/strings_car.xml b/packages/SystemUI/res/values-af/strings_car.xml
index 7c6f609..87462fc 100644
--- a/packages/SystemUI/res/values-af/strings_car.xml
+++ b/packages/SystemUI/res/values-af/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Ry veilig"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Bly heeltemal bewus van bestuurtoestande en gehoorsaam toepaslike wette altyd. Rigtingaanwysings sal dalk onakkuraat, onvolledig, gevaarlik, ontoepaslik of verbode wees of behels dat administratiewe gebiede oorgesteek word. Besigheidsinligting sal dalk ook onakkuraat of onvolledig wees. Data is nie intyds nie en liggingakkuraatheid kan nie gewaarborg word nie. Moenie jou mobiele toestel hanteer of programme wat nie vir Android Auto bedoel is, gebruik terwyl jy bestuur nie."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Onbekend"</string>
+ <string name="start_driving" msgid="864023351402918991">"Begin ry"</string>
</resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index a217d3c..b641a49 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"ፍለጋ"</string>
<string name="description_direction_up" msgid="7169032478259485180">"ለ<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ወደ ላይ አንሸራትት።"</string>
<string name="description_direction_left" msgid="7207478719805562165">"ለ<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ወደ ግራ አንሸራትት።"</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"እርስዎ ከወሰንዋቸው ማንቂያዎች፣ አስታዋሾች፣ ክስተቶች እና ደዋዮች በስተቀር፣ በድምጾች እና ንዝረቶች አይረበሹም። ሙዚቃ፣ ቪዲዮዎች እና ጨዋታዎች ጨምሮ ለመጫወት የሚመርጡትን ማንኛውም ነገር አሁንም ይሰማሉ።"</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"ከማንቂያዎች በስተቀር፣ በድምጾች እና ንዝረቶች አይረበሹም። ሙዚቃ፣ ቪዲዮዎች እና ጨዋታዎች ጨምሮ ለመጫወት የሚመርጡትን ማንኛውም ነገር አሁንም ይሰማሉ።"</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"እርስዎ ከገለጿቸው ማንቂያዎች፣ አስታዋሾች፣ ክስተቶች እና ደዋዮች በስተቀር በድምጾች እና ንዝረቶች አይረበሹም።"</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"አብጅ"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"ይሄ ማንቂያዎችን፣ ሙዚቃን፣ ቪዲዮዎችን እና ጨዋታዎችንም ጨምሮ ሁሉንም ድምጾች እና ንዝረቶች ያጠፋል። አሁንም የድምጽ ጥሪዎችን ማድረግ ይችላሉ።"</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"ይሄ ማንቂያዎችን፣ ሙዚቃን፣ ቪዲዮዎችን እና ጨዋታዎችንም ጨምሮ ሁሉንም ድምጾች እና ንዝረቶች ያጠፋል።"</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"እርስዎ ኢሜይሎችን፣ መተግበሪያዎችን እና ድር ጣቢያዎችንም ጨምሮ የግል የአውታረ መረብ እንቅስቃሴዎን ከሚከታተለው ከ<xliff:g id="APPLICATION">%1$s</xliff:g> ጋር ተገናኝተዋል።"</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"የእርስዎ የሥራ መገለጫ በ<xliff:g id="ORGANIZATION">%1$s</xliff:g> የሚተዳደር ነው። መገለጫው ኢሜይሎችን፣ መተግበሪያዎችን፣ እና የድር ጣቢያዎችን ጨምሮ የአውታረ መረብ እንቅስቃሴን መቆጣጠር ከሚችለው ከ<xliff:g id="APPLICATION">%2$s</xliff:g> ጋር ተገናኝቷል።\n\nለተጨማሪ መረጃ የእርስዎን አስተዳዳሪ ያነጋግሩ።"</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"የእርስዎ የሥራ መገለጫ በ<xliff:g id="ORGANIZATION">%1$s</xliff:g> የሚተዳደር ነው። መገለጫው ኢሜይሎችን፣ መተግበሪያዎችን፣ እና የድር ጣቢያዎችን ጨምሮ የአውታረ መረብ እንቅስቃሴን መቆጣጠር ከሚችለው ከ<xliff:g id="APPLICATION_WORK">%2$s</xliff:g> ጋር ተገናኝቷል።\n\nበተጨማሪ የእርስዎን የግል የአውታረ መረብ እንቅስቃሴ መቆጣጠር ከሚችለው ከ<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> ጋር ተገናኝተዋል።"</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"ለ<xliff:g id="USER_NAME">%1$s</xliff:g> ተከፍቷል"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> እያሄደ ነው"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"እራስዎ እስኪከፍቱት ድረስ መሣሪያ እንደተቆለፈ ይቆያል"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"ማሳወቂያዎችን ፈጥነው ያግኙ"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"ከመክፈትዎ በፊት ይመልከቷቸው"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"አይ፣ አመሰግናለሁ"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"አዋቅር"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>። <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"አሁን አጥፋ"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"አሁን ጨርስ"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"አስፋ"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"ሰብስብ"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"ማያ ገጽ ተሰክቷል"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"ጠፍቷል"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"በኃይል ማሳወቂያ መቆጣጠሪያዎች አማካኝነት የአንድ መተግበሪያ ማሳወቂያዎች የአስፈላጊነት ደረጃ ከ0 እስከ 5 ድረስ ማዘጋጀት ይችላሉ። \n\n"<b>"ደረጃ 5"</b>" \n- በማሳወቂያ ዝርዝሩ አናት ላይ አሳይ \n- የሙሉ ማያ ገጽ ማቋረጥን ፍቀድ \n- ሁልጊዜ አጮልቀው ይመልከቱ \n\n"<b>"ደረጃ 4"</b>" \n- የሙሉ ማያ ገጽ ማቋረጥን ከልክል \n- ሁልጊዜ አጮልቀው ይመልከቱ \n\n"<b>"ደረጃ 3"</b>" \n- የሙሉ ማያ ገጽ ማቋረጥን ከልክል \n- በፍጹም አጮልቀው አይምልከቱ \n\n"<b>"ደረጃ 2"</b>" \n- የሙሉ ማያ ገጽ ማቋረጥን ይከልክሉ \n- በፍጹም አጮልቀው አይመልከቱ \n- ድምፅ እና ንዝረትን በፍጹም አይኑር \n\n"<b>"ደረጃ 1"</b>" \n- የሙሉ ማያ ገጽ ማቋረጥን ይከልክሉ \n- በፍጹም አጮልቀው አይመልከቱ \n- ድምፅ ወይም ንዝረትን በፍጹም አያደርጉ \n- ከመቆለፊያ ገጽ እና የሁኔታ አሞሌ ይደብቁ \n- በማሳወቂያ ዝርዝር ግርጌ ላይ አሳይ \n\n"<b>"ደረጃ 0"</b>" \n- ሁሉንም የመተግበሪያው ማሳወቂያዎች ያግዱ"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"ማሳወቂያዎች"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"እነዚህን ማሳወቂያዎች ከእንግዲህ አያግኙዋቸውም።"</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"እነዚህን ማሳወቂያዎች ከእንግዲህ አያገኟቸውም"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> የማሳወቂያ ምድቦች"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"ይህ መተግበሪያ የማሳወቂያ ምድቦች የሉትም"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 ከ<xliff:g id="NUMBER_1">%d</xliff:g> የማሳወቂያ ምድቦች ከዚህ መተግበሪያ</item>
<item quantity="other">1 <xliff:g id="NUMBER_1">%d</xliff:g> የማሳወቂያ ምድቦች ከዚህ መተግበሪያ</item>
@@ -570,13 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"የማሳወቂያ መቆጣጠሪያዎች"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"የማሳወቂያ ማሸለቢያ አማራጮች"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 ደቂቃዎች"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 ደቂቃዎች"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 ሰዓት"</string>
- <!-- no translation found for snooze_option_2_hour (8332218255658969475) -->
- <skip />
<string name="snooze_undo" msgid="6074877317002985129">"ቀልብስ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"ለ<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> አሸልቧል"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"የባትሪ አጠቃቀም"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"ኃይል በሚሞላበት ጊዜ ባትሪ ቆጣቢ አይገኝም"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"ባትሪ ቆጣቢ"</string>
diff --git a/packages/SystemUI/res/values-am/strings_car.xml b/packages/SystemUI/res/values-am/strings_car.xml
index 8550c42..5ebb05a 100644
--- a/packages/SystemUI/res/values-am/strings_car.xml
+++ b/packages/SystemUI/res/values-am/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"ደህንነትዎን ጠብቀው ያሽከርክሩ"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"የመኪና አነዳድ ሁኔታዎችን በተመለከተ ሙሉ በሙሉ ግንዛቤ ይኑርዎት፣ እንዲሁም የሚመለከታቸውን ሕጎች ሁልጊዜ ያክብሩ። የሚሰጡ አቅጣጫዎች ምናልባት ትክክል ያልሆኑ፣ ያልተሟሉ፣ አደገኛ፣ አግባብ ያልሆኑ፣ የተከለከሉ ወይም አስተዳደራዊ አካባቢዎችን ማቋረጥን የሚያካትቱ ሊሆኑ ይችላሉ። በተጨማሪም የንግድ ሥራ መረጃ ትክክል ያልሆነ ወይም ያልተሟላ ሊሆን ይችላል። ውሂብ ቅጽበታዊ አይደለም፣ እና የአካባቢ ትክክለኛነት ዋስትና ሊሰጥበት አይችልም። መኪና በሚነዱበት ጊዜ የእርስዎን ተንቀሳቃሽ መሣሪያ አይነካኩ ወይም ለAndroid Auto የታለሙ መተግበሪያዎችን አይጠቀሙ።"</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"ያልታወቀ"</string>
+ <string name="start_driving" msgid="864023351402918991">"መንዳት ይጀምሩ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 8281bf9..e37152f 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -360,8 +360,7 @@
<string name="description_target_search" msgid="3091587249776033139">"بحث"</string>
<string name="description_direction_up" msgid="7169032478259485180">"تمرير لأعلى لـ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"تمرير لليسار لـ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"لن يتم إزعاجك بالأصوات والاهتزاز، عدا من التنبيهات والتذكيرات والأحداث والمتصلين الذين تحددهم. وسيظل بإمكانك سماع أي عناصر أخرى تختار تشغيلها، بما في ذلك الموسيقى ومقاطع الفيديو والألعاب."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"لن يتم إزعاجك بالأصوات والاهتزاز، عدا من التنبيهات. وسيظل بإمكانك سماع أي عناصر أخرى تختار تشغيلها، بما في ذلك الموسيقى ومقاطع الفيديو والألعاب."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"لن يتم إزعاجك بأصوات أو اهتزاز، عدا من التنبيهات والتذكيرات والأحداث والمتصلين الذين تحددهم."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"تخصيص"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"سيؤدي هذا إلى حظر جميع الأصوات والاهتزازات، بما في ذلك ما يرد من التنبيهات والموسيقى والفيديو والألعاب. إلا أنه سيظل بإمكانك إجراء مكالمات هاتفية."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"سيؤدي هذا إلى حظر جميع الأصوات والاهتزازات، بما في ذلك ما يرد من التنبيهات والموسيقى والفيديو والألعاب."</string>
@@ -475,13 +474,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"أنت متصل بـ <xliff:g id="APPLICATION">%1$s</xliff:g>، الذي يمكنه مراقبة أنشطتك الشخصية على الشبكة، بما في ذلك الرسائل الإلكترونية والتطبيقات ومواقع الويب."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"يخضع الملف الشخصي للعمل لإدارة <xliff:g id="ORGANIZATION">%1$s</xliff:g>. تم ربط الملف الشخصي بـ <xliff:g id="APPLICATION">%2$s</xliff:g>، الذي يمكنه مراقبة أنشطة شبكتك، بما في ذلك الرسائل الإلكترونية والتطبيقات ومواقع الويب.\n\nيمكنك الاتصال بالمشرف للحصول على مزيد من المعلومات."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"يخضع الملف الشخصي للعمل لإدارة <xliff:g id="ORGANIZATION">%1$s</xliff:g>. تم ربط هذا الملف الشخصي بـ <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>، الذي يمكنه مراقبة أنشطة شبكتك، بما في ذلك الرسائل الإلكترونية والتطبيقات ومواقع الويب.\n\nتم ربطك بـ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>، الذي يمكنه مراقبة أنشطة شبكتك الشخصية."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"تم إلغاء القفل لـ <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> قيد التشغيل"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"سيظل الجهاز مقفلاً إلى أن يتم إلغاء قفله يدويًا"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"الحصول على الإشعارات بشكل أسرع"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"الاطلاع عليها قبل إلغاء القفل"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"لا، شكرًا"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"إعداد"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"إيقاف التشغيل الآن"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"إنهاء الآن"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"توسيع"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"تصغير"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"تم تثبيت الشاشة"</string>
@@ -556,9 +557,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"إيقاف"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"باستخدام عناصر التحكم في إشعار التشغيل، يمكنك تعيين مستوى الأهمية من 0 إلى 5 لإشعارات التطبيق. \n\n"<b>"المستوى 5"</b>" \n- العرض أعلى قائمة الإشعارات \n- يسمح بمقاطعة ملء الشاشة \n- الظهور الخاطف دائمًا \n\n"<b>"المستوى 4"</b>" \n- منع مقاطعة ملء الشاشة \n- الظهور الخاطف دائمًا \n\n"<b>"المستوى 3"</b>" \n- منع مقاطعة ملء الشاشة \n- عدم الظهور الخاطف أبدًا \n\n"<b>"المستوى 2"</b>" \n- منع مقاطعة ملء الشاشة \n- عدم الظهور الخاطف أبدًا \n- عدم إصدار أصوات واهتزاز \n\n"<b>"المستوى 1"</b>" \n- منع مقاطعة ملء الشاشة \n- عدم الظهور الخاطف أبدًا \n- عدم إصدار أصوات أو اهتزاز أبدًا \n- الإخفاء من شاشة التأمين وشريط الحالة \n- العرض أسفل قائمة الإشعارات \n\n"<b>"المستوى 0"</b>" \n- حظر جميع الإشعارات من التطبيق"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"الإشعارات"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"لن تتلقى هذه الإشعارات بعد الآن."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"لن تتلقى هذه الإشعارات بعد الآن."</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> فئة إشعار"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"لا يحتوي هذا التطبيق على فئات إشعار"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="zero">1 من إجمالي <xliff:g id="NUMBER_1">%d</xliff:g> فئة إشعار من هذا التطبيق</item>
<item quantity="two">1 من إجمالي فئتي إشعار (<xliff:g id="NUMBER_1">%d</xliff:g>) من هذا التطبيق</item>
@@ -586,12 +589,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"عناصر التحكم في الإشعارات"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"خيارات تأجيل الإشعارات"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"١٥ دقيقة"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"٣۰ دقيقة"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"ساعة واحدة"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"ساعتان"</string>
<string name="snooze_undo" msgid="6074877317002985129">"تراجع"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"تم تأجيل الإشعار لمدة <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"استخدام البطارية"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"وضع توفير شحن البطارية غير متاح أثناء الشحن."</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"توفير شحن البطارية"</string>
diff --git a/packages/SystemUI/res/values-ar/strings_car.xml b/packages/SystemUI/res/values-ar/strings_car.xml
index 0f315a4..7fec955 100644
--- a/packages/SystemUI/res/values-ar/strings_car.xml
+++ b/packages/SystemUI/res/values-ar/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"القيادة بأمان"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"عليك التعرف بشكل تام على ظروف القيادة والالتزام بالقوانين السارية. ويمكن أن تكون الاتجاهات غير دقيقة أو غير مكتملة أو خطيرة أو غير مناسبة أو محظورة أو تتضمن عبور مناطق إدارية. ويمكن أن تكون معلومات الأنشطة التجارية أيضًا غير دقيقة أو غير مكتملة. ولا يتم نشر البيانات في الوقت الفعلي، كما لا يمكن ضمان دقة المواقع. ولا تتعامل مع جهازك الجوّال أو تستخدم تطبيقات ليست متوافقة مع Android Auto أثناء القيادة."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"غير معروف"</string>
+ <string name="start_driving" msgid="864023351402918991">"بدء القيادة"</string>
</resources>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 40bd32a..ddb1848 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Axtar"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> üçün yuxarı sürüşdürün."</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> üçün sola sürüşdür."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Seçdiyiniz siqnal, xatırladıcı, tədbir və zənglər istisna olmaqla səslər və vibrasiyalar Sizi narahat etməyəcək. Musiqi, video və oyunlar da daxil olmaqla oxutmaq istədiyiniz hər şeyi eşidəcəksiniz."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Siqnallar istisna olmaqla səslər və vibrasiyalar Sizi narahat etməyəcək. Musiqi, video və oyunlar da daxil olmaqla oxutmaq istədiyiniz hər şeyi eşidəcəksiniz."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Zəng, xatırlatma, hadisə və seçdiyiniz zəng edənlər istisna olmaqla səs və vibrasiya Sizi narahat etməyəcək."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Fərdiləşdirin"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Bu, zəng, musiqi, video və oyunlar daxil olmaqla BÜTÜN səs və vibrasiyanı bloklayır. Yenə də telefon zəngi edə bilərsiniz."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Bu, zəng, musiqi, video və oyunlar daxil olmaqla BÜTÜN səs və vibrasiyanı bloklayır."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"<xliff:g id="APPLICATION">%1$s</xliff:g> tətbiqinə qoşulmusunuz və o, e-məktublar, tətbiq və veb saytlar daxil olmaqla şəxsi şəbəkə fəaliyyətinizə nəzarət edə bilər."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"İş profili <xliff:g id="ORGANIZATION">%1$s</xliff:g> tərəfindən idarə olunur. Profil e-poçt, tətbiq və veb saytlar da daxil olmaqla şəbəkə fəaliyyətinə nəzarət edən <xliff:g id="APPLICATION">%2$s</xliff:g> tətbiqinə qoşuludur.\n\nƏtraflı məlumat üçün admin ilə əlaqə saxlayın."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"İş profili <xliff:g id="ORGANIZATION">%1$s</xliff:g> tərəfindən idarə edilir. Profil e-poçt, tətbiq və veb saytlar da daxil olmaqla şəbəkə fəaliyyətinə nəzarət edən <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> tətbiqinə qoşuludur.\n\nEyni zamanda şəxsi şəbəkə fəaliyyətinə nəzarət edən <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> tətbiqinə qoşulusunuz."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> üçün kiliddən çıxarıldı"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> işləyir"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Device will stay locked until you manually unlock"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Bildirişləri daha sürətlə əldə edin"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Kiliddən çıxarmadan öncə onları görün"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Yox, çox sağ olun"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Quraşdırın"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"İndi deaktiv edin"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"İndi qurtarın"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Genişləndirin"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Yığcamlaşdırın"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekrana sancaq taxıldı"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Deaktiv"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Enerji bildiriş nəzarəti ilə, tətbiq bildirişləri üçün əhəmiyyət səviyyəsini 0-dan 5-ə kimi ayarlaya bilərsiniz. \n\n"<b>"Səviyyə 5"</b>" \n- Bildiriş siyahısının yuxarı hissəsində göstərin \n- Tam ekran kəsintisinə icazə verin \n- Hər zaman izləyin \n\n"<b>"Səviyyə 4"</b>" \n- Tam ekran kəsintisinin qarşısını alın \n- Hər zaman izləyin \n\n"<b>"Level 3"</b>" \n- Tam ekran kəsintisinin qarşısını alın \n- Heç vaxt izləməyin \n\n"<b>"Level 2"</b>" \n- Tam ekran kəsintisinin qarşısını alın \n- Heç vaxt izləməyin \n- Heç vaxt səsliyə və ya vibrasiyaya qoymayın \n\n"<b>"Səviyyə 1"</b>" \n- Prevent full screen interruption \n- Heç vaxt izləməyin \n- Heç vaxt səsliyə və ya vibrasiyaya qoymayın \n- Ekran kilidi və ya status panelindən gizlədin \n- Bildiriş siyahısının yuxarı hissəsində göstərin \n\n"<b>"Səviyyə 0"</b>" \n- Bütün bildirişləri tətbiqdən blok edin"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Bildirişlər"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Bu bildirişlər daha sizə göndərilməyəcək."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Bu bildirişlər daha sizə göndərilməyəcək"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> bildiriş kateqoriyaları"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Bu tətbiqin bildiriş kateqoriyası yoxdur"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">Bu tətbiqin <xliff:g id="NUMBER_1">%d</xliff:g> bildiriş kateqoriyasından 1 kanal</item>
<item quantity="one">Bu tətbiqin <xliff:g id="NUMBER_0">%d</xliff:g> bildiriş kateqoriyasından 1 kanal</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"bildiriş nəzarəti"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"bildiriş təxirə salma seçimləri"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 dəqiqə"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 dəqiqə"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 saat"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 saat"</string>
<string name="snooze_undo" msgid="6074877317002985129">"GERİ QAYTARIN"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> üçün təxirə salınıb"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Batareya istifadəsi"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Enerji Qənaəti doldurulma zamanı əlçatan deyil"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Enerji Qənaəti"</string>
diff --git a/packages/SystemUI/res/values-az/strings_car.xml b/packages/SystemUI/res/values-az/strings_car.xml
index 9ac7ce0..0b49eda 100644
--- a/packages/SystemUI/res/values-az/strings_car.xml
+++ b/packages/SystemUI/res/values-az/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Təhlükəsiz sürün"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Yol şəraitindən tam xəbərdar olun və hər zaman tətbiq olunan qaydalara riayət edin. İstiqamətlər qeyri-dəqiq, natamam, təhlükəli, uyğun olmayan, qadağan edilmiş və ya inzibati sahələrə keçid ilə nəticələnə bilər. Biznes məlumatı da qeyri-dəqiq və ya natamam ola bilər. Data real vaxtda deyil və məkan dəqiqliyinə zəmanət verilmir. Avtomobil idarə edərkən mobil cihazınızı elinizə almayın və ya Android Avto üçün nəzərdə tutulmamış tətbiqlərdən istifadə etməyin."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Naməlum"</string>
+ <string name="start_driving" msgid="864023351402918991">"Sürməyə başlayın"</string>
</resources>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 6e27760..42a3aec 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Pretraga"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Prevucite nagore za <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Prevucite ulevo za <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Neće vas uznemiravati zvukovi i vibracije osim za alarme, podsetnike, događaje i pozivaoce koje navedete. I dalje ćete čuti sve što odaberete da pustite, uključujući muziku, video snimke i igre."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Neće vas uznemiravati zvukovi i vibracije osim za alarme. I dalje ćete čuti sve što odaberete da pustite, uključujući muziku, video snimke i igre."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Neće vas uznemiravati zvukovi i vibracije, osim za alarme, podsetnike, događaje i pozivaoce koje izaberete."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Prilagodi"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Ovo blokira SVE zvukove i vibracije uključujući alarme, muziku, video snimke i igre. I dalje ćete moći da upućujete pozive."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Ovo blokira SVE zvukove i vibracije uključujući alarme, muziku, video snimke i igre."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Povezani ste sa aplikacijom <xliff:g id="APPLICATION">%1$s</xliff:g>, koja može da nadgleda aktivnosti na ličnoj mreži, uključujući imejlove, aplikacije i veb-sajtove."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Profilom za Work upravlja <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Povezan je sa aplikacijom <xliff:g id="APPLICATION">%2$s</xliff:g>, koja može da nadgleda aktivnosti na poslovnoj mreži, uključujući imejlove, aplikacije i veb-sajtove.\n\nViše informacija potražite od administratora."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Profilom za Work upravlja <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Povezan je sa aplikacijom <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, koja može da nadgleda aktivnosti na poslovnoj mreži, uključujući imejlove, aplikacije i veb-sajtove.\n\nPovezani ste i sa aplikacijom <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, koja može da nadgleda aktivnosti na ličnoj mreži."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Otključano za korisnika <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"Funkcija <xliff:g id="TRUST_AGENT">%1$s</xliff:g> je pokrenuta"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Uređaj će ostati zaključan dok ga ne otključate ručno"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Brže dobijajte obaveštenja"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Pregledajte ih pre otključavanja"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Ne, hvala"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Aktiviraj"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Isključi odmah"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Prekini odmah"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Proširi"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Skupi"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekran je zakačen"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Isključeno"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Pomoću naprednih kontrola za obaveštenja možete da podesite nivo važnosti od 0. do 5. za obaveštenja aplikacije. \n\n"<b>"5. nivo"</b>" \n– Prikazuju se u vrhu liste obaveštenja \n- Dozvoli prekid režima celog ekrana \n– Uvek zaviruj \n\n"<b>"4. nivo"</b>" \n– Spreči prekid režima celog ekrana \n– Uvek zaviruj \n\n"<b>"3. nivo"</b>" \n– Spreči prekid režima celog ekrana \n– Nikada ne zaviruj \n\n"<b>"2. nivo"</b>" \n– Spreči prekid režima celog ekrana \n– Nikada ne zaviruj \n– Nikada ne proizvodi zvuk ili vibraciju \n\n"<b>"1. nivo"</b>" \n– Spreči prekid režima celog ekrana \n– Nikada ne zaviruj \n– Nikada ne proizvodi zvuk ili vibraciju \n– Sakrij na zaključanom ekranu i statusnoj traci \n– Prikazuju se u dnu liste obaveštenja \n\n"<b>"0. nivo"</b>" \n– Blokiraj sva obaveštenja iz aplikacije"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Obaveštenja"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Više nećete da dobijate ova obaveštenja."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Više nećete dobijati ova obaveštenja"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"Kategorija obaveštenja: <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Ova aplikacija nema kategorije obaveštenja"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 od <xliff:g id="NUMBER_1">%d</xliff:g> kategorije obaveštenja za ovu aplikaciju</item>
<item quantity="few">1 od <xliff:g id="NUMBER_1">%d</xliff:g> kategorije obaveštenja za ovu aplikaciju</item>
@@ -574,12 +577,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"kontrole obaveštenja"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"opcije za odlaganje obaveštenja"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minuta"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minuta"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 sat"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 sata"</string>
<string name="snooze_undo" msgid="6074877317002985129">"OPOZOVI"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Odloženo je za <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Potrošnja baterije"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Ušteda baterije nije dostupna tokom punjenja"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Ušteda baterije"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings_car.xml b/packages/SystemUI/res/values-b+sr+Latn/strings_car.xml
index f45af7c..ac65171 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings_car.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Vozite bezbedno"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Uvek vodite računa o uslovima vožnje i uvek poštujte primenjive zakone. Uputstva mogu da budu netačna, nepotpuna, opasna, neprikladna ili zabranjena, odnosno da podrazumevaju prelazak između administrativnih oblasti. I podaci o preduzeću mogu da budu netačni ili nepotpuni. Podaci ne nastaju u realnom vremenu i ne možemo da garantujemo preciznost lokacije. Nemojte da upotrebljavate mobilni uređaj niti da koristite aplikacije koje nisu namenjene za Android Auto tokom vožnje."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Nepoznato"</string>
+ <string name="start_driving" msgid="864023351402918991">"Počnite da vozite"</string>
</resources>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 1096141..d48ee4f 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -358,8 +358,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Пошук"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Правядзіце пальцам уверх, каб атрымаць <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Правядзіце пальцам улева, каб атрымаць <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Вас не будуць турбаваць гукі і вібрацыя, за выключэннем будзільнікаў, напамінаў, падзей і выбраных вамі абанентаў. Вы будзеце чуць усё, што ўключыце, у тым ліку музыку, відэа і гульні."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Вас не будуць турбаваць гукі і вібрацыя, за выключэннем будзільнікаў. Вы будзеце чуць усё, што ўключыце, у тым ліку музыку, відэа і гульні."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Вас не будуць турбаваць гукі і вібрацыя, за выключэннем будзільнікаў, мерапрыемстваў, падзей і выбраных вамі абанентаў."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Дапасаваць"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Гэта заблакіруе ЎСЕ гукі і вібрацыі, у тым ліку ад будзільнікаў, музыкі, відэа і гульняў. Вы па-ранейшаму зможаце тэлефанаваць."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Гэта заблакіруе ЎСЕ гукі і вібрацыі, у тым ліку ад будзільнікаў, музыкі, відэа і гульняў."</string>
@@ -473,13 +472,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Вы падключаны да праграмы <xliff:g id="APPLICATION">%1$s</xliff:g>, якая можа сачыць за вашай асабістай сеткавай дзейнасцю, уключаючы электронную пошту, праграмы і вэб-сайты."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Ваш працоўны профіль знаходзіцца пад кіраваннем <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ён падключаны да праграмы <xliff:g id="APPLICATION">%2$s</xliff:g>, якая можа сачыць за вашай працоўнай сеткавай актыўнасцю, уключаючы электронную пошту, праграмы і вэб-сайты.\n\nДля атрымання дадатковай інфармацыі звярніцеся да адміністратара."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Ваш працоўны профіль знаходзіцца пад кіраваннем <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ён падключаны да праграмы <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, якая можа сачыць за вашай працоўнай сеткавай актыўнасцю, уключаючы электронную пошту, праграмы і вэб-сайты.\n\nВы таксама падключаны да праграмы <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, якая можа сачыць за вашай асабістай сеткавай актыўнасцю."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Разблакіравана для карыстальніка <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> працуе"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Прылада будзе заставацца заблакіраванай, пакуль вы не разблакіруеце яе ўручную"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Атрымлівайце апавяшчэнні хутчэй"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Праглядайце іх перад разблакіроўкай"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Не, дзякуй"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Наладзіць"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Адключыць"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Завяршыць зараз"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Разгарнуць"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Згарнуць"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Экран замацаваны"</string>
@@ -554,9 +555,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Выключана"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"З дапамогай пашыранага кіравання апавяшчэннямі вы можаце задаваць узровень важнасці апавяшчэнняў праграмы ад 0 да 5. \n\n"<b>"Узровень 5"</b>" \n- Паказваць уверсе спіса апавяшчэнняў \n- Дазваляць перапыняць рэжым поўнага экрана \n- Заўсёды дазваляць кароткі паказ \n\n"<b>"Узровень 4"</b>" \n- Забараняць перапыняць рэжым поўнага экрана \n- Заўсёды дазваляць кароткі паказ \n\n"<b>"Узровень 3"</b>" \n- Забараняць перапыняць рэжым поўнага экрана \n- Ніколі не дазваляць кароткі паказ \n\n"<b>"Узровень 2"</b>" \n- Забараняць перапыняць рэжым поўнага экрана \n- Ніколі не дазваляць кароткі паказ \n- Ніколі не прайграваць гук і не вібрыраваць \n\n"<b>"Узровень 1"</b>" \n- Забараняць перапыняць рэжым поўнага экрана \n- Ніколі не дазваляць кароткі паказ \n- Ніколі не прайграваць гук і не вібрыраваць \n- Хаваць з экрана блакіроўкі і панэлі стану \n- Паказваць унізе спіса апавяшчэнняў \n\n"<b>"Узровень 0"</b>" \n- Блакіраваць усе апавяшчэнні ад праграмы"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Апавяшчэнні"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Вы больш не будзеце атрымліваць гэтыя апавяшчэнні."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Вы больш не будзеце атрымліваць гэтыя апавяшчэнні"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"Катэгорый апавяшчэнняў: <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"У гэтай праграме няма катэгорый апавяшчэнняў"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 з <xliff:g id="NUMBER_1">%d</xliff:g> катэгорыі апавяшчэнняў у гэтай праграме</item>
<item quantity="few">1 з <xliff:g id="NUMBER_1">%d</xliff:g> катэгорый апавяшчэнняў у гэтай праграме</item>
@@ -580,12 +583,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"кіраванне апавяшчэннямі"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"параметры адкладвання апавяшчэнняў"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 хвілін"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 хвілін"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 гадзіна"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 гадзіны"</string>
<string name="snooze_undo" msgid="6074877317002985129">"АДРАБІЦЬ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Адкладзена на <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Выкарыстанне зараду"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Эканомія зараду акумулятара недаступная падчас зарадкі"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Эканомія зараду"</string>
diff --git a/packages/SystemUI/res/values-be/strings_car.xml b/packages/SystemUI/res/values-be/strings_car.xml
index e3ef7c6..7d53c97 100644
--- a/packages/SystemUI/res/values-be/strings_car.xml
+++ b/packages/SystemUI/res/values-be/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Кіруйце аўтамабілем бяспечна"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Будзьце заўсёды ў курсе дарожных умоў і заўсёды прытрымлівайцеся дзеючага заканадаўства. Указанні могуць быць недакладнымі, няпоўнымі, небяспечнымі, непадыходзячымі, забароненымі ці прадугледжваць уезд на адміністрацыйныя тэрыторыі. Інфармацыя пра кампаніі таксама можа быць недакладнай ці няпоўнай. Даныя не прадстаўляюцца ў рэжыме рэальнага часу, і дакладнасць вызначэння месцазнаходжання не можа быць гарантавана. Не выкарыстоўвайце сваю мабільную прыладу або праграмы, не прызначаныя для Android Auto, падчас кіравання."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Невядомы"</string>
+ <string name="start_driving" msgid="864023351402918991">"Пачаць паездку"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 00a1a17..1095134 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Търсене"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Плъзнете нагоре за <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Плъзнете наляво за <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Няма да бъдете обезпокоявани от звуци и вибрирания освен от будилници, напомняния, събития и обаждания от посочени от вас контакти. Пак ще чувате всичко, което изберете да се пусне, включително музика, видеоклипове и игри."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Няма да бъдете обезпокоявани от звуци и вибрирания освен от будилници. Пак ще чувате всичко, което изберете да се пусне, включително музика, видеоклипове и игри."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Няма да бъдете обезпокоявани от звуци и вибрирания, различни от будилници, напомняния, събития и обаждания от посочени от вас контакти."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Персонализиране"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Този режим блокира ВСИЧКИ звуци и вибрирания, включително от будилници, музика, видеоклипове и игри. Пак ще можете да извършвате телефонни обаждания."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Този режим блокира ВСИЧКИ звуци и вибрирания, включително от будилници, музика, видеоклипове и игри."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Установена е връзка с приложението <xliff:g id="APPLICATION">%1$s</xliff:g>, което може да наблюдава личната ви активност в мрежата, включително имейли, приложения и уебсайтове."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Служебният ви потребителски профил се управлява от <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Той е свързан с приложението <xliff:g id="APPLICATION">%2$s</xliff:g>, което може да наблюдава служебната ви активност в мрежата, включително имейли, приложения и уебсайтове.\n\nЗа още информация се свържете с администратора си."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Служебният ви потребителски профил се управлява от <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Той е свързан с приложението <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, което може да наблюдава служебната ви активност в мрежата, включително имейли, приложения и уебсайтове.\n\nУстановена е връзка и с приложението <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, което може да наблюдава личната ви активност в мрежата."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Отключено за <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> се изпълнява"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Устройството ще остане заключено, докато не го отключите ръчно"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Получавайте известия по-бързо"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Вижте известията, преди да отключите"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Няма нужда"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Настройване"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Изключване сега"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Прекратяване сега"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Разгъване"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Свиване"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Екранът е фиксиран"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Изкл."</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"С помощта на контролите за известията можете да зададете ниво на важност от 0 до 5 за известията от дадено приложение. \n\n"<b>"Ниво 5"</b>" \n– Показване най-горе в списъка с известия. \n– Разрешаване на прекъсването на цял екран. \n– Известията винаги се показват мимолетно. \n\n"<b>"Ниво 4"</b>" \n– Предотвратяване на прекъсването на цял екран. \n– Известията винаги се показват мимолетно. \n\n"<b>"Ниво 3"</b>" \n– Предотвратяване на прекъсването на цял екран. \n– Известията никога не се показват мимолетно. \n\n"<b>"Ниво 2"</b>" \n– Предотвратяване на прекъсването на цял екран. \n– Известията никога не се показват мимолетно. \n– Без издаване на звуков сигнал и вибриране. \n\n"<b>"Ниво 1"</b>" \n– Предотвратяване на прекъсването на цял екран. \n– Известията никога не се показват мимолетно. \n– Без издаване на звуков сигнал и вибриране. \n– Скриване от заключения екран и лентата на състоянието. \n– Показване най-долу в списъка с известия. \n\n"<b>"Ниво 0"</b>" \n– Блокиране на всички известия от приложението."</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Известия"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Вече няма да получавате тези известия."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Вече няма да получавате тези известия"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> категории известия"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"За това приложение няма категории на известията"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 от <xliff:g id="NUMBER_1">%d</xliff:g> категории известия от това приложение</item>
<item quantity="one">1 от <xliff:g id="NUMBER_0">%d</xliff:g> категория известия от това приложение</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> от <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"контроли за известията"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"опции за отлагане на известията"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 минути"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 минути"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 час"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 часа"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ОТМЯНА"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Отложено за <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Ползв. на батерията"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Режимът за запазване на батерията не е налице при зареждане"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Режим за запазване на батерията"</string>
diff --git a/packages/SystemUI/res/values-bg/strings_car.xml b/packages/SystemUI/res/values-bg/strings_car.xml
index bd9fe79..c7f6974 100644
--- a/packages/SystemUI/res/values-bg/strings_car.xml
+++ b/packages/SystemUI/res/values-bg/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Карайте внимателно"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Бъдете осведомени за условията при шофиране и винаги спазвайте приложимите закони. Упътванията може да са неточни, непълни, опасни, неподходящи, забранени или да включват преминаване през административни райони. Бизнес информацията може също да е неточна или непълна. Данните не са в реално време и точността на местоположението не може да се гарантира. Не работете с мобилното си устройство, нито използвайте приложения, които не са предназначени за Android Auto, докато шофирате."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Няма информация"</string>
+ <string name="start_driving" msgid="864023351402918991">"Започнете да шофирате"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 65ea6b1..9ad4011 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"অনুসন্ধান করুন"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> এর জন্য উপরের দিকে স্লাইড করুন৷"</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> এর জন্য বাঁ দিকে স্লাইড করুন৷"</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"অ্যালার্ম, রিমাইন্ডার, ইভেন্ট, এবং আপনার নির্দিষ্ট করে দেওয়া ব্যক্তিদের কল ছাড়া অন্য কোনও আওয়াজ বা ভাইব্রেশন হবে না। তবে সঙ্গীত, ভিডিও, এবং গেমের আওয়াজ শুনতে পাবেন।"</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"অ্যালার্ম ছাড়া অন্য কোনও আওয়াজ বা ভাইব্রেশন হবে না। তবে সঙ্গীত, ভিডিও, এবং গেমের আওয়াজ শুনতে পাবেন।"</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"আপনার নির্দিষ্ট অ্যালার্ম, অনুস্মারক, ইভেন্ট, এবং কলারগুলি ব্যতীত আপনাকে শব্দ এবং কম্পনগুলির দ্বারা বিরক্ত করা হবে না৷"</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"কাস্টমাইজ করুন"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"এটি অ্যালার্ম, সংগীত, ভিডিও এবং গেমগুলি থেকে আসা সমস্ত রকমের ধ্বনি এবং কম্পনগুলিকে বন্ধ করে৷ আপনি এখনও ফোন কলগুলি করতে পারবেন৷"</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"এটি অ্যালার্ম, সংগীত, ভিডিও এবং গেমগুলি থেকে আসা সমস্ত রকমের ধ্বনি এবং কম্পনগুলিকে বন্ধ করে৷"</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"আপনি <xliff:g id="APPLICATION">%1$s</xliff:g> এর সাথে সংযুক্ত হয়েছেন, যা ইমেল, অ্যাপ এবং ওয়েবসাইটগুলি সহ আপনার ব্যক্তিগত নেটওয়ার্কের কার্যকলাপ নিরীক্ষণ করবে৷"</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> আপনার কর্মস্থলের প্রোফাইল পরিচালনা করে। প্রোফাইলটি <xliff:g id="APPLICATION">%2$s</xliff:g> এর সাথে সংযুক্ত, যেটি ইমেল, অ্যাপ, ও ওয়েবসাইট সহ আপনার কর্মস্থলের নেটওয়ার্ক কার্যকলাপের উপরে নজর রাখতে পারে।\n\nআরো তথ্যের জন্য প্রশাসকের সাথে যোগাযোগ করুন।"</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> আপনার কর্মস্থলের প্রোফাইল পরিচালনা করে। প্রোফাইলটি <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> এর সাথে সংযুক্ত, যেটি ইমেল অ্যাপ, ও ওয়েবসাইট সহ আপনার কর্মস্থলের নেটওয়ার্ক কার্যকলাপের উপরে নজর রাখতে পারে।\n\n এ ছাড়াও আপনি <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> এর সাথে সংযুক্ত, যেটি আপনার ব্যক্তিগত নেটওয়ার্কে নজর রাখে।"</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> এর জন্য আনলক করা হয়েছে"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> চালু আছে"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"আপনি নিজে আনলক না করা পর্যন্ত ডিভাইসটি লক হয়ে থাকবে"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"বিজ্ঞপ্তিগুলি আরো দ্রুত পান"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"আপনি আনলক করার আগে ওগুলো দেখুন"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"না থাক"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"সেট আপ"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"এখনই বন্ধ করুন"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"এখন সমাপ্ত করুন"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"প্রসারিত করুন"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"সঙ্কুচিত করুন"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"স্ক্রীন পিন করা হয়েছে"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"বন্ধ আছে"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"পাওয়ার বিজ্ঞপ্তির নিয়ন্ত্রণগুলি ব্যহবার করে, আপনি কোনো অ্যাপ্লিকেশানের বিজ্ঞপ্তির জন্য ০ থেকে ৫ পর্যন্ত একটি গুরুত্বের লেভেলকে সেট করতে পারবেন৷ \n\n"<b>"লেভেল ৫"</b>" \n- বিজ্ঞপ্তি তালিকার শীর্ষে দেখায় \n- পূর্ণ স্ক্রীনের বাধাকে অনুমতি দেয় \n- সর্বদা স্ক্রীনে উপস্থিত হয় \n\n"<b>"লেভেল ৪"</b>" \n- পূর্ণ স্ক্রীনের বাধাকে আটকায় \n- সর্বদা স্ক্রীনে উপস্থিত হয় \n\n"<b>"লেভেল ৩"</b>" \n- পূর্ণ স্ক্রীনের বাধাকে আটকায় \n- কখনই স্ক্রীনে উপস্থিত হয় না \n\n"<b>"লেভেল ২"</b>" \n- পূর্ণ স্ক্রীনের বাধাকে আটকায় \n- কখনই স্ক্রীনে উপস্থিত হয় না \n- কখনই শব্দ এবং কম্পন করে না \n\n"<b>"লেভেল ১"</b>" \n- পূর্ণ স্ক্রীনের বাধাকে আটকায় \n- কখনই স্ক্রীনে উপস্থিত হয় না \n- কখনই শব্দ এবং কম্পন করে না \n- লক স্ক্রীন এবং স্থিতি দন্ড থেকে লুকায় \n- বিজ্ঞপ্তি তালিকার নীচের দিকে দেখায় \n\n"<b>"লেভেল ০"</b>" \n- অ্যাপ্লিকেশান থেকে সমস্ত বিজ্ঞপ্তিকে অবরূদ্ধ করে"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"বিজ্ঞপ্তি"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"আপনি আর এই বিজ্ঞপ্তিগুলি পাবেন না।"</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"আপনি এই বিজ্ঞপ্তিগুলি আর পাবেন না"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> বিজ্ঞপ্তির বিভাগগুলি"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"এই অ্যাপটিতে বিজ্ঞপ্তির বিভাগ নেই"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">এই অ্যাপের <xliff:g id="NUMBER_1">%d</xliff:g>টি বিজ্ঞপ্তির বিভাগের মধ্যে ১টি</item>
<item quantity="other">এই অ্যাপের <xliff:g id="NUMBER_1">%d</xliff:g>টি বিজ্ঞপ্তির বিভাগের মধ্যে ১টি</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"বিজ্ঞপ্তির নিয়ন্ত্রণগুলি"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"বিজ্ঞপ্তি মনে করিয়ে দেওয়ার বিকল্পগুলি"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"১৫ মিনিট"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"৩০ মিনিট"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"১ ঘণ্টা"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"২ ঘণ্টা"</string>
<string name="snooze_undo" msgid="6074877317002985129">"পূর্বাবস্থায় ফিরুন"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> পরে আবার মনে করানো হবে"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"ব্যাটারির ব্যবহার"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"চার্জ করার সময় ব্যাটারি সেভার উপলব্ধ নয়"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"ব্যাটারি সেভার"</string>
diff --git a/packages/SystemUI/res/values-bn/strings_car.xml b/packages/SystemUI/res/values-bn/strings_car.xml
index d8a8732..d014c02 100644
--- a/packages/SystemUI/res/values-bn/strings_car.xml
+++ b/packages/SystemUI/res/values-bn/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"সাবধানে চালান"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"গাড়ি চালানোর সময় সর্বদা সতর্ক থাকুন এবং প্রযোজ্য আইন মেনে চলুন৷ দিকনির্দেশ ভুল, অসম্পূর্ণ, বিপজ্জনক, অনুপযুক্ত, নিষিদ্ধ হতে পারে বা প্রশাসনিক এলাকাগুলি অতিক্রম করতে হতে পারে৷ বাণিজ্যিক তথ্য ভুল বা অসম্পূর্ণ হতে পারে৷ ডেটা প্রকৃত সময়ের নয় এবং অবস্থানের নির্ভুলতা নিশ্চিত করাও সম্ভব নয়৷ গাড়ি চালানোর সময় আপনার মোবাইল ডিভাইসটিকে বা Android Auto এর জন্য উপযুক্ত নয় এমন অ্যাপগুলিকে ব্যবহার করবেন না৷"</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"অজানা"</string>
+ <string name="start_driving" msgid="864023351402918991">"ড্রাইভিং শুরু করুন"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index e420ae3..1d1632f 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Pretraživanje"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Povucite gore za <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Povucite lijevo za <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Neće vas ometati zvukovi i vibracije, osim alarma, podsjetnika, događaja i pozivalaca koje odredite. I dalje ćete čuti sve što ste izabrali za reprodukciju, uključujući muziku, videozapise i igre."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Neće vas ometati zvukovi i vibracije, osim alarma. I dalje ćete čuti sve što ste izabrali za reprodukciju, uključujući muziku, videozapise i igre."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Neće vas ometati zvukovi i vibracije, osim alarma, podsjetnika, događaja i pozivalaca koje odredite."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Prilagodi"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Ovim se blokiraju SVI zvukovi i vibracije, uključujući alarme, muziku, videozapise i igre. I dalje ćete moći obavljati pozive."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Ovim se blokiraju SVI zvukovi i vibracije, uključujući alarme, muziku, video zapise i igre."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Povezani ste na aplikaciju <xliff:g id="APPLICATION">%1$s</xliff:g>, koja može pratiti vaše privatne aktivnosti na mreži, uključujući e-poštu, aplikacije i web stranice."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Vašim radnim profilom upravlja <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profil je povezan s aplikacijom <xliff:g id="APPLICATION">%2$s</xliff:g>, koja može pratiti vašu aktivnost na radnoj mreži, uključujući e-poruke, aplikacije i web lokacije.\n\nZa više informacija, obratite se svom administratoru."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Radnim profilom upravlja <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profil je povezan s aplikacijom <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, koja može pratiti vašu aktivnost na radnoj mreži, uključujući e-poruke, aplikacije i web lokacije.\n\nPovezani ste i sa aplikacijom <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, koja može pratiti vašu aktivnost na privatnoj mreži."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Otključano za korisnika <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"Agent <xliff:g id="TRUST_AGENT">%1$s</xliff:g> je pokrenut"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Uređaj će ostati zaključan dok ga ručno ne otključate"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Brže primaj obavještenja"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Vidi ih prije otključavanja"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Ne, hvala"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Postavi"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Isključi sada"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Završi sada"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Proširi"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Skupi"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekran je prikačen"</string>
@@ -552,9 +553,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Isključeno"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Uz kontrolu obavještenja o napajanju, možete postaviti nivo značaja obavještenja iz aplikacije, i to od nivoa 0 do 5. \n\n"<b>"Nivo 5"</b>" \n- Prikaži na vrhu liste obavještenja \n- Dopusti prekid prikaza cijelog ekrana \n- Uvijek izviruj \n\n"<b>"Nvio 4"</b>" \n- Spriječi prekid prikaza cijelog ekrana \n- Uvijek izviruj \n\n"<b>"Nivo 3"</b>" \n- Spriječi prekid prikaza cijelog ekrana \n- Nikad ne izviruj \n\n"<b>"Nivo 2"</b>" \n- Spriječi prekid prikaza cijelog ekrana \n- Nikad ne izviruj \n- Nikada ne puštaj zvuk ili vibraciju \n\n"<b>"Nivo 1"</b>" \n- Spriječi prekid prikaza cijelog ekrana \n- Nikada ne izviruj \n- Nikada ne puštaj zvuk ili vibraciju \n- Sakrij sa ekrana za zaključavanje i statusne trake \n- Prikaži na dnu liste obavještenja \n\n"<b>"Nivo 0"</b>" \n- Blokiraj sva obavještenja iz aplikacije"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Obavještenja"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Nećete više primati ova obavještenja."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Nećete više primati ova obavještenja"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"Kategorije obavještenja: <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Ova aplikacija nema kategorije obavještenja"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 od <xliff:g id="NUMBER_1">%d</xliff:g> kategorije obavještenja iz ove aplikacije</item>
<item quantity="few">1 od <xliff:g id="NUMBER_1">%d</xliff:g> kategorije obavještenja iz ove aplikacije</item>
@@ -576,12 +579,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"kontrole obavještenja"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"opcije za odgodu obavještenja"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minuta"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minuta"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 sat"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 sata"</string>
<string name="snooze_undo" msgid="6074877317002985129">"OPOZOVI"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Odgođeno za <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Potrošnja baterije"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Ušteda baterije je isključena prilikom punjenja"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Ušteda baterije"</string>
diff --git a/packages/SystemUI/res/values-bs/strings_car.xml b/packages/SystemUI/res/values-bs/strings_car.xml
index 744eea8..d38620b 100644
--- a/packages/SystemUI/res/values-bs/strings_car.xml
+++ b/packages/SystemUI/res/values-bs/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Vozite sigurno"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Uvijek budite upoznati s uslovima za vožnju i poštujte važeće zakone. Upute za kretanje mogu biti netačne, nepotpune, opasne, neprikladne, zabranjene ili takve da obuhvataju prelaženje preko administrativnih područja. Poslovne informacije takođe mogu biti netačne ili nepotpune. Podaci nisu u realnom vremenu, a tačnost lokacije se ne može garantirati. U vožnji nemojte rukovati mobilnim uređajem ili koristiti aplikacije koje nisu namijenjene za Android Auto."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Nepoznato"</string>
+ <string name="start_driving" msgid="864023351402918991">"Početak vožnje"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index d8292ec..f354ad4 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Cerca"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Llisca cap amunt per <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Llisca cap a l\'esquerra per <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"No t\'interromprà cap so ni cap vibració, tret dels de les alarmes, recordatoris, esdeveniments i trucades de les persones que especifiquis. Continuaràs sentint tot allò que decideixis reproduir, com ara música, vídeos i jocs."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"No t\'interromprà cap so ni cap vibració, tret dels de les alarmes. Continuaràs sentint tot allò que decideixis reproduir, com ara música, vídeos i jocs."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"No t\'interromprà cap so ni cap vibració, tret dels sons de les alarmes, recordatoris, esdeveniments i trucades de les persones que especifiquis."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Personalitza"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Es bloquejaran TOTS els sons i totes les vibracions, inclosos els de vídeos, jocs, alarmes i música. Encara podràs fer trucades."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Es bloquejaran TOTS els sons i totes les vibracions, inclosos els de vídeos, jocs, alarmes i música."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Estàs connectat a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pot supervisar la teva activitat personal a la xarxa, com ara els correus electrònics, les aplicacions i els llocs web."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> gestiona el teu perfil professional. El perfil està connectat a <xliff:g id="APPLICATION">%2$s</xliff:g>, que pot supervisar la teva activitat a la xarxa de treball, com ara els correus electrònics, les aplicacions i els llocs web.\n\nPer obtenir més informació, contacta amb l\'administrador."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> gestiona el teu perfil professional. El perfil està connectat a <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, que pot supervisar la teva activitat a la xarxa de treball, com ara els correus electrònics, les aplicacions i els llocs web.\n\nTambé estàs connectat a <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, que pot supervisar la teva activitat personal a la xarxa."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Desbloquejat per a <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> s\'està executant"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"El dispositiu continuarà bloquejat fins que no el desbloquegis manualment."</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Rep notificacions més ràpidament"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Mostra-les abans de desbloquejar"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"No"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Configura"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Desactiva ara"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Finalitza ara"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Amplia"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Replega"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"La pantalla està fixada"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Desactivat"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Amb els controls de notificació millorats, pots establir un nivell d\'importància d\'entre 0 i 5 per a les notificacions d\'una aplicació. \n\n"<b>"Nivell 5"</b>" \n- Mostra les notificacions a la part superior de la llista \n- Permet la interrupció de la pantalla completa \n- Permet sempre la previsualització \n\n"<b>"Nivell 4"</b>" \n- No permet la interrupció de la pantalla completa \n- Permet sempre la previsualització \n\n"<b>"Nivell 3"</b>" \n- No permet la interrupció de la pantalla completa \n- No permet mai la previsualització \n\n"<b>"Nivell 2"</b>" \n- No permet la interrupció de la pantalla completa \n- No permet mai la previsualització \n- Les notificacions no poden emetre sons ni vibracions \n\n"<b>"Nivell 1"</b>" \n- No permet la interrupció de la pantalla completa \n- No permet mai la previsualització \n- No activa mai el so ni la vibració \n- Amaga les notificacions de la pantalla de bloqueig i de la barra d\'estat \n- Mostra les notificacions a la part inferior de la llista \n\n"<b>"Nivell 0"</b>" \n- Bloqueja totes les notificacions de l\'aplicació"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Notificacions"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Ja no rebràs aquestes notificacions."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Ja no rebràs aquestes notificacions"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> categories de notificació"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Aquesta aplicació no té categories de notificació"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 de <xliff:g id="NUMBER_1">%d</xliff:g> categories de notificació d\'aquesta aplicació</item>
<item quantity="one">1 de <xliff:g id="NUMBER_0">%d</xliff:g> categoria de notificació d\'aquesta aplicació</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"controls de notificació"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"opcions per posposar la notificació"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minuts"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minuts"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 hora"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 hores"</string>
<string name="snooze_undo" msgid="6074877317002985129">"DESFÉS"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"S\'ha posposat <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Ús de la bateria"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"La funció Estalvi de bateria no està disponible durant la càrrega"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Estalvi de bateria"</string>
diff --git a/packages/SystemUI/res/values-ca/strings_car.xml b/packages/SystemUI/res/values-ca/strings_car.xml
index efcac23..32a019e 100644
--- a/packages/SystemUI/res/values-ca/strings_car.xml
+++ b/packages/SystemUI/res/values-ca/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Condueix amb precaució"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Estigues al cas de la conducció i respecta sempre les lleis aplicables. Les indicacions poden ser inexactes, perilloses, inadequades o incompletes, o bé poden comportar maniobres prohibides o que creuis circumscripcions territorials. La informació de les empreses també pot ser inexacta o incompleta. No s\'ofereixen dades en temps real i no es pot garantir la precisió de les ubicacions. Mentre condueixes, no utilitzis el dispositiu mòbil ni cap aplicació que no estigui destinada a Android Auto."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Desconegut"</string>
+ <string name="start_driving" msgid="864023351402918991">"Comença a conduir"</string>
</resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 84ee9ee..cb8fa86 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -358,8 +358,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Vyhledávání"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Přejeďte prstem nahoru: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>"</string>
<string name="description_direction_left" msgid="7207478719805562165">"Přejeďte prstem doleva: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>"</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Nebudou vás rušit zvuky ani vibrace s výjimkou budíků, upozornění, událostí a vybraných volajících. Nadále uslyšíte veškerý obsah, který si sami pustíte (např. hudba, videa nebo hry)."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Nebudou vás rušit zvuky ani vibrace s výjimkou budíků. Nadále uslyšíte veškerý obsah, který si sami pustíte (např. hudba, videa nebo hry)."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Nebudou vás rušit zvuky ani vibrace s výjimkou budíků, upozornění, událostí a volajících, které zadáte."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Přizpůsobit"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"V tomto režimu budou blokovány VŠECHNY zvuky a vibrace, včetně těch z budíků, hudby, videí a her. Telefonovat budete moci i nadále."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"V tomto režimu budou blokovány VŠECHNY zvuky a vibrace, včetně těch z budíků, hudby, videí a her."</string>
@@ -473,13 +472,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Jste připojeni k aplikaci <xliff:g id="APPLICATION">%1$s</xliff:g>, která může sledovat vaši osobní aktivitu v síti, včetně e-mailů, aplikací a webů."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Váš pracovní profil spravuje organizace <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Je připojen k aplikaci <xliff:g id="APPLICATION">%2$s</xliff:g>, která může sledovat vaši aktivitu v síti, včetně e-mailů, aplikací a webů.\n\nDalší informace vám poskytne administrátor."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Váš pracovní profil spravuje organizace <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Je připojen k aplikaci <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, která může sledovat vaši aktivitu v síti, včetně e-mailů, aplikací a webů.\n\nTaké jste připojeni k aplikaci <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, která může sledovat vaši osobní aktivitu v síti."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Odemknuto pro uživatele <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> běží"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Zařízení zůstane uzamčeno, dokud je ručně neodemknete"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Čtěte si oznámení rychleji"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Můžete si je přečíst před odemčením obrazovky."</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Ne, děkuji"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Nastavit"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Vypnout"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Ukončit"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Rozbalit"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Sbalit"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Obrazovka je připnuta"</string>
@@ -554,9 +555,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Vypnuto"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Rozšířené ovládací prvky oznámení umožňují nastavit úroveň důležitosti oznámení aplikace od 0 do 5. \n\n"<b>"Úroveň 5"</b>" \n– Zobrazit na začátku seznamu oznámení \n– Povolit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 4"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 3"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n\n"<b>"Úroveň 2"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat žádný zvukový signál ani nevibrovat \n\n"<b>"Úroveň 1"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat zvukový signál ani nevibrovat \n– Skrýt z obrazovky uzamčení a stavového řádku \n– Zobrazovat na konci seznamu oznámení \n\n"<b>";Úroveň 0"</b>" \n– Blokovat všechna oznámení z aplikace"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Oznámení"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Tato oznámení již nebudete dostávat."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Tato oznámení již nebudete dostávat"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"Kategorie oznámení: <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Tato aplikace nemá kategorie oznámení"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="few">1 ze <xliff:g id="NUMBER_1">%d</xliff:g> kategorií oznámení z této aplikace</item>
<item quantity="many">1 z <xliff:g id="NUMBER_1">%d</xliff:g> kategorie oznámení z této aplikace</item>
@@ -580,12 +583,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> aplikace <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"Nastavení oznámení"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"Možnosti odložení oznámení"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minut"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minut"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 hodina"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 hodiny"</string>
<string name="snooze_undo" msgid="6074877317002985129">"VRÁTIT ZPĚT"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Odloženo o <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Využití baterie"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Spořič baterie při nabíjení není k dispozici."</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Spořič baterie"</string>
diff --git a/packages/SystemUI/res/values-cs/strings_car.xml b/packages/SystemUI/res/values-cs/strings_car.xml
index d5e3324..b8a0d3e 100644
--- a/packages/SystemUI/res/values-cs/strings_car.xml
+++ b/packages/SystemUI/res/values-cs/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Řiďte bezpečně"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Věnujte plnou pozornost řízení a dodržujte platné předpisy. Trasy mohou být nepřesné, neúplné, nebezpečné, nevhodné, zakázané nebo mohou zahrnovat překročení hranic. Informace o firmách mohou být také nepřesné nebo neúplné. Data se neaktualizují v reálném čase a přesnost polohy nelze zaručit. Mobilní zařízení ani aplikace určené pro Android Auto nepoužívejte za jízdy."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Neznámé"</string>
+ <string name="start_driving" msgid="864023351402918991">"Zahájit jízdu"</string>
</resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 3fb7acf..b19e901 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Søgning"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Glid op for at <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Glid til venstre for at <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Du bliver ikke forstyrret af lyde eller vibrationer, undtagen fra alarmer, påmindelser, begivenheder og opkald fra personer, du angiver. Du kan stadig høre alt, du vælger at afspille, bl.a. musik samt lyd i videoer og spil."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Du bliver ikke forstyrret af lyde eller vibrationer, undtagen fra alarmer. Du kan stadig høre alt, du vælger at afspille, bl.a. musik samt lyd i videoer og spil."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Du bliver ikke forstyrret af lyde og vibrationer undtagen fra de alarmer, påmindelser, begivenheder og opkaldere, som du angiver."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Tilpas"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Dette blokerer ALLE lyde og vibrationer, bl.a. fra alarmer, musik, videoer og spil. Du vil stadig kunne foretage telefonopkald."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Dette blokerer ALLE lyde og vibrationer, bl.a. fra alarmer, musik, videoer og spil."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Du har forbindelse til <xliff:g id="APPLICATION">%1$s</xliff:g>, som kan overvåge din private netværksaktivitet, bl.a. e-mails, apps og websites."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Din arbejdsprofil administreres af <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profilen har forbindelse til <xliff:g id="APPLICATION">%2$s</xliff:g>, som kan overvåge din netværksaktivitet, bl.a. mails, apps og websites.\n\nKontakt din administrator for at få flere oplysninger."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Din arbejdsprofil administreres af <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profilen har forbindelse til <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, som kan overvåge din netværksaktivitet, bl.a. mails, apps og websites.\n\nDu har også forbindelse til <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, som kan overvåge din personlige netværksaktivitet."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Låst op for <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> kører"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Enheden vil forblive låst, indtil du manuelt låser den op"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Modtag underretninger hurtigere"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Se dem, før du låser op"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Nej tak"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Konfigurer"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Deaktiver nu"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Afslut nu"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Udvid"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Skjul"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Skærmen er fastgjort"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Fra"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Med kontrolelementer til underretninger om strøm kan du konfigurere et vigtighedsniveau fra 0 til 5 for en apps underretninger. \n\n"<b>"Niveau 5"</b>\n"- Vis øverst på listen over underretninger \n- Tillad afbrydelse af fuld skærm \n- Se altid smugkig \n\n"<b>"Niveau 4"</b>\n"- Ingen afbrydelse af fuld skærm \n- Se altid smugkig \n\n"<b>"Niveau 3"</b>\n"- Ingen afbrydelse af fuld skærm \n- Se aldrig smugkig \n\n"<b>"Niveau 2"</b>\n"- Ingen afbrydelse af fuld skærm \n Se aldrig smugkig \n- Ingen lyd og vibration \n\n"<b>"Niveau 1"</b>\n"- Ingen afbrydelse af fuld skærm \n- Se aldrig smugkig \n- Ingen lyd eller vibration \n- Skjul fra låseskærm og statusbjælke \n- Vis nederst på listen over underretninger \n\n"<b>"Niveau 0"</b>\n"- Bloker alle underretninger fra appen."</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Underretninger"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Du modtager ikke længere disse underretninger."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Du modtager ikke længere disse underretninger"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> underretningskategorier"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Denne app har ingen underretningskategorier"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 ud af <xliff:g id="NUMBER_1">%d</xliff:g> underretningskategori fra denne app</item>
<item quantity="other">1 ud af <xliff:g id="NUMBER_1">%d</xliff:g> underretningskategorier fra denne app</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"kontrolelementer til underretninger"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"Indstillinger for udsættelse"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minutter"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minutter"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 time"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 timer"</string>
<string name="snooze_undo" msgid="6074877317002985129">"FORTRYD"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Udsat i <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Batteriforbrug"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Batterisparefunktionen er ikke tilgængelig under opladning"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Batterisparefunktion"</string>
diff --git a/packages/SystemUI/res/values-da/strings_car.xml b/packages/SystemUI/res/values-da/strings_car.xml
index 6a421da..26843ac 100644
--- a/packages/SystemUI/res/values-da/strings_car.xml
+++ b/packages/SystemUI/res/values-da/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Kør forsigtigt"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Vær opmærksom på køreforholdene, og overhold altid færdselsloven. Rutevejledningen kan være unøjagtig, ufuldstændig, farlig, upassende, forbudt eller involvere krydsning af forbudte områder. Virksomhedsoplysninger kan også være unøjagtige eller ufuldstændige. Data er ikke i realtid, og placeringers nøjagtighed kan ikke garanteres. Håndter ikke din mobilenhed, og brug ikke apps, der ikke er beregnet til Android Auto, mens du kører."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Ukendt"</string>
+ <string name="start_driving" msgid="864023351402918991">"Kør"</string>
</resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 0697ead..f6216c2 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Suche"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Zum <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> nach oben schieben"</string>
<string name="description_direction_left" msgid="7207478719805562165">"Zum <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> nach links schieben"</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Klingeltöne und die Vibration werden deaktiviert, außer für Weckrufe, Erinnerungen, Termine sowie Anrufe von zuvor von dir festgelegten Personen. Du hörst jedoch weiterhin Sound, wenn du Musik und Videos wiedergibst oder Spiele spielst."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Klingeltöne und die Vibration werden deaktiviert, außer für Weckrufe. Du hörst jedoch weiterhin Sound, wenn du Musik und Videos wiedergibst oder Spiele spielst."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Klingeltöne und die Vibration werden deaktiviert, außer für Weckrufe, Erinnerungen, Termine sowie Anrufe von zuvor von dir festgelegten Personen."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Anpassen"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Hierdurch werden alle Klingeltöne und Vibrationsalarme stummgeschaltet, auch für Weckrufe, Musik, Videos und Spiele. Anrufe kannst du jedoch weiterhin tätigen."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Hierdurch werden alle Klingeltöne und Vibrationsalarme stummgeschaltet, auch für Weckrufe, Musik, Videos und Spiele."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Du bist mit der App \"<xliff:g id="APPLICATION">%1$s</xliff:g>\" verbunden. Diese kann deine persönlichen Netzwerkaktivitäten erfassen, einschließlich E-Mails, Apps und Websites."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Dein Arbeitsprofil wird von <xliff:g id="ORGANIZATION">%1$s</xliff:g> verwaltet. Das Profil ist mit <xliff:g id="APPLICATION">%2$s</xliff:g> verknüpft. Diese App kann deine Netzwerkaktivitäten überwachen, einschließlich E-Mails, Apps und Websites.\n\nWeitere Informationen erhältst du von deinem Administrator."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Dein Arbeitsprofil wird von <xliff:g id="ORGANIZATION">%1$s</xliff:g> verwaltet. Das Profil ist mit <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> verknüpft. Diese App kann deine Netzwerkaktivitäten überwachen, einschließlich E-Mails, Apps und Websites.\n\nDu bist auch mit <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> verbunden, die deine persönlichen Netzwerkaktivitäten überwachen kann."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Entsperrt für <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> wird ausgeführt"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Das Gerät bleibt gesperrt, bis du es manuell entsperrst."</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Benachrichtigungen schneller erhalten"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Vor dem Entsperren anzeigen"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Nein danke"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Einrichten"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Jetzt deaktivieren"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Jetzt beenden"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Maximieren"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Minimieren"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Bildschirm ist fixiert"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Aus"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Mit den erweiterten Benachrichtigungseinstellungen kannst du für App-Benachrichtigungen eine Wichtigkeitsstufe von 0 bis 5 festlegen. \n\n"<b>"Stufe 5"</b>" \n- Auf der Benachrichtigungsleiste ganz oben anzeigen \n- Vollbildunterbrechung zulassen \n- Immer kurz einblenden \n\n"<b>"Stufe 4"</b>" \n- Keine Vollbildunterbrechung \n- Immer kurz einblenden \n\n"<b>"Stufe 3"</b>" \n- Keine Vollbildunterbrechung \n- Nie kurz einblenden \n\n"<b>"Stufe 2"</b>" \n- Keine Vollbildunterbrechung \n- Nie kurz einblenden \n- Weder Ton noch Vibration \n\n"<b>"Stufe 1"</b>" \n- Keine Vollbildunterbrechung \n- Nie kurz einblenden \n- Weder Ton noch Vibration \n- Auf Sperrbildschirm und Statusleiste verbergen \n- Auf der Benachrichtigungsleiste ganz unten anzeigen \n\n"<b>"Stufe 0"</b>" \n- Alle Benachrichtigungen der App sperren"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Benachrichtigungen"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Du erhältst diese Benachrichtigungen nicht mehr."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Du erhältst diese Benachrichtigungen nicht mehr"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> Benachrichtigungskategorien"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Diese App hat keine Benachrichtigungskategorien"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 von <xliff:g id="NUMBER_1">%d</xliff:g> Benachrichtigungskategorien von dieser App</item>
<item quantity="one">1 von <xliff:g id="NUMBER_0">%d</xliff:g> Benachrichtigungskategorie von dieser App</item>
@@ -572,12 +575,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> – <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"Benachrichtigungseinstellungen"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"Optionen für spätere Erinnerung bei Benachrichtigungen"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 Minuten"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 Minuten"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 Stunde"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 Stunden"</string>
<string name="snooze_undo" msgid="6074877317002985129">"RÜCKGÄNGIG"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Erinnerung in <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Akkunutzung"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Der Energiesparmodus ist beim Aufladen nicht verfügbar."</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Energiesparmodus"</string>
diff --git a/packages/SystemUI/res/values-de/strings_car.xml b/packages/SystemUI/res/values-de/strings_car.xml
index 5b5920e..76ff268 100644
--- a/packages/SystemUI/res/values-de/strings_car.xml
+++ b/packages/SystemUI/res/values-de/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Sicher fahren"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Achte stets auf die Straßenverhältnisse und halte dich an die geltenden Gesetze. Routen können unter Umständen fehlerhaft, unvollständig, gefährlich, ungeeignet oder verboten sein oder das Überqueren von Verwaltungsgrenzen erfordern. Informationen zum Unternehmen können ebenfalls fehlerhaft oder unvollständig sein. Die Datenübertragung erfolgt nicht in Echtzeit und die Genauigkeit der Standortangaben kann nicht gewährleistet werden. Bediene während der Fahrt nicht dein Mobilgerät und verwende keine Apps, die du nicht über Android Auto steuern kannst."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Unbekannt"</string>
+ <string name="start_driving" msgid="864023351402918991">"Losfahren"</string>
</resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 094fd84..8fd5e35 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Αναζήτηση"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Κύλιση προς τα επάνω για <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Κύλιση προς τα αριστερά για <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Δεν θα ενοχλείστε από ήχους και δονήσεις, παρά μόνο από ξυπνητήρια, υπενθυμίσεις, συμβάντα και καλούντες που έχετε καθορίσει. Θα εξακολουθείτε να ακούτε όλο το περιεχόμενο που επιλέγετε να αναπαραγάγετε, συμπεριλαμβανομένων μουσικής, βίντεο και παιχνιδιών."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Δεν θα ενοχλείστε από ήχους και δονήσεις, παρά μόνο από ξυπνητήρια. Θα εξακολουθείτε να ακούτε όλο το περιεχόμενο που επιλέγετε να αναπαραγάγετε, συμπεριλαμβανομένων μουσικής, βίντεο και παιχνιδιών."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Δεν θα διακόπτεστε από ήχους και δονήσεις, με εξαίρεση τα ξυπνητήρια, τις υπενθυμίσεις, τα συμβάντα και τους καλούντες που έχετε ορίσει."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Προσαρμογή"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Αυτή η επιλογή αποκλείει όλους τους ήχους και τις δονήσεις, μεταξύ των οποίων των ξυπνητηριών, της μουσικής, των βίντεο και των παιχνιδιών. Θα εξακολουθείτε να είστε σε θέση να πραγματοποιήσετε τηλεφωνικές κλήσεις."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Αυτή η επιλογή αποκλείει όλους τους ήχους και τις δονήσεις, μεταξύ των οποίων των ξυπνητηριών, της μουσικής, των βίντεο και των παιχνιδιών."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Έχετε συνδεθεί στην εφαρμογή <xliff:g id="APPLICATION">%1$s</xliff:g>, η οποία μπορεί να παρακολουθεί τη δραστηριότητα του προσωπικού σας δικτύου, συμπεριλαμβανομένων μηνυμάτων ηλεκτρονικού ταχυδρομείου, εφαρμογών και ιστοτόπων."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Ο οργανισμός <xliff:g id="ORGANIZATION">%1$s</xliff:g> διαχειρίζεται το προφίλ εργασίας σας. Το προφίλ είναι συνδεδεμένο στην εφαρμογή <xliff:g id="APPLICATION">%2$s</xliff:g>, η οποία μπορεί να παρακολουθήσει τη δραστηριότητα του δικτύου εργασίας σας, συμπεριλαμβανομένων μηνυμάτων ηλεκτρονικού ταχυδρομείου, εφαρμογών και ιστοτόπων.\n\nΓια περισσότερες πληροφορίες, επικοινωνήστε με τον διαχειριστή σας."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Ο οργανισμός <xliff:g id="ORGANIZATION">%1$s</xliff:g> διαχειρίζεται το προφίλ εργασίας σας. Το προφίλ συνδέεται με την εφαρμογή <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, η οποία μπορεί να παρακολουθεί τη δραστηριότητα του δικτύου της εργασίας σας, συμπεριλαμβανομένων μηνυμάτων ηλεκτρονικού ταχυδρομείου, εφαρμογών και ιστοτόπων.\n\nΕπίσης, είστε συνδεδεμένοι στην εφαρμογή <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, που έχει τη δυνατότητα παρακολούθησης της δραστηριότητας του προσωπικού σας δικτύου."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Ξεκλειδώθηκε για τον χρήστη <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"Εκτελείται ο παράγοντας εμπιστοσύνης <xliff:g id="TRUST_AGENT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Η συσκευή θα παραμείνει κλειδωμένη μέχρι να την ξεκλειδώσετε μη αυτόματα"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Λάβετε ειδοποιήσεις γρηγορότερα"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Εμφάνιση πριν το ξεκλείδωμα"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Όχι"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Ρύθμιση"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Απενεργοποίηση τώρα"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Τερματισμός τώρα"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Ανάπτυξη"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Σύμπτυξη"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Η οθόνη καρφιτσώθηκε"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Ανενεργή"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Με τα στοιχεία ελέγχου ειδοποίησης ισχύος, μπορείτε να ορίσετε ένα επίπεδο βαρύτητας από 0 έως 5 για τις ειδοποιήσεις μιας εφαρμογής. \n\n"<b>"Επίπεδο 5"</b>" \n- Εμφάνιση στην κορυφή της λίστας ειδοποιήσεων \n- Να επιτρέπεται η διακοπή πλήρους οθόνης \n- Να γίνεται πάντα σύντομη προβολή \n\n"<b>"Επίπεδο 4"</b>" \n- Αποτροπή διακοπής πλήρους οθόνης \n- Να γίνεται πάντα σύντομη προβολή \n\n"<b>"Επίπεδο 3"</b>" \n- Αποτροπή διακοπής πλήρους οθόνης \n- Να μην γίνεται ποτέ σύντομη προβολή \n\n"<b>"Επίπεδο 2"</b>" \n- Αποτροπή διακοπής πλήρους οθόνης \n- Να μην γίνεται ποτέ σύντομη προβολή \n- Να μην χρησιμοποιείται ποτέ ήχος και δόνηση \n\n"<b>"Επίπεδο 1"</b>" \n- Αποτροπή διακοπής πλήρους οθόνης \n- Να μην γίνεται ποτέ σύντομη προβολή \n- Να μην χρησιμοποιείται ποτέ ήχος και δόνηση \n- Απόκρυψη από την οθόνη κλειδώματος και τη γραμμή κατάστασης \n- Εμφάνιση στο κάτω μέρος της λίστας ειδοποιήσεων \n\n"<b>"Επίπεδο 0"</b>" \n- Αποκλεισμός όλων των ειδοποιήσεων από την εφαρμογή"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Ειδοποιήσεις"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Δεν θα λαμβάνεται πλέον αυτές τις ειδοποιήσεις."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Δεν θα λαμβάνετε πλέον αυτές τις ειδοποιήσεις"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> κατηγορίες ειδοποιήσεων"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Αυτή η εφαρμογή δεν διαθέτει κατηγορίες ειδοποιήσεων"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 από <xliff:g id="NUMBER_1">%d</xliff:g> κατηγορίες ειδοποιήσεων από αυτή την εφαρμογή</item>
<item quantity="one">1 από <xliff:g id="NUMBER_0">%d</xliff:g> κατηγορία ειδοποιήσεων από αυτή την εφαρμογή</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"στοιχεία ελέγχου ειδοποιήσεων"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"επιλογές αφύπνισης ειδοποιήσεων"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 λεπτά"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 λεπτά"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 ώρα"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 ώρες"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ΑΝΑΙΡΕΣΗ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Σε αφύπνιση για <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Χρήση της μπαταρίας"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Η εξοικονόμηση μπαταρίας δεν είναι διαθέσιμη κατά τη διάρκεια της φόρτισης"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Εξοικονόμηση μπαταρίας"</string>
diff --git a/packages/SystemUI/res/values-el/strings_car.xml b/packages/SystemUI/res/values-el/strings_car.xml
index e960713..09a2a39 100644
--- a/packages/SystemUI/res/values-el/strings_car.xml
+++ b/packages/SystemUI/res/values-el/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Οδηγείτε προσεκτικά"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Μείνετε πλήρως ενημερωμένοι για τις προϋποθέσεις οδήγησης και υπακούτε πάντα τους ισχύοντες νόμους. Οι οδηγίες μπορεί να είναι ανακριβείς, ελλιπείς, επικίνδυνες, ακατάλληλες, απαγορευμένες ή να ανήκουν σε άλλες διοικητικές περιοχές. Οι πληροφορίες επιχειρήσεων μπορεί επίσης να είναι ανακριβείς ή ελλιπείς. Τα δεδομένα δεν είναι σε πραγματικό χρόνο και η ακρίβεια των τοποθεσιών δεν είναι εγγυημένη. Μη χειρίζεστε την κινητή συσκευή σας και μη χρησιμοποιείτε εφαρμογές που δεν προορίζονται για το Android Auto ενώ οδηγείτε."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Άγνωστο"</string>
+ <string name="start_driving" msgid="864023351402918991">"Έναρξη οδήγησης"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 35263c2..93753bb 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Search"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Slide up for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Slide left for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"You won’t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers that you specify. You\'ll still hear anything that you choose to play, including music, videos and games."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"You won’t be disturbed by sounds and vibrations, except from alarms. You\'ll still hear anything that you choose to play, including music, videos and games."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"You won’t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers that you specify."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Customise"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"This blocks ALL sounds and vibrations, including from alarms, music, videos and games. You’ll still be able to make phone calls."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"This blocks ALL sounds and vibrations, including from alarms, music, videos and games."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"You\'re connected to <xliff:g id="APPLICATION">%1$s</xliff:g>, which can monitor your personal network activity, including emails, apps and websites."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Your work profile is managed by <xliff:g id="ORGANIZATION">%1$s</xliff:g>. The profile is connected to <xliff:g id="APPLICATION">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps and websites.\n\nFor more information, contact your admin."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Your work profile is managed by <xliff:g id="ORGANIZATION">%1$s</xliff:g>. The profile is connected to <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps and websites.\n\nYou\'re also connected to <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, which can monitor your personal network activity."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Unlocked for <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> is running"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Device will stay locked until you manually unlock"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Get notifications faster"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"See them before you unlock"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"No, thanks"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Setup"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Turn off now"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"End now"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Expand"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Collapse"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Screen is pinned"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Off"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications. \n\n"<b>"Level 5"</b>" \n- Show at the top of the notification list \n- Allow full screen interruption \n- Always peek \n\n"<b>"Level 4"</b>" \n- Prevent full screen interruption \n- Always peek \n\n"<b>"Level 3"</b>" \n- Prevent full screen interruption \n- Never peek \n\n"<b>"Level 2"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound and vibration \n\n"<b>"Level 1"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound or vibrate \n- Hide from lock screen and status bar \n- Show at the bottom of the notification list \n\n"<b>"Level 0"</b>" \n- Block all notifications from the app"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Notifications"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"You won\'t get these notifications anymore."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"You won\'t get these notifications anymore"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> notification categories"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"This app doesn\'t have notification categories"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 out of <xliff:g id="NUMBER_1">%d</xliff:g> notification categories from this app</item>
<item quantity="one">1 out of <xliff:g id="NUMBER_0">%d</xliff:g> notification category from this app</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"notification controls"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"notification snooze options"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minutes"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minutes"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 hour"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 hours"</string>
<string name="snooze_undo" msgid="6074877317002985129">"UNDO"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Snoozed for <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Battery usage"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Battery Saver not available during charging"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Battery Saver"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings_car.xml b/packages/SystemUI/res/values-en-rAU/strings_car.xml
index 81089ba..27b916e 100644
--- a/packages/SystemUI/res/values-en-rAU/strings_car.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Drive safely"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Stay fully aware of driving conditions and always obey applicable laws. Directions may be inaccurate, incomplete, dangerous, not suitable, prohibited or involve crossing administrative areas. Business information may also be inaccurate or incomplete. Data is not real time and location accuracy cannot be guaranteed. Do not handle your mobile device or use apps not intended for Android Auto while driving."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Unknown"</string>
+ <string name="start_driving" msgid="864023351402918991">"Start Driving"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 35263c2..93753bb 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Search"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Slide up for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Slide left for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"You won’t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers that you specify. You\'ll still hear anything that you choose to play, including music, videos and games."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"You won’t be disturbed by sounds and vibrations, except from alarms. You\'ll still hear anything that you choose to play, including music, videos and games."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"You won’t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers that you specify."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Customise"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"This blocks ALL sounds and vibrations, including from alarms, music, videos and games. You’ll still be able to make phone calls."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"This blocks ALL sounds and vibrations, including from alarms, music, videos and games."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"You\'re connected to <xliff:g id="APPLICATION">%1$s</xliff:g>, which can monitor your personal network activity, including emails, apps and websites."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Your work profile is managed by <xliff:g id="ORGANIZATION">%1$s</xliff:g>. The profile is connected to <xliff:g id="APPLICATION">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps and websites.\n\nFor more information, contact your admin."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Your work profile is managed by <xliff:g id="ORGANIZATION">%1$s</xliff:g>. The profile is connected to <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps and websites.\n\nYou\'re also connected to <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, which can monitor your personal network activity."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Unlocked for <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> is running"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Device will stay locked until you manually unlock"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Get notifications faster"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"See them before you unlock"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"No, thanks"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Setup"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Turn off now"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"End now"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Expand"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Collapse"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Screen is pinned"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Off"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications. \n\n"<b>"Level 5"</b>" \n- Show at the top of the notification list \n- Allow full screen interruption \n- Always peek \n\n"<b>"Level 4"</b>" \n- Prevent full screen interruption \n- Always peek \n\n"<b>"Level 3"</b>" \n- Prevent full screen interruption \n- Never peek \n\n"<b>"Level 2"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound and vibration \n\n"<b>"Level 1"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound or vibrate \n- Hide from lock screen and status bar \n- Show at the bottom of the notification list \n\n"<b>"Level 0"</b>" \n- Block all notifications from the app"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Notifications"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"You won\'t get these notifications anymore."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"You won\'t get these notifications anymore"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> notification categories"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"This app doesn\'t have notification categories"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 out of <xliff:g id="NUMBER_1">%d</xliff:g> notification categories from this app</item>
<item quantity="one">1 out of <xliff:g id="NUMBER_0">%d</xliff:g> notification category from this app</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"notification controls"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"notification snooze options"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minutes"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minutes"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 hour"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 hours"</string>
<string name="snooze_undo" msgid="6074877317002985129">"UNDO"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Snoozed for <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Battery usage"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Battery Saver not available during charging"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Battery Saver"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings_car.xml b/packages/SystemUI/res/values-en-rGB/strings_car.xml
index 81089ba..27b916e 100644
--- a/packages/SystemUI/res/values-en-rGB/strings_car.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Drive safely"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Stay fully aware of driving conditions and always obey applicable laws. Directions may be inaccurate, incomplete, dangerous, not suitable, prohibited or involve crossing administrative areas. Business information may also be inaccurate or incomplete. Data is not real time and location accuracy cannot be guaranteed. Do not handle your mobile device or use apps not intended for Android Auto while driving."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Unknown"</string>
+ <string name="start_driving" msgid="864023351402918991">"Start Driving"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 35263c2..93753bb 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Search"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Slide up for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Slide left for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"You won’t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers that you specify. You\'ll still hear anything that you choose to play, including music, videos and games."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"You won’t be disturbed by sounds and vibrations, except from alarms. You\'ll still hear anything that you choose to play, including music, videos and games."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"You won’t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers that you specify."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Customise"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"This blocks ALL sounds and vibrations, including from alarms, music, videos and games. You’ll still be able to make phone calls."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"This blocks ALL sounds and vibrations, including from alarms, music, videos and games."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"You\'re connected to <xliff:g id="APPLICATION">%1$s</xliff:g>, which can monitor your personal network activity, including emails, apps and websites."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Your work profile is managed by <xliff:g id="ORGANIZATION">%1$s</xliff:g>. The profile is connected to <xliff:g id="APPLICATION">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps and websites.\n\nFor more information, contact your admin."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Your work profile is managed by <xliff:g id="ORGANIZATION">%1$s</xliff:g>. The profile is connected to <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps and websites.\n\nYou\'re also connected to <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, which can monitor your personal network activity."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Unlocked for <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> is running"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Device will stay locked until you manually unlock"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Get notifications faster"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"See them before you unlock"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"No, thanks"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Setup"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Turn off now"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"End now"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Expand"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Collapse"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Screen is pinned"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Off"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications. \n\n"<b>"Level 5"</b>" \n- Show at the top of the notification list \n- Allow full screen interruption \n- Always peek \n\n"<b>"Level 4"</b>" \n- Prevent full screen interruption \n- Always peek \n\n"<b>"Level 3"</b>" \n- Prevent full screen interruption \n- Never peek \n\n"<b>"Level 2"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound and vibration \n\n"<b>"Level 1"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound or vibrate \n- Hide from lock screen and status bar \n- Show at the bottom of the notification list \n\n"<b>"Level 0"</b>" \n- Block all notifications from the app"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Notifications"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"You won\'t get these notifications anymore."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"You won\'t get these notifications anymore"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> notification categories"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"This app doesn\'t have notification categories"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 out of <xliff:g id="NUMBER_1">%d</xliff:g> notification categories from this app</item>
<item quantity="one">1 out of <xliff:g id="NUMBER_0">%d</xliff:g> notification category from this app</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"notification controls"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"notification snooze options"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minutes"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minutes"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 hour"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 hours"</string>
<string name="snooze_undo" msgid="6074877317002985129">"UNDO"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Snoozed for <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Battery usage"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Battery Saver not available during charging"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Battery Saver"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings_car.xml b/packages/SystemUI/res/values-en-rIN/strings_car.xml
index 81089ba..27b916e 100644
--- a/packages/SystemUI/res/values-en-rIN/strings_car.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Drive safely"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Stay fully aware of driving conditions and always obey applicable laws. Directions may be inaccurate, incomplete, dangerous, not suitable, prohibited or involve crossing administrative areas. Business information may also be inaccurate or incomplete. Data is not real time and location accuracy cannot be guaranteed. Do not handle your mobile device or use apps not intended for Android Auto while driving."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Unknown"</string>
+ <string name="start_driving" msgid="864023351402918991">"Start Driving"</string>
</resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 3b77658..e04ab5f 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Buscar"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Desliza el dedo hacia arriba para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Desliza el dedo hacia la izquierda para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"No te molestarán los sonidos ni las vibraciones, excepto las alarmas, los recordatorios, los eventos y las llamadas de los emisores que especifiques. Seguirás escuchando el contenido que reproduzcas, como música, videos y juegos."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"No te molestarán los sonidos ni las vibraciones, excepto las alarmas. Seguirás escuchando el contenido que reproduzcas, como música, videos y juegos."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"No te molestarán los sonidos ni las vibraciones, excepto que se trate de alarmas, recordatorios, eventos y emisores que especifiques."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Personalizar"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Esta acción bloquea TODOS los sonidos y las vibraciones, incluidas las que provienen de alarmas, videos y juegos. Podrás realizar llamadas telefónicas."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Esta acción bloquea TODOS los sonidos y las vibraciones, incluso los que provienen de alarmas, música, videos y juegos."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Te conectaste a <xliff:g id="APPLICATION">%1$s</xliff:g>, que puede supervisar la actividad de tu red personal, incluidos los correos electrónicos, las apps y los sitios web."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> administra tu perfil de trabajo. El perfil está conectado a <xliff:g id="APPLICATION">%2$s</xliff:g>, que puede controlar tu actividad de red de trabajo, incluidos los correos electrónicos, apps y sitios web.\n\nPara obtener más información, comunícate con tu administrador."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> administra tu perfil de red. El perfil está conectado a <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, que puede controlar tu actividad de red de trabajo, incluidos los correos electrónicos, las apps y los sitios web.\n\nTambién estás conectado a <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, que puede controlar tu actividad de red personal."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Se desbloqueó para <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"Se está ejecutando <xliff:g id="TRUST_AGENT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"El dispositivo permanecerá bloqueado hasta que lo desbloquees manualmente."</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Recibe notificaciones más rápido"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Ver antes de desbloquear"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"No"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Configurar"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Desactivar ahora"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Finalizar ahora"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Expandir"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Contraer"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Pantalla fija"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Desactivado"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Con los controles de activación de notificaciones, puedes establecer un nivel de importancia para las notificaciones de una app. \n\n"<b>"Nivel 5"</b>" \n- Mostrar en la parte superior de la lista de notificaciones. \n- Permitir interrupción en la pantalla completa. \n- Mostrar siempre. \n\n"<b>"Nivel 4"</b>" \n- No permitir interrupción en la pantalla completa. \n- Mostrar siempre. \n\n"<b>"Nivel 3"</b>" \n- No permitir interrupción en la pantalla completa. \n- No mostrar. \n\n"<b>"Nivel 2"</b>" \n- No permitir interrupción en la pantalla completa. \n- No mostrar. \n- No sonar ni vibrar. \n\n"<b>"Nivel 1"</b>" \n- No permitir interrupción en la pantalla completa. \n- No mostrar. \n- No sonar ni vibrar. \n- Ocultar de la pantalla bloqueada y la barra de estado. \n- Mostrar al final de la lista de notificaciones. \n\n"<b>"Nivel 0"</b>" \n- Bloquear todas las notificaciones de la app."</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Notificaciones"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Ya no recibirás estas notificaciones."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Ya no recibirás estas notificaciones"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> categorías de notificaciones"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Esta app no tiene categorías de notificación"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 de <xliff:g id="NUMBER_1">%d</xliff:g> categorías de notificación de esta app</item>
<item quantity="one">1 de <xliff:g id="NUMBER_0">%d</xliff:g> categoría de notificación de esta app</item>
@@ -572,12 +575,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> de <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"controles de notificación"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"opciones para posponer notificaciones"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minutos"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minutos"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 hora"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 horas"</string>
<string name="snooze_undo" msgid="6074877317002985129">"DESHACER"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Posponer <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Uso de la batería"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Ahorro de batería no está disponible durante la carga"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Ahorro de batería"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings_car.xml b/packages/SystemUI/res/values-es-rUS/strings_car.xml
index 647236a..382602a 100644
--- a/packages/SystemUI/res/values-es-rUS/strings_car.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Conducir de forma segura"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Permanece atento a la situación de conducción y cumple siempre con las leyes vigentes. Es posible que las indicaciones sean imprecisas, inadecuadas o peligrosas, que estén incompletas, que sugieran maniobras prohibidas o que impliquen atravesar áreas administrativas. La información de las empresas también puede ser imprecisa o estar incompleta. Los datos no se proporcionan en tiempo real ni se puede garantizar la precisión de las ubicaciones. No uses tu dispositivo móvil ni apps no diseñadas para Android Auto mientras conduces."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Desconocido"</string>
+ <string name="start_driving" msgid="864023351402918991">"Comenzar a conducir"</string>
</resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index adba00a..a7160fa 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Buscar"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Desliza el dedo hacia arriba para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Desliza el dedo hacia la izquierda para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"No te molestarán los sonidos ni las vibraciones, excepto las alarmas, los recordatorios, los eventos y las llamadas que especifiques. Seguirás escuchando el contenido que quieras reproducir, como música, vídeos y juegos."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"No te molestarán los sonidos ni las vibraciones, excepto las alarmas. Seguirás escuchando el contenido que quieras reproducir, como música, vídeos y juegos."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"No te interrumpirán sonidos ni vibraciones, salvo los de las alarmas, los recordatorios, los eventos y las llamadas que especifiques."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Personalizar"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Este modo permite bloquear TODOS los sonidos y todas las vibraciones (p. ej., los de alarmas, música, vídeos y juegos). Seguirás pudiendo hacer llamadas de teléfono."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Este modo permite bloquear TODOS los sonidos y todas las vibraciones (p. ej., los de alarmas, música, vídeos y juegos)."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Estas conectado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que puede controlar tu actividad de red personal, como correos electrónicos, aplicaciones y sitios web."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> gestiona tu perfil de trabajo. El perfil está conectado a <xliff:g id="APPLICATION">%2$s</xliff:g>, que puede supervisar tu actividad de red profesional, como los correos electrónicos, las aplicaciones y los sitios web.\n\nPara obtener más información, ponte en contacto con el administrador."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> gestiona tu perfil de trabajo. El perfil está conectado a <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, que puede supervisar tu actividad de red profesional, como los correos electrónicos, las aplicaciones y los sitios web.\n\nTambién te has conectado a <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, que puede supervisar tu actividad de red personal."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Desbloqueado para <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> se está ejecutando"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"El dispositivo permanecerá bloqueado hasta que se desbloquee manualmente"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Recibe notificaciones más rápido"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Ver antes de desbloquear"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"No, gracias"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Configurar"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Desactivar"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Finalizar ahora"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Mostrar"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Ocultar"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Pantalla fijada"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Desactivado"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Los controles de energía de las notificaciones permiten establecer un nivel de importancia de 0 a 5 para las notificaciones de las aplicaciones. \n\n"<b>"Nivel 5"</b>" \n- Mostrar en la parte superior de la lista de notificaciones \n- Permitir interrumpir en el modo de pantalla completa \n- Mostrar siempre \n\n"<b>"Nivel 4"</b>" \n- Evitar interrumpir en el modo de pantalla completa \n- Mostrar siempre \n\n"<b>"Nivel 3"</b>" \n- Evitar interrumpir en el modo de pantalla completa \n- No mostrar nunca \n\n"<b>"Nivel 2"</b>" \n- Evitar interrumpir en el modo de pantalla completa\n- No mostrar nunca \n- No emitir sonido ni vibrar nunca \n\n"<b>"Nivel 1"</b>" \n- Evitar interrumpir en el modo de pantalla completa \n- No mostrar nunca \n- No emitir sonido ni vibrar nunca \n- Ocultar de la pantalla de bloqueo y de la barra de estado \n- Mostrar en la parte inferior de la lista de notificaciones \n\n"<b>"Nivel 0"</b>" \n- Bloquear todas las notificaciones de la aplicación"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Notificaciones"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Ya no recibirás estas notificaciones."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Ya no recibirás estas notificaciones"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> categorías de notificación"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Esta aplicación no tiene categorías de notificación"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 de <xliff:g id="NUMBER_1">%d</xliff:g> categorías de notificación de esta aplicación</item>
<item quantity="one">1 de <xliff:g id="NUMBER_0">%d</xliff:g> categoría de notificación de esta aplicación</item>
@@ -572,12 +575,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> de <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"Controles de las notificaciones"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"Opciones para posponer las notificaciones"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minutos"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minutos"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 hora"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 horas"</string>
<string name="snooze_undo" msgid="6074877317002985129">"DESHACER"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Volverá a mostrarse en <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Uso de la batería"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Ahorro de batería no disponible mientras se carga el dispositivo"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Ahorro de batería"</string>
diff --git a/packages/SystemUI/res/values-es/strings_car.xml b/packages/SystemUI/res/values-es/strings_car.xml
index e19ca78..47a40b0 100644
--- a/packages/SystemUI/res/values-es/strings_car.xml
+++ b/packages/SystemUI/res/values-es/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Conduce de forma segura"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Permanece atento a la conducción y respeta siempre las normas de tráfico. Las indicaciones pueden ser inexactas, incompletas, peligrosas o inadecuadas o dar lugar a maniobras prohibidas o al cruce de zonas regionales diferentes. La información sobre empresas también puede ser inexacta o estar incompleta. No se ofrecen datos en tiempo real ni se puede garantizar la exactitud de las ubicaciones. No utilices el dispositivo móvil ni aplicaciones que no estén destinadas a Android Auto mientras conduces."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Desconocido"</string>
+ <string name="start_driving" msgid="864023351402918991">"Empezar a conducir"</string>
</resources>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index f4605e0..e21baa6 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Otsing"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Lohistage üles: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Lohistage vasakule: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Helid ja vibratsioonid ei sega teid. Kuulete siiski enda määratud alarme, meeldetuletusi, sündmusi ja helistajaid. Samuti kuulete kõike, mille esitamise ise valite, sh muusika, videod ja mängud."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Helid ja vibratsioonid ei sega teid. Kuulete siiski alarme. Samuti kuulete kõike, mille esitamise ise valite, sh muusika, videod ja mängud."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Teid segatakse helide ja värinaga vaid teie määratud alarmide, meeldetuletuste, sündmuste ning helistajate puhul."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Kohanda"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"See blokeerib KÕIK – sealhulgas alarmide, muusika, videote ja mängude – helid ja värinad. Saate siiski helistada."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"See blokeerib KÕIK – sealhulgas alarmide, muusika, videote ja mängude – helid ja vibratsioonid."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Olete ühendatud rakendusega <xliff:g id="APPLICATION">%1$s</xliff:g>, mis võib jälgida teie isiklikke võrgutegevusi, sh meile, rakendusi ja veebisaite."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Teie tööprofiili haldab <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profiil on ühendatud rakendusega <xliff:g id="APPLICATION">%2$s</xliff:g>, mis saab jälgida teie töökoha võrgutegevusi, sh meile, rakendusi ja veebisaite.\n\nLisateabe saamiseks võtke ühendust administraatoriga."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Teie tööprofiili haldab <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profiil on ühendatud rakendusega <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, mis saab jälgida teie töökoha võrgutegevusi, sh meile, rakendusi ja veebisaite.\n\nOlete ühendatud ka rakendusega <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, mis saab jälgida teie isiklikke võrgutegevusi."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Avatud kasutaja <xliff:g id="USER_NAME">%1$s</xliff:g> jaoks"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"Funktsioon <xliff:g id="TRUST_AGENT">%1$s</xliff:g> töötab"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Seade jääb lukku, kuni selle käsitsi avate"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Saate märguandeid kiiremini"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Näete neid enne avamist"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Tänan, ei"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Seadistus"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Lülita kohe välja"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Lõpeta nüüd"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Laiendamine"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Ahendamine"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekraan on kinnitatud"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Väljas"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Toite märguannete juhtnuppudega saate määrata rakenduse märguannete tähtsuse taseme vahemikus 0–5. \n\n"<b>"5. tase"</b>" \n- Kuva märguannete loendi ülaosas\n- Luba täisekraanil häirimine \n- Kuva alati ekraani servas \n\n"<b>"4. tase"</b>" \n- Keela täisekraanil häirimine \n- Kuva alati ekraani servas \n\n"<b>"3. tase"</b>" \n- Keela täisekraanil häirimine \n- Ära kunagi kuva ekraani servas \n\n"<b>"2. tase"</b>" \n- Keela täisekraanil häirimine \n- Ära kunagi kuva ekraani servas \n- Ära kunagi helise ega vibreeri \n\n"<b>"1. tase"</b>" \n- Keela täisekraanil häirimine \n- Ära kunagi kuva ekraani servas \n- Ära kunagi helise ega vibreeri \n- Peida lukustuskuval ja olekuribal \n- Kuva märguannete loendi allosas \n\n"<b>"Tase 0"</b>" \n- Blokeeri kõik rakenduse märguanded"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Märguanded"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Te ei saa enam neid märguandeid."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Te ei saa enam neid märguandeid"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> märguandekategooriat"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Sellel rakendusel ei ole märguannete kategooriaid"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 <xliff:g id="NUMBER_1">%d</xliff:g>-st märguannete kategooriast sellelt rakenduselt</item>
<item quantity="one">1 <xliff:g id="NUMBER_0">%d</xliff:g>-st märguannete kategooriast sellelt rakenduselt</item>
@@ -572,12 +575,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"märguannete juhtnupud"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"märguannete edasilükkamise valikud"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minutit"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minutit"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"Üks tund"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"kaks tundi"</string>
<string name="snooze_undo" msgid="6074877317002985129">"VÕTA TAGASI"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Edasi lükatud <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Akukasutus"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Akusäästja pole laadimise ajal saadaval"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Akusäästja"</string>
diff --git a/packages/SystemUI/res/values-et/strings_car.xml b/packages/SystemUI/res/values-et/strings_car.xml
index c41385a..a63c3f3 100644
--- a/packages/SystemUI/res/values-et/strings_car.xml
+++ b/packages/SystemUI/res/values-et/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Sõitke turvaliselt"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Olge teadlik sõidutingimustest ja järgige alati kohaldatavaid seadusi. Juhised võivad olla ebatäpsed, mittetäielikud, ohtlikud, sobimatud, keelatud või hõlmata kattuvaid administratiivpiirkondi. Ka ettevõtteteave võib olla ebatäpne või mittetäielik. Andmed pole reaalajas ja asukoha täpsust ei saa garanteerida. Ärge kasutage auto juhtimisel mobiilseadet ega rakendusi, mis pole mõeldud teenuse Android Auto jaoks."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Teadmata"</string>
+ <string name="start_driving" msgid="864023351402918991">"Sõidu alustamine"</string>
</resources>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 1919df3..9276020 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Bilatu"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Lerratu gora hau egiteko: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Lerratu ezkerrera hau egiteko: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Gailuak ez du egingo ez soinurik ez dardararik, baina alarmak, gertaeren abisu-tonuak eta aukeratzen dituzun deitzaileen dei-tonuak joko ditu. Bestalde, zuk erreproduzitutako guztia entzungo duzu, besteak beste, musika, bideoak eta jokoak."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Gailuak ez du egingo ez soinurik ez dardararik, baina alarmak joko ditu. Hala ere, zuk erreproduzitutako guztia entzun ahal izango duzu, besteak beste, musika, bideoak eta jokoak."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Soinuek eta dardarek ez zaituzte oztoporik egingo, zehazten dituzun alarmek, abisuek, gertaerek eta deitzaileek izan ezik."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Pertsonalizatu"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Soinu eta dardara GUZTIAK blokeatuko dira, besteak beste, alarmak, musika, bideoak eta jokoak. Telefono-deiak egiteko aukera izaten jarraituko duzu."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Soinu eta dardara GUZTIAK blokeatuko dira, besteak beste, alarmak, musika, bideoak eta jokoak."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"<xliff:g id="APPLICATION">%1$s</xliff:g> aplikaziora konektatuta zaude. Aplikazio horrek sarean egiten dituzun jarduera pertsonalak kontrola ditzake, mezu elektronikoak, aplikazioak eta webguneak barne."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> erakundeak kudeatzen dizu laneko profila. <xliff:g id="APPLICATION">%2$s</xliff:g> aplikaziora dago konektatuta profila, eta aplikazio horrek sarean egiten dituzun jarduerak kontrola ditzake, mezu elektronikoak, aplikazioak eta webguneak barne.\n\nInformazio gehiago lortzeko, jarri administratzailearekin harremanetan."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> erakundeak kudeatzen dizu laneko profila. <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> aplikaziora dago konektatuta profila, eta aplikazio horrek sarean egiten dituzun jarduerak kontrola ditzake, mezu elektronikoak, aplikazioak eta webguneak barne. \n\n<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> aplikaziora ere zaude konektatuta, eta hark sare pertsonalean egiten dituzun jarduerak kontrola ditzake."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> erabiltzailearentzat desblokeatu da"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> abian da"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Gailua blokeatuta egongo da eskuz desblokeatu arte"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Eskuratu jakinarazpenak azkarrago"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Ikusi desblokeatu baino lehen"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Ez, eskerrik asko"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Konfiguratu"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Desaktibatu"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Amaitu"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Zabaldu"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Tolestu"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Pantaila ainguratuta dago"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Desaktibatuta"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Bateria-mailaren arabera jakinarazpenak kontrolatzeko aukerekin, 0 eta 5 bitarteko garrantzi-mailetan sailka ditzakezu aplikazioen jakinarazpenak. \n\n"<b>"5. maila"</b>" \n- Erakutsi jakinarazpenen zerrendaren goialdean. \n- Baimendu etetea pantaila osoko moduan zaudenean. \n- Agerrarazi beti jakinarazpenak. \n\n"<b>"4. maila"</b>" \n- Galarazi etetea pantaila osoko moduan zaudenean. \n- Agerrarazi beti jakinarazpenak. \n\n"<b>"3. maila"</b>" \n- Galarazi etetea pantaila osoko moduan zaudenean. \n- Ez agerrarazi jakinarazpenik inoiz. \n\n"<b>"2. maila"</b>" \n- Galarazi etetea pantaila osoko moduan zaudenean. \n- Ez agerrarazi jakinarazpenik inoiz. \n- Ez egin soinurik edo dardararik inoiz. \n\n"<b>"1. maila"</b>" \n- Galarazi etetea pantaila osoko moduan zaudenean. \n- Ez agerrarazi jakinarazpenik inoiz. \n- Ez egin soinurik edo dardararik inoiz. \n- Ezkutatu pantaila blokeatutik eta egoera-barratik. \n- Erakutsi jakinarazpenen zerrendaren behealdean. \n\n"<b>"0. maila"</b>" \n- Blokeatu aplikazioaren jakinarazpen guztiak."</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Jakinarazpenak"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Aurrerantzean ez duzu jasoko horrelako jakinarazpenik."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Aurrerantzean ez duzu jasoko horrelako jakinarazpenik"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"Jakinarazpenen <xliff:g id="NUMBER">%d</xliff:g> kategoria"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Aplikazio honek ez du jakinarazpen-kategoriarik"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">Aplikazio honen 1/<xliff:g id="NUMBER_1">%d</xliff:g> jakinarazpen-kategoria</item>
<item quantity="one">Aplikazio honen 1/<xliff:g id="NUMBER_0">%d</xliff:g> jakinarazpen-kategoria</item>
@@ -572,12 +575,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"jakinarazpena kontrolatzeko aukerak"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"jakinarazpena atzeratzeko aukerak"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minutu"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minutu"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 ordu"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 ordu"</string>
<string name="snooze_undo" msgid="6074877317002985129">"DESEGIN"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g>z atzeratu da"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Bateriaren erabilera"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Bateria-aurrezlea ez dago erabilgarri gailua kargatzen ari denean"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Bateria-aurrezlea"</string>
diff --git a/packages/SystemUI/res/values-eu/strings_car.xml b/packages/SystemUI/res/values-eu/strings_car.xml
index c4371f7..59c2bb2 100644
--- a/packages/SystemUI/res/values-eu/strings_car.xml
+++ b/packages/SystemUI/res/values-eu/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Gidatu zentzuz"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Egon erne errepidera begira eta gorde lege aplikagarri oro. Agian jarraibideak ez dira guztiz zehatzak, osatuak edo egokiak izango; arriskutsuak izan daitezke edo debekatuta dagoen zerbait egitea edo mugak zeharkatzea proposa diezazukete. Baliteke enpresei buruzko informazioa ere guztiz zehatza edo osatua ez izatea. Datuak ez dira une-unekoak eta ezin da bermatu kokapenaren zehaztasuna. Gidatu bitartean, ez erabili gailu mugikorrik edo Android Auto zerbitzuarekin erabiltzeko egina ez dagoen aplikaziorik."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Ezezaguna"</string>
+ <string name="start_driving" msgid="864023351402918991">"Hasi gidatzen"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index b16ff8e..cc5f46e 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"جستجو"</string>
<string name="description_direction_up" msgid="7169032478259485180">"لغزاندن به بالا برای <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"لغزاندن به چپ برای <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"بجز هشدارها، یادآوریها، رویدادها و تماسگیرندگانی که خودتان مشخص میکنید، هیچ صدا و لرزشی نخواهید داشت. همچنان صدای مواردی را که پخش میکنید میشنوید از جمله صدای موسیقی، ویدیو و بازی."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"بجز هشدارها، هیچ صدا و لرزشی نخواهید داشت. همچنان صدای مواردی را که پخش میکنید میشنوید از جمله صدای موسیقی، ویدیو و بازی."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"صداها و لرزشهایی به جز هشدارها، یادآوریها، رویدادها و تماسگیرندههایی که مشخص میکنید، مزاحم شما نمیشوند."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"سفارشی کردن"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"این کار «همه» صداها و لرزشها از جمله هشدارها، موسیقی، ویدئوها و بازیها را مسدود میکند. همچنان میتوانید تماس تلفنی برقرار کنید."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"این کار «همه» صداها و لرزشها از جمله هشدارها، موسیقی، ویدئوها و بازیها را مسدود میکند."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"به <xliff:g id="APPLICATION">%1$s</xliff:g> وصل شدهاید، که میتواند فعالیت شبکه شخصی شما را (ازجمله رایانامهها، برنامهها و وبسایتها) کنترل کند."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"نمایه کاری شما توسط <xliff:g id="ORGANIZATION">%1$s</xliff:g> مدیریت میشود. این نمایه به <xliff:g id="APPLICATION">%2$s</xliff:g> متصل است که میتواند فعالیت شما در شبکه (ازجمله رایانامهها، برنامهها و وبسایتها) را پایش کند.\n\nبرای اطلاعات بیشتر، با سرپرست سیستم تماس بگیرید."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"نمایه کاریتان توسط <xliff:g id="ORGANIZATION">%1$s</xliff:g> مدیریت میشود. این نمایه به <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> متصل است که میتواند تنظیمات، دسترسی شرکتی، برنامهها، دادههای مرتبط با دستگاه و اطلاعات مکان دستگاه شما را پایش کند.\n\nشما همچنین به <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> متصل هستید که میتواند فعالیت خصوصی شما را در شبکه پایش کند."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"قفل برای <xliff:g id="USER_NAME">%1$s</xliff:g> باز شد"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> درحال اجرا است"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"دستگاه قفل باقی میماند تا زمانی که قفل آن را به صورت دستی باز کنید"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"دریافت سریعتر اعلانها"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"قبل از باز کردن قفل آنها را مشاهده کنید"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"نه متشکرم"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"راهاندازی"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"اکنون خاموش کنید"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"اکنون به پایان برسد"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"بزرگ کردن"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"کوچک کردن"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"صفحه نمایش پین شد"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"خاموش"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"با کنترلهای قدرتمند اعلان میتوانید سطح اهمیت اعلانهای هر برنامه را از ۰ تا ۵ تعیین کنید. \n\n"<b>"سطح ۵"</b>" \n- در صدر فهرست اعلانها نشان داده میشود \n- وقفه برای نمایش تمامصفحه مجاز است \n- همیشه اجمالی نشان داده میشود \n\n"<b>"سطح ۴"</b>" \n- وقفه برای نمایش تمامصفحه مجاز نیست \n- همیشه اجمالی نشان داده میشود \n\n"<b>"سطح ۳"</b>" \n- وقفه برای نمایش تمامصفحه مجاز نیست \n- هیچوقت اجمالی نشان داده نمیشود \n\n"<b>"سطح ۲"</b>" \n- وقفه برای نمایش تمامصفحه مجاز نیست \n- هیچوقت اجمالی نشان داده نمیشود \n- هیچوقت صدا و لرزش ایجاد نمیکند \n\n"<b>"سطح ۱"</b>" \n- نمایش تمام صفحه مجاز نیست \n- هیچوقت اجمالی نشان داده نمیشود \n- هیچوقت صدا یا لرزش ایجاد نمیکند \n- در قفل صفحه و نوار وضعیت پنهان است \n- در پایین فهرست اعلانها نشان داده میشود \n\n"<b>"سطح ۰"</b>" \n- همه اعلانهای این برنامه مسدود است"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"اعلانها"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"دیگر این اعلانها را دریافت نخواهید کرد."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"دیگر این اعلانها را دریافت نخواهید کرد"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> دسته اعلان"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"این برنامه دسته اعلان ندارد"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">۱ از <xliff:g id="NUMBER_1">%d</xliff:g> دسته اعلان این برنامه</item>
<item quantity="other">۱ از <xliff:g id="NUMBER_1">%d</xliff:g> دسته اعلان این برنامه</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"کنترلهای اعلان"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"گزینههای تعویق اعلان"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"۱۵ دقیقه"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"۳۰ دقیقه"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"۱ ساعت"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"۲ ساعت"</string>
<string name="snooze_undo" msgid="6074877317002985129">"واگرد"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> به تعویق افتاد"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"مصرف باتری"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"هنگام شارژ شدن، «بهینهسازی باتری» در دسترس نیست"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"بهینهسازی باتری"</string>
diff --git a/packages/SystemUI/res/values-fa/strings_car.xml b/packages/SystemUI/res/values-fa/strings_car.xml
index e8433fa..e914796 100644
--- a/packages/SystemUI/res/values-fa/strings_car.xml
+++ b/packages/SystemUI/res/values-fa/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"با ایمنی برانید"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"کاملاً از شرایط رانندگی آگاه باشید و همیشه قوانین مربوطه را رعایت کنید. مسیرها ممکن است غیردقیق، ناقص، ناکامل، خطرناک، نامناسب، ممنوع یا مستلزم عبور از تقسیمات کشوری باشند. اطلاعات کسب و کار نیز ممکن است غیردقیق یا ناکامل باشند. دادهها بیدرنگ نیستند و دقت مکان نمیتواند تضمین شود. هنگام رانندگی دستگاه همراه را در دست نگیرید یا از برنامههایی که ویژه Android Auto نیستند استفاده نکنید."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"نامشخص"</string>
+ <string name="start_driving" msgid="864023351402918991">"شروع رانندگی"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index f4e5616..e630cfc4 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Haku"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Liu\'uta ylös ja <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Liu\'uta vasemmalle ja <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Äänet ja värinät eivät häiritse sinua, paitsi jos ne ovat hälytyksiä, muistutuksia, tapahtumia tai määrittämiäsi soittajia. Kuulet edelleen kaiken valitsemasi sisällön, kuten musiikin, videot ja pelit."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Äänet ja värinät eivät häiritse sinua, paitsi jos ne ovat hälytyksiä. Kuulet edelleen kaiken valitsemasi sisällön, kuten musiikin, videot ja pelit."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Äänet ja värinät eivät häiritse sinua, paitsi jos ne ovat hälytyksiä, muistutuksia, tapahtumia tai määrittämiäsi soittajia."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Muokkaa"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Tämä estää KAIKKI äänet ja värinät, mukaan lukien hälytysten, musiikin, videoiden ja pelien äänet ja värinät. Voit kuitenkin soittaa puheluita."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Tämä estää KAIKKI äänet ja värinät, mukaan lukien hälytysten, musiikin, videoiden ja pelien äänet ja värinät."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Olet muodostanut yhteyden sovellukseen <xliff:g id="APPLICATION">%1$s</xliff:g>, joka voi valvoa henkilökohtaista toimintaasi verkossa. Sovellus voi esimerkiksi seurata avaamiasi sähköposteja, sovelluksia ja verkkosivustoja."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> hallinnoi työprofiiliasi. Se on yhteydessä sovellukseen <xliff:g id="APPLICATION">%2$s</xliff:g>, joka voi valvoa toimintaasi verkossa, esimerkiksi sähköposteja, sovelluksia ja verkkosivustoja.\n\nPyydä lisätietoja järjestelmänvalvojalta."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> hallinnoi työprofiiliasi. Se on yhteydessä sovellukseen <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, joka voi valvoa toimintaasi verkossa, esimerkiksi sähköposteja, sovelluksia ja verkkosivustoja.\n\nLisäksi olet yhteydessä sovellukseen <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, joka voi valvoa henkilökohtaista toimintaasi verkossa."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Lukitus avattu käyttäjälle <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> on käytössä"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Laite pysyy lukittuna, kunnes se avataan käsin"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Näe ilmoitukset nopeammin"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Näytä ennen lukituksen avaamista"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Ei kiitos"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Määritä asetukset"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Sammuta nyt"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Lopeta nyt"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Laajenna."</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Tiivistä."</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Näyttö on kiinnitetty"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Pois käytöstä"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Ilmoitusten tehohallinnan avulla voit määrittää sovelluksen ilmoituksille tärkeystason väliltä 0–5. \n\n"<b>"Taso 5"</b>" \n– Ilmoitukset näytetään ilmoitusluettelon yläosassa \n– Näkyminen koko näytön tilassa sallitaan \n– Ilmoitukset kurkistavat aina näytölle\n\n"<b>"Taso 4"</b>" \n– Näkyminen koko näytön tilassa estetään \n– Ilmoitukset kurkistavat aina näytölle \n\n"<b>"Taso 3"</b>" \n– Näkyminen koko näytön tilassa estetään \n– Ei kurkistamista \n\n"<b>"Taso 2"</b>" \n– Näkyminen koko näytön tilassa estetään \n– Ei kurkistamista \n– Ei ääniä eikä värinää \n\n"<b>"Taso 1"</b>" \n– Näkyminen koko näytön tilassa estetään \n– Ei kurkistamista \n– Ei ääniä eikä värinää \n– Ilmoitukset piilotetaan lukitusnäytöltä ja tilapalkista \n– Ilmoitukset näytetään ilmoitusluettelon alaosassa \n\n"<b>"Taso 0"</b>" \n– Kaikki sovelluksen ilmoitukset estetään"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Ilmoitukset"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Et saa näitä ilmoituksia enää."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Et saa näitä ilmoituksia enää."</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> ilmoitusluokkaa"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Tällä sovelluksella ei ole ilmoitusluokkia."</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">Tämä sovellus: 1/<xliff:g id="NUMBER_1">%d</xliff:g> ilmoitusluokkaa</item>
<item quantity="one">Tämä sovellus: 1/<xliff:g id="NUMBER_0">%d</xliff:g> ilmoitusluokkaa</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"Ilmoitusten hallinta"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"Ilmoitusten torkkuasetukset"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minuuttia"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minuuttia"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 tunti"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 tuntia"</string>
<string name="snooze_undo" msgid="6074877317002985129">"KUMOA"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Torkku: <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Akun käyttö"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Virransäästö ei ole käytettävissä latauksen aikana."</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Virransäästö"</string>
diff --git a/packages/SystemUI/res/values-fi/strings_car.xml b/packages/SystemUI/res/values-fi/strings_car.xml
index fc94b90..927b13d 100644
--- a/packages/SystemUI/res/values-fi/strings_car.xml
+++ b/packages/SystemUI/res/values-fi/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Aja varovasti"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Tarkkaile huolellisesti ajo-olosuhteita ja noudata aina voimassa olevia lakeja. Reittiohjeet saattavat olla epätarkkoja, epätäydellisiä, vaarallisia, epäsopivia, kiellettyjä tai kulkea hallintoalueiden läpi. Myös yritystiedot voivat olla epätarkkoja tai epätäydellisiä. Tietoja ei päivitetä reaaliajassa eikä sijaintien tarkkuutta taata. Älä käytä ajon aikana mobiililaitettasi tai sovelluksia, joita ei ole suunniteltu Android Autolle."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Tuntematon"</string>
+ <string name="start_driving" msgid="864023351402918991">"Aloita ajaminen"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 827958c..4dd3c47 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Recherche"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Faire glisser le doigt vers le haut : <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>"</string>
<string name="description_direction_left" msgid="7207478719805562165">"Faites glisser votre doigt vers la gauche pour <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Vous ne serez pas dérangé par les sons et les vibrations, sauf pour les alarmes, les rappels, les événements et les appelants. Vous entendrez tout ce que vous choisissez d\'écouter, y compris la musique, les vidéos et les jeux."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Vous ne serez pas dérangé par les sons et les vibrations, sauf pour les alarmes. Vous entendrez tout ce que vous choisissez d\'écouter, y compris la musique, les vidéos et les jeux."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Vous ne serez pas dérangé par les sonneries ni par les vibrations, sauf pour les alarmes, les rappels, les événements et les appels des personnes que vous spécifiez."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Personnaliser"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Cette option permet de bloquer TOUS les sons et vibrations, y compris pour les alarmes, la musique, les vidéos et les jeux. Vous pourrez quand même faire des appels téléphoniques."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Cette option permet de bloquer TOUS les sons et vibrations, y compris pour les alarmes, la musique, les vidéos et les jeux."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Vous êtes connecté à <xliff:g id="APPLICATION">%1$s</xliff:g>. Cette application peut contrôler votre activité personnelle sur le réseau, y compris les courriels, les applications et les sites Web."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Votre profil professionnel est géré par <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ce profil est connecté à <xliff:g id="APPLICATION">%2$s</xliff:g>, qui peut contrôler votre activité professionnelle sur le réseau, y compris l\'activité relative aux courriels, aux applications et aux sites Web.\n\nPour en savoir plus, communiquez avec votre administrateur."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Votre profil professionnel est géré par <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ce profil est connecté à <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, qui peut contrôler votre activité professionnelle sur le réseau, y compris l\'activité relative aux courriels, aux applications et aux sites Web.\n\nVous êtes également connecté à <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, qui peut contrôler votre activité personnelle sur le réseau."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Déverrouillé pour for <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> fonctionne"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"L\'appareil restera verrouillé jusqu\'à ce que vous le déverrouilliez manuellement"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Voir les notifications plus rapidement"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Afficher les notifications avant de déverrouiller l\'appareil"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Non, merci"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Configurer"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Désactiver maintenant"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Arrêter maintenant"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Développer"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Réduire"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"L\'écran est épinglé"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Désactivé"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Avec les réglages avancés des notifications, vous pouvez définir un degré d\'importance de 0 à 5 pour les notifications d\'une application. \n\n"<b>"Niveau 5"</b>" \n- Afficher dans le haut de la liste des notifications \n- Autoriser les interruptions en mode plein écran \n- Toujours afficher les aperçus \n\n"<b>"Niveau 4"</b>" \n- Empêcher les interruptions en mode plein écran \n- Toujours afficher les aperçus \n\n"<b>"Niveau 3"</b>" \n- Empêcher les interruptions en mode plein écran \n- Ne jamais afficher les aperçus \n\n"<b>"Niveau 2"</b>" \n- Empêcher les interruptions en mode plein écran \n- Ne jamais afficher les aperçus \n- Ne pas autoriser les sons et les vibrations \n\n"<b>"Niveau 1"</b>" \n- Empêcher les interruptions en mode plein écran \n- Ne jamais afficher les aperçus \n- Ne pas autoriser les sons et les vibrations \n- Masquer de l\'écran de verrouillage et de la barre d\'état status bar \n- Afficher dans le bas de la liste des notifications \n\n"<b>"Level 0"</b>" \n- Bloquer toutes les notifications de l\'application"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Notifications"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Vous ne recevrez plus ces notifications."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Vous ne recevrez plus ces notifications"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> catégories de notification"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Cette application n\'a pas de catégories de notification"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 catégorie de notification sur <xliff:g id="NUMBER_1">%d</xliff:g> provenant de cette application</item>
<item quantity="other">1 catégorie de notification sur <xliff:g id="NUMBER_1">%d</xliff:g> provenant de cette application</item>
@@ -572,12 +575,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"paramètres des notifications"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"options de répétition des notifications"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minutes"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minutes"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 heure"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 heures"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ANNULER"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Reporté pour <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Utilisation de la pile"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Le mode Économie d\'énergie n\'est pas accessible pendant la charge"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Économie d\'énergie"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings_car.xml b/packages/SystemUI/res/values-fr-rCA/strings_car.xml
index 10e1fd5..a88dc3b 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings_car.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Conduisez prudemment"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Tenez compte des conditions de la route et respectez toujours les lois en vigueur. Les itinéraires peuvent être incorrects, incomplets, dangereux, inappropriés ou interdits, et ils peuvent traverser des zones administratives. Les renseignements sur les entreprises peuvent également être incorrects ou incomplets. Les données ne sont pas fournies en temps réel, et la précision de la localisation n\'est pas garantie. Ne manipulez pas votre appareil mobile et n\'utilisez pas d\'applications non conçues pour Android Auto lorsque vous conduisez."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Inconnu"</string>
+ <string name="start_driving" msgid="864023351402918991">"Commencer à conduire"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index e78c892..6494710 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Rechercher"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Faites glisser vers le haut pour <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Faites glisser vers la gauche pour <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Vous ne serez pas dérangé par des sons ou des vibrations, hormis ceux des alarmes, des rappels, des événements et des appelants de votre choix. Vous entendrez toujours les sons que vous choisirez de jouer, notamment la musique, les vidéos et les jeux."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Vous ne serez pas dérangé par des sons ou des vibrations, hormis ceux des alarmes. Vous entendrez toujours les sons que vous choisirez de jouer, notamment la musique, les vidéos et les jeux."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Vous ne serez pas dérangé par les sonneries ni les vibrations, sauf pour les alarmes, les rappels, les événements et les appels des personnes que vous avez spécifiées."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Personnaliser"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Cette option permet de bloquer TOUS les sons et les vibrations, y compris pour les alarmes, la musique, les vidéos et les jeux. Vous pourrez toujours passer des appels téléphoniques."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Cette option permet de bloquer TOUS les sons et les vibrations, y compris pour les alarmes, la musique, les vidéos et les jeux."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Vous êtes connecté à <xliff:g id="APPLICATION">%1$s</xliff:g>. Cette application peut contrôler votre activité personnelle sur le réseau, y compris les e-mails, les applications et les sites Web."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Votre profil professionnel est géré par <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ce profil est connecté à <xliff:g id="APPLICATION">%2$s</xliff:g>, qui peut contrôler votre activité professionnelle sur le réseau, y compris l\'activité relative aux e-mails, aux applications et aux sites Web.\n\nPour plus d\'informations, contactez votre administrateur."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Votre profil professionnel est géré par <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Ce profil est connecté à <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, qui peut contrôler votre activité professionnelle sur le réseau, y compris l\'activité relative aux e-mails, aux applications et aux sites Web.\n\nVous êtes également connecté à <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, qui peut contrôler votre activité personnelle sur le réseau."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Déverrouillé pour <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> est en cours d\'exécution"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"L\'appareil restera verrouillé jusqu\'à ce que vous le déverrouilliez manuellement."</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Recevoir les notifications plus vite"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Afficher les notifications avant de déverrouiller l\'appareil"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Non, merci"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Configurer"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Désactiver"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Arrêter maintenant"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Développer"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Réduire"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Écran épinglé"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Désactivé"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Grâce aux commandes de gestion des notifications, vous pouvez définir le niveau d\'importance (compris entre 0 et 5) des notifications d\'une application. \n\n"<b>"Niveau 5"</b>" \n- Afficher en haut de la liste des notifications \n- Autoriser l\'interruption en plein écran \n- Toujours en aperçu \n\n"<b>"Niveau 4"</b>" \n- Empêcher l\'interruption en plein écran \n- Toujours en aperçu \n\n"<b>"Niveau 3"</b>" \n- Empêcher l\'interruption en plein écran \n- Jamais en aperçu \n\n"<b>"Niveau 2"</b>" \n- Empêcher l\'interruption en plein écran \n- Jamais en aperçu \n- Ne jamais émettre de signal sonore ni déclencher le vibreur \n\n"<b>"Niveau 1"</b>" \n- Empêcher l\'interruption en plein écran \n- Jamais en aperçu \n- Ne jamais émettre de signal sonore ni déclencher le vibreur \n- Masquer les notifications dans l\'écran de verrouillage et la barre d\'état \n- Afficher au bas de la liste des notifications \n\n"<b>"Niveau 0"</b>" \n- Bloquer toutes les notifications de l\'application"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Notifications"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Vous ne recevrez plus ces notifications."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Vous ne recevrez plus ces notifications"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> catégories de notification"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Cette application n\'a pas de catégories de notification"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 catégorie de notification sur <xliff:g id="NUMBER_1">%d</xliff:g> provenant de cette application</item>
<item quantity="other">1 catégorie de notification sur <xliff:g id="NUMBER_1">%d</xliff:g> provenant de cette application</item>
@@ -572,12 +575,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> : <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"paramètres des notifications"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"options de répétition des notifications"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minutes"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minutes"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 heure"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 heures"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ANNULER"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Répétée après <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Utilisation batterie"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"L\'économiseur de batterie n\'est pas disponible lorsque l\'appareil est en charge."</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Économiseur de batterie"</string>
diff --git a/packages/SystemUI/res/values-fr/strings_car.xml b/packages/SystemUI/res/values-fr/strings_car.xml
index d42586e..a88dc3b 100644
--- a/packages/SystemUI/res/values-fr/strings_car.xml
+++ b/packages/SystemUI/res/values-fr/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Soyez prudent sur la route"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Tenez compte des conditions de conduite et respectez toujours les lois en vigueur. Les itinéraires peuvent être incorrects, incomplets, dangereux, inappropriés ou interdits, et traverser des frontières administratives. Les informations sur les établissements peuvent également être incorrectes ou incomplètes. Les données ne sont pas fournies en temps réel, et la précision de la localisation n\'est pas garantie. Ne manipulez pas votre appareil mobile et n\'utilisez pas d\'applications non conçues pour Android Auto lorsque vous conduisez."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Inconnu"</string>
+ <string name="start_driving" msgid="864023351402918991">"Commencer à conduire"</string>
</resources>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 3cd4e71..f8daa9e 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Buscar"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Pasa o dedo cara arriba para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Pasa o dedo cara a esquerda para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Non te molestará ningún son nin vibración, agás os procedentes de alarmas, recordatorios, eventos e os emisores de chamada especificados. Seguirás escoitando todo aquilo que decidas reproducir, mesmo a música, os vídeos e os xogos."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Non te molestará ningún son nin vibración, agás os procedentes de alarmas. Seguirás escoitando todo aquilo que decidas reproducir, mesmo a música, os vídeos e os xogos."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Non te molestará ningún son nin vibración, agás os procedentes de alarmas, recordatorios, eventos e os emisores de chamada especificados."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Personalizar"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Esta acción bloquea TODOS os sons e vibracións, incluídos os das alarmas, música, vídeos e xogos. Aínda podes facer chamadas de teléfono."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Esta acción bloquea TODOS os sons e vibracións, incluídos os das alarmas, música, vídeos e xogos."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Estás conectado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode supervisar a túa actividade persoal na rede, incluídos os correos electrónicos, as aplicacións e os sitios web."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> xestiona o teu perfil de traballo, que está conectado a <xliff:g id="APPLICATION">%2$s</xliff:g>. Esta aplicación pode controlar a túa actividade na rede, mesmo os correos electrónicos, as aplicacións e os sitios web.\n\nPara obter máis información, contacta co administrador."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> xestiona o teu perfil de traballo, que está conectado a <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>. Esta aplicación pode controlar a túa actividade na rede, mesmo os correos electrónicos, as aplicacións e os sitios web.\n\nTamén estás conectado a <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, que pode controlar a túa actividade persoal na rede."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Desbloqueado para: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"Estase executando: <xliff:g id="TRUST_AGENT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"O dispositivo permanecerá bloqueado ata que o desbloquees manualmente"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Recibir notificacións máis rápido"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Consúltaas antes de desbloquear"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Non, grazas"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Configurar"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Desactivar agora"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Finalizar agora"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Ampliar"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Contraer"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"A pantalla está fixada"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Desactivar"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Cos controis de notificacións mellorados, podes asignarlles un nivel de importancia comprendido entre 0 e 5 ás notificacións dunha aplicación determinada. \n\n"<b>"Nivel 5"</b>" \n- Mostrar na parte superior da lista de notificacións. \n- Permitir interrupcións no modo de pantalla completa. \n- Mostrar sempre. \n\n"<b>"Nivel 4"</b>" \n- Impedir interrupcións no modo de pantalla completa. \n- Mostrar sempre. \n\n"<b>"Nivel 3"</b>" \n- Impedir interrupcións no modo de pantalla completa. \n- Non mostrar nunca. \n\n"<b>"Nivel 2"</b>" \n- Impedir interrupcións no modo de pantalla completa. \n- Non mostrar nunca. \n- Non soar nin vibrar nunca. \n\n"<b>"Nivel 1"</b>" \n- Impedir interrupcións no modo de pantalla completa. \n- Non mostrar nunca. \n- Non soar nin vibrar nunca. \n- Ocultar na pantalla de bloqueo e na barra de estado. \n- Mostrar na parte inferior da lista de notificacións. \n\n"<b>"Nivel 0"</b>" \n- Bloquear todas as notificacións da aplicación."</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Notificacións"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Deixarás de recibir estas notificacións."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Deixarás de recibir estas notificacións"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> categorías de notificacións"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Esta aplicación non ten categorías de notificacións"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 de <xliff:g id="NUMBER_1">%d</xliff:g> categorías de notificacións desta aplicación</item>
<item quantity="one">1 de <xliff:g id="NUMBER_0">%d</xliff:g> categoría de notificación desta aplicación</item>
@@ -572,12 +575,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> de <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"controis de notificacións"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"opcións para adiar notificacións"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minutos"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minutos"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 hora"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 horas"</string>
<string name="snooze_undo" msgid="6074877317002985129">"DESFACER"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Adiouse <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Uso de batería"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"A función de aforro da batería non está dispoñible durante a carga"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Aforro de batería"</string>
diff --git a/packages/SystemUI/res/values-gl/strings_car.xml b/packages/SystemUI/res/values-gl/strings_car.xml
index bb3f8eb..e6c6298 100644
--- a/packages/SystemUI/res/values-gl/strings_car.xml
+++ b/packages/SystemUI/res/values-gl/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Conduce de forma segura"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Ten moi en conta as condicións de condución e respecta sempre as leis aplicables. É posible que as indicacións sexan imprecisas, incompletas, perigosas, inadecuadas, estean prohibidas ou que impliquen atravesar áreas administrativas. A información das empresas tamén pode ser imprecisa ou estar incompleta. Os datos non se proporcionan en tempo real e non se garante a precisión da localización. Non manipules o teu dispositivo móbil nin utilices aplicacións que non estean deseñadas para Android Auto mentres conduces."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Descoñecido"</string>
+ <string name="start_driving" msgid="864023351402918991">"Comezar a conducir"</string>
</resources>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 2cb19ed..13e47f9 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"શોધો"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> માટે ઉપર સ્લાઇડ કરો."</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> માટે ડાબે સ્લાઇડ કરો."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"તમને ધ્વનિ કે વાઇબ્રેશનો દ્વારા ખલેલ પહોંચાડવામાં આવશે નહીં, સિવાય કે અલાર્મ, સ્મૃતિપત્રો, ઇવેન્ટ અને તમે ઉલ્લેખ કરો તે કૉલર. તમે સંગીત, વીડિયો અને રમતો સહિત તમે જે કંઈપણ ચલાવવાનું પસંદ કરો તે હજુ પણ સંભળાશે."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"તમને ધ્વનિ કે વાઇબ્રેશનો દ્વારા ખલેલ પહોંચાડવામાં આવશે નહીં, સિવાય કે અલાર્મ. તમે સંગીત, વીડિયો અને રમતો સહિત તમે જે કંઈપણ ચલાવવાનું પસંદ કરો તે હજુ પણ સંભળાશે."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"તમને તમે ઉલ્લેખિત એલાર્મ્સ, સ્મૃતિપત્રો, ઇવેન્ટ્સ અને કૉલર્સ સિવાયના ધ્વનિઓ અને વાઇબ્રેશન્સથી ખલેલ પહોંચાડવામાં આવશે નહીં."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"કસ્ટમાઇઝ કરો"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"આ એલાર્મ્સ, સંગીત, વિડિઓઝ અને રમતો સહિત તમામ ધ્વનિઓ અને વાઇબ્રેશન્સને અવરોધિત કરે છે. તમે હજુ પણ ફોન કૉલ્સ કરવા માટે સમર્થ હશો."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"એલાર્મ્સ, સંગીત, વિડિઓઝ અને રમતો સહિત તમામ ધ્વનિઓ અને વાઇબ્રેશન્સને આ અવરોધિત કરે છે."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"તમે <xliff:g id="APPLICATION">%1$s</xliff:g> સાથે કનેક્ટ થયાં છો, જે ઇમેઇલ્સ, ઍપ્લિકેશનો અને વેબસાઇટ્સ સહિત તમારી વ્યક્તિગત નેટવર્ક પ્રવૃત્તિને મૉનિટર કરી શકે છે."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"તમારી કાર્ય પ્રોફાઇલ <xliff:g id="ORGANIZATION">%1$s</xliff:g> દ્વારા સંચાલિત કરાય છે. આ પ્રોફાઇલ <xliff:g id="APPLICATION">%2$s</xliff:g> સાથે કનેક્ટ કરેલ છે, જે ઇમેઇલ, ઍપ્લિકેશનો અને વેબસાઇટો સહિતની તમારી નેટવર્ક પ્રવૃત્તિનું નિયમન કરી શકે છે.\n\nવધુ માહિતી માટે, તમારા વ્યવસ્થાપકનો સંપર્ક કરો."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"તમારી કાર્ય પ્રોફાઇલ <xliff:g id="ORGANIZATION">%1$s</xliff:g> દ્વારા સંચાલિત થાય છે. આ પ્રોફાઇલ <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> સાથે કનેક્ટ કરેલ છે, જે ઇમેઇલ, ઍપ્લિકેશનો અને વેબસાઇટો સહિતની તમારી નેટવર્ક પ્રવૃત્તિનું નિયમન કરી શકે છે.\n\nતમે <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> સાથે પણ કનેક્ટ કરેલું છે, જે તમારી વ્યક્તિગત નેટવર્ક પ્રવૃત્તિનું નિયમન કરી શકે છે."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> માટે અનલૉક કર્યુ"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> ચાલી રહ્યું છે"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"તમે ઉપકરણને મેન્યુઅલી અનલૉક કરશો નહીં ત્યાં સુધી તે લૉક રહેશે"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"વધુ ઝડપથી સૂચનાઓ મેળવો"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"તમે અનલૉક કરો તે પહેલાં તેમને જુઓ"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"નહીં આભાર"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"સેટ અપ"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"હમણાં બંધ કરો"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"હવે સમાપ્ત કરો"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"વિસ્તૃત કરો"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"સંકુચિત કરો"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"સ્ક્રીન પિન કરેલ છે"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"બંધ"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"પાવર સૂચના નિયંત્રણો સાથે, તમે ઍપ્લિકેશનની સૂચનાઓ માટે 0 થી 5 સુધીના મહત્વના સ્તરને સેટ કરી શકો છો. \n\n"<b>"સ્તર 5"</b>" \n- સૂચના સૂચિની ટોચ પર બતાવો \n- પૂર્ણ સ્ક્રીન અવરોધની મંજૂરી આપો \n- હંમેશાં ત્વરિત દૃષ્ટિ કરો \n\n"<b>"સ્તર 4"</b>" \n- પૂર્ણ સ્ક્રીન અવરોધ અટકાવો \n- હંમેશાં ત્વરિત દૃષ્ટિ કરો \n\n"<b>"સ્તર 3"</b>" \n- પૂર્ણ સ્ક્રીન અવરોધ અટકાવો \n- ક્યારેય ત્વરિત દૃષ્ટિ કરશો નહીં \n\n"<b>"સ્તર 2"</b>" \n- પૂર્ણ સ્ક્રીન અવરોધ અટકાવો \n- ક્યારેય ત્વરિત દૃષ્ટિ કરશો નહીં \n- ક્યારેય અવાજ અને વાઇબ્રેશન કરશો નહીં \n\n"<b>"સ્તર 1"</b>" \n- પૂર્ણ સ્ક્રીન અવરોધ અટકાવો \n- ક્યારેય ત્વરિત દૃષ્ટિ કરશો નહીં \n- ક્યારેય અવાજ અથવા વાઇબ્રેટ કરશો નહીં \n- લૉક સ્ક્રીન અને સ્થિતિ બારથી છુપાવો \n- સૂચના સૂચિના તળિયા પર બતાવો \n\n"<b>"સ્તર 0"</b>" \n- ઍપ્લિકેશનની તમામ સૂચનાઓને અવરોધિત કરો"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"સૂચનાઓ"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"તમને હવે આ સૂચનાઓ મળશે નહીં."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"તમને હવે આ સૂચનાઓ મળશે નહીં"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> સૂચના કૅટેગરીઓ"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"આ ઍપ્લિકેશનમાં સૂચના કૅટેગરી નથી"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">આ ઍપ્લિકેશનમાંની <xliff:g id="NUMBER_1">%d</xliff:g> સૂચના કૅટેગરીમાંથી 1</item>
<item quantity="other">આ ઍપ્લિકેશનમાંની <xliff:g id="NUMBER_1">%d</xliff:g> સૂચના કૅટેગરીમાંથી 1</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"સૂચના નિયંત્રણો"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"સૂચના સ્નૂઝ કરવાના વિકલ્પો"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 મિનિટ"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 મિનિટ"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 કલાક"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 કલાક"</string>
<string name="snooze_undo" msgid="6074877317002985129">"પૂર્વવત્ કરો"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> માટે સ્નૂઝ કરો"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"બૅટરી વપરાશ"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"ચાર્જિંગ દરમિયાન બૅટરી બચતકર્તા ઉપલબ્ધ નથી"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"બૅટરી બચતકર્તા"</string>
diff --git a/packages/SystemUI/res/values-gu/strings_car.xml b/packages/SystemUI/res/values-gu/strings_car.xml
index b22b688..57a6f02 100644
--- a/packages/SystemUI/res/values-gu/strings_car.xml
+++ b/packages/SystemUI/res/values-gu/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"સુરક્ષિત રીતે વાહન ચલાવો"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"ડ્રાઇવિંગ સ્થિતિઓથી સંપૂર્ણપણે વાકેફ રહો અને હંમેશા લાગુ કાયદાઓનું પાલન કરો. દિશા નિર્દેશો અચોક્કસ, અપૂર્ણ, જોખમમકારક, બિન અનુકૂળ, પ્રતિબંધિત અથવા વહીવટી વિસ્તારોને ઓળંગવાનું સમાવતા હોઇ શકે છે. વ્યવસાય માહિતી પણ અચોક્કસ અથવા અપૂર્ણ હોઇ શકે છે. ડેટા રિઅલ-ટાઇમ નથી અને સ્થાન ચોકસાઈની ગેરંટી આપી શકતાં નથી. ડ્રાઇવિંગ કરતી વખતે Android Auto માટે તમારું મોબાઇલ સાધન હેન્ડલ કરવું અથવા ઍપ્લિકેશનનો ઉપયોગ કરવાનું કોઇ પ્રયોજન નથી."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"અજાણ"</string>
+ <string name="start_driving" msgid="864023351402918991">"ડ્રાઇવિંગ શરૂ કરો"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index e5c013f..65eda75 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"खोजें"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> के लिए ऊपर स्लाइड करें."</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> के लिए बाएं स्लाइड करें."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"आपको अलार्म, रिमाइंडर, इवेंट और आपने जिन कॉलर के बारे में विशेष रूप से बताया है, उन्हें छोड़कर ध्वनियों और कंपनों से परेशान नहीं किया जाएगा. आपको अभी भी संगीत, वीडियो और गेम सहित वह सब कुछ सुनाई देगा जो आपने चलाने के लिए चुना है."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"आपको अलार्म छोड़कर दूसरी ध्वनियों और कंपनों से परेशान नहीं किया जाएगा. आपको अभी भी संगीत, वीडियो और गेम सहित वह सब कुछ सुनाई देगा जो आपने चलाने के लिए चुना है."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"आपको आपके द्वारा निर्दिष्ट किए गए अलार्म, रिमाइंडर्स, इवेंट और कॉलर को छोड़कर अन्य ध्वनियों और कंपनों के द्वारा परेशान नहीं किया जाएगा."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"कस्टमाइज़ करें"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"इससे अलार्म, संगीत, वीडियो और गेम सहित सभी ध्वनियां और कंपन अवरुद्ध हो जाते हैं. आप अभी भी फ़ोन काॅल कर सकेंगे."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"इससे अलार्म, संगीत, वीडियो और गेम सहित सभी ध्वनियां और कंपन अवरुद्ध हो जाते हैं."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"आप <xliff:g id="APPLICATION">%1$s</xliff:g> से कनेक्ट हैं, जो ईमेल, ऐप्लिकेशन और वेबसाइट सहित आपकी व्यक्तिगत नेटवर्क गतिविधि को मॉनिटर कर सकता है."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"आपकी कार्य प्रोफ़ाइल का प्रबंधन <xliff:g id="ORGANIZATION">%1$s</xliff:g> करता है. प्रोफ़ाइल <xliff:g id="APPLICATION">%2$s</xliff:g> से कनेक्ट है, जो ईमेल, ऐप्लिकेशन और वेबसाइटों सहित आपकी नेटवर्क गतिविधि की निगरानी कर सकता है.\n\nअधिक जानकारी के लिए, अपने व्यवस्थापक से संपर्क करें."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"आपकी कार्य प्रोफ़ाइल का प्रबंधन <xliff:g id="ORGANIZATION">%1$s</xliff:g> करता है. प्रोफ़ाइल <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> से कनेक्ट है, जो ईमेल, ऐप्लिकेशन और वेबसाइटों सहित आपकी नेटवर्क गतिविधि की निगरानी कर सकता है.\n\nआप <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> से भी कनेक्ट हैं, जो आपकी व्यक्तिगत नेटवर्क गतिविधि की निगरानी कर सकता है."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> के लिए अनलॉक किया गया"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> चल रहा है"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"जब तक कि आप मैन्युअल रूप से अनलॉक नहीं करते तब तक डिवाइस लॉक रहेगा"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"सूचनाएं अधिक तेज़ी से प्राप्त करें"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"आपके द्वारा उन्हें अनलॉक किए जाने से पहले देखें"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"रहने दें"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"सेट करें"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"अभी बंद करें"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"अब समाप्त करें"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"विस्तृत करें"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"संक्षिप्त करें"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"स्क्रीन पिन कर दी गई है"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"बंद"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"पावर नोटिफ़िकेशन नियंत्रण के द्वारा, आप किसी ऐप्लिकेशन के नोटिफ़िकेशन के लिए 0 से 5 तक महत्व का लेवल सेट कर सकते हैं. \n\n"<b>"लेवल 5"</b>" \n- नोटिफ़िकेशन सूची के शीर्ष पर दिखाएं \n- पूर्ण स्क्रीन बाधा की अनुमति दें \n- हमेशा तांक-झांक करें \n\n"<b>"लेवल 4"</b>" \n- पूर्ण स्क्रीन बाधा को रोकें \n- हमेशा तांक-झांक करें \n\n"<b>"लेवल 3"</b>" \n- पूर्ण स्क्रीन बाधा को रोकें \n- कभी भी तांक-झांक ना करें \n\n"<b>"लेवल 2"</b>" \n- पूर्ण स्क्रीन बाधा को रोकें \n- कभी भी तांक-झांक ना करें \n- कभी भी ध्वनि या कंपन ना करें \n\n"<b>"लेवल 1"</b>" \n- पूर्ण स्क्रीन बाधा को रोकें \n- कभी भी तांक-झांक ना करें \n- कभी भी ध्वनि या कंपन ना करें \n- लॉक स्क्रीन और स्थिति बार से छिपाएं \n- नोटिफ़िकेशन सूची के नीचे दिखाएं \n\n"<b>"लेवल 0"</b>" \n- ऐप्लिकेशन के सभी नोटिफ़िकेशन अवरुद्ध कर दें"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"नोटिफ़िकेशन"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"अब आपको ये नोटिफ़िकेशन नहीं मिलेंगे."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"अब आपको ये नोटिफ़िकेशन नहीं मिलेंगे"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"नोटिफ़िकेशन की <xliff:g id="NUMBER">%d</xliff:g> श्रेणियां"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"इस ऐप्लिकेशन में नोटिफ़िकेशन श्रेणियां नहीं हैं"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">इस ऐप्लिकेशन की <xliff:g id="NUMBER_1">%d</xliff:g> नोटिफ़िकेशन श्रेणियों में से 1 श्रेणी</item>
<item quantity="other">इस ऐप्लिकेशन की <xliff:g id="NUMBER_1">%d</xliff:g> नोटिफ़िकेशन श्रेणियों में से 1 श्रेणी</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"नोटिफ़िकेशन नियंत्रण"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"नोटिफ़िकेशन की याद दिलाने के विकल्प"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 मिनट"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 मिनट"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 घंटा"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 घंटे"</string>
<string name="snooze_undo" msgid="6074877317002985129">"पहले जैसा करें"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> के लिए याद दिलाया गया"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"बैटरी उपयोग"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"चार्ज किए जाने के दौरान बैटरी सेवर उपलब्ध नहीं है"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"बैटरी सेवर"</string>
diff --git a/packages/SystemUI/res/values-hi/strings_car.xml b/packages/SystemUI/res/values-hi/strings_car.xml
index a643bd8..3cf1fb3 100644
--- a/packages/SystemUI/res/values-hi/strings_car.xml
+++ b/packages/SystemUI/res/values-hi/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"सुरक्षित ढंग से गाड़ी चलाएं"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"गाड़ी चलाने की स्थितियों के प्रति पूरी तरह से सतर्क रहें और हमेशा लागू कानूनों का पालन करें. दिशाएं गलत, अपूर्ण, खतरनाक, निषिद्ध हो सकती हैं या उनमें प्रशासनिक क्षेत्रों को पार करना शामिल हो सकता है. व्यावसायिक जानकारी भी गलत या अपूर्ण हो सकती है. डेटा रीयल-टाइम नहीं है और स्थान सटीकता की गारंटी नहीं दी जा सकती. गाड़ी चलाते समय अपने मोबाइल डिवाइस या फ़ोन या Android Auto के लिए अभिप्रेत न किए गए ऐप्स का उपयोग ना करें."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"अज्ञात"</string>
+ <string name="start_driving" msgid="864023351402918991">"ड्राइविंग शुरू करें"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index e71a6a6..99df2bf 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Pretraživanje"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Kliznite prema gore za <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Kliznite lijevo za <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Neće vas ometati zvukovi i vibracije, osim alarma, podsjetnika, događaja i pozivatelja koje navedete. I dalje ćete čuti sve što želite reproducirati, uključujući glazbu, videozapise i igre."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Neće vas ometati zvukovi i vibracije, osim alarma. I dalje ćete čuti sve što želite reproducirati, uključujući glazbu, videozapise i igre."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Neće vas prekidati zvukovi i vibracije, osim alarma, podsjetnika, događaja i pozivatelja koje navedete."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Prilagodi"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"To blokira SVE zvukove i vibracije, uključujući alarme, glazbu, videozapise i igre. I dalje ćete moći telefonirati."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"To blokira SVE zvukove i vibracije, uključujući alarme, glazbu, videozapise i igre."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Povezani ste s aplikacijom <xliff:g id="APPLICATION">%1$s</xliff:g> koja može nadzirati vašu osobnu aktivnost na mreži, uključujući e-poštu, aplikacije i web-lokacije."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Vašim radnim profilom upravlja organizacija <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profil je povezan s aplikacijom <xliff:g id="APPLICATION">%2$s</xliff:g> koja može nadzirati vaše poslovne aktivnosti na mreži, uključujući e-poruke, aplikacije i web-lokacije.\n\nAko vam je potrebno više informacija, obratite se administratoru."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Vašim radnim profilom upravlja organizacija <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profil je povezan s aplikacijom <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> koja može nadzirati vaše poslovne aktivnosti na mreži, uključujući e-poruke, aplikacije i web-lokacije.\n\nPovezani ste i s aplikacijom <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> koja može nadzirati vaše osobne aktivnosti na mreži."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Otključano za korisnika <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"Izvodi se <xliff:g id="TRUST_AGENT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Uređaj će ostati zaključan dok ga ručno ne otključate"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Primajte obavijesti brže"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Pogledajte ih prije otključavanja"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Ne, hvala"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Postavi"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Odmah isključi"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Prekini sada"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Proširivanje"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Sažimanje"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Zaslon je prikvačen"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Isključeno"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Napredne kontrole obavijesti omogućuju vam da postavite razinu važnosti za obavijesti aplikacije od 0 do 5. \n\n"<b>"Razina 5"</b>" \n– prikaži na vrhu popisa obavijesti \n– dopusti prekide prikaza na cijelom zaslonu \n– uvijek dopusti brzi pregled \n\n"<b>"Razina 4"</b>" \n– onemogući prekid prikaza na cijelom zaslonu \n– uvijek dopusti brzi pregled \n\n"<b>"Razina 3"</b>" \n– onemogući prekid prikaza na cijelom zaslonu \n– nikad ne dopusti brzi pregled\n\n"<b>"Razina 2"</b>" \n– onemogući prekid prikaza na cijelom zaslonu \n– nikad ne dopusti brzi pregled \n– nikad ne emitiraj zvuk ni vibraciju \n\n"<b>"Razina 1"</b>" \n– onemogući prekid prikaza na cijelom zaslonu \n– nikad ne dopusti brzi pregled \n– nikad ne emitiraj zvuk ni vibraciju \n– ne prikazuj na zaključanom zaslonu i traci statusa \n– prikaži na dnu popisa obavijesti \n\n"<b>"Razina 0"</b>" \n– blokiraj sve obavijesti aplikacije"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Obavijesti"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Više nećete primati te obavijesti."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Više nećete primati te obavijesti"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"Broj kategorija obavijesti: <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Ova aplikacija nema kategorije obavijesti"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 od <xliff:g id="NUMBER_1">%d</xliff:g> kategorije obavijesti iz ove aplikacije</item>
<item quantity="few">1 od <xliff:g id="NUMBER_1">%d</xliff:g> kategorije obavijesti iz ove aplikacije</item>
@@ -574,12 +577,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"kontrole obavijesti"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"opcije odgode obavijesti"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minuta"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minuta"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 sat"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 sata"</string>
<string name="snooze_undo" msgid="6074877317002985129">"PONIŠTI"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Odgođeno <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Potrošnja baterije"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Štednja baterije nije dostupna tijekom punjenja"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Štednja baterije"</string>
diff --git a/packages/SystemUI/res/values-hr/strings_car.xml b/packages/SystemUI/res/values-hr/strings_car.xml
index 034bd71d..d38620b 100644
--- a/packages/SystemUI/res/values-hr/strings_car.xml
+++ b/packages/SystemUI/res/values-hr/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Vozite sigurno"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Pažljivo pratite promet i uvijek se pridržavajte primjenjivih zakona. Upute mogu biti neprecizne, nepotpune, opasne, neprikladne, zabranjene ili mogu uključivati prelazak administrativnih granica. Podaci o tvrtkama također mogu biti neprecizni i nepotpuni. Podaci nisu u stvarnom vremenu i preciznost lokacije nije zajamčena. Tijekom vožnje ne rukujte mobilnim uređajem i ne upotrebljavajte aplikacije koje nisu namijenjene za Android Auto."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Nepoznato"</string>
+ <string name="start_driving" msgid="864023351402918991">"Početak vožnje"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 05dd386..2afaf61 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Keresés"</string>
<string name="description_direction_up" msgid="7169032478259485180">"A(z) <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> művelethez csúsztassa felfelé."</string>
<string name="description_direction_left" msgid="7207478719805562165">"A(z) <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> művelethez csúsztassa balra."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Az Ön által meghatározott ébresztéseken, emlékeztetőkön, eseményeken és hívókon kívül nem fogja Önt más hang vagy rezgés megzavarni. Továbbra is lesz hangjuk azoknak a tartalmaknak, amelyeket Ön elindít, például zenék, videók és játékok."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Az ébresztéseken kívül nem fogja Önt más hang és rezgés megzavarni. Továbbra is lesz hangjuk azoknak a tartalmaknak, amelyeket Ön elindít, például zenék, videók és játékok."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Nem zavarják majd hanghatások, sem rezgés, kivéve az ébresztéseket, emlékeztetőket, eseményeket és az Ön által megjelölt hívókat."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Személyre szabás"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Ez letiltja az ÖSSZES hanghatást és rezgést, beleértve az ébresztések, zeneszámok, videók és játékok hangjait is. Telefonhívást továbbra is indíthat majd."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Ez letiltja az ÖSSZES hanghatást és rezgést, beleértve az ébresztések, zeneszámok, videók és játékok hangjait is."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Ön a(z) <xliff:g id="APPLICATION">%1$s</xliff:g> alkalmazáshoz csatlakozik, amely figyelheti személyes hálózati tevékenységét, beleértve az e-maileket, alkalmazásokat és webhelyeket."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Munkaprofilját a(z) <xliff:g id="ORGANIZATION">%1$s</xliff:g> kezeli. A profil csatlakozik a(z) <xliff:g id="APPLICATION">%2$s</xliff:g> alkalmazáshoz, amely figyelheti az Ön hálózati tevékenységeit, beleértve az e-maileket, alkalmazásokat és webhelyeket.\n\nTovábbi információért forduljon a rendszergazdához."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Munkaprofilját a(z) <xliff:g id="ORGANIZATION">%1$s</xliff:g> kezeli. A profil csatlakozik a(z) <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> alkalmazáshoz, amely figyelheti az Ön hálózati tevékenységeit, beleértve az e-maileket, alkalmazásokat és webhelyeket.\n\nCsatlakoztatta továbbá a(z) <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> alkalmazást, amely figyelheti az Ön személyes hálózati tevékenységeit."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Felnyitva a következő számára: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"A(z) <xliff:g id="TRUST_AGENT">%1$s</xliff:g> jelenleg fut"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Az eszköz addig zárolva marad, amíg kézileg fel nem oldja"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Gyorsabban megkaphatja az értesítéseket"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Már a képernyőzár feloldása előtt megtekintheti őket"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Nem, köszönöm"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Beállítás"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Kikapcsolás most"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Befejezés most"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Kibontás"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Összecsukás"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"A képernyő rögzítve van"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Kikapcsolva"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Az értesítési beállítások révén 0-tól 5-ig állíthatja be a fontossági szintet az alkalmazás értesítéseinél. \n\n"<b>"5. szint"</b>" \n– Megjelenítés az értesítési lista tetején \n– Teljes képernyő megszakításának engedélyezése \n– Mindig felugrik \n\n"<b>"4. szint"</b>" \n– Teljes képernyő megszakításának megakadályozása \n– Mindig felugrik \n\n"<b>"3. szint"</b>" \n– Teljes képernyő megszakításának megakadályozása \n– Soha nem ugrik fel \n\n"<b>"2. szint"</b>" \n– Teljes képernyő megszakításának megakadályozása \n– Soha nem ugrik fel \n– Soha nincs hangjelzés és rezgés \n\n"<b>"1. szint"</b>" \n– Teljes képernyő megszakításának megakadályozása \n– Soha nem ugrik fel \n– Soha nincs hangjelzés vagy rezgés \n– Elrejtés a lezárási képernyőről és az állapotsávról \n– Megjelenítés az értesítési lista alján \n\n"<b>"0. szint"</b>" \n– Az alkalmazás összes értesítésének letiltása"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Értesítések"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Többé nem jelennek meg ezek az értesítések."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Többé nem jelennek meg ezek az értesítések"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> értesítéskategória"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Az alkalmazás nem rendelkezik értesítési kategóriákkal"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other"><xliff:g id="NUMBER_1">%d</xliff:g>/1 értesítési kategória az alkalmazásból</item>
<item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g>/1 értesítési kategória az alkalmazásból</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> – <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"értesítésvezérlők"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"értesítések halasztási beállításai"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 perc"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 perc"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 óra"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 óra"</string>
<string name="snooze_undo" msgid="6074877317002985129">"VISSZAVONÁS"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Elhalasztva: <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Akkumulátorhasználat"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Az Akkumulátorkímélő módot töltés közben nem lehet használni"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Akkumulátorkímélő mód"</string>
diff --git a/packages/SystemUI/res/values-hu/strings_car.xml b/packages/SystemUI/res/values-hu/strings_car.xml
index 688d88b..46bf09d 100644
--- a/packages/SystemUI/res/values-hu/strings_car.xml
+++ b/packages/SystemUI/res/values-hu/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Vezessen óvatosan"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Mindig legyen teljes mértékben tisztában a vezetési körülményekkel, és a vonatkozó törvényeket tartsa be! Az útvonaltervek pontatlanok, hiányosak, veszélyesek, nem megfelelők vagy tiltottak lehetnek, illetve a közforgalom számára nem használható utakat érinthetnek. Az üzleti információk is pontatlanok vagy hiányosak lehetnek. Az adatok nem valós idejűek, ezért nem garantálhatjuk a helyadatok pontosságát. Vezetés közben ne kezelje mobileszközét, illetve ne használjon olyan alkalmazásokat az Android Auto rendszerrel, amelyeket nem ahhoz terveztek."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Ismeretlen"</string>
+ <string name="start_driving" msgid="864023351402918991">"Kezdhet vezetni"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 12c4b60..cc6608a 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Որոնել"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Սահեցրեք վերև <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>-ի համար:"</string>
<string name="description_direction_left" msgid="7207478719805562165">"Սահեցրեք ձախ` <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>-ի համար:"</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Ձայները և թրթռոցները չեն անհանգստացնի ձեզ, բացի ձեր կողմից նշված զարթուցիչները, հիշեցումները, միջոցառումների ծանուցումները և զանգերը։ Դուք կլսեք ձեր ընտրածի նվագարկումը, այդ թվում՝ երաժշտություն, տեսանյութեր և խաղեր:"</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Ձայները և թրթռոցները, բացի զարթուցիչներից, չեն անհանգստացնի ձեզ: Դուք կլսեք ձեր ընտրածի նվագարկումը, այդ թվում՝ երաժշտություն, տեսանյութեր և խաղեր:"</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Բոլոր ձայներն ու թրթռոցները կանջատվեն։ Ձեզ կանհանգստացնեն միայն զարթուցիչը, հիշեցումները, միջոցառումների մասին ծանուցումները և զանգերը ձեր ընտրած մարդկանցից։"</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Հարմարեցնել"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Այս գործողությունն արգելափակում է ԲՈԼՈՐ ձայներն ու թրթռոցները, այդ թվում զարթուցիչները, երաժշտությունը, տեսանյութերի և խաղերի ձայները: Դուք կկարողանաք հեռախոսազանգեր անել։"</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Այս գործողությունն արգելափակում է ԲՈԼՈՐ ձայներն ու թրթռոցները, այդ թվում նաև զարթուցիչների, երաժշտության, տեսանյութերի և խաղերի ձայներն ու թրթռոցները:"</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Դուք կապակցված եք <xliff:g id="APPLICATION">%1$s</xliff:g> հավելվածին, որը կարող է վերահսկել անձնական ցանցում կատարած ձեր գործողությունները, այդ թվում նաև էլփոստի հաշիվները, հավելվածները և կայքերը:"</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Ձեր աշխատանքային պրոֆիլի կառավարիչն է <xliff:g id="ORGANIZATION">%1$s</xliff:g> կազմակերպությունը: Այն կապակցված է <xliff:g id="APPLICATION">%2$s</xliff:g> հավելվածին, որը կարող է վերահսկել աշխատանքային ցանցում կատարած գործունեությունը, այդ թվում նաև էլփոստի հաշիվները, հավելվածները և կայքերը:\n\nԼրացուցիչ տեղեկություններ ստանալու համար դիմեք ձեր ադմինիստրատորին։"</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Ձեր աշխատանքային պրոֆիլի կառավարիչն է <xliff:g id="ORGANIZATION">%1$s</xliff:g> կազմակերպությունը: Այն կապակցված է <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> հավելվածին, որը կարող է վերահսկել աշխատանքային ցանցում կատարած գործունեությունը, այդ թվում նաև էլփոստի հաշիվները, հավելվածները և կայքերը:\n\nԴուք կապակցված եք նաև <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> հավելվածին, որը կարող է վերահսկել անձնական ցանցում կատարած ձեր գործողությունները:"</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Ապակողպվել է <xliff:g id="USER_NAME">%1$s</xliff:g> օգտատիրոջ համար"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g>-ն աշխատում է"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Սարքը կմնա արգելափակված՝ մինչև ձեռքով չբացեք"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Ավելի արագ ստացեք ծանուցումները"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Տեսեք դրանք մինչև ապակողպելը"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Ոչ"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Կարգավորել"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Անջատել հիմա"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Ավարտել"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Ընդարձակել"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Կոծկել"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Էկրանն ամրացված է"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Անջատել"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Ծանուցումների ընդլայնված կառավարման օգնությամբ կարող եք յուրաքանչյուր հավելվածի ծանուցումների համար նշանակել կարևորության աստիճան՝ 0-5 սահմաններում: \n\n"<b>"5-րդ աստիճան"</b>" \n- Ցուցադրել ծանուցումների ցանկի վերևում \n- Թույլատրել լիաէկրան ընդհատումները \n- Միշտ ցուցադրել կարճ ծանուցումները \n\n"<b>"4-րդ աստիճան"</b>" \n- Արգելել լիաէկրան ընդհատումները \n- Միշտ ցուցադրել կարճ ծանուցումները \n\n"<b>"3-րդ աստիճան"</b>" \n- Արգելել լիաէկրան ընդհատումները \n- Արգելել կարճ ծանուցումների ցուցադրումը \n\n"<b>"2-րդ աստիճան"</b>" \n- Արգելել լիաէկրան ընդհատումները \n- Արգելել կարճ ծանուցումների ցուցադրումը \n- Անջատել ձայնը և թրթռումը \n\n"<b>"1-ին աստիճան"</b>" \n- Արգելել լիաէկրան ընդհատումները \n- Արգելել կարճ ծանուցումների ցուցադրումը \n- Անջատել ձայնը և թրթռումը \n- Չցուցադրել կողպէկրանում և կարգավիճակի գոտում \n- Ցուցադրել ծանուցումների ցանկի ներքևում \n\n"<b>"0-րդ աստիճան"</b>\n"- Արգելափակել հավելվածի բոլոր ծանուցումները"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Ծանուցումներ"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Այս ծանուցումներն այլևս չեք ստանա։"</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Այս ծանուցումներն այլևս չեք ստանա"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> ծանուցման կատեգորիաներ"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Այս հավելվածը ծանուցման կատեգորիաներ չունի"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 out of <xliff:g id="NUMBER_1">%d</xliff:g> notification categories from this app</item>
<item quantity="other">1 ալիք` այս հավելվածի <xliff:g id="NUMBER_1">%d</xliff:g> կատեգորիաներից</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"ծանուցման կառավարներ"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"ծանուցման հետաձգման ընտրանքներ"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 րոպե"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 րոպե"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 ժամ"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 ժամ"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ՀԵՏԱՐԿԵԼ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Հետաձգվել է <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>ով"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Մարտկոցի օգտագործում"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Մարտկոցի տնտեսումը լիցքավորման ժամանակ հասանելի չէ"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Մարտկոցի տնտեսում"</string>
diff --git a/packages/SystemUI/res/values-hy/strings_car.xml b/packages/SystemUI/res/values-hy/strings_car.xml
index 4f214f7..3d7f225 100644
--- a/packages/SystemUI/res/values-hy/strings_car.xml
+++ b/packages/SystemUI/res/values-hy/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Վարեք ապահով"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Երթևեկության պայմաններին միշտ լավատեղյակ եղեք և կատարեք համապատասխան օրենքների պահանջները: Երթուղիները կարող են լինել սխալ, կիսատ, վտանգավոր, ոչ պատշաճ, արգելված կամ կարող են անցնել վարչական միավորների միջով: Բիզնես տվյալները նույնպես կարող են լինել սխալ կամ կիսատ: Տվյալներն իրական ժամանակում չեն թարմացվում, իսկ տեղադրության ճշգրտությունը չի կարող երաշխավորվել: Մեքենա վարելիս մի օգտագործեք ձեր շարժական սարքը, ինչպես նաև Android Auto-ի համար չնախատեսված հավելվածները:"</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Անհայտ"</string>
+ <string name="start_driving" msgid="864023351402918991">"Սկսել վարումը"</string>
</resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 5f72bb2..e71a999 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Telusuri"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Geser ke atas untuk <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Geser ke kiri untuk <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Anda tidak akan terganggu oleh suara dan getaran, kecuali dari alarm, pengingat, acara, dan penelepon yang Anda tentukan. Anda akan tetap mendengar apa pun yang telah dipilih untuk diputar, termasuk musik, video, dan game."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Anda tidak akan terganggu oleh suara dan getaran, kecuali dari alarm. Anda akan tetap mendengar apa pun yang telah dipilih untuk diputar, termasuk musik, video, dan game."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Anda tidak akan diganggu oleh suara dan getaran, kecuali dari alarm, pengingat, acara, dan penelepon tertentu."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Sesuaikan"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"SEMUA suara dan getaran, termasuk dari alarm, musik, video, dan game akan diblokir. Anda tetap dapat melakukan panggilan telepon."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"SEMUA suara dan getaran, termasuk dari alarm, musik, video, dan game akan diblokir."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Anda tersambung ke <xliff:g id="APPLICATION">%1$s</xliff:g>, yang dapat memantau aktivitas jaringan pribadi, termasuk email, aplikasi, dan situs web.."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Profil kerja Anda dikelola oleh <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profil tersambung ke <xliff:g id="APPLICATION">%2$s</xliff:g>, yang dapat memantau aktivitas jaringan kerja, termasuk email, aplikasi, dan situs.\n\nHubungi admin untuk mendapatkan informasi lebih lanjut."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Profil kerja Anda dikelola oleh <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profil tersambung ke <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, yang dapat memantau aktivitas jaringan, termasuk email, aplikasi, dan situs.\n\nAnda juga tersambung ke <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, yang dapat memantau aktivitas jaringan pribadi."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Dibuka kuncinya untuk <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> sedang berjalan"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Perangkat akan tetap terkunci hingga Anda membukanya secara manual"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Dapatkan pemberitahuan lebih cepat"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Lihat sebelum membuka kunci"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Tidak"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Siapkan"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Nonaktifkan sekarang"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Akhiri sekarang"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Luaskan"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Ciutkan"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Layar dipasangi pin"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Nonaktif"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Dengan kontrol notifikasi daya, Anda dapt menyetel level kepentingan notifikasi aplikasi dari 0 sampai 5. \n\n"<b>"Level 5"</b>" \n- Muncul di atas daftar notifikasi \n- Izinkan interupsi layar penuh \n- Selalu intip pesan \n\n"<b>"Level 4"</b>" \n- Jangan interupsi layar penuh \n- Selalu intip pesan \n\n"<b>"Level 3"</b>" \n- Jangan interupsi layar penuh \n- Tak pernah intip pesan \n\n"<b>"Level 2"</b>" \n- Jangan interupsi layar penuh \n- Tak pernah intip pesan \n- Tanpa suara dan getaran \n\n"<b>"Level 1"</b>" \n- Jangan interupsi layar penuh \n- Tak pernah intip pesan \n- Tanpa suara atau getaran \n- Sembunyikan dari layar kunci dan bilah status \n- Muncul di bawah daftar notifikasi \n\n"<b>"Level 0"</b>" \n- Blokir semua notifikasi dari aplikasi"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Notifikasi"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Anda tidak akan mendapatkan notifikasi ini lagi."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Anda tidak akan mendapatkan notifikasi ini lagi"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> kategori notifikasi"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Aplikasi ini tidak memiliki kategori notifikasi"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 dari <xliff:g id="NUMBER_1">%d</xliff:g> kategori notifikasi dari aplikasi ini</item>
<item quantity="one">1 dari <xliff:g id="NUMBER_0">%d</xliff:g> kategori notifikasi dari aplikasi ini</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"kontrol notifikasi"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"opsi tunda notifikasi"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 menit"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 menit"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 jam"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 jam"</string>
<string name="snooze_undo" msgid="6074877317002985129">"URUNG"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Ditunda selama <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Pemakaian baterai"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Penghemat Baterai tidak tersedia selama pengisian daya"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Penghemat Baterai"</string>
diff --git a/packages/SystemUI/res/values-in/strings_car.xml b/packages/SystemUI/res/values-in/strings_car.xml
index cc23ecf..09f31b0 100644
--- a/packages/SystemUI/res/values-in/strings_car.xml
+++ b/packages/SystemUI/res/values-in/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Hati-hati saat berkendara"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Tetap perhatikan keadaan saat mengemudi dan selalu patuhi peraturan yang berlaku. Petunjuk arah mungkin tidak akurat, tidak lengkap, berbahaya, tidak cocok, terlarang, atau dapat melewati wilayah administratif. Informasi bisnis juga mungkin tidak akurat atau tidak lengkap. Data tidak dalam waktu nyata dan keakuratan lokasi tidak dijamin. Jangan menggunakan perangkat seluler atau aplikasi yang tidak berkaitan dengan Android Auto saat mengemudi."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Tidak diketahui"</string>
+ <string name="start_driving" msgid="864023351402918991">"Mulai Mengemudi"</string>
</resources>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index ce665e2..bc5bcaa 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Leita"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Strjúktu upp til að <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Strjúktu til vinstri til að <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Þú verður ekki fyrir truflunum frá hljóðmerkjum og titringi, fyrir utan vekjara, áminningar, viðburði og símtöl frá þeim sem þú leyfir fyrirfram. Þú heyrir áfram í öllu sem þú velur að spila, svo sem tónlist, myndskeiðum og leikjum."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Þú verður ekki fyrir truflunum frá hljóðmerkjum og titringi, fyrir utan vekjara. Þú heyrir áfram í öllu sem þú velur að spila, svo sem tónlist, myndskeiðum og leikjum."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Þú verður ekki fyrir truflunum af hljóðmerkjum og titringi, fyrir utan vekjara, áminningar, viðburði og símtöl sem þú leyfir fyrir fram."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Sérsníða"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Þetta lokar á ÖLL hljóðmerki og titring, þ.m.t. frá vekjurum, tónlist, myndskeiðum og leikjum. Þú getur áfram hringt símtöl."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Þetta lokar á ÖLL hljóðmerki og titring, þ.m.t. frá vekjurum, tónlist, myndskeiðum og leikjum."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Þú ert með tengingu við <xliff:g id="APPLICATION">%1$s</xliff:g>, sem getur fylgst með persónulegri netnotkun þinni, þ. á m. tölvupósti, forritum og vefsvæðum."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Vinnusniðinu þínu er stýrt af <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Sniðið er tengt <xliff:g id="APPLICATION">%2$s</xliff:g>, sem getur fylgst með netnotkun þinni, þ. á m. tölvupósti, forritum og vefsvæðum\n\nFrekari upplýsingar fást hjá kerfisstjóra."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Vinnusniðinu þínu er stýrt af <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Sniðið er tengt <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, sem getur fylgst með netnotkun þinni, þ. á m. tölvupósti, forritum og vefsvæðum\n\nÞú ert einnig með tengingu við <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, sem getur fylgst með persónulegri netnotkun þinni."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Opnað fyrir <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> er í gangi"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Tækið verður læst þar til þú opnar það handvirkt"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Fáðu tilkynningar hraðar"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Sjáðu þær áður en þú opnar"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Nei, takk"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Setja upp"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Slökkva núna"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Hætta núna"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Stækka"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Minnka"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Skjárinn er festur"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Slökkt"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Með orkutilkynningastýringum geturðu stillt mikilvægi frá 0 upp í 5 fyrir tilkynningar forrita. \n\n"<b>"Stig 5"</b>" \n- Sýna efst á tilkynningalista \n- Leyfa truflun þegar birt er á öllum skjánum \n- Kíkja alltaf \n\n"<b>"Stig 4"</b>" \n- Hindra truflun við birtingu á öllum skjánum \n- Kíkja alltaf \n\n"<b>"Stig 3"</b>" \n- Hindra truflun við birtingu á öllum skjánum \n- Kíkja aldrei \n\n"<b>"Stig 2"</b>" \n- Hindra truflun við birtingu á öllum skjánum \n- Kíkja aldrei \n- Slökkva á hljóði og titringi \n\n"<b>"Stig 1"</b>" \n- Hindra truflun við birtingu á öllum skjánum \n- Kíkja aldrei \n- Slökkva á hljóði og titringi \n- Fela á lásskjá og stöðustiku \n- Sýna neðst á tilkynningalista \n\n"<b>"Stig 0"</b>" \n- Setja allar tilkynningar frá forriti á bannlista"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Tilkynningar"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Þú færð þessar tilkynningar ekki framar."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Þú færð þessar tilkynningar ekki framar"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> tilkynningaflokkar"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Þetta forrit er ekki með tilkynningaflokka"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 af <xliff:g id="NUMBER_1">%d</xliff:g> tilkynningaflokki frá þessu forriti</item>
<item quantity="other">1 af <xliff:g id="NUMBER_1">%d</xliff:g> tilkynningaflokkum frá þessu forriti</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"tilkynningastýringar"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"þöggunarstillingar tilkynninga"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 mínútur"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 mínútur"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 klukkustund"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 klukkustundir"</string>
<string name="snooze_undo" msgid="6074877317002985129">"AFTURKALLA"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Þaggað í <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Rafhlöðunotkun"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Ekki er hægt að nota rafhlöðusparnað meðan á hleðslu stendur"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Rafhlöðusparnaður"</string>
diff --git a/packages/SystemUI/res/values-is/strings_car.xml b/packages/SystemUI/res/values-is/strings_car.xml
index 65117be..5c89c76 100644
--- a/packages/SystemUI/res/values-is/strings_car.xml
+++ b/packages/SystemUI/res/values-is/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Aktu varlega"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Vertu vakandi fyrir akstursskilyrðum hverju sinni og farðu ævinlega eftir gildandi lögum. Leiðarlýsing kann að vera ónákvæm, ófullkomin, ekki við hæfi, bönnuð eða fela í sér ferðir um opinber svæði. Upplýsingar um fyrirtæki kunna einnig að vera ónákvæmar eða ófullkomnar. Gögn eru ekki í rauntíma og ekki er hægt að tryggja nákvæmni staðsetningarupplýsinga. Ekki handleika fartækið þitt meðan á akstri stendur eða nota forrit sem ekki eru hugsuð fyrir Android Auto."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Óþekkt"</string>
+ <string name="start_driving" msgid="864023351402918991">"Keyra af stað"</string>
</resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 5ff02b0..90838c0 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Ricerca"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Su per <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"A sinistra per <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Non verrai disturbato da suoni e vibrazioni, ad eccezione di sveglie, promemoria, eventi, chiamate da contatti da te specificati ed elementi che hai scelto di continuare a riprodurre, inclusi video, musica e giochi."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Non verrai disturbato da suoni e vibrazioni, ad eccezione delle sveglie e degli elementi che hai scelto di continuare a riprodurre, inclusi video, musica e giochi."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Non ti disturberanno: suoni e vibrazioni, ad eccezione di sveglie, promemoria, eventi e chiamate da contatti da te specificati."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Personalizza"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Verranno bloccati TUTTI i suoni e le vibrazioni, anche di sveglie, musica, video e giochi. Potrai ancora telefonare."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Verranno bloccati TUTTI i suoni e le vibrazioni, anche di sveglie, musica, video e giochi."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Sei collegato a <xliff:g id="APPLICATION">%1$s</xliff:g>, che consente di monitorare la tua attività di rete personale, inclusi siti web, email e app."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Il tuo profilo di lavoro è gestito da <xliff:g id="ORGANIZATION">%1$s</xliff:g> ed è collegato a <xliff:g id="APPLICATION">%2$s</xliff:g>, da cui è possibile monitorare la tua attività di rete lavorativa, inclusi siti web, email e app.\n\nPer ulteriori informazioni, contatta l\'amministratore."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Il tuo profilo di lavoro è gestito da <xliff:g id="ORGANIZATION">%1$s</xliff:g> ed è collegato a <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, da cui è possibile monitorare la tua attività di rete lavorativa, inclusi siti web, email e app.\n\nSei collegato anche a <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, da cui è possibile monitorare la tua attività di rete personale."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Sbloccato per <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> in esecuzione"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Il dispositivo resterà bloccato fino allo sblocco manuale"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Ricevi notifiche più velocemente"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Visualizza prima di sbloccare"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"No, grazie"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Configura"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Disattiva ora"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Termina adesso"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Espandi"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Comprimi"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"La schermata è bloccata"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Off"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"I controlli di gestione delle notifiche ti consentono di impostare un livello di importanza compreso tra 0 e 5 per le notifiche di un\'app. \n\n"<b>"Livello 5"</b>" \n- Mostra in cima all\'elenco di notifiche \n- Consenti l\'interruzione a schermo intero \n- Visualizza sempre \n\n"<b>"Livello 4"</b>" \n- Impedisci l\'interruzione a schermo intero \n- Visualizza sempre \n\n"<b>"Livello 3"</b>" \n- Impedisci l\'interruzione a schermo intero \n- Non visualizzare mai \n\n"<b>"Livello 2"</b>" \n- Impedisci l\'interruzione a schermo intero \n- Non visualizzare mai \n- Non emettere mai suoni e vibrazioni \n\n"<b>"Livello 1"</b>" \n- Impedisci l\'interruzione a schermo intero \n- Non visualizzare mai \n- Non emettere mai suoni e vibrazioni \n- Nascondi da schermata di blocco e barra di stato \n- Mostra in fondo all\'elenco di notifiche \n\n"<b>"Livello 0"</b>" \n- Blocca tutte le notifiche dell\'app"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Notifiche"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Non riceverai più queste notifiche."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Non riceverai più queste notifiche"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> categorie di notifica"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Questa app non ha categorie di notifica"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 categoria di notifica su <xliff:g id="NUMBER_1">%d</xliff:g> di questa app</item>
<item quantity="one">1 categoria di notifica su <xliff:g id="NUMBER_0">%d</xliff:g> di questa app</item>
@@ -572,12 +575,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"gestione delle notifiche"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"opzioni di posticipazione notifiche"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minuti"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minuti"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 ora"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 ore"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ANNULLA"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Posticipato di <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Utilizzo batteria"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Risparmio energetico non disponibile durante la ricarica"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Risparmio energetico"</string>
diff --git a/packages/SystemUI/res/values-it/strings_car.xml b/packages/SystemUI/res/values-it/strings_car.xml
index 19c4e2b..65a90f8 100644
--- a/packages/SystemUI/res/values-it/strings_car.xml
+++ b/packages/SystemUI/res/values-it/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Guida in modo sicuro"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"È necessario essere sempre pienamente informati sulle condizioni della strada e rispettare la legislazione vigente. Le indicazioni stradali potrebbero essere imprecise, incomplete, pericolose, inadatte, vietate o richiedere l\'attraversamento di aree amministrative. Anche le informazioni sugli esercizi commerciali potrebbero essere imprecise o incomplete. I dati forniti non sono aggiornati in tempo reale e non è possibile garantire la precisione della geolocalizzazione. Non maneggiare dispositivi mobili e app non destinate ad Android Auto durante la guida."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Sconosciuto"</string>
+ <string name="start_driving" msgid="864023351402918991">"Inizia la guida"</string>
</resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index b3a8146..687156f 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -356,8 +356,7 @@
<string name="description_target_search" msgid="3091587249776033139">"חיפוש"</string>
<string name="description_direction_up" msgid="7169032478259485180">"הסט למעלה כדי להציג <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"הסט שמאלה כדי להציג <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"כדי לא להפריע לך, המכשיר לא ירטוט ולא ישמיע שום צליל, חוץ מהתראות, תזכורות, אירועים ושיחות ממתקשרים מסוימים לבחירתך. המצב הזה לא ישפיע על צלילים שהם חלק מתוכן שבחרת להפעיל, כמו מוזיקה, סרטונים ומשחקים."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"כדי לא להפריע לך, המכשיר לא ירטוט ולא ישמיע שום צליל, חוץ מהתראות. המצב הזה לא ישפיע על צלילים שהם חלק מתוכן שבחרת להפעיל, כמו מוזיקה, סרטונים ומשחקים."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"צלילים ורטט לא יופעלו, פרט להתראות, תזכורות, אירועים ואנשים מסוימים שמתקשרים אליך, אם יש כאלה שציינת."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"התאמה אישית"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"פעולה זו מבטלת את כל הצלילים והרטט, כולל צלילים ורטט שמקורם בהתראות, מוזיקה, סרטונים ומשחקים. בכל מקרה, עדיין אפשר להתקשר."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"פעולה זו מבטלת את כל הצלילים והרטט, כולל בהתראות, מוזיקה, סרטונים ומשחקים."</string>
@@ -471,13 +470,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"אתה מחובר לאפליקציה <xliff:g id="APPLICATION">%1$s</xliff:g>, שיכולה לעקוב אחר הפעילות שלך ברשת הפרטית, כולל הודעות אימייל, אפליקציות ואתרים."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"פרופיל העבודה שלך מנוהל על ידי <xliff:g id="ORGANIZATION">%1$s</xliff:g>. הפרופיל מחובר לאפליקציה <xliff:g id="APPLICATION">%2$s</xliff:g>, שיכולה לעקוב אחר הפעילות שלך ברשת, כולל הודעות אימייל, אפליקציות ואתרים.\n\nלמידע נוסף, פנה למנהל המערכת."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"פרופיל העבודה שלך מנוהל על ידי <xliff:g id="ORGANIZATION">%1$s</xliff:g>. הפרופיל מחובר לאפליקציה <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, שיכולה לעקוב אחר הפעילות שלך ברשת, כולל הודעות אימייל, אפליקציות ואתרים.\n\nהפרופיל מחובר גם לאפליקציה <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, שיכולה לעקוב אחר הפעילות שלך ברשת."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"הנעילה בוטלה על ידי <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> פועל"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"המכשיר יישאר נעול עד שתבטל את נעילתו באופן ידני"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"קבל התראות מהר יותר"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"צפה בהן לפני שתבטל נעילה"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"לא, תודה"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"הגדר"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"כבה עכשיו"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"סיים כעת"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"הרחב"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"כווץ"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"המסך מוצמד"</string>
@@ -552,9 +553,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"כבוי"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"בעזרת פקדים של הודעות הפעלה, תוכל להגדיר רמת חשיבות מ-0 עד 5 להודעות אפליקציה. \n\n"<b>"רמה 5"</b>" \n- הצג בראש רשימת ההודעות \n- אפשר הפרעה במסך מלא \n- תמיד אפשר הצצה \n\n"<b>"רמה 4"</b>" \n- מנע הפרעה במסך מלא \n- תמיד אפשר הצצה \n\n"<b>"רמה 3"</b>" \n- מנע הפרעה במסך מלא \n- אף פעם אל תאפשר הצצה \n\n"<b>"רמה 2"</b>" \n- מנע הפרעה במסך מלא \n- אף פעם אל תאפשר הצצה \n- אף פעם אל תאפשר קול ורטט \n\n"<b>"רמה 1"</b>" \n- מנע הפרעה במסך מלא \n- אף פעם אל תאפשר הצצה \n- אף פעם אל תאפשר קול ורטט \n- הסתר ממסך הנעילה ומשורת הסטטוס \n- הצג בתחתית רשימת ההודעות \n\n"<b>"רמה 0"</b>" \n- חסום את כל ההודעות מהאפליקציה"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"הודעות"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"לא תקבל את ההודעות האלה יותר."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"לא תקבל את ההודעות האלה יותר"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> קטגוריות של התראות"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"האפליקציה הזו לא תומכת בקטגוריות של הודעות"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="two">קטגוריית הודעות אחת מתוך <xliff:g id="NUMBER_1">%d</xliff:g> מאפליקציה זו</item>
<item quantity="many">קטגוריית הודעות אחת מתוך <xliff:g id="NUMBER_1">%d</xliff:g> מאפליקציה זו</item>
@@ -578,12 +581,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"בקרת הודעות"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"אפשרויות של דחיית הודעות לטיפול בהמשך"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 דקות"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 דקות"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"שעה אחת"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"שעתיים"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ביטול"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"נדחה לטיפול בעוד <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"שימוש בסוללה"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"תכונת החיסכון בסוללה אינה זמינה בעת טעינת המכשיר"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"חיסכון בסוללה"</string>
diff --git a/packages/SystemUI/res/values-iw/strings_car.xml b/packages/SystemUI/res/values-iw/strings_car.xml
index 4b7ba97..d172094 100644
--- a/packages/SystemUI/res/values-iw/strings_car.xml
+++ b/packages/SystemUI/res/values-iw/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"נסיעה בטוחה"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"שמור על עירנות לתנאי הנסיעה וציית תמיד לכללים והחוקים. ייתכן שהמסלול לא מדויק, לא שלם, מסוכן, לא מתאים, אסור למעבר או כרוך בחציית אזורים מנהליים. ייתכן גם שחלק מהמידע העסקי לא מדויק או לא שלם. הנתונים אינם מדווחים בזמן אמת ולא ניתן להבטיח דיוק במיקום. אל תתעסק במכשיר הנייד ואל תשתמש באפליקציות שאינן מיועדות ל-Android Auto בזמן הנהיגה."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"לא ידוע"</string>
+ <string name="start_driving" msgid="864023351402918991">"עבור למצב נהיגה"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index f63af80..cf553b3 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"検索します"</string>
<string name="description_direction_up" msgid="7169032478259485180">"上にスライドして<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>を行います。"</string>
<string name="description_direction_left" msgid="7207478719805562165">"左にスライドして<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>を行います。"</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"アラーム、リマインダー、予定、指定した人からの着信以外の音やバイブレーションに煩わされることはありません。音楽、動画、ゲームなど再生対象として選択したコンテンツは引き続き再生されます。"</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"アラーム以外の音やバイブレーションに煩わされることはありません。音楽、動画、ゲームなど再生対象として選択したコンテンツは引き続き再生されます。"</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"アラーム、リマインダー、予定、指定した人からの着信以外で音やバイブレーションに煩わされることはありません。"</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"カスタマイズ"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"アラーム、音楽、動画、ゲームを含むすべての音とバイブレーションがブロックされます。電話をかけることはできます。"</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"アラーム、音楽、動画、ゲームを含むすべての音とバイブレーションがブロックされます。"</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"「<xliff:g id="APPLICATION">%1$s</xliff:g>」に接続しています。このアプリはあなたの個人のネットワーク アクティビティ(メール、アプリ、ウェブサイトなど)を監視できます。"</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"この仕事用プロファイルは<xliff:g id="ORGANIZATION">%1$s</xliff:g>によって管理され、<xliff:g id="APPLICATION">%2$s</xliff:g> に接続しています。このアプリはあなたの仕事のネットワーク アクティビティ(メール、アプリ、ウェブサイトなど)を監視できます。\n\n詳しくは管理者にお問い合わせください。"</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"この仕事用プロファイルは<xliff:g id="ORGANIZATION">%1$s</xliff:g>によって管理され、<xliff:g id="APPLICATION_WORK">%2$s</xliff:g> に接続しています。このアプリはあなたの仕事のネットワーク アクティビティ(メール、アプリ、ウェブサイトなど)を管理できます。\n\nまた、<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> にも接続しているため、あなたの個人のネットワーク アクティビティも監視できます。"</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> さんのロックを解除しました"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> を実行しています"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"手動でロックを解除するまでロックされたままとなります"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"通知をすばやく確認できます"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"ロックを解除する前にご確認ください"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"キャンセル"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"設定"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>。<xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"OFF にする"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"今すぐ終了"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"展開"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"折りたたむ"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"画面が固定されました"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"OFF"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"電源通知管理では、アプリの通知の重要度をレベル 0~5 で設定できます。\n\n"<b>"レベル 5"</b>" \n- 通知リストの一番上に表示する \n- 全画面表示を許可する \n- 常にポップアップする \n\n"<b>"レベル 4"</b>" \n- 全画面表示しない \n- 常にポップアップする \n\n"<b>"レベル 3"</b>" \n- 全画面表示しない \n- ポップアップしない \n\n"<b>"レベル 2"</b>" \n- 全画面表示しない \n- ポップアップしない \n- 音やバイブレーションを使用しない \n\n"<b>"レベル 1"</b>" \n- 全画面表示しない \n- ポップアップしない \n- 音やバイブレーションを使用しない \n- ロック画面やステータスバーに表示しない \n- 通知リストの一番下に表示する \n\n"<b>"レベル 0"</b>" \n- アプリからのすべての通知をブロックする"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"通知"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"今後、この通知は配信されません。"</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"今後、この通知は配信されません"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> 個の通知カテゴリ"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"このアプリでは通知カテゴリが設定されていません"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">このアプリの通知カテゴリ <xliff:g id="NUMBER_1">%d</xliff:g> 件中 1 件</item>
<item quantity="one">このアプリの通知カテゴリ <xliff:g id="NUMBER_0">%d</xliff:g> 件中 1 件</item>
@@ -572,12 +575,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"通知管理"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"通知スヌーズ設定"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15分"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30分"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1時間"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2時間"</string>
<string name="snooze_undo" msgid="6074877317002985129">"元に戻す"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"スヌーズ: <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"電池の使用状況"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"充電中はバッテリー セーバーは利用できません"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"バッテリー セーバー"</string>
diff --git a/packages/SystemUI/res/values-ja/strings_car.xml b/packages/SystemUI/res/values-ja/strings_car.xml
index 667de81..15caa95 100644
--- a/packages/SystemUI/res/values-ja/strings_car.xml
+++ b/packages/SystemUI/res/values-ja/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"安全運転を心がけましょう"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"道路状況に十分気を配り、適用される法律を遵守してください。経路は、不正確、不完全、危険、不適切である場合や、通行が禁止されている、管理区域を通行する必要があるなどの場合があります。ビジネス情報も不正確または不完全である可能性があります。データはリアルタイムではなく、場所の正確性は保証されません。運転中はモバイル端末を手に持って操作したり、Android Auto とは無関係のアプリを使用したりしないでください。"</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"不明"</string>
+ <string name="start_driving" msgid="864023351402918991">"運転を開始"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 5efe0ab..f19c9dd 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"ძიება"</string>
<string name="description_direction_up" msgid="7169032478259485180">"გაასრიალეთ ზემოთ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>-თვის."</string>
<string name="description_direction_left" msgid="7207478719805562165">"გაასრიალეთ მარცხნივ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>-თვის."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"თქვენ მიერ მითითებული მაღვიძარების, შეხსენებების, ღონისძიებებისა და ზარების გარდა, არავითარი ხმა და ვიბრაცია არ შეგაწუხებთ. თქვენ მაინც შეგეძლებათ ნებისმიერი სასურველი რამის, მაგალითად, მუსიკის, ვიდეოებისა და თამაშების მოსმენა."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"მაღვიძარების გარდა, არავითარი ხმა და ვიბრაცია არ შეგაწუხებთ. თქვენ მაინც შეგეძლებათ ნებისმიერი სასურველი რამის, მაგალითად, მუსიკის, ვიდეოებისა და თამაშების მოსმენა."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"თქვენ მიერ მითითებული გაფრთხილებების, შეხსენებების, ღონისძიებებისა და აბონენტების გარდა, არავითარი ხმა და ვიბრაცია არ შეგაწუხებთ."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"მორგება"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"ეს ბლოკავს ყველა ხმასა და ვიბრაციას, მათ შორის, მაღვიძარების, მუსიკის, ვიდეოებისა და თამაშების. მიუხედავად ამისა, თქვენ მაინც შეძლებთ სატელეფონო ზარების განხორციელებას."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"ეს ბლოკავს ყველა ხმასა და ვიბრაციას, მათ შორის, მაღვიძარების, მუსიკის, ვიდეოებისა და თამაშების."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"თქვენ დაუკავშირდით <xliff:g id="APPLICATION">%1$s</xliff:g>-ს, რომელსაც თქვენი პირადი ქსელის აქტივობის მონიტორინგი შეუძლია, მათ შორის, ელფოსტის, აპებისა და ვებსაიტების."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"თქვენს სამსახურის პროფილს მართავს <xliff:g id="ORGANIZATION">%1$s</xliff:g>. პროფილი დაკავშირებულია <xliff:g id="APPLICATION">%2$s</xliff:g>-თან, რომელსაც შეუძლია ქსელში თქვენი სამსახურეობრივი აქტივობის (მათ შორის, ელფოსტის, აპებისა და ვებსაიტების) მონიტორინგი.\n\nდამატებითი ინფორმაციისთვის დაუკავშირდით თქვენს ადმინისტრატორს."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"თქვენს სამსახურის პროფილს მართავს <xliff:g id="ORGANIZATION">%1$s</xliff:g>. პროფილი დაკავშირებულია <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>-თან, რომელსაც შეუძლია ქსელში თქვენი სამსახურეობრივი აქტივობის (მათ შორის, ელფოსტის, აპებისა და ვებსაიტების) მონიტორინგი.\n\nგარდა ამისა, თქვენ დაკავშირებული ხართ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>-თან, რომელსაც ქსელში თქვენი პირადი აქტივობის მონიტორინგი შეუძლია."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"განბლოკილია მომხმარებლისთვის: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> გაშვებულია"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"მოწყობილობის დარჩება ჩაკეტილი, სანამ ხელით არ გახსნით"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"შეტყობინებების უფრო სწრაფად მიღება"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"იხილეთ განბლოკვამდე"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"არა, გმადლობთ"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"დაყენება"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"ახლავე გამორთვა"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"გამორთვა"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"გავრცობა"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"ჩაკეცვა"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"ეკრანი ჩამაგრებულია"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"გამორთული"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"შეტყობინებების მართვის საშუალებების მეშვეობით, შეგიძლიათ განსაზღვროთ აპის შეტყობინებების მნიშვნელობის დონე 0-დან 5-მდე დიაპაზონში. \n\n"<b>"დონე 5"</b>" \n— შეტყობინებათა სიის თავში ჩვენება \n— სრულეკრანიანი რეჟიმის შეფერხების დაშვება \n— ეკრანზე ყოველთვის გამოჩენა \n\n"<b>"დონე 4"</b>" \n— სრულეკრანიანი რეჟიმის შეფერხების აღკვეთა \n— ეკრანზე ყოველთვის გამოჩენა \n\n"<b>"დონე 3"</b>" \n— სრულეკრანიანი რეჟიმის შეფერხების აღკვეთა \n— ეკრანზე გამოჩენის აღკვეთა \n\n"<b>"დონე 2"</b>" \n— სრულეკრანიანი რეჟიმის შეფერხების აღკვეთა \n— ეკრანზე გამოჩენის აღკვეთა \n— ხმისა და ვიბრაციის აღკვეთა \n\n"<b>"დონე 1"</b>" \n— სრულეკრანიანი რეჟიმის შეფერხების აღკვეთა \n— ეკრანზე გამოჩენის აღკვეთა \n— ხმისა და ვიბრაციის აღკვეთა \n— ჩაკეტილი ეკრანიდან და სტატუსის ზოლიდან დამალვა \n— შეტყობინებათა სიის ბოლოში ჩვენება \n\n"<b>"დონე 0"</b>" \n— აპის ყველა შეტყობინების დაბლოკვა"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"შეტყობინებები"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"ამ შეტყობინებებს აღარ მიიღებთ."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"ამ შეტყობინებებს აღარ მიიღებთ"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"შეტყობინებების <xliff:g id="NUMBER">%d</xliff:g> კატეგორია"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"ამ აპს შეტყობინებების კატეგორიები არ აქვს"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">ამ აპის შეტყობინებების <xliff:g id="NUMBER_1">%d</xliff:g> კატეგორიიდან 1</item>
<item quantity="one">ამ აპის შეტყობინებების <xliff:g id="NUMBER_0">%d</xliff:g> კატეგორიიდან 1</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"შეტყობინებების მართვის საშუალებები"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"შეტყობინებების ჩაჩუმების ვარიანტები"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 წუთი"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 წუთი"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 საათი"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 საათი"</string>
<string name="snooze_undo" msgid="6074877317002985129">"მოქმედების გაუქმება"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"ჩაჩუმებული იქნება <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"ბატარეის მოხმარება"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"ბატარეის დამზოგი დატენვისას მიწვდომელია"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"ბატარეის დამზოგი"</string>
diff --git a/packages/SystemUI/res/values-ka/strings_car.xml b/packages/SystemUI/res/values-ka/strings_car.xml
index c8e17dd..d6f5693 100644
--- a/packages/SystemUI/res/values-ka/strings_car.xml
+++ b/packages/SystemUI/res/values-ka/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"უსაფრთხოდ მართვის წესები"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"ყოველთვის გულდასმით გაეცანით მდგომარეობას გზებზე და დაიცავით მოქმედი კანონები. შეთავაზებული მიმართულებები შეიძლება იყოს უზუსტო, არასრული, სახიფათო, შეუფერებელი, აკრძალული, ან ადმინისტრაციული ერთეულების გადაკვეთას გულისხმობდეს. ბიზნეს-ინფორმაცია შეიძლება ასევე იყოს უზუსტო ან არასრული. მონაცემების განახლება რეალური დროის რეჟიმში არ ხდება და შესაბამისად, მდებარეობის სიზუსტე გარანტირებული ვერ იქნება. ავტომობილის მართვისას ნუ შეეცდებით თქვენი მობილური მოწყობილობით მანიპულირებას, ან ისეთი აპების გამოყენებას, რომლებიც Android Auto-სთვის შექმნილი არ არის."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"უცნობი"</string>
+ <string name="start_driving" msgid="864023351402918991">"დაიწყეთ მართვა"</string>
</resources>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 9478a96..3ba4cab 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Іздеу"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> үшін жоғары сырғыту."</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> үшін солға сырғыту."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Дабылдар, еске салғыштар, оқиғалар мен өзіңіз көрсеткен контактілердің қоңырауларынан басқа дыбыстар мен дірілдер мазаламайтын болады. Музыка, бейне және ойындар сияқты ойнатылатын мазмұндардың дыбысы өшірілмейді."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Дабылдардан басқа ешқандай дыбыстар мен дірілдер мазаламайтын болады. Музыка, бейне және ойындар сияқты ойнатылатын мазмұндардың дыбысы өшірілмейді."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Дабылдар, еске салғыштар, оқиғалар мен өзіңіз көрсеткен контактілердің қоңырауларынан басқа дыбыстар мен дірілдер мазаламайтын болады."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Реттеу"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"БАРЛЫҚ, соның ішінде дабылдардың, музыканың, бейнелердің және ойындардың дыбыстары мен дірілдері өшіріледі. Бірақ телефон қоңыраулары шалына береді."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"БАРЛЫҚ, соның ішінде дабылдардың, музыканың, бейнелердің және ойындардың дыбыстары мен дірілдері өшіріледі."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Жеке желідегі әрекеттеріңізді, соның ішінде электрондық пошта хабарларын, қолданбаларды және вебсайттарды бақылай алатын <xliff:g id="APPLICATION">%1$s</xliff:g> қолданбасына қосылғансыз."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Жұмыс профиліңізді <xliff:g id="ORGANIZATION">%1$s</xliff:g> басқарады. Бұл профиль жұмыс желісіндегі белсенділігіңізді, соның ішінде электрондық хабарларды, қолданбаларды және веб-сайттарды бақылай алатын <xliff:g id="APPLICATION">%2$s</xliff:g> қолданбасына қосылған.\n\nҚосымша ақпарат алу үшін әкімшіге хабарласыңыз."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Жұмыс профиліңізді <xliff:g id="ORGANIZATION">%1$s</xliff:g> басқарады. Бұл профиль жұмыс желісіндегі белсенділігіңізді, соның ішінде электрондық хабарларды, қолданбаларды және веб-сайттарды бақылай алатын <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> қолданбасына қосылған.\n\nСондай-ақ сіз жеке желідегі белсенділігіңізді бақылай алатын <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> қолданбасына қосылғансыз."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> үшін құлпы ашылды"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> белсенді"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Қолмен бекітпесін ашқанша құрылғы бекітілген күйде қалады"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Хабарландыруларды тезірек алу"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Бекітпесін ашу алдында оларды көру"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Жоқ, рақмет"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Реттеу"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Қазір өшіру"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Қазір өшіру"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Жаю"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Жию"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Экран түйрелді"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Өшірулі"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Қуат хабарландыруының басқару элементтерімен қолданбаның хабарландырулары үшін 0-ден бастап 5-ке дейін маңыздылық деңгейін орнатуға болады. \n\n"<b>"5-деңгей"</b>" \n- Хабарландыру тізімінің ең басында көрсету \n- Толық экранға ашылуын рұқсат ету \n- Әрдайым қалқымалы хабарландыру түрінде көрсету \n\n"<b>"4-деңгей"</b>" \n- Толық экранға шығармау \n- Әрдайым қалқымалы хабарландыру түрінде көрсету \n\n"<b>"3-деңгей"</b>" \n- Толық экранға шығармау \n- Ешқашан қалқымалы хабарландыру түрінде көрсетпеу \n\n"<b>"2-деңгей"</b>" \n- Толық экранға шығармау \n- Ешқашан қалқымалы хабарландыру түрінде көрсетпеу \n- Ешқашан дыбыс және діріл шығармау \n\n"<b>"1-деңгей"</b>" \n- Толық экранға шығармау \n- Ешқашан қалқымалы хабарландыру түрінде көрсетпеу \n- Ешқашан дыбыс немесе діріл шығармау \n- Құлыпталған экраннан және күйін көрсету жолағынан жасыру \n- Хабарландыру тізімінің ең астында көрсету \n\n"<b>"0-деңгей"</b>" \n- Қолданбадағы барлық хабарландыруларға тыйым салу"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Хабарландырулар"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Сізге енді бұл хабарландырулар жіберілмейді."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Енді сізге бұл хабарландырулар жіберілмейді"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> хабарландыру санаттары"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Бұл қолданбада хабарландыру санаттары жоқ"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">Осы қолданбадан <xliff:g id="NUMBER_1">%d</xliff:g> ішінен 1 хабарландыру санаты</item>
<item quantity="one">Осы қолданбадан <xliff:g id="NUMBER_0">%d</xliff:g> ішінен 1 хабарландыру санаты</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"хабарландыруларды басқару элементтері"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"хабарландыруды кідірту опциялары"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 минут"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 минут"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 сағат"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 сағат"</string>
<string name="snooze_undo" msgid="6074877317002985129">"КЕРІ ҚАЙТАРУ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> кідіртілді"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Батареяны пайдалану"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Зарядтау кезінде Батарея үнемдегіш қол жетімді емес"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Батарея үнемдегіш"</string>
diff --git a/packages/SystemUI/res/values-kk/strings_car.xml b/packages/SystemUI/res/values-kk/strings_car.xml
index 056b75c..d8c6337 100644
--- a/packages/SystemUI/res/values-kk/strings_car.xml
+++ b/packages/SystemUI/res/values-kk/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Қауіпсіз жүргізу"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Жүргізу жағдайларын толығымен ұғыныңыз және тиісті заңдарды әрқашан сақтаңыз. Бағыттар дәл емес, толық емес, қауіпті, жарамсыз, тыйым салынған болуы немесе әкімшілік аумақтарды қиып өтуі мүмкін. Іскери ақпарат та дәл емес немесе толық емес болуы мүмкін. Деректер нақты уақыттағы деректер емес және орын дәлдігіне кепілдік берілмейді. Жүргізу кезінде мобильді құрылғыңызды ұстамаңыз немесе Android Auto қолданбасына арналмаған қолданбаларды пайдаланбаңыз."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Белгісіз"</string>
+ <string name="start_driving" msgid="864023351402918991">"Жүргізуді бастау"</string>
</resources>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 6954d2b..1784aab 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"ស្វែងរក"</string>
<string name="description_direction_up" msgid="7169032478259485180">"រុញឡើងលើដើម្បី <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ។"</string>
<string name="description_direction_left" msgid="7207478719805562165">"រុញទៅឆ្វេងដើម្បី <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ។"</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"សំឡេង និងរំញ័រមិនរំខានដល់អ្នកឡើយ លើកលែងតែសំឡេងរោទ៍ ការរំលឹក ព្រឹត្តិការណ៍ និងអ្នកហៅទូរសព្ទដែលអ្នកបញ្ជាក់។ អ្នកនៅតែឮអ្វីគ្រប់យ៉ាងដែលអ្នកជ្រើសរើសឲ្យលេង រួមទាំងតន្រ្តី វីដេអូ និងហ្គេមផងដែរ។"</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"សំឡេង និងរំញ័រមិនរំខានដល់អ្នកឡើយ លើកលែងតែសំឡេងរោទ៍។ អ្នកនៅតែឮអ្វីគ្រប់យ៉ាងដែលអ្នកជ្រើសរើសឲ្យលេង រួមទាំងតន្រ្តី វីដេអូ និងហ្គេមផងដែរ។"</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"អ្នកនឹងគ្មានការរំខានដោយសារសំឡេង និងភាពរំញ័រឡើយ លើកលែងតែសំឡេងរោទ៍ ការរំលឹក ព្រឹត្តិការណ៍ និងអ្នកហៅទូរសព្ទដែលអ្នកបានបញ្ជាក់ប៉ុណ្ណោះ។"</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"ប្ដូរតាមបំណង"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"វារារាំងសំឡេង និងរំញ័រទាំងអស់ ដោយរួមបញ្ចូលទាំងសំឡេងរោទ៍ តន្ត្រី វីដេអូ និងហ្គេម។ អ្នកនឹងនៅតែអាចធ្វើការហៅទូរសព្ទបានដដែល។"</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"វារារាំងសំឡេង និងរំញ័រទាំងអស់ដែលចេញពីម៉ោងរោទិ៍ តន្ត្រី វីដេអូ និងហ្គេម។"</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"អ្នកត្រូវបានភ្ជាប់ទៅ <xliff:g id="APPLICATION">%1$s</xliff:g> ដែលអាចឃ្លាំមើលសកម្មភាពបណ្តាញរបស់អ្នក រាប់បញ្ចូលទាំងអ៊ីមែល កម្មវិធី និងគេហទំព័រ។"</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"កម្រងព័ត៌មានការងាររបស់អ្នកស្ថិតក្រោមការគ្រប់គ្រងរបស់ <xliff:g id="ORGANIZATION">%1$s</xliff:g> ។ កម្រងព័ត៌មាននេះត្រូវបានភ្ជាប់ទៅ <xliff:g id="APPLICATION">%2$s</xliff:g> ដែលអាចតាមដានសកម្មភាពបណ្តាញរបស់អ្នក រួមទាំងអ៊ីមែល កម្មវិធី និងគេហទំព័រផងដែរ។\n\nសម្រាប់ព័ត៌មានបន្ថែម សូមទាក់ទងទៅអ្នកគ្រប់គ្រងរបស់អ្នក។"</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"កម្រងព័ត៌មានការងាររបស់អ្នកស្ថិតក្រោមគ្រប់គ្រងរបស់ <xliff:g id="ORGANIZATION">%1$s</xliff:g> ។ កម្រងព័ត៌មាននេះត្រូវបានភ្ជាប់ទៅ <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> ដែលអាចតាមដានសកម្មភាពបណ្តាញរបស់អ្នក រួមទាំងអ៊ីមែល កម្មវិធី និងគេហទំព័រផងដែរ។\n\nអ្នកក៏ត្រូវបានភ្ជាប់ទៅ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> ដែលអាចតាមដានសកម្មភាពបណ្តាញផ្ទាល់ខ្លួនរបស់អ្នក។"</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"បានដោះសោសម្រាប់ <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> កំពុងដំណើរការ"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"ឧបករណ៍នឹងចាក់សោរហូតដល់អ្នកដោះសោដោយដៃ"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"ទទួលបានការជូនដំណឹងកាន់តែលឿន"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"ឃើញពួកវាមុនពេលដោះសោ"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"ទេ អរគុណ!"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"រៀបចំ"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"បិទឥឡូវនេះ"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"បញ្ចប់ឥឡូវនេះ"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"ពង្រីក"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"បង្រួម"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"អេក្រង់ត្រូវបានភ្ជាប់"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"បិទ"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"ជាមួយអង្គគ្រប់គ្រងការជូនដំណឹងថាមពល អ្នកអាចកំណត់កម្រិតសំខាន់ពី 0 ទៅ 5 សម្រាប់ការជូនដំណឹងរបស់កម្មវិធី។ \n\n"<b>"កម្រិត 5"</b>" \n- បង្ហាញនៅផ្នែកខាងលើបញ្ជីជូនដំណឹង \n- អនុញ្ញាតការរំខានលើអេក្រង់ពេញ \n- លោតឡើងជានិច្ច \n\n"<b>"កម្រិត 4"</b>" \n- រារាំងការរំខានលើអេក្រង់ពេញ \n- លោតឡើងជានិច្ច \n\n"<b>"កម្រិត 3"</b>" \n- រារាំងការរំខានលើអេក្រង់ពេញ \n- លោតឡើងជានិច្ច \n\n"<b>"កម្រិត 2"</b>" \n- រារាំងការរំខានលើអេក្រង់ពេញ \n- លោតឡើងជានិច្ច \n- មិនបន្លឺសំឡេង ឬញ័រ \n\n"<b>"កម្រិត 1"</b>" \n- រារាំងការរំខានលើអេក្រង់ពេញ \n- លោតឡើងជានិច្ច \n- មិនបន្លឺសំឡេង ឬញ័រ \n- លាក់ពីអេក្រង់ចាក់សោ និងរបារស្ថានភាព \n- បង្ហាញនៅផ្នែកខាងក្រោមបញ្ជីជូនដំណឹង \n\n"<b>"កម្រិត 0"</b>" \n- រារាំងការជូនដំណឹងទាំងអស់ពីកម្មវិធី"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"ការជូនដំណឹង"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"អ្នកនឹងមិនទទួលបានការជូនដំណឹងទាំងនេះទៀតទេ។"</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"អ្នកនឹងមិនទទួលបានការជូនដំណឹងទាំងនេះទៀតទេ"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"ប្រភេទនៃការជូនដំណឹងចំនួន <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"កម្មវិធីនេះមិនមានប្រភេទនៃការជូនដំណឹងទេ"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">ប្រភេទនៃការជូនដំណឹង 1 ក្នុងចំណោម <xliff:g id="NUMBER_1">%d</xliff:g> ដែលបានពីកម្មវិធីនេះ</item>
<item quantity="one">ប្រភេទនៃការជូនដំណឹង 1 ក្នុងចំណោម <xliff:g id="NUMBER_0">%d</xliff:g> ដែលបានពីកម្មវិធីនេះ</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"ការគ្រប់គ្រងការជូនដំណឹង"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"ជម្រើសផ្អាកការជូនដំណឹង"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 នាទី"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 នាទី"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 ម៉ោង"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 ម៉ោង"</string>
<string name="snooze_undo" msgid="6074877317002985129">"មិនធ្វើវិញ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"បានផ្អាករយៈពេល <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"ការប្រើប្រាស់ថ្ម"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"កម្មវិធីសន្សំថ្មមិនអាចប្រើបានអំឡុងពេលសាកថ្មទេ"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"កម្មវិធីសន្សំថ្ម"</string>
diff --git a/packages/SystemUI/res/values-km/strings_car.xml b/packages/SystemUI/res/values-km/strings_car.xml
index 9648a5fb..5c9b1d6 100644
--- a/packages/SystemUI/res/values-km/strings_car.xml
+++ b/packages/SystemUI/res/values-km/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"បើកបរដោយសុវត្ថិភាព"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"ត្រូវមានការគ្រប់គ្រងពេញលេញលើស្ថានភាពបើកបរ និងគោរពច្បាប់ដែលត្រូវអនុវត្តជានិច្ច។ ទិសដៅអាចនឹងមិនមានភាពសុក្រិត មិនពេញលេញ គ្រោះថ្នាក់ មិនសមស្រប ឬពាក់ព័ន្ធនឹងការឆ្លងកាត់តំបន់រដ្ឋបាល។ ព័ត៌មានផ្នែកអាជីវកម្មក៏អាចនឹងមិនមានភាពសុក្រិត ឬមិនពេញលេញផងដែរ។ ទិន្នន័យគឺមិនមែនបញ្ជូនបន្តផ្ទាល់នោះទេ ហើយភាពសុក្រិតនៃទីតាំងក៏មិនអាចធានាបានផងដែរ។ កុំកាន់ឧបករណ៍ចល័តរបស់អ្នក ឬប្រើកម្មវិធីដែលមិនមែនសម្រាប់ Android Auto ខណៈពេលកំពុងបើកបរ។"</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"មិនស្គាល់"</string>
+ <string name="start_driving" msgid="864023351402918991">"ចាប់ផ្តើមបើកបរ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 096416e..4357e8c 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"ಹುಡುಕಿ"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ಗಾಗಿ ಮೇಲಕ್ಕೆ ಸ್ಲೈಡ್ ಮಾಡಿ."</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ಗಾಗಿ ಎಡಕ್ಕೆ ಸ್ಲೈಡ್ ಮಾಡಿ."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"ಅಲಾರಾಂಗಳು, ಜ್ಞಾಪನೆಗಳು, ಈವೆಂಟ್ಗಳು ಹಾಗೂ ನೀವು ಸೂಚಿಸಿರುವ ಕರೆದಾರರನ್ನು ಹೊರತುಪಡಿಸಿ ಬೇರಾವುದೇ ಸದ್ದುಗಳು ಅಥವಾ ವೈಬ್ರೇಶನ್ಗಳು ನಿಮಗೆ ತೊಂದರೆ ನೀಡುವುದಿಲ್ಲ. ಹಾಗಿದ್ದರೂ, ನೀವು ಪ್ಲೇ ಮಾಡುವ ಸಂಗೀತ, ವೀಡಿಯೊಗಳು ಮತ್ತು ಆಟಗಳ ಆಡಿಯೊ ನಿಮಗೆ ಕೇಳಿಸುತ್ತದೆ."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"ಅಲಾರಾಂಗಳನ್ನು ಹೊರತುಪಡಿಸಿ, ಬೇರಾವುದೇ ಸದ್ದುಗಳು ಅಥವಾ ವೈಬ್ರೇಶನ್ಗಳು ನಿಮಗೆ ತೊಂದರೆ ನೀಡುವುದಿಲ್ಲ. ಹಾಗಿದ್ದರೂ, ನೀವು ಪ್ಲೇ ಮಾಡುವ ಸಂಗೀತ, ವೀಡಿಯೊಗಳು ಮತ್ತು ಆಟಗಳ ಆಡಿಯೊ ನಿಮಗೆ ಕೇಳಿಸುತ್ತದೆ."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"ಅಲಾರಮ್ಗಳು, ಜ್ಞಾಪನೆಗಳು, ಈವೆಂಟ್ಗಳು ಮತ್ತು ನೀವು ಸೂಚಿಸುವ ಕರೆದಾರರನ್ನು ಹೊರತುಪಡಿಸಿ, ಧ್ವನಿಗಳು ಮತ್ತು ವೈಬ್ರೇಶನ್ಗಳಿಂದ ನಿಮಗೆ ತೊಂದರೆ ಆಗುವುದಿಲ್ಲ."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"ಕಸ್ಟಮೈಸ್ ಮಾಡು"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"ಇದು ಅಲಾರಮ್ಗಳು, ಸಂಗೀತ, ವೀಡಿಯೊಗಳು, ಮತ್ತು ಗೇಮ್ಗಳು ಸೇರಿದಂತೆ ಎಲ್ಲಾ ಧ್ವನಿಗಳು ಮತ್ತು ವೈಬ್ರೇಶನ್ಗಳನ್ನು ನಿರ್ಬಂಧಿಸುತ್ತದೆ. ನಿಮಗೆ ಈಗಲೂ ಫೋನ್ ಕರೆಗಳನ್ನು ಮಾಡಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"ಇದು ಅಲಾರಮ್ಗಳು, ಸಂಗೀತ, ವೀಡಿಯೊಗಳು, ಮತ್ತು ಗೇಮ್ಗಳು ಸೇರಿದಂತೆ ಎಲ್ಲಾ ಧ್ವನಿಗಳು ಮತ್ತು ವೈಬ್ರೇಶನ್ಗಳನ್ನು ನಿರ್ಬಂಧಿಸುತ್ತದೆ."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"ನೀವು ಇಮೇಲ್ಗಳು, ಅಪ್ಲಿಕೇಶನ್ಗಳು, ಮತ್ತು ವೆಬ್ಸೈಟ್ಗಳನ್ನು ಒಳಗೊಂಡಂತೆ ನಿಮ್ಮ ವೈಯಕ್ತಿಕ ನೆಟ್ವರ್ಕ್ ಚಟುವಟಿಕೆಯ ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಬಹುದಾದ <xliff:g id="APPLICATION">%1$s</xliff:g> ಗೆ ಸಂಪರ್ಕಗೊಂಡಿರುವಿರಿ."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"ನಿಮ್ಮ ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ ಅನ್ನು <xliff:g id="ORGANIZATION">%1$s</xliff:g> ನಿರ್ವಹಿಸುತ್ತಿದೆ. <xliff:g id="APPLICATION">%2$s</xliff:g> ಗೆ ಪ್ರೊಫೈಲ್ ಸಂಪರ್ಕಗೊಂಡಿರುವ ಕಾರಣ, ಅದು ನಿಮ್ಮ ಇಮೇಲ್ಗಳು, ಅಪ್ಲಿಕೇಶನ್ಗಳು ಹಾಗೂ ವೆಬ್ಸೈಟ್ಗಳೂ ಸೇರಿದಂತೆ ನೆಟ್ವರ್ಕ್ ಚಟುವಟಿಕೆಯನ್ನು ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಬಹುದು.\n\nಹೆಚ್ಚಿನ ಮಾಹಿತಿಗಾಗಿ, ನಿಮ್ಮ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"ನಿಮ್ಮ ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ ಅನ್ನು <xliff:g id="ORGANIZATION">%1$s</xliff:g> ನಿರ್ವಹಿಸುತ್ತಿದೆ. <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> ಗೆ ಪ್ರೊಫೈಲ್ ಸಂಪರ್ಕಗೊಂಡಿರುವ ಕಾರಣ, ಅದು ನಿಮ್ಮ ಇಮೇಲ್ಗಳು, ಅಪ್ಲಿಕೇಶನ್ಗಳು ಮತ್ತು ವೆಬ್ಸೈಟ್ಗಳೂ ಸೇರಿದಂತೆ ನೆಟ್ವರ್ಕ್ ಚಟುವಟಿಕೆಯನ್ನು ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಬಹುದು.\n\nನೀವು <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> ಗೆ ಕೂಡಾ ಸಂಪರ್ಕಗೊಂಡಿದ್ದೀರಿ, ಇದು ನಿಮ್ಮ ವೈಯಕ್ತಿಕ ನೆಟ್ವರ್ಕ್ ಚಟುವಟಿಕೆಯನ್ನು ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಬಹುದು."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> ಗಾಗಿ ಅನ್ಲಾಕ್ ಮಾಡಿ"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> ಚಾಲನೆಯಲ್ಲಿದೆ"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"ನೀವಾಗಿಯೇ ಅನ್ಲಾಕ್ ಮಾಡುವವರೆಗೆ ಸಾಧನವು ಲಾಕ್ ಆಗಿಯೇ ಇರುತ್ತದೆ"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"ವೇಗವಾಗಿ ಅಧಿಸೂಚನೆಗಳನ್ನು ಪಡೆದುಕೊಳ್ಳಿ"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"ನೀವು ಅನ್ಲಾಕ್ ಮಾಡುವ ಮೊದಲೇ ಅವುಗಳನ್ನು ನೋಡಿ"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"ಧನ್ಯವಾದಗಳು"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"ಹೊಂದಿಸು"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"ಈಗ ಆಫ್ ಮಾಡಿ"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"ಈಗಲೇ ಅಂತ್ಯಗೊಳಿಸು"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"ವಿಸ್ತರಿಸು"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"ಸಂಕುಚಿಸು"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"ಪರದೆಯನ್ನು ಪಿನ್ ಮಾಡಲಾಗಿದೆ"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"ಆಫ್"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"ಪವರ್ ಅಧಿಸೂಚನೆ ನಿಯಂತ್ರಣಗಳ ಮೂಲಕ, ನೀವು ಅಪ್ಲಿಕೇಶನ್ಗಳ ಅಧಿಸೂಚನೆಗಳನ್ನು 0 ರಿಂದ 5 ರವರೆಗಿನ ಹಂತಗಳ ಪ್ರಾಮುಖ್ಯತೆಯನ್ನು ಹೊಂದಿಸಬಹುದು. \n\n"<b>"ಹಂತ 5"</b>" \n- ಮೇಲಿನ ಅಧಿಸೂಚನೆ ಪಟ್ಟಿಯನ್ನು ತೋರಿಸಿ \n- ಪೂರ್ಣ ಪರದೆ ಅಡಚಣೆಯನ್ನು ಅನುಮತಿಸಿ \n- ಯಾವಾಗಲು ಇಣುಕು ನೋಟ \n\n"<b>"ಹಂತ 4"</b>" \n- ಪೂರ್ಣ ಪರದೆ ಅಡಚಣೆಯನ್ನು ತಡೆಯಿರಿ \n- ಯಾವಾಗಲು ಇಣುಕು ನೋಟ\n\n"<b>"ಹಂತ 3"</b>" \n- ಪೂರ್ಣ ಪರದೆ ಅಡಚಣೆಯನ್ನು ತಡೆಯಿರಿ \n- ಎಂದಿಗೂ ಇಣುಕು ನೋಟ ಬೇಡ \n\n"<b>"ಹಂತ 2"</b>" \n- ಪೂರ್ಣ ಪರದೆ ಅಡಚಣೆಯನ್ನು ತಡೆಯಿರಿ \n- ಎಂದಿಗೂ ಇಣುಕು ನೋಟ ಬೇಡ \n- ಶಬ್ದ ಮತ್ತು ವೈಬ್ರೇಷನ್ ಎಂದಿಗೂ ಮಾಡಬೇಡಿ \n\n"<b>"ಹಂತ 1"</b>" \n- ಪೂರ್ಣ ಪರದೆ ಅಡಚಣೆಯನ್ನು ತಡೆಯಿರಿ \n- ಎಂದಿಗೂ ಇಣುಕು ನೋಟ ಬೇಡ \n- ಶಬ್ದ ಮತ್ತು ವೈಬ್ರೇಷನ್ ಎಂದಿಗೂ ಮಾಡಬೇಡಿ \n- ಸ್ಥಿತಿ ಪಟ್ಟಿ ಮತ್ತು ಲಾಕ್ ಪರದೆಯಿಂದ ಮರೆಮಾಡಿ \n- ಕೆಳಗಿನ ಅಧಿಸೂಚನೆ ಪಟ್ಟಿಯನ್ನು ತೋರಿಸಿ \n\n"<b>"ಹಂತ 0"</b>" \n- ಅಪ್ಲಿಕೇಶನ್ನಿಂದ ಎಲ್ಲಾ ಅಧಿಸೂಚನೆಗಳನ್ನು ನಿರ್ಬಂಧಿಸಿ"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"ಅಧಿಸೂಚನೆಗಳು"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"ನೀವು ಇನ್ನು ಮುಂದೆ ಈ ಅಧಿಸೂಚನೆಗಳನ್ನು ಪಡೆಯುವುದಿಲ್ಲ."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"ನೀವು ಇನ್ನು ಮುಂದೆ ಈ ಅಧಿಸೂಚನೆಗಳನ್ನು ಪಡೆಯುವುದಿಲ್ಲ"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> ಅಧಿಸೂಚನೆ ವರ್ಗಗಳು"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಅಧಿಸೂಚನೆ ವರ್ಗಗಳನ್ನು ಹೊಂದಿಲ್ಲ"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">ಈ ಅಪ್ಲಿಕೇಶನ್ನಿಂದ <xliff:g id="NUMBER_1">%d</xliff:g> ಅಧಿಸೂಚನೆ ವರ್ಗಗಳಲ್ಲಿ 1 ವರ್ಗ</item>
<item quantity="other">ಈ ಅಪ್ಲಿಕೇಶನ್ನಿಂದ <xliff:g id="NUMBER_1">%d</xliff:g> ಅಧಿಸೂಚನೆ ವರ್ಗಗಳಲ್ಲಿ 1 ವರ್ಗ</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"ಅಧಿಸೂಚನೆ ನಿಯಂತ್ರಣಗಳು"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"ಅಧಿಸೂಚನೆ ಸ್ನೂಜ್ ಆಯ್ಕೆಗಳು"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 ನಿಮಿಷಗಳು"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 ನಿಮಿಷಗಳು"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 ಗಂಟೆ"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 ಗಂಟೆಗಳು"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ರದ್ದುಮಾಡಿ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> ಗೆ ಸ್ನೂಜ್ ಮಾಡಲಾಗಿದೆ"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"ಬ್ಯಾಟರಿ ಬಳಕೆ"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"ಚಾರ್ಜಿಂಗ್ ಸಮಯದಲ್ಲಿ ಬ್ಯಾಟರಿ ಸೇವರ್ ಲಭ್ಯವಿರುವುದಿಲ್ಲ"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"ಬ್ಯಾಟರಿ ಸೇವರ್"</string>
diff --git a/packages/SystemUI/res/values-kn/strings_car.xml b/packages/SystemUI/res/values-kn/strings_car.xml
index e8e99c4..bc1f7fc 100644
--- a/packages/SystemUI/res/values-kn/strings_car.xml
+++ b/packages/SystemUI/res/values-kn/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"ಸುರಕ್ಷಿತವಾಗಿ ಚಾಲನೆ ಮಾಡಿ"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"ಚಾಲನೆ ಸ್ಥಿತಿಗಳ ಕುರಿತು ಸಂಪೂರ್ಣವಾಗಿ ತಿಳಿದುಕೊಳ್ಳಿ ಮತ್ತು ಅನ್ವಯವಾಗುವ ಕಾನೂನುಗಳನ್ನು ಯಾವಾಗಲೂ ಅನುಸರಿಸಿ. ದಿಕ್ಕುಗಳು ಸರಿಯಾಗಿ ಇಲ್ಲದಿರಬಹುದು, ಅಪೂರ್ಣವಾಗಿರಬಹುದು, ಅಪಾಯಕಾರಿಯಾಗಿರಬಹುದು, ಸೂಕ್ತವಾಗಿಲ್ಲದಿರಬಹುದು, ನಿಷೇಧಿಸಿರಬಹುದು ಅಥವಾ ನಿರ್ವಹಣೆ ಪ್ರದೇಶಗಳ ಮೂಲಕ ಹಾದು ಹೋಗಬೇಕಾಗಬಹುದು. ವ್ಯಾಪಾರ ಮಾಹಿತಿಯು ಸಹ ತಪ್ಪಾಗಿರಬಹುದು ಅಥವಾ ಅಪೂರ್ಣವಾಗಿರಬಹುದು. ಡೇಟಾ ನೈಜ ಸಮಯದ್ದಾಗಿರದಿರಬಹುದು ಮತ್ತು ಸ್ಥಳ ನಿಖರತೆಗೆ ಖಾತ್ರಿ ನೀಡಲಾಗುವುದಿಲ್ಲ. ನಿಮ್ಮ ಮೊಬೈಲ್ ಸಾಧನವನ್ನು ನಿರ್ವಹಿಸಬೇಡಿ ಅಥವಾ ಚಾಲನೆ ಮಾಡುತ್ತಿರುವಾಗ Android Auto ಗೆ ಅಲ್ಲದಿರುವ ಅಪ್ಲಿಕೇಶನ್ಗಳನ್ನು ಬಳಸಬೇಡಿ."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"ಅಪರಿಚಿತ"</string>
+ <string name="start_driving" msgid="864023351402918991">"ಡ್ರೈವ್ ಮಾಡಲು ಆರಂಭಿಸಿ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index cf5fc2c..898618b 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"검색"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>하려면 위로 슬라이드"</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>하려면 왼쪽으로 슬라이드"</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"알람, 알림, 일정 및 지정한 발신자부터 받은 연락을 제외하고 소리와 진동을 끕니다. 음악, 동영상, 게임 등에서 재생하도록 선택한 소리는 정상적으로 들립니다."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"알람을 제외하고 소리와 진동을 끕니다. 음악, 동영상, 게임 등에서 재생하도록 선택한 소리는 정상적으로 들립니다."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"지정한 알람, 알림, 이벤트, 발신자를 제외하고 소리와 진동을 끕니다."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"맞춤설정"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"알람, 음악, 동영상, 게임을 포함하여 모든 소리와 진동을 끕니다. 전화는 걸 수 있습니다."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"알람, 음악, 동영상, 게임을 포함하여 모든 소리와 진동을 끕니다."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"<xliff:g id="APPLICATION">%1$s</xliff:g>에 연결되었습니다. 이 앱은 이메일, 앱, 웹사이트와 같은 내 개인 네트워크 활동을 모니터링할 수 있습니다."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"직장 프로필은 <xliff:g id="ORGANIZATION">%1$s</xliff:g>에서 관리합니다. 프로필이 <xliff:g id="APPLICATION">%2$s</xliff:g>에 연결되어 이메일, 앱, 웹사이트와 같은 직장 네트워크 활동을 모니터링할 수 있습니다.\n\n자세한 내용을 확인하려면 관리자에게 문의하세요."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"직장 프로필은 <xliff:g id="ORGANIZATION">%1$s</xliff:g>에서 관리합니다. 이 프로필은 <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>에 연결되어 이메일, 앱, 웹사이트와 같은 직장 네트워크 활동을 모니터링할 수 있습니다.\n\n개인 네트워크 활동을 모니터링할 수 있는 <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>에도 연결됩니다."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g>님 잠금 해제됨"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> 실행 중"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"수동으로 잠금 해제할 때까지 기기가 잠금 상태로 유지됩니다."</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"알림을 더욱 빠르게 받기"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"잠금 해제하기 전에 알림을 봅니다."</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"사용 안함"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"설정"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"지금 사용 중지"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"지금 종료"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"펼치기"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"접기"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"화면 고정됨"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"사용 안함"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"전원 알림 컨트롤을 사용하면 앱 알림 관련 중요도를 0부터 5까지로 설정할 수 있습니다. \n\n"<b>"레벨 5"</b>" \n- 알림 목록 상단에 표시 \n- 전체 화면일 경우 알림 표시 허용 \n- 항상 엿보기 표시 \n\n"<b>"레벨 4"</b>" \n- 전체 화면에 알림 표시 금지 \n- 항상 엿보기 표시 \n\n"<b>"레벨 3"</b>" \n- 전체 화면에 알림 표시 금지 \n- 엿보기 표시 안함 \n\n"<b>"레벨 2"</b>" \n- 전체 화면에 알림 표시 금지 \n- 엿보기 표시 안함 \n- 소리나 진동으로 알리지 않음 \n\n"<b>"레벨 1"</b>" \n- 전체 화면에 알림 표시 금지 \n- 엿보기 표시 안함 \n- 소리나 진동으로 알리지 않음 \n- 잠금 화면 및 상태 표시줄에서 숨김 \n- 알림 목록 하단에 표시 \n\n"<b>"레벨 0"</b>" \n- 앱의 모든 알림 차단"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"알림"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"더 이상 다음의 알림을 받지 않습니다."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"더 이상 다음의 알림을 받지 않습니다."</string>
<string name="notification_num_channels" msgid="2048144408999179471">"알림 카테고리 <xliff:g id="NUMBER">%d</xliff:g>개"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"이 앱에는 알림 카테고리가 없습니다."</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">이 앱에서 <xliff:g id="NUMBER_1">%d</xliff:g>개의 알림 카테고리 중 1개가 정의됨</item>
<item quantity="one">이 앱에서 <xliff:g id="NUMBER_0">%d</xliff:g>개의 알림 카테고리 중 1개가 정의됨</item>
@@ -572,12 +575,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"알림 관리"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"알림 일시 중지 옵션"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15분"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30분"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1시간"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2시간"</string>
<string name="snooze_undo" msgid="6074877317002985129">"실행취소"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> 동안 일시 중지됨"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"배터리 사용량"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"충전하는 동안 배터리 세이버는 사용할 수 없습니다."</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"배터리 세이버"</string>
diff --git a/packages/SystemUI/res/values-ko/strings_car.xml b/packages/SystemUI/res/values-ko/strings_car.xml
index fcc2d7b..6d31bbf 100644
--- a/packages/SystemUI/res/values-ko/strings_car.xml
+++ b/packages/SystemUI/res/values-ko/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"안전 운전"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"도로 상황에 주의를 기울이고 관련 법규를 항상 준수하세요. 경로가 정확하지 않거나 불완전하거나 위험하거나 적합하지 않을 수 있으며 금지된 지역이나 여러 행정 구역을 통과할 수 있습니다. 비즈니스 정보 또한 부정확하거나 불완전할 수 있습니다. 데이터는 실시간이 아니며 위치의 정확도를 보증할 수 없습니다. 운전 중에 휴대기기를 조작하거나 Android Auto가 지원되지 않는 앱을 사용해서는 안 됩니다."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"알 수 없음"</string>
+ <string name="start_driving" msgid="864023351402918991">"운전 시작"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index aceb1c2..464f866 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Издөө"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> үчүн жогору жылмыштырыңыз."</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> үчүн солго жылмыштырыңыз."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Ойготкучтардан, эскертүүлөрдөн, жылнаамадагы иш-чараларды эстеткичтерден жана белгиленген байланыштардын чалууларынан тышкары башка үндөр жана дирилдөөлөр тынчыңызды албайт. Бирок ойнотулуп жаткан музыканы, видеолорду жана оюндарды мурдагыдай эле уга бересиз."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Ойготкучтардан башка үндөр жана дирилдөөлөр тынчыңызды албайт. Бирок ойнотулуп жаткан музыканы, видеолорду жана оюндарды мурдагыдай эле уга бересиз."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Эскертүүлөрдөн, жылнаамадагы иш-чараларды эстеткичтерден жана белгиленген байланыштардын чалууларынан тышкары башка үндөр жана дирилдөөлөр тынчыңызды албайт."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Ыңгайлаштыруу"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Ушуну менен эскертүүлөрдүн, музыканын, видеолордун жана оюндардын үндөрү жана дирилдөөлөрү өчүрүлөт. Бирок телефон менен сүйлөшө бересиз."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Ушуну менен эскертүүлөрдүн, музыканын, видеолордун жана оюндардын үндөрү жана дирилдөөлөрү өчүрүлөт."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Электрондук почта, колдонмолор жана вебсайттар сыяктуу тармактагы жеке аракеттериңизди тескей турган <xliff:g id="APPLICATION">%1$s</xliff:g> колдонмосуна туташып турасыз."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Жумуш профилиңизди <xliff:g id="ORGANIZATION">%1$s</xliff:g> башкарат. Ал электрондук почта, колдонмолор жана вебсайттар сыяктуу жумуш тармагыңыздагы аракеттерди көзөмөлдөй турган <xliff:g id="APPLICATION">%2$s</xliff:g> колдонмосуна туташкан.\n\nКөбүрөөк маалымат алуу үчүн администраторуңузга кайрылыңыз."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Жумуш профилиңизди <xliff:g id="ORGANIZATION">%1$s</xliff:g> башкарат. Ал электрондук почта, колдонмолор жана вебсайттар сыяктуу жумуш тармагыңыздагы аракеттерди көзөмөлдөй турган <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> колдонмосуна туташкан.\n\nМындан тышкары, тармактагы жеке аракеттериңизди көзөмөлдөгөн <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> колдонмосуна туташып турасыз."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> үчүн кулпусу ачылды"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> иштеп жатат"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Түзмөктүн кулпусу кол менен ачылмайынча кулпуланган бойдон алат"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Эскертмелерди тезирээк алуу"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Аларды кулпудан чыгараардан мурун көрүңүз"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Жок, рахмат"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Орнотуу"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Азыр өчүрүлсүн"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Азыр бүтүрүү"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Жайып көрсөтүү"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Жыйнап коюу"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Экран кадалган"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Өчүк"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Бул функциянын жардамы менен ар бир колдонмо үчүн эскертменин маанилүүлүк деңгээлин 0дон 5ке чейин койсоңуз болот. \n\n"<b>"5-деңгээл"</b>" \n- Эскертмелер тизмесинин башында көрсөтүлсүн \n- Эскертмелер толук экранда көрсөтүлсүн \n- Калкып чыгуучу эскертмелерге уруксат берилсин \n\n"<b>"4-деңгээл"</b>" \n- Эскертмелер толук экранда көрсөтүлбөсүн \n- Калкып чыгуучу эскертмелерге уруксат берилсин \n\n"<b>"3-деңгээл"</b>" \n- Эскертмелер толук экранда көрсөтүлбөсүн \n- Калкып чыгуучу эскертмелерге тыюу салынсын \n\n"<b>"2-деңгээл"</b>" \n- Эскертмелер толук экранда көрсөтүлбөсүн \n- Калкып чыгуучу эскертмелерге тыюу салынсын \n- Эч качан добуш чыгып же дирилдебесин \n\n"<b>"1-деңгээл"</b>" \n- Эскертмелер толук экранда көрсөтүлбөсүн \n- Калкып чыгуучу эскертмелерге тыюу салынсын \n- Эч качан добуш чыгып же дирилдебесин \n- Кулпуланган экрандан жана абал тилкесинен жашырылсын \n- Эскертмелер тизмесинин аягында көрсөтүлсүн \n\n"<b>"0-деңгээл"</b>" \n- Колдонмодон алынган бардык эскертмелер бөгөттөлсүн"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Эскертмелер"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Мындан ары бул эскертмелер сизге жөнөтүлбөйт."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Мындан ары бул эскертмелер сизге жөнөтүлбөйт"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"Эскертмелердин <xliff:g id="NUMBER">%d</xliff:g> категориясы"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Бул колдонмонун эскертме категориялары жок"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">Бул колдонмодогу <xliff:g id="NUMBER_1">%d</xliff:g> эскертме категориянын ичинен 1 категория</item>
<item quantity="one">Бул колдонмодогу <xliff:g id="NUMBER_0">%d</xliff:g> эскертме категориянын ичинен 1 категория</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"эскертмелерди башкаруу каражаттары"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"эскертмени тындыруу опциялары"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 мүнөт"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 мүнөт"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 саат"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 саат"</string>
<string name="snooze_undo" msgid="6074877317002985129">"КАЙТАРУУ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> тындырылды"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Батарея колдонулушу"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Батареяны үнөмдөгүч түзмөк кубатталып жатканда иштебейт"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Батареяны үнөмдөгүч"</string>
diff --git a/packages/SystemUI/res/values-ky/strings_car.xml b/packages/SystemUI/res/values-ky/strings_car.xml
index 1d99ff2..51a46b9 100644
--- a/packages/SystemUI/res/values-ky/strings_car.xml
+++ b/packages/SystemUI/res/values-ky/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Коопсуздук эрежелерин сактаңыз"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Унаа айдап бара жатканда жолдогу шарттар менен эрежелерге ар дайым көңүл буруңуз. Багыттар так же толук эмес берилип, кесип өтүү үчүн опурталдуу же ылайыксыз болгон административдик аймактар аркылуу өтүшү мүмкүн. Ишканалар тууралуу маалымат да так же толук эмес болушу мүмкүн. Дайындар тез-тез жаңыртылып турбагандыктан, жайгашкан жердин так аныкталгандыгына кепилдик жок. Андыктан унаа айдап бара жатканда, мобилдик түзмөгүңүздү же Android Auto\'го арналбаган колдонмолорду пайдаланбаңыз."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Белгисиз"</string>
+ <string name="start_driving" msgid="864023351402918991">"Унаа айдап баштоо"</string>
</resources>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index d7f1200..a401e8a 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"ຊອກຫາ"</string>
<string name="description_direction_up" msgid="7169032478259485180">"ເລື່ອນຂຶ້ນເພື່ອ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"ເລື່ອນໄປທາງຊ້າຍເພື່ອ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"ທ່ານຈະບໍ່ໄດ້ຮັບການລົບກວນຈາກສຽງ ຫຼື ການສັ່ງເຕືອນ, ຍົກເວັ້ນໃນເວລາໂມງ, ການແຈ້ງເຕືອນ, ນັດໝາຍ ແລະປະຕິທິນທີ່ທ່ານບຸ . ທ່ານອາດຍັງຄົງໄດ້ຍິນຫາກທ່ານເລືອກຫຼິ້ນເພລງ, ວິດີໂອ ລລະ ເກມ."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"ທ່ານຈະບໍ່ໄດ້ຮັບການລົບກວນຈາກສຽງ ຫຼື ການສັ່ງເຕືອນ, ຍົກເວັ້ນໃນເວລາໂມງປຸກດັງ. ທ່ານອາດຍັງຄົງໄດ້ຍິນຫາກທ່ານເລືອກຫຼິ້ນເພລງ, ວິດີໂອ ລລະ ເກມ."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"ທ່ານຈະບໍ່ຖືກລົບກວນຈາກສຽງ ແລະການສັ່ນ, ຍົກເວັ້ນຈາກໂມງປຸກ, ການເຕືອນ, ເຫດການ, ແລະຜູ້ໂທທີ່ທ່ານລະບຸ."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"ປັບແຕ່ງ"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"ນີ້ຈະເປັນການປິດສຽງ ແລະ ການສັ່ນທັງໝົດ ຮວມທັງສຽງໂມງປຸກ, ເພງ, ວິດີໂອ ແລະ ເກມນຳ. ແຕ່ທ່ານຍັງສາມາດໂທລະສັບໄດ້ປົກກະຕິ."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"ອັນນີ້ບລັອກທຸກສຽງ ແລະການສັ່ນ, ລວມທັງຈາກໂມງປຸກ, ເພງ, ວິດີໂອ, ແລະເກມ."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"ທ່ານເຊື່ອມຕໍ່ກັບ <xliff:g id="APPLICATION">%1$s</xliff:g> ແລ້ວ, ເຊິ່ງສາມາດຕິດຕາມການເຄື່ອນໄຫວເຄືອຂ່າຍສ່ວນຕົວຂອງທ່ານ ຮວມທັງອີເມວ, ແອັບ ແລະເວັບໄຊໄດ້."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກຂອງທ່ານແມ່ນຖືກຈັດການໂດຍ <xliff:g id="ORGANIZATION">%1$s</xliff:g>. ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກດັ່ງກ່າວເຊື່ອມຕໍ່ຫາ <xliff:g id="APPLICATION">%2$s</xliff:g>, ເຊິ່ງສາມາດຕິດຕາມການເຄື່ອນໄຫວເຄືອຂ່າຍຂອງທ່ານ, ຮວມທັງອີເມວ, ແອັບ ແລະ ເວັບໄຊໄດ້.\nສຳລັບຂໍ້ມູນເພີ່ມເຕີມ, ໃຫ້ຕິດຕໍ່ຫາຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານ\n."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກຂອງທ່ານແມ່ນຖືກຈັດການໂດຍ <xliff:g id="ORGANIZATION">%1$s</xliff:g>. ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກດັ່ງກ່າວເຊື່ອມຕໍ່ຫາ <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, ເຊິ່ງສາມາດຕິດຕາມການເຄື່ອນໄຫວເຄືອຂ່າຍຂອງທ່ານ, ຮວມທັງອີເມວ, ແອັບ ແລະ ເວັບໄຊໄດ້.\n\nນອກຈາກນັ້ນ, ທ່ານຍັງເຊື່ອມຕໍ່ຫາ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, ເຊິ່ງສາມາດຕິດຕາມການເຄື່ອນໄຫວເຄືອຂ່າຍສ່ວນຕົວຂອງທ່ານໄດ້."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"ປົດລັອກສຳລັບ <xliff:g id="USER_NAME">%1$s</xliff:g> ແລ້ວ"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> ກຳລັງເຮັດວຽກຢູ່"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Device will stay locked until you manually unlock"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"ຮັບເອົາການແຈ້ງເຕືອນໄວຂຶ້ນ"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"ເບິ່ງພວກມັນກ່ອນທ່ານຈະປົດລັອກ"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"ບໍ່, ຂອບໃຈ"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"ຕັ້ງຄ່າ"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"ປິດດຽວນີ້"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"ຢຸດດຽວນີ້"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"ຂະຫຍາຍ"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"ຫຍໍ້ລົງ"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"ປັກໝຸດໜ້າຈໍແລ້ວ"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"ປິດ"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"ດ້ວຍການຄວບຄຸມການແຈ້ງເຕືອນ, ທ່ານສາມາດຕັ້ງລະດັບຄວາມສຳຄັນຈາກ 0 ຮອດ 5 ໃຫ້ກັບການແຈ້ງເຕືອນແອັບໃດໜຶ່ງໄດ້. \n\n"<b>"ລະດັບ 5"</b>" \n- ສະແດງຢູ່ເທິງສຸດຂອງລາຍການແຈ້ງເຕືອນ \n- ອະນຸຍາດໃຫ້ຂັດຈັງຫວະຕອນເປີດເຕັມຈໍ \n- ແນມເບິ່ງທຸກເທື່ອ \n\n"<b>"ລະດັບ 4"</b>" \n- ກັນບໍ່ໃຫ້ຂັດຈັງຫວະຕອນເປີດເຕັມຈໍ \n- ແນມເບິ່ງທຸກເທື່ອ \n\n"<b>"ລະດັບ 3"</b>" \n- ກັນບໍ່ໃຫ້ຂັດຈັງຫວະຕອນເປີດເຕັມຈໍ \n- ບໍ່ແນມເບິ່ງ \n\n"<b>"ລະດັບ 2"</b>" \n- ກັນບໍ່ໃຫ້ຂັດຈັງຫວະຕອນເປີດເຕັມຈໍ \n- ບໍ່ແນມເບິ່ງ \n- ບໍ່ມີສຽງ ແລະ ບໍ່ມີການສັ່ນເຕືອນ \n\n"<b>"ລະດັບ 1"</b>" \n- ກັນບໍ່ໃຫ້ຂັດຈັງຫວະຕອນເປີດເຕັມຈໍ \n- ບໍ່ແນມເບິ່ງ \n- ບໍ່ມີສຽງ ແລະ ບໍ່ມີການສັ່ນເຕືອນ \n- ເຊື່ອງຈາກໜ້າຈໍລັອກ ແລະ ແຖບສະຖານະ \n- ສະແດງຢູ່ລຸ່ມສຸດຂອງລາຍການແຈ້ງເຕືອນ \n\n"<b>"ລະດັບ 0"</b>" \n- ປິດກັ້ນການແຈ້ງເຕືອນທັງໝົດຈາກແອັບ"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"ການແຈ້ງເຕືອນ"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"ທ່ານຈະບໍ່ໄດ້ຮັບການແຈ້ງເຕືອນເຫຼົ່ານີ້ອີກຕໍ່ໄປ."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"ທ່ານຈະບໍ່ໄດ້ຮັບການແຈ້ງເຕືອນເຫຼົ່ານີ້ອີກຕໍ່ໄປ"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> ໝວດໝູ່ການແຈ້ງເຕືອນ"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"ແອັບນີ້ບໍ່ມີໝວດໝູ່ການແຈ້ງເຕືອນ"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 ຈາກທັງໝົດ <xliff:g id="NUMBER_1">%d</xliff:g> ໝວດໝູ່ການແຈ້ງເຕືອນຈາກແອັບນີ້</item>
<item quantity="one">1 ຈາກທັງໝົດ <xliff:g id="NUMBER_0">%d</xliff:g> ໝວດໝູ່ການແຈ້ງເຕືອນຈາກແອັບນີ້</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"ການຄວບຄຸມການແຈ້ງເຕືອນ"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"ຕົວເລືອກການເລື່ອນການແຈ້ງເຕືອນ"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 ນາທີ"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 ນາທີ"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 ຊົ່ວໂມງ"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 ຊົ່ວໂມງ"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ຍົກເລີກ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"ເລື່ອນໄປ <xliff:g id="TIME_AMOUNT">%1$s</xliff:g> ນາທີແລ້ວ"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"ການໃຊ້ແບັດເຕີຣີ"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"ຕົວປະຢັດແບັດເຕີຣີບໍ່ມີໃຫ້ນຳໃຊ້ໃນລະຫວ່າງການສາກ"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"ຕົວປະຢັດແບັດເຕີຣີ"</string>
diff --git a/packages/SystemUI/res/values-lo/strings_car.xml b/packages/SystemUI/res/values-lo/strings_car.xml
index 3388f54..038e43b 100644
--- a/packages/SystemUI/res/values-lo/strings_car.xml
+++ b/packages/SystemUI/res/values-lo/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"ຂັບຂີ່ຢ່າງປອດໄພ"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"ຮູ້ຈັກສະພາບການຂັບຂີ່ຢ່າງຄົບຖ້ວນ ແລະ ປະຕິບັດຕາມກົດໝາຍທີ່ນຳໃຊ້ສະເໝີ. ຄຳແນະນຳເສັ້ນທາງອາດຈະບໍ່ຖືກຕ້ອງ, ບໍ່ຄົບຖ້ວນ, ອັນຕະລາຍ, ບໍ່ເໝາະສົມ, ຖືກຫ້າມ ຫຼື ກ່ຽວຂ້ອງກັບການຂ້າມເຂດປົກຄອງ. ຂໍ້ມູນທຸລະກິດອາດຈະບໍ່ຖືກຕ້ອງ ຫຼື ບໍ່ຄົບຖ້ວນ. ຂໍ້ມູນບໍ່ແມ່ນຂໍ້ມູນສົດ ແລະ ບໍ່ສາມາດຮັບປະກັນຄວາມຖືກຕ້ອງຂອງສະຖານທີ່ໄດ້. ຢ່າໃຊ້ອຸປະກອນມືຖືຂອງທ່ານ ຫຼື ໃຊ້ແອັບທີ່ບໍ່ມີໄວ້ສຳລັບ Android Auto ໃນຂະນະທີ່ຂັບຂີ່."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"ບໍ່ຮູ້ຈັກ"</string>
+ <string name="start_driving" msgid="864023351402918991">"ເລີ່ມການຂັບ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 8b03861..26552d6 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -356,8 +356,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Paieška"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Slyskite aukštyn link <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Slyskite į kairę link <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Jūsų netrikdys garsai ir vibravimas, išskyrus nurodytų signalų, priminimų, įvykių ir skambintojų garsus. Vis tiek girdėsite viską, ką pasirinksite leisti, įskaitant muziką, vaizdo įrašus ir žaidimus."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Jūsų netrikdys garsai ir vibravimas, išskyrus signalus. Vis tiek girdėsite viską, ką pasirinksite leisti, įskaitant muziką, vaizdo įrašus ir žaidimus."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Jūsų netrikdys garsai ir vibravimas, išskyrus nurodytų signalų, priminimų, įvykių ir skambintojų garsus ir vibravimą."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Tinkinti"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Taip bus užblokuoti VISI garsai ir vibravimas, įskaitant signalų, muzikos, vaizdo įrašų ir žaidimų garsus ir vibravimą. Vis tiek galėsite skambinti telefonu."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Taip bus užblokuoti VISI garsai ir vibravimas, įskaitant signalų, muzikos, vaizdo įrašų ir žaidimų garsus ir vibravimą."</string>
@@ -471,13 +470,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Esate prisijungę prie programos „<xliff:g id="APPLICATION">%1$s</xliff:g>“, kuri gali stebėti asmeninio tinklo veiklą, įskaitant el. laiškus, programas ir svetaines."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Jūsų darbo profilį tvarko „<xliff:g id="ORGANIZATION">%1$s</xliff:g>“. Profilis susietas su programa „<xliff:g id="APPLICATION">%2$s</xliff:g>“, kuri gali stebėti jūsų tinklo veiklą, įskaitant el. laiškus, programas ir svetaines.\n\nJei reikia daugiau informacijos, susisiekite su administratoriumi."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Jūsų darbo profilį tvarko „<xliff:g id="ORGANIZATION">%1$s</xliff:g>“. Profilis susietas su programa „<xliff:g id="APPLICATION_WORK">%2$s</xliff:g>“, kuri gali stebėti jūsų tinklo veiklą, įskaitant el. laiškus, programas ir svetaines.\n\nTaip pat esate prisijungę prie programos „<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>“, kuri gali stebėti asmeninio tinklo veiklą."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Atrakinta (<xliff:g id="USER_NAME">%1$s</xliff:g>)"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"„<xliff:g id="TRUST_AGENT">%1$s</xliff:g>“ vykdoma"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Įrenginys liks užrakintas, kol neatrakinsite jo neautomatiniu būdu"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Greičiau gaukite pranešimus"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Peržiūrėti prieš atrakinant"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Ne, ačiū"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Nustatyti"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Išjungti dabar"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Baigti dabar"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Išskleisti"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Sutraukti"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekranas prisegtas"</string>
@@ -552,9 +553,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Išjungta"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Naudodami pranešimų valdiklius galite nustatyti programos pranešimų svarbos lygį nuo 0 iki 5. \n\n"<b>"5 lygis"</b>" \n– Rodyti pranešimų sąrašo viršuje \n– Leisti pertraukti, kai veikia viso ekrano režimas \n– Visada rodyti pranešimus \n\n"<b>"4 lygis"</b>" \n– Neleisti pertraukti viso ekrano režimo \n– Visada rodyti pranešimus \n\n"<b>"3 lygis"</b>" \n– Neleisti pertraukti viso ekrano režimo \n– Niekada nerodyti pranešimų \n\n"<b>"2 lygis"</b>" \n– Neleisti pertraukti viso ekrano režimo \n– Niekada nerodyti pranešimų \n– Niekada neleisti garso ir nevibruoti \n\n"<b>"1 lygis"</b>" \n– Neleisti pertraukti viso ekrano režimo \n– Niekada nerodyti pranešimų \n– Niekada neleisti garso ir nevibruoti \n– Slėpti užrakinimo ekrane ir būsenos juostoje \n– Rodyti pranešimų sąrašo apačioje \n\n"<b>"0 lygis"</b>" \n– Blokuoti visus programos pranešimus"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Pranešimai"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Nebegausite šių pranešimų."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Nebegausite šių pranešimų"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"Pranešimų kategorijų: <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Šioje programoje nėra pranešimų kategorijų"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 iš <xliff:g id="NUMBER_1">%d</xliff:g> šios programos pranešimų kategorijos</item>
<item quantity="few">1 iš <xliff:g id="NUMBER_1">%d</xliff:g> šios programos pranešimų kategorijų</item>
@@ -578,12 +581,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"pranešimų valdikliai"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"pranešimų snaudimo parinktys"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 min."</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 min."</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 val."</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 val."</string>
<string name="snooze_undo" msgid="6074877317002985129">"ANULIUOTI"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Nustatyta snausti <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Akum. energ. vartoj."</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Akumuliatoriaus tausojimo priemonė nepasiekiama įkraunant"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Akumuliatoriaus tausojimo priemonė"</string>
diff --git a/packages/SystemUI/res/values-lt/strings_car.xml b/packages/SystemUI/res/values-lt/strings_car.xml
index 03d656a..4bdd5a7 100644
--- a/packages/SystemUI/res/values-lt/strings_car.xml
+++ b/packages/SystemUI/res/values-lt/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Vairuokite saugiai"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Visada atsižvelkite į vairavimo sąlygas ir laikykitės galiojančių įstatymų. Nuorodos gali būti netikslios, neužbaigtos, pavojingos, netinkamos, draudžiamos ar nukreipiančios per administracines sritis. Įmonių informacija taip pat gali būti netiksli ar neužbaigta. Duomenys nėra teikiami realiuoju laiku ir vietovių tikslumo negalima garantuoti. Vairuodami nenaudokite mobiliojo įrenginio ar programų, kurios nėra skirtos „Android Auto“."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Nežinoma"</string>
+ <string name="start_driving" msgid="864023351402918991">"Pradėti vairuoti"</string>
</resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 26f4370..f635178 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Meklēt"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Velciet uz augšu, lai veiktu šādu darbību: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Velciet pa kreisi, lai veiktu šādu darbību: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Jūs netraucēs skaņas un vibrācija, izņemot signālus, atgādinājumus, pasākumus un zvanītājus, ko būsiet norādījis. Jūs joprojām dzirdēsiet atskaņošanai izvēlētos vienumus, tostarp mūziku, videoklipus un spēles."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Jūs netraucēs skaņas un vibrācija, izņemot signālus. Jūs joprojām dzirdēsiet atskaņošanai izvēlētos vienumus, tostarp mūziku, videoklipus un spēles."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Jūs netraucēs skaņas un vibrosignāli, ja vien tie nebūs modinātāji, atgādinājumi, pasākumi vai konkrēti zvanītāji, kurus būsiet norādījis."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Pielāgot"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Tiks bloķētas VISAS skaņas un vibrosignāli, tostarp modinātāja, mūzikas, videoklipu un spēļu skaņas un signāli. Jūs joprojām varēsiet veikt tālruņa zvanus."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Tiks bloķētas VISAS skaņas un vibrosignāli, tostarp modinātāja, mūzikas, videoklipu un spēļu skaņas un signāli."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Ir izveidots savienojums ar lietotni <xliff:g id="APPLICATION">%1$s</xliff:g>, kas var pārraudzīt jūsu tīklā veiktās privātās darbības, tostarp e-pasta ziņojumus, lietotnes un vietnes."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Jūsu darba profilu pārvalda <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profils ir saistīts ar lietotni <xliff:g id="APPLICATION">%2$s</xliff:g>, kura var pārraudzīt jūsu tīklā veiktās darbības, tostarp saņemtos un nosūtītos e-pasta ziņojumus, izmantotās lietotnes un apmeklētās tīmekļa vietnes.\n\nLai iegūtu plašāku informāciju, sazinieties ar administratoru."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Jūsu darba profilu pārvalda <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profils ir saistīts ar lietotni <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, kura var pārraudzīt jūsu tīklā veiktās darbības, tostarp saņemtos un nosūtītos e-pasta ziņojumus, instalētās lietotnes un apmeklētās tīmekļa vietnes.\n\nIr izveidots savienojums ar lietotni <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, kas var pārraudzīt jūsu tīklā veiktās privātās darbības, tostarp saņemtos un nosūtītos e-pasta ziņojumus, instalētās lietotnes un apmeklētās tīmekļa vietnes."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Atbloķēta lietotājam <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> darbojas"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Ierīce būs bloķēta, līdz to manuāli atbloķēsiet."</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Saņemiet paziņojumus ātrāk"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Skatiet tos pirms atbloķēšanas."</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Nē"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Iestatīt"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Izslēgt tūlīt"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Izslēgt"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Izvērst"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Sakļaut"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekrāns ir piesprausts"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Izslēgts"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Izmantojot barošanas paziņojumu vadīklas, varat lietotnes paziņojumiem iestatīt svarīguma līmeni (no 0 līdz 5). \n\n"<b>"5. līmenis"</b>" \n- Tiek rādīts paziņojumu saraksta augšdaļā \n- Tiek atļauta pilnekrāna režīma pārtraukšana \n- Ieskats vienmēr atļauts \n\n"<b>"4. līmenis"</b>" \n- Tiek novērsta pilnekrāna režīma pārtraukšana \n- Ieskats vienmēr atļauts \n\n"<b>"3. līmenis"</b>" \n- Tiek novērsta pilnekrāna režīma pārtraukšana \n- Ieskats nav atļauts \n\n"<b>"2. līmenis"</b>" \n- Tiek novērsta pilnekrāna režīma pārtraukšana \n- Ieskats nav atļauts \n- Nav atļautas skaņas un vibrosignāls \n\n"<b>"1. līmenis"</b>" \n- Tiek novērsta pilnekrāna režīma pārtraukšana \n- Ieskats nav atļauts \n- Nav atļautas skaņas un vibrosignāls \n- Paziņojumi tiek paslēpti bloķēšanas ekrānā un statusa joslā \n- Paziņojumi tiek rādīti paziņojumu saraksta apakšdaļā \n\n"<b>"0. līmenis"</b>" \n- Visi lietotnes paziņojumi tiek bloķēti"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Paziņojumi"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Jūs vairs nesaņemsiet šos paziņojumus."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Jūs vairs nesaņemsiet šos paziņojumus"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> paziņojumu kategorijas"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Paziņojumu kategorijas šajā lietotnē nav pieejamas."</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="zero">1 no <xliff:g id="NUMBER_1">%d</xliff:g> šīs lietotnes paziņojumu kategorijām.</item>
<item quantity="one">1 no <xliff:g id="NUMBER_1">%d</xliff:g> šīs lietotnes paziņojumu kategorijas.</item>
@@ -574,12 +577,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"paziņojumu vadīklas"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"paziņojumu atlikšanas opcijas"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minūtes"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minūtes"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 stunda"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 stundas"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ATSAUKT"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Atlikts: <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Akumulatora lietojums"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Akumulatora jaudas taupīšanas režīms uzlādes laikā nav pieejams."</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Akumulatora jaudas taupīšanas režīms"</string>
diff --git a/packages/SystemUI/res/values-lv/strings_car.xml b/packages/SystemUI/res/values-lv/strings_car.xml
index 0171624..d804f86 100644
--- a/packages/SystemUI/res/values-lv/strings_car.xml
+++ b/packages/SystemUI/res/values-lv/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Droša braukšana"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Pievērsiet visu uzmanību braukšanas apstākļiem un vienmēr ievērojiet piemērojamos tiesību aktus. Norādes var būt neprecīzas, nepilnīgas, bīstamas, nepiemērotas, aizliegtas vai var ietvert administratīvo teritoriju šķērsošanu. Arī uzņēmumu informācija var būt neprecīza vai nepilnīga. Dati netiek nodrošināti reāllaikā, un netiek garantēta atrašanās vietu precizitāte. Vadot transportlīdzekli, nelietojiet mobilo ierīci rokās un neizmantojiet lietotnes, kas nav paredzētas pakalpojumam Android Auto."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Nezināms"</string>
+ <string name="start_driving" msgid="864023351402918991">"Sākt braukšanu"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 4d4e227..fd773ad 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Пребарај"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Лизгај нагоре за <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Лизгај налево за <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Нема да ве вознемируваат звуци и вибрации, освен од аларми, потсетници, настани и повикувачи што ќе ги наведете. Сѐ уште ќе слушате сѐ што ќе изберете да пуштите, што опфаќа музика, видеа и игри."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Нема да ве вознемируваат звуци и вибрации, освен од аларми. Сѐ уште ќе слушате сѐ што ќе изберете да пуштите, што опфаќа музика, видеа и игри."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Нема да ви пречат звуци и вибрации, освен од аларми, потсетници, настани и повикувачи што ќе ги наведете."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Приспособи"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Ова ги блокира СИТЕ звуци и вибрации, вклучувајќи ги и оние од алармите, музиката, видеата и игрите. Сѐ уште ќе може да воспоставувате телефонски повици."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Ова ги блокира СИТЕ звуци и вибрации, вклучувајќи ги и оние од алармите, музиката, видеата и игрите."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Поврзани сте на <xliff:g id="APPLICATION">%1$s</xliff:g>, којашто може да ја следи вашата лична активност на мрежата, вклучувајќи ги е-пораките, апликациите и веб-локациите."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> управува со вашиот работен профил. Профилот е поврзан на <xliff:g id="APPLICATION">%2$s</xliff:g>, што може да ја следи вашата работна активност на мрежата, заедно со е-пораките, апликациите и веб-сајтовите.\n\nЗа повеќе информации, контактирајте со вашиот администратор."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> управува со вашиот работен профил. Профилот е поврзан на <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, што може да ја следи вашата работна активност на мрежата, заедно со е-пораките, апликациите и веб-сајтовите.\n\nПоврзани сте и на <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, што може да ја следи вашата лична активност на мрежата."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Отклучен за <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> работи"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Уредот ќе остане заклучен додека рачно не го отклучите"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Добивајте известувања побрзо"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Видете ги пред да отклучите"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Не, фала"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Постави"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Исклучи сега"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Заврши сега"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Прошири"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Собери"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Екранот е прикачен"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Исклучено"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Со контролите за известувањата за напојување, може да поставите ниво на важност од 0 до 5 за известувањата на која било апликација. \n\n"<b>"Ниво 5"</b>" \n- Прикажувај на врвот на списокот со известувања \n- Дозволи прекин во цел екран \n- Секогаш користи појавување \n\n"<b>"Ниво 4"</b>" \n- Спречи прекин во цел екран \n- Секогаш користи појавување \n\n"<b>"Ниво 3"</b>" \n- Спречи прекин во цел екран \n- Без појавување \n\n"<b>"Ниво 2"</b>" \n- Спречи прекин во цел екран \n- Без појавување \n- Без звук и вибрации \n\n"<b>"Ниво 1"</b>" \n- Спречи прекин во цел екран \n- Без појавување \n- Без звук и вибрации \n- Сокриј од заклучен екран и статусна лента \n- Прикажувај на дното на списокот со известувања \n\n"<b>"Ниво 0"</b>" \n- Блокирај ги сите известувања од апликацијата"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Известувања"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Веќе нема да ги добивате овие известувања."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Веќе нема да ги добивате овие известувања"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> категории известувања"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Апликацијава нема катерии известувања"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 од <xliff:g id="NUMBER_1">%d</xliff:g> категорија известувања од апликацијава</item>
<item quantity="other">1 од <xliff:g id="NUMBER_1">%d</xliff:g> категории известувања од апликацијава</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"контроли за известувањето"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"опции за одложување на известувањето"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 минути"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 минути"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 час"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 часа"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ВРАТИ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Одложено за <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Користење батерија"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Штедачот на батерија не е достапен при полнење"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Штедач на батерија"</string>
diff --git a/packages/SystemUI/res/values-mk/strings_car.xml b/packages/SystemUI/res/values-mk/strings_car.xml
index f5ed824..9220ccb 100644
--- a/packages/SystemUI/res/values-mk/strings_car.xml
+++ b/packages/SystemUI/res/values-mk/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Возете безбедно"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Секогаш бидете известени за условите за возење и почитувајте ги важечките закони. Насоките може да бидат неточни, нецелосни, опасни, несоодветни, забранети или да вклучуваат премин преку административни области. Бизнис информациите исто така може да бидат неточни или нецелосни. Податоците не се даваат во реално време и не може да се гарантира точноста на локацијата. Не ракувајте со вашиот мобилен уред и не користете апликации што не се наменети за Android Auto додека возите."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Непознато"</string>
+ <string name="start_driving" msgid="864023351402918991">"Започнете да возите"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 67d7404..32a36b3 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"തിരയൽ"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> എന്നതിനായി മുകളിലേയ്ക്ക് സ്ലൈഡുചെയ്യുക."</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> എന്നതിനായി ഇടത്തേയ്ക്ക് സ്ലൈഡുചെയ്യുക."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"നിങ്ങൾ സജ്ജീകരിച്ച അലാറങ്ങൾ, റിമൈൻഡറുകൾ, ഇവന്റുകൾ, കോളർമാർ എന്നിവയിൽ നിന്നുള്ള ശബ്ദങ്ങളും വൈബ്രേഷനുകളുമൊഴികെ മറ്റൊന്നും നിങ്ങളെ ശല്യപ്പെടുത്തുകയില്ല. സംഗീതം, വീഡിയോകൾ, ഗെയിമുകൾ എന്നിവയുൾപ്പെടെ പ്ലേ ചെയ്യുന്നതെന്തും നിങ്ങൾക്ക് തുടർന്നും കേൾക്കാൻ കഴിയും."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"അലാറങ്ങളിൽ നിന്നുള്ള ശബ്ദങ്ങളും വൈബ്രേഷനുകളുമൊഴികെ മറ്റൊന്നും നിങ്ങളെ ശല്യപ്പെടുത്തുകയില്ല. സംഗീതം, വീഡിയോകൾ, ഗെയിമുകൾ എന്നിവയുൾപ്പെടെ പ്ലേ ചെയ്യുന്നതെന്തും നിങ്ങൾക്ക് തുടർന്നും കേൾക്കാൻ കഴിയും."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"നിങ്ങൾ വ്യക്തമാക്കിയിട്ടുള്ള അലാറങ്ങൾ, റിമൈൻഡറുകൾ, ഇവന്റുകൾ, കോളർമാർ എന്നിവ ഒഴികെയുള്ള ശബ്ദങ്ങളോ വൈബ്രേഷനുകളോ കാരണം നിങ്ങൾക്ക് ശല്യമുണ്ടാകില്ല."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"ഇഷ്ടാനുസൃതമാക്കുക"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"ഇത് അലാറങ്ങൾ, സംഗീതം, വീഡിയോകൾ, ഗെയിമുകൾ എന്നിവയിൽ നിന്നുൾപ്പെടെ എല്ലാ ശബ്ദങ്ങളും വൈബ്രേഷനുകളും തടയുന്നു. നിങ്ങൾക്ക് തുടർന്നും ഫോൺ വിളിക്കാനാകും."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"ഇത് അലാറങ്ങൾ, സംഗീതം, വീഡിയോകൾ, ഗെയിമുകൾ എന്നിവയിൽ നിന്നുൾപ്പെടെ എല്ലാ ശബ്ദങ്ങളും വൈബ്രേഷനുകളും തടയുന്നു."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"നിങ്ങൾ <xliff:g id="APPLICATION">%1$s</xliff:g> ആപ്പിലേക്ക് കണക്റ്റുചെയ്തിരിക്കുന്നു, ഇമെയിലുകൾ, ആപ്സ്, വെബ്സൈറ്റുകൾ എന്നിവ ഉൾപ്പെടെ നെറ്റ്വർക്ക് ആക്റ്റിവിറ്റി നിരീക്ഷിക്കാൻ ഈ ആപ്പിന് കഴിയും."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> ആണ് നിങ്ങളുടെ ഔദ്യോഗിക പ്രൊഫൈൽ മാനേജുചെയ്യുന്നത്. <xliff:g id="APPLICATION">%2$s</xliff:g> ആപ്പിലേക്ക് പ്രൊഫൈൽ കണക്റ്റുചെയ്തിരിക്കുന്നു, അതിന് ഇമെയിലുകൾ, ആപ്പുകൾ, വെബ്സൈറ്റുകൾ എന്നിവ ഉൾപ്പെടെ നിങ്ങളുടെ ഔദ്യോഗിക നെറ്റ്വർക്ക് ആക്റ്റിവിറ്റി നിരീക്ഷിക്കാനാകും.\n\nകൂടുതൽ വിവരങ്ങൾക്ക് നിങ്ങളുടെ അഡ്മിനെ ബന്ധപ്പെടുക."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> ആണ് നിങ്ങളുടെ ഔദ്യോഗിക പ്രൊഫൈൽ മാനേജുചെയ്യുന്നത്. <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> ആപ്പിലേക്ക് പ്രൊഫൈൽ കണക്റ്റുചെയ്തിരിക്കുന്നു, അതിന് ഇമെയിലുകൾ, ആപ്പുകൾ, വെബ്സൈറ്റുകൾ എന്നിവ ഉൾപ്പെടെ നിങ്ങളുടെ ഔദ്യോഗിക നെറ്റ്വർക്ക് ആക്റ്റിവിറ്റി നിരീക്ഷിക്കാനാകും.\n\n<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> ആപ്പിലേക്കും നിങ്ങൾ കണക്റ്റുചെയ്തിരിക്കുന്നു, അതിന് നിങ്ങളുടെ വ്യക്തിഗത നെറ്റ്വർക്ക് ആക്റ്റിവിറ്റി നിരീക്ഷിക്കാനാകും."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> എന്നയാൾക്കായി അൺലോക്കുചെയ്തു"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> റൺ ചെയ്യുന്നു"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"നിങ്ങൾ സ്വമേധയാ അൺലോക്കുചെയ്യുന്നതുവരെ ഉപകരണം ലോക്കുചെയ്തതായി തുടരും"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"അറിയിപ്പുകൾ വേഗത്തിൽ സ്വീകരിക്കുക"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"അൺലോക്കുചെയ്യുന്നതിന് മുമ്പ് അവ കാണുക"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"വേണ്ട, നന്ദി"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"സജ്ജീകരിക്കുക"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"ഇപ്പോൾ ഓഫ് ചെയ്യുക"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"ഇപ്പോള് അവസാനിപ്പിക്കുക"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"വികസിപ്പിക്കുക"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"ചുരുക്കുക"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"സ്ക്രീൻ പിൻ ചെയ്തു"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"ഓഫ്"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"പവർ അറിയിപ്പ് നിയന്ത്രണം ഉപയോഗിച്ച്, ഒരു ആപ്പിനായുള്ള അറിയിപ്പുകൾക്ക് 0 മുതൽ 5 വരെയുള്ള പ്രാധാന്യ ലെവലുകളിലൊന്ന് നിങ്ങൾക്ക് സജ്ജമാക്കാവുന്നതാണ്. \n\n"<b>"ലെവൽ 5"</b>" \n- അറിയിപ്പ് ലിസ്റ്റിന്റെ മുകളിൽ കാണിക്കുക \n- മുഴുവൻ സ്ക്രീൻ തടസ്സം അനുവദിക്കുക \n- എല്ലായ്പ്പോഴും ദൃശ്യമാക്കുക \n\n"<b>"ലെവൽ 4"</b>" \n- മുഴുവൻ സ്ക്രീൻ തടസ്സം തടയുക \n- എല്ലായ്പ്പോഴും ദൃശ്യമാക്കുക \n\n"<b>"ലെവൽ 3"</b>" \n- മുഴുവൻ സ്ക്രീൻ തടസ്സം തടയുക \n- ഒരിക്കലും സൃശ്യമാക്കരുത് \n\n"<b>"ലെവൽ 2"</b>" \n- മുഴുവൻ സ്ക്രീൻ തടസ്സം തടയുക \n- ഒരിക്കലും ദൃശ്യമാക്കരുത് \n- ഒരിക്കലും ശബ്ദവും വൈബ്രേഷനും ഉണ്ടാക്കരുത് \n\n"<b>"ലെവൽ 1"</b>" \n- മുഴുവൻ സ്ക്രീൻ തടസ്സം തടയുക \n- ഒരിക്കലും ദൃശ്യമാക്കരുത് \n- ഒരിക്കലും ശബ്ദവും വൈബ്രേഷനും ഉണ്ടാക്കരുത് \n- ലോക്ക് സ്ക്രീനിൽ നിന്നും സ്റ്റാറ്റസ് ബാറിൽ നിന്നും മറയ്ക്കുക \n- അറിയിപ്പ് ലിസ്റ്റിന്റെ അടിയിൽ കാണിക്കുക \n\n"<b>"ലെവൽ 0"</b>" \n- ആപ്പിൽ നിന്നുള്ള എല്ലാ അറിയിപ്പുകളും ബ്ലോക്കുചെയ്യുക"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"അറിയിപ്പുകൾ"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"നിങ്ങൾക്ക് ഈ അറിയിപ്പുകൾ ഇനിയങ്ങോട്ട് ലഭിക്കില്ല."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"നിങ്ങൾക്ക് ഇനി ഈ അറിയിപ്പുകൾ ലഭിക്കില്ല"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> അറിയിപ്പ് വിഭാഗങ്ങൾ"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"ഈ ആപ്പിന് അറിയിപ്പ് വിഭാഗങ്ങളില്ല"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">ഈ ആപ്പിൽ നിന്ന് 1 / <xliff:g id="NUMBER_1">%d</xliff:g> അറിയിപ്പ് വിഭാഗങ്ങൾ</item>
<item quantity="one">ഈ ആപ്പിൽ നിന്ന് 1 / <xliff:g id="NUMBER_0">%d</xliff:g> അറിയിപ്പ് വിഭാഗം</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"അറിയിപ്പ് നിയന്ത്രണങ്ങൾ"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"അറിയിപ്പ് സ്നൂസ് ഓപ്ഷനുകൾ"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 മിനിറ്റ്"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 മിനിറ്റ്"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"ഒരു മണിക്കൂർ"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 മണിക്കൂർ"</string>
<string name="snooze_undo" msgid="6074877317002985129">"പഴയപടിയാക്കുക"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> സമയത്തേക്ക് സ്നൂസ് ചെയ്തു"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"ബാറ്ററി ഉപയോഗം"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"ചാർജുചെയ്യുന്ന സമയത്ത് ബാറ്ററി സേവർ ലഭ്യമല്ല"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"ബാറ്ററി സേവർ"</string>
diff --git a/packages/SystemUI/res/values-ml/strings_car.xml b/packages/SystemUI/res/values-ml/strings_car.xml
index b53029c..eb4e1e6 100644
--- a/packages/SystemUI/res/values-ml/strings_car.xml
+++ b/packages/SystemUI/res/values-ml/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"സുരക്ഷിതമായി ഡ്രൈവ് ചെയ്യുക"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"ഡ്രൈവിംഗ് സാഹചര്യങ്ങളെ കുറിച്ച് നല്ലവണ്ണം അറിഞ്ഞുവയ്ക്കുക, എല്ലായ്പ്പോഴും ബാധകമായ നിയമങ്ങൾ അനുസരിക്കുക. ദിശാസൂചനകൾ കൃത്യമല്ലാത്തതും അപൂർണ്ണവും അപകടകരവും അനുയോജ്യമല്ലാത്തതും നിരോധിക്കപ്പെട്ടതും അഡ്മിനിസ്ട്രേറ്റീവ് ഏരിയകൾ അതിലംഘിച്ച് പോകേണ്ടതുമായിരിക്കാം. വിവരങ്ങൾ തത്സമയം എടുത്തിട്ടുള്ളതല്ല, അതിനാൽ ലൊക്കേഷൻ കൃത്യത ഉറപ്പാക്കാൻ കഴിയില്ല. ഡ്രൈവ് ചെയ്യുന്ന സമയത്ത് മൊബൈൽ ഉപകരണങ്ങളോ Android Auto-യ്ക്കായി ഉദ്ദേശിക്കപ്പെട്ടിട്ടില്ലാത്ത ആപ്സോ കൈകാര്യം ചെയ്യരുത്"</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"അറിഞ്ഞുകൂടാത്തത്"</string>
+ <string name="start_driving" msgid="864023351402918991">"ഡ്രൈവ് ചെയ്തുതുടങ്ങുക"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index ea5ab82..faa09f6 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -350,8 +350,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Хайх"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>-г гулсуулах."</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> хийх зүүнлүү гулсуулах."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Танд сэрүүлэг, сануулга, үйл ажиллагаа, таны сонгосон дуудлага илгээгчээс бусад дуу, чичиргээ саад болохгүй. Та хөгжим, видео, тоглоом зэрэг тоглуулахыг хүссэн бүх зүйлээ сонсох боломжтой хэвээр байна."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Танд сэрүүлгээс бусад дуу, чичиргээ саад болохгүй. Та хөгжим, видео, тоглоом зэрэг тоглуулахыг хүссэн бүх зүйлээ сонсох боломжтой хэвээр байна."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Танд сэрүүлгэ, сануулга, үйл явдлын сануулга, таны сануулсан дуудлага зэргээс бусад дуу чимээ, чичиргээ танд садаа болохгүй."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Тохируулах"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Энэ нь сэрүүлэг, хөгжим, видео, тоглоом зэргийг оруулаад зэрэг БҮХ дуу, чичиргээг блоклодог. Та дуудлага хийх боломжтой хэвээр байна."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Энэ нь сэрүүлэг, хөгжим, видео, тоглоом зэргийг оруулаад зэрэг БҮХ дуу, чичиргээг блоклодог."</string>
@@ -465,13 +464,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Та имэйл, апп, вэб хуудас зэрэг хувийн сүлжээнийхээ үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION">%1$s</xliff:g>-д холбогдсон байна."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Таны ажлын профайлыг <xliff:g id="ORGANIZATION">%1$s</xliff:g> удирддаг. Энэ нь таны имэйл, апп, вэб хуудас зэрэг ажлын сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION">%2$s</xliff:g>-д холбогдсон. \n\nДэлгэрэнгүй мэдээллийг авахын тулд админтай холбогдоно уу."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Таны ажлын профайлыг <xliff:g id="ORGANIZATION">%1$s</xliff:g> удирддаг. Энэ нь таны имэйл, апп, вэб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>-тай холбогдсон. \n\nМөн таны сүлжээний хувийн үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>-д холбогдсон байна."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g>-н түгжээг тайлсан"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> ажиллаж байна"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Таныг гараар нээх хүртэл төхөөрөмж түгжээтэй байх болно"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Мэдэгдлийг хурдан авах"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Түгжээг тайлахын өмнө үзнэ үү"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Үгүй"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Тохируулах"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Одоо унтраах"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Одоо дуусгах"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Дэлгэх"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Хураах"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Дэлгэц эхэнд байрлуулагдсан"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Идэвхгүй"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Тэжээлийн мэдэгдлийн удирдлагын тусламжтайгаар та апп-н мэдэгдэлд 0-5 хүртэлх ач холбогдлын түвшин тогтоох боломжтой. \n\n"<b>"5-р түвшин"</b>" \n- Мэдэгдлийн жагсаалтын хамгийн дээр харуулна \n- Бүтэн дэлгэцэд саад болно \n- Дэлгэцэд тогтмол гарч ирнэ \n\n"<b>"4-р түвшин"</b>" \n- Бүтэн дэлгэцэд саад болохоос сэргийлнэ \n- Дэлгэцэд тогтмол гарч ирнэ \n\n"<b>"3-р түвшин"</b>" \n- Бүтэн дэлгэцэд саад болохоос сэргийлнэ \n- Дэлгэцэд хэзээ ч гарч ирэхгүй \n\n"<b>"2-р түвшин"</b>" \n- Бүтэн дэлгэцэд саад болохоос сэргийлнэ \n- Дэлгэцэд хэзээ ч гарч ирэхгүй \n- Дуу болон чичиргээ хэзээ ч гаргахгүй \n\n"<b>"1-р түвшин"</b>" \n- Бүтэн дэлгэцэд саад болохоос сэргийлнэ \n- Дэлгэцэд хэзээ ч гарч ирэхгүй \n- Дуу болон чичиргээ хэзээ ч гаргахгүй \n- Түгжигдсэн дэлгэц болон статусын самбараас нууна \n- Мэдэгдлийн жагсаалтын доор харуулна \n\n"<b>"0-р түвшин"</b>" \n- Энэ апп-н бүх мэдэгдлийг блоклоно"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Мэдэгдэл"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Ta цаашид мэдэгдэл авахгүй."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Ta цаашид эдгээр мэдэгдлийг авахгүй"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> мэдэгдлийн ангилал"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Энэ апп-д мэдэгдлийн категори алга"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">Энэ аппын <xliff:g id="NUMBER_1">%d</xliff:g> мэдэгдлийн категорийн 1</item>
<item quantity="one">Энэ аппын <xliff:g id="NUMBER_0">%d</xliff:g> мэдэгдлийн категорийн 1</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"мэдэгдлийн удирдлага"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"мэдэгдэл түр хойшлуулагчийн сонголт"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 минут"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 минут"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 цаг"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 цаг"</string>
<string name="snooze_undo" msgid="6074877317002985129">"БУЦААХ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g>-д түр хойшлуулсан"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Тэжээл ашиглалт"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Цэнэглэх үед тэжээл хэмнэгч ажиллахгүй"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Тэжээл хэмнэгч"</string>
diff --git a/packages/SystemUI/res/values-mn/strings_car.xml b/packages/SystemUI/res/values-mn/strings_car.xml
index d48580f..74b983a 100644
--- a/packages/SystemUI/res/values-mn/strings_car.xml
+++ b/packages/SystemUI/res/values-mn/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Жолооны аюулгүй байдал"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Жолоодох нөхцөлийн талаар бүрэн ойлгож, холбогдох дүрэм журмыг мөрдөөрэй. Чиглэл нь тодорхой бус, гүйцэт бус, аюултай, тохиромжгүй, хориглосон эсвэл тусгай хамгаалалттай газар нутагт нэвтэрсэн байж болзошгүй. Бизнесийн мэдээлэл мөн адил тохиромжгүй эсвэл гүйцэт бус байна. Өгөгдөл нь тухайн цаг хугацааных биш учир байршлыг зөв тодорхойлсон эсэхийг батлах боломжгүй. Жолоодож байхдаа гар утсаа болон Android Автод зориулаагүй апп-г бүү ашиглаарай."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Тодорхойгүй"</string>
+ <string name="start_driving" msgid="864023351402918991">"Жолоо барьж эхлэх"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index fd6b0c3..ed8b034 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"शोध"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> साठी वर स्लाइड करा."</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> साठी डावीकडे स्लाइड करा."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"अलार्म, रिमाइंडर्स, इव्हेंट आणि तुम्ही निश्चित केलेल्या कॉलरचा अपवाद वगळता तुम्हाला कोणत्याही आवाज आणि कंपनांचा व्यत्त्यय आणला जाणार नाही. तसे असले तरीही तुम्ही प्ले करायचे ठरवलेले कोणतेही संगीत, व्हिडिओ आणि गेमचे आवाज चालू ठेवले जातील."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"अलार्मचा अपवाद सोडल्यास तुम्हाला कोणत्याही आवाज आणि कंपनांचा व्यत्त्यय आणला जाणार नाही. तसे असले तरीही तुम्ही प्ले करायचे ठरवलेले कोणतेही संगीत, व्हिडिओ आणि गेमचे आवाज चालू ठेवले जातील."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"आपण निर्दिष्ट करता ते अलार्म, स्मरणपत्रे, इव्हेंट आणि कॉलर व्यतिरिक्त, आपल्याला आवाज आणि कंपनांमुळे व्यत्यय येणार नाही."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"सानुकूलित करा"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"हे अलार्म, संगीत, व्हिडिओ आणि गेम यासह, सर्व आवाज आणि कंपने अवरोधित करते. आपण तरीही फोन कॉल करण्यात सक्षम व्हाल."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"हे अलार्म, संगीत, व्हिडिओ आणि गेम यासह, सर्व आवाज आणि कंपने अवरोधित करते."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"आपण <xliff:g id="APPLICATION">%1$s</xliff:g> शी कनेक्ट केले आहे, जो ईमेल, अॅप्स आणि वेबसाइटसह आपल्या वैयक्तिक नेटवर्क क्रियाकलापाचे परीक्षण करू शकतो."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"आपले कार्य प्रोफाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g> द्वारे व्यवस्थापित केले जाते. प्रोफाइल <xliff:g id="APPLICATION">%2$s</xliff:g> शी कनेक्ट केले आहे, जे ईमेल, अॅप्स आणि वेबसाइटसह आपल्या कार्य नेटवर्क क्रियाकलापाचे परीक्षण करू शकते.\n\nअधिक माहितीसाठी, आपल्या प्रशासकाशी संपर्क साधा."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"आपले कार्य प्रोफाइल <xliff:g id="ORGANIZATION">%1$s</xliff:g> द्वारे व्यवस्थापित केले जाते. प्रोफाइल <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> शी कनेक्ट केले आहे, जे ईमेल, अॅप्स आणि वेबसाइटसह आपल्या कार्य नेटवर्क क्रियाकलापाचे परीक्षण करू शकते.\n\nआपण <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> शीदेखील कनेक्ट केले आहे, जे आपल्या वैयक्तिक नेटवर्क क्रियाकलापाचे परीक्षण करू शकते."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> साठी अनलॉक केले"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> चालू आहे"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"आपण व्यक्तिचलितपणे अनलॉक करेपर्यंत डिव्हाइस लॉक केलेले राहील"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"सूचना अधिक जलद मिळवा"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"आपण अनलॉक करण्यापूर्वी त्यांना पहा"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"नाही धन्यवाद"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"सेट अप"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"आता बंद करा"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"आता समाप्त करा"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"विस्तृत करा"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"संकुचित करा"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"स्क्रीन पिन केलेली आहे"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"बंद"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"पॉवर सूचना नियंत्रणांसह, आपण अॅपच्या सूचनांसाठी महत्त्व स्तर 0 ते 5 पर्यंत सेट करू शकता. \n\n"<b>"स्तर 5"</b>" \n- सूचना सूचीच्या शीर्षस्थानी दर्शवा \n- पूर्ण स्क्रीन व्यत्ययास अनुमती द्या \n- नेहमी डोकावून पहा \n\n"<b>"स्तर 4"</b>" \n- पूर्ण स्क्रीन व्यत्ययास प्रतिबंधित करा \n- नेहमी डोकावून पहा \n\n"<b>"स्तर 3"</b>" \n- पूर्ण स्क्रीन व्यत्ययास प्रतिबंधित करा \n- कधीही डोकावून पाहू नका \n\n"<b>"स्तर 2"</b>" \n- पूर्ण स्क्रीन व्यत्ययास प्रतिबंधित करा \n- कधीही डोकावून पाहू नका \n- कधीही ध्वनी किंवा कंपन करू नका \n\n"<b>"स्तर 1"</b>" \n- पूर्ण स्क्रीन व्यत्ययास प्रतिबंधित करा \n- कधीही डोकावून पाहू नका \n- कधीही ध्वनी किंवा कंपन करू नका \n- लॉक स्क्रीन आणि स्टेटस बार मधून लपवा \n- सूचना सूचीच्या तळाशी दर्शवा \n\n"<b>"स्तर 0"</b>" \n- अॅपमधील सर्व सूचना अवरोधित करा"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"सूचना"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"आपल्याला यापुढे या सूचना प्राप्त होणार नाहीत."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"तुम्हाला यापुढे या सूचना प्राप्त होणार नाहीत"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> सूचना श्रेण्या"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"या अॅपला सूचना श्रेण्या नाहीत"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">या अॅपकडील <xliff:g id="NUMBER_1">%d</xliff:g> पैकी 1 सूचना श्रेणी</item>
<item quantity="other">या अॅपकडील <xliff:g id="NUMBER_1">%d</xliff:g> पैकी 1 सूचना श्रेणी</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"सूचना नियंत्रणे"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"सूचना स्नूझ पर्याय"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 मिनिटे"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 मिनिटे"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 तास"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 तास"</string>
<string name="snooze_undo" msgid="6074877317002985129">"पूर्ववत करा"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> साठी स्नूझ करा"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"बॅटरी वापर"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"चार्ज करताना बॅटरी बचतकर्ता उपलब्ध नाही"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"बॅटरी बचतकर्ता"</string>
diff --git a/packages/SystemUI/res/values-mr/strings_car.xml b/packages/SystemUI/res/values-mr/strings_car.xml
index 7dc5d17..5d66f14 100644
--- a/packages/SystemUI/res/values-mr/strings_car.xml
+++ b/packages/SystemUI/res/values-mr/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"सुरक्षितपणे वाहन चालवा"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"वाहन चालविण्याच्या शर्तींची पूर्णपणे माहिती असू द्या आणि नेहमी लागू असलेल्या कायद्यांचे पालन करा. दिशानिर्देश कदाचित चुकीचे, अपूर्ण, धोकादायक, अनुकूल नसलेले, प्रतिबंधित किंवा प्रशासकीय क्षेत्रांना ओलांडणारे असू शकतात. व्यवसाय माहिती देखील चुकीची किंवा अपूर्ण असू शकते. डेटा हा रिअल-टाइम नसतो आणि स्थान अचूकतेची हमी दिली जाऊ शकत नाही. वाहन चालविताना Android Auto च्या उद्देशासाठी नसलेले आपले मोबाईल डिव्हाइस हाताळू नका किंवा अॅप्स वापरू नका."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"अज्ञात"</string>
+ <string name="start_driving" msgid="864023351402918991">"वाहन चालवणे सुरू करा"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 46fcf43..0e60d5d 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Carian"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Luncurkan ke atas untuk <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Luncurkan ke kiri untuk <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Anda tidak akan diganggu oleh bunyi dan getaran, kecuali daripada penggera, peringatan, acara dan pemanggil yang anda tetapkan. Anda masih mendengar item lain yang anda pilih untuk dimainkan termasuk muzik, video dan permainan."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Anda tidak akan diganggu oleh bunyi dan getaran, kecuali daripada penggera. Anda masih mendengar item lain yang anda pilih untuk dimainkan termasuk muzik, video dan permainan."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Anda tidak akan diganggu oleh bunyi dan getaran kecuali daripada penggera, peringatan, acara dan pemanggil yang anda tentukan."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Peribadikan"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Mod ini menyekat SEMUA bunyi dan getaran, termasuk daripada penggera, muzik, video dan permainan. Anda masih boleh membuat panggilan telefon."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Mod ini menyekat SEMUA bunyi dan getaran, termasuk daripada penggera, muzik, video dan permainan."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Anda disambungkan ke <xliff:g id="APPLICATION">%1$s</xliff:g>, yang boleh memantau aktiviti rangkaian peribadi anda, termasuk e-mel, apl dan tapak web."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Profil kerja anda diurus oleh <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profil itu dihubungkan ke <xliff:g id="APPLICATION">%2$s</xliff:g>, yang boleh memantau aktiviti rangkaian kerja anda, termasuk e-mel, apl dan tapak web.\n\nUntuk mendapatkan maklumat lanjut, hubungi pentadbir anda."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Profil kerja anda diurus oleh <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profil itu dihubungkan ke <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, yang boleh memantau aktiviti rangkaian kerja anda, termasuk e-mel, apl dan tapak web.\n\nAnda turut dihubungkan ke <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, yang boleh memantau aktiviti rangkaian peribadi anda."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Kunci dibuka untuk <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> sedang berjalan"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Peranti akan kekal terkunci sehingga anda membuka kunci secara manual"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Dapatkan pemberitahuan lebih cepat"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Lihat sebelum anda membuka kunci"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Tidak"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Sediakan"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Matikan sekarang"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Tamatkan sekarang"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Kembangkan"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Runtuhkan"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Skrin telah disemat"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Mati"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Dengan kawalan pemberitahuan berkuasa, anda boleh menetapkan tahap kepentingan dari 0 hingga 5 untuk pemberitahuan apl. \n\n"<b>"Tahap 5"</b>" \n- Tunjukkan pada bahagian atas senarai pemberitahuan \n- Benarkan gangguan skrin penuh \n- Sentiasa intai \n\n"<b>"Tahap 4"</b>" \n- Halang gangguan skrin penuh \n- Sentiasa intai \n\n"<b>"Tahap 3"</b>" \n- Halang gangguan skrin penuh \n- Jangan intai \n\n"<b>"Tahap 2"</b>" \n- Halang gangguan skrin penuh \n- Jangan intai \n- Jangan berbunyi dan bergetar \n\n"<b>"Tahap 1"</b>" \n- Halang gangguan skrin penuh \n- Jangan intai \n- Jangan berbunyi atau bergetar \n- Sembunyikan daripada skrin kunci dan bar status \n- Tunjukkan di bahagian bawah senarai pemberitahuan \n\n"<b>"Tahap 0"</b>" \n- Sekat semua pemberitahuan daripada apl"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Pemberitahuan"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Anda tidak akan menerima pemberitahuan ini lagi."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Anda tidak akan menerima pemberitahuan ini lagi"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> kategori pemberitahuan"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Apl ini tiada kategori pemberitahuan"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 daripada <xliff:g id="NUMBER_1">%d</xliff:g> kategori pemberitahuan daripada apl ini</item>
<item quantity="one">1 daripada <xliff:g id="NUMBER_0">%d</xliff:g> kategori pemberitahuan daripada apl ini</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"kawalan pemberitahuan"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"pilihan tunda pemberitahuan"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minit"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minit"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 jam"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 jam"</string>
<string name="snooze_undo" msgid="6074877317002985129">"BUAT ASAL"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Ditunda selama <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Penggunaan bateri"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Penjimat Bateri tidak tersedia semasa mengecas"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Penjimat Bateri"</string>
diff --git a/packages/SystemUI/res/values-ms/strings_car.xml b/packages/SystemUI/res/values-ms/strings_car.xml
index 392530a..0daa093 100644
--- a/packages/SystemUI/res/values-ms/strings_car.xml
+++ b/packages/SystemUI/res/values-ms/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Pandu dengan selamat"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Pastikan anda sentiasa sedar sepenuhnya akan keadaan pemanduan dan mematuhi undang-undang yang dikenakan pada sepanjang masa. Arah mungkin tidak tepat, tidak lengkap, berbahaya, tidak sesuai, dilarang atau melibatkan lintasan kawasan pentadbiran. Maklumat perniagaan juga mungkin tidak tepat atau tidak lengkap. Data bukan masa sebenar dan ketepatan lokasi tidak dapat dijamin. Jangan kendalikan peranti mudah alih anda atau gunakan apl yang bukan untuk Android Auto semasa memandu."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Tidak diketahui"</string>
+ <string name="start_driving" msgid="864023351402918991">"Mulakan Pemanduan"</string>
</resources>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index dd7e923..740ded7 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"ရှာဖွေရန်"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> အတွက် အပေါ်ကို ပွတ်ဆွဲပါ"</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> အတွက် ဖယ်ဘက်ကို ပွတ်ဆွဲပါ"</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"နှိုးစက်သံ၊ သတိပေးချက်အသံများ၊ ပွဲစဉ်သတိပေးသံများနှင့် သင်ခွင့်ပြုထားသူများထံမှ ဖုန်းခေါ်မှုများမှလွဲ၍ အခြားအသံများနှင့် တုန်ခါမှုများက သင့်ကို အနှောင့်အယှက်ပြုမည် မဟုတ်ပါ။ သို့သော်လည်း သီချင်း၊ ဗီဒီယိုနှင့် ဂိမ်းများအပါအဝင် သင်ကရွေးချယ်ဖွင့်ထားသည့် အရာတိုင်း၏ အသံကိုမူ ကြားနေရဆဲဖြစ်ပါလိမ့်မည်။"</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"နှိုးစက်သံမှလွဲ၍ အခြားအသံများနှင့် တုန်ခါမှုများက သင့်ကို အနှောင့်အယှက်ပြုမည် မဟုတ်ပါ။ သို့သော်လည်း သီချင်း၊ ဗီဒီယိုနှင့် ဂိမ်းများအပါအဝင် သင်ကရွေးချယ်ဖွင့်ထားသည့် အရာတိုင်း၏ အသံကိုမူ ကြားနေရဆဲဖြစ်ပါလိမ့်မည်။"</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"နှိုးစက်များ၊ အသိပေးချက်များ၊ ဖြစ်ရပ်များ နှင့် သင်သတ်မှတ်ထားသည့် ခေါ်ဆိုသူများမှ လွဲ၍ အသံများနှင့် တုန်ခါမှုများသည် သင့်အား နှောင့်ယှက်မည် မဟုတ်ပါ။"</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"စိတ်ကြိုက် ပြုလုပ်ရန်"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"ဤအရာမှ နှိုးစက်၊ ဂီတ၊ ဗွီဒီယိုများနှင့် ဂိမ်းများ အပါအဝင်၊ အသံအားလုံးနှင့် တုန်ခါမှုများအား တားဆီးပေးသည်။ ဖုန်းခေါ်ဆိုမှုများ ပြုလုပ်နိုင်ဆဲဖြစ်မည်။"</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"နှိုးစက်၊ ဂီတ၊ ဗွီဒီယိုများနှင့် ဂိမ်းများ အပါအဝင်၊ အသံအားလုံးနှင့် တုန်ခါမှုများအား ဤအရာမှ တားဆီးပေး၏။"</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"သင်သည် အီးမေးလ်၊ အက်ပ်နှင့် ဝဘ်ဆိုက်များအပါအဝင် သင့်ကိုယ်ရေးကိုယ်တာ ကွန်ရက်အသုံးပြုမှုကို စောင့်ကြည့်နိုင်သည့် <xliff:g id="APPLICATION">%1$s</xliff:g> သို့ ချိတ်ဆက်ထားပါသည်။"</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"သင်၏ အလုပ်ပရိုဖိုင်ကို <xliff:g id="ORGANIZATION">%1$s</xliff:g> က စီမံခန့်ခွဲထားပါသည်။ ပရိုဖိုင်သည် အီးမေး၊ အက်ပ်နှင့် ဝဘ်ဆိုက်များအပါအဝင် သင်၏ကွန်ရက် လုပ်ဆောင်ချက်များကို စောင့်ကြည့်နိုင်သည့် <xliff:g id="APPLICATION">%2$s</xliff:g> သို့ ချိတ်ဆက်ထားပါသည်။\n\nနောက်ထပ် အချက်အလက်များအတွက် သင်၏ စီမံခန့်ခွဲသူကို ဆက်သွယ်ပါ။"</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"သင်၏ အလုပ်ပရိုဖိုင်ကို <xliff:g id="ORGANIZATION">%1$s</xliff:g> က စီမံခန့်ခွဲထားသည်။ ပရိုဖိုင်သည် အီးမေး၊ အက်ပ်နှင့် ဝဘ်ဆိုက်များအပါအဝင် သင်၏ကွန်ရက် လုပ်ဆောင်ချက်များကို စောင့်ကြည့်နိုင်သည့် <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> သို့ ချိတ်ဆက်ထားပါသည်။\n\nသင်၏ ကိုယ်ရေးကိုယ်တာ ကွန်ရက်လုပ်ဆောင်ချက်များကို စောင့်ကြည့်နိုင်သည့် <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> သို့လည်း ချိတ်ဆက်ထားပါသည်။"</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> အတွက် လော့ခ်ဖွင့်ထားသည်"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> ပွင့်နေပါသည်"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"သင်က လက်ဖြင့် သော့မဖွင့်မချင်း ကိရိယာမှာ သော့ပိတ်လျက် ရှိနေမည်"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"အကြောင်းကြားချက်များ မြန်မြန်ရရန်"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"မဖွင့်ခင် ၎င်းတို့ကို ကြည့်ပါ"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"မလိုအပ်ပါ"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"သတ်မှတ်ရန်"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>။ <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"ပိတ်ရန်"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"ယခု အဆုံးသတ်ရန်"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"တိုးချဲ့ရန်"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"ခေါက်သိမ်းရန်..."</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"မျက်နှာပြင် ပင်ထိုးပြီးပါပြီ"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"ပိတ်ပါ"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"ပါဝါအကြောင်းကြားချက် ထိန်းချုပ်မှုများကိုအသုံးပြုပြီး အက်ပ်တစ်ခု၏ အကြောင်းကြားချက် အရေးပါမှု ၀ မှ ၅ အထိသတ်မှတ်ပေးနိုင်သည်။ \n\n"<b>"အဆင့် ၅"</b>" \n- အကြောင်းကြားချက်စာရင်း၏ ထိပ်ဆုံးတွင် ပြသည် \n- မျက်နှာပြင်အပြည့် ကြားဖြတ်ဖော်ပြခြင်းကို ခွင့်ပြုသည် \n- အမြဲတမ်း ခေတ္တပြပါမည် \n\n"<b>"အဆင့် ၄"</b>" \n- မျက်နှာပြင်အပြည့် ကြားဖြတ်ဖော်ပြခြင်း မရှိစေရန် ကာကွယ်ပေးသည် \n- အမြဲတမ်း ခေတ္တပြပါမည် \n\n"<b>"အဆင့် ၃"</b>" \n- မျက်နှာပြင်အပြည့် ကြားဖြတ်ဖော်ပြခြင်း မရှိစေရန် ကာကွယ်ပေးသည် \n- ဘယ်တော့မှ ခေတ္တပြခြင်း မရှိပါ \n\n"<b>"အဆင့် ၂"</b>" \n- မျက်နှာပြင်အပြည့် ကြားဖြတ်ဖော်ပြခြင်း မရှိစေရန် ကာကွယ်ပေးသည် \n- ဘယ်တော့မှ ခေတ္တပြခြင်း မရှိပါ \n- အသံမြည်ခြင်းနှင့် တုန်ခါခြင်းများ ဘယ်တော့မှ မပြုလုပ်ပါ \n\n"<b>"အဆင့် ၁"</b>" \n- မျက်နှာပြင်အပြည့် ကြားဖြတ်ဖော်ပြခြင်း မရှိစေရန် ကာကွယ်ပေးသည် \n- ဘယ်တော့မှ ခေတ္တပြခြင်း မရှိပါ \n- အသံမြည်ခြင်းနှင့် တုန်ခါခြင်းများ ဘယ်တော့မှ မပြုလုပ်ပါ \n- လော့ခ်ချထားသည့် မျက်နှာပြင်နှင့် အခြေအနေဘားတန်းတို့တွင် မပြပါ \n- အကြောင်းကြားချက်စာရင်း အောက်ဆုံးတွင်ပြသည် \n\n"<b>"အဆင့် ၀"</b>" \n- အက်ပ်မှ အကြောင်းကြားချက်များ အားလုံးကို ပိတ်ဆို့သည်"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"အကြောင်းကြားချက်များ"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"သင်သည် ဤအကြောင်းကြားချက်များကို လက်ခံရရှိတော့မည် မဟုတ်ပါ။"</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"သင်သည် ဤအကြောင်းကြားချက်များကို နောက်ထပ် လက်ခံရရှိတော့မည် မဟုတ်ပါ"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"အကြောင်းကြားချက် အမျိုးအစား <xliff:g id="NUMBER">%d</xliff:g> ခု"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"ဤအက်ပ်တွင် အကြောင်းကြားချက် အမျိုးအစားများ မရှိပါ"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">ဤအက်ပ်ရှိ အကြောင်းကြားချက်အမျိုးအစား <xliff:g id="NUMBER_1">%d</xliff:g> ခု အနက်မှ ၁ ခု</item>
<item quantity="one">ဤအက်ပ်ရှိ အကြောင်းကြားချက်အမျိုးအစား <xliff:g id="NUMBER_0">%d</xliff:g> ခု အနက်မှ ၁ ခု</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"အကြောင်းကြားချက် ထိန်းချုပ်မှုများ"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"အကြောင်းကြားချက်များကို ဆိုင်းငံ့ရန် ရွေးချယ်စရာများ"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"၁၅ မိနစ်"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"၃၀ မိနစ်"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"၁ နာရီ"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"၂ နာရီ"</string>
<string name="snooze_undo" msgid="6074877317002985129">"တစ်ဆင့် နောက်ပြန်ပြန်ပါ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> ဆိုင်းငံ့ရန်"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"ဘက်ထရီ အသုံးပြုမှု"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"အားသွင်းနေချိန်မှာ Battery Saver ကို သုံးမရပါ"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Battery Saver"</string>
diff --git a/packages/SystemUI/res/values-my/strings_car.xml b/packages/SystemUI/res/values-my/strings_car.xml
index dd41713..c2700c4 100644
--- a/packages/SystemUI/res/values-my/strings_car.xml
+++ b/packages/SystemUI/res/values-my/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"ဘေးကင်းလုံခြုံစွာ မောင်းနှင်ပါ"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"ယာဉ်မောင်းမှုအခြေအနေများကို အပြည့်အဝအာရုံစိုက်ပြီး သက်ဆိုင်ရာဥပဒေများကို အမြဲတမ်းလိုက်နာပါ။ လမ်းညွှန်ချက်များသည် မှန်ကန်မှုမရှိခြင်း၊ မပြည့်စုံခြင်း၊ အန္တရာယ်ရှိခြင်း၊ ကိုက်ညီမှုမရှိခြင်း၊ တားမြစ်ထားခြင်း၊ သို့မဟုတ် လမ်းဖြတ်ကူးခြင်း ကြီးကြပ်ထားသည့်နေရာများ ဖြစ်နေနိုင်ပါသည်။ စီးပွားရေးအချက်အလက်များသည်လည်း မမှန်ကန်ခြင်း သို့မဟုတ် မပြည့်စုံခြင်းများ ရှိနိုင်ပါသည်။ ဒေတာသည် အချိန်နှင့်တစ်ပြေးညီ မဟုတ်ပါ၊ တည်နေရာမှန်ကန်မှုကိုလည်း အာမ မခံနိုင်ပါ။ ယာဉ်မောင်းနှင်နေစဉ် Android Auto အတွက်ရည်ရွယ်ထားခြင်းမဟုတ်သည့် မိုဘိုင်းစက်ပစ္စည်း ကိုမကိုင်တွယ်ပါနှင့် သို့မဟုတ် အက်ပ်များကို အသုံးမပြုပါနှင့်။ိ"</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"အမျိုးအမည်မသိ"</string>
+ <string name="start_driving" msgid="864023351402918991">"စတင် မောင်းနှင်ရန်"</string>
</resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 3fdc266..b86c256 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Søk"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Dra opp for å <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Dra til venstre for å <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Du blir ikke forstyrret av lyder og vibrasjoner, med unntak av alarmer, påminnelser, aktiviteter og oppringere du angir. Du kan fremdeles høre alt du velger å spille av, for eksempel musikk, videoer og spill."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Du blir ikke forstyrret av lyder og vibrasjoner, med unntak av alarmer. Du kan fremdeles høre alt du velger å spille av, for eksempel musikk, videoer og spill."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Du blir ikke forstyrret av lyder og vibrasjoner, bortsett fra alarmer, påminnelser, aktiviteter og oppringere du angir."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Tilpass"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Dette blokkerer ALLE lyder og vibrasjoner, inkludert fra alarmer, musikk, videoer og spill. Du kan fremdeles ringe."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Dette blokkerer ALLE lyder og vibrasjoner, inkludert fra alarmer, musikk, videoer og spill."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Enheten er koblet til <xliff:g id="APPLICATION">%1$s</xliff:g>, som kan overvåke den personlige nettverksaktiviteten din, inkludert e-post, apper og nettsteder."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Jobbprofilen din administreres av <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profilen er koblet til <xliff:g id="APPLICATION">%2$s</xliff:g>, som kan overvåke nettverksaktiviteten din på jobben, inkludert e-poster, apper og nettsteder.\n\nTa kontakt med administratoren hvis du vil ha mer informasjon."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Jobbprofilen din administreres av <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profilen er koblet til <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, som kan overvåke nettverksaktiviteten din på jobben, inkludert e-poster, apper og nettsteder.\n\nDu er også koblet til <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, som kan overvåke den personlige nettverksaktiviteten din."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Låst opp for <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> kjører"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Enheten forblir låst til du låser den opp manuelt"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Motta varsler raskere"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Se dem før du låser opp"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Nei takk"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Konfigurer"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Slå av nå"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Avslutt nå"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Utvid"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Skjul"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Skjermen er låst"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Av"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Med effektive varselinnstillinger kan du angi viktighetsnivåer fra 0 til 5 for appvarsler. \n\n"<b>"Nivå 5"</b>" \n– Vis øverst på varsellisten \n– Tillat forstyrrelser ved fullskjermmodus \n– Vis alltid raskt \n\n"<b>"Nivå 4"</b>" \n– Forhindre forstyrrelser ved fullskjermmodus \n– Vis alltid raskt \n\n"<b>"Nivå 3"</b>" \n– Forhindre forstyrrelser ved fullskjermmodus \n– Vis aldri raskt \n\n"<b>"Nivå 2"</b>" \n– Forhindre forstyrrelser ved fullskjermmodus \n– Vis aldri fort \n– Tillat aldri lyder eller vibrering \n\n"<b>"Nivå 1"</b>" \n– Forhindre forstyrrelser ved fullskjermmodus \n– Vis aldri raskt \n– Tillat aldri lyder eller vibrering \n– Skjul fra låseskjermen og statusfeltet \n– Vis nederst på varsellisten \n\n"<b>"Nivå 0"</b>" \n– Blokkér alle varsler fra appen"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Varsler"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Du får ikke disse varslene lenger."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Du får ikke disse varslene lenger"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> varselkategorier"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Denne appen har ikke varselkategorier"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 av <xliff:g id="NUMBER_1">%d</xliff:g> varselkategorier fra denne appen</item>
<item quantity="one">1 av <xliff:g id="NUMBER_0">%d</xliff:g> varselkategori fra denne appen</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"varselinnstillinger"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"slumrealternativer for varsler"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minutter"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minutter"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 time"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 timer"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ANGRE"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Slumrer i <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Batteribruk"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Batterisparing er ikke tilgjengelig under lading"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Batterisparing"</string>
diff --git a/packages/SystemUI/res/values-nb/strings_car.xml b/packages/SystemUI/res/values-nb/strings_car.xml
index 421420e..2a1b3ca 100644
--- a/packages/SystemUI/res/values-nb/strings_car.xml
+++ b/packages/SystemUI/res/values-nb/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Kjør forsiktig"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Ha full oversikt over kjøreforhold, og følg alltid gjeldende lov og rett. Veibeskrivelser kan være unøyaktige, ufullstendige, farlige, upassende, forbudte eller de kan involvere kjøring på administrative områder. Bedriftsinformasjon kan også være unøyaktig eller ufullstendig. Data er ikke i sanntid, og posisjonsnøyaktighet kan ikke garanteres. Ikke bruk mobilenheten din eller apper som ikke er ment for Android Auto, mens du kjører."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Ukjent"</string>
+ <string name="start_driving" msgid="864023351402918991">"Begynn å kjøre"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 038587e..601fcae 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"खोज्नुहोस्"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>को लागि माथि धिसार्नुहोस्"</string>
<string name="description_direction_left" msgid="7207478719805562165">"स्लाइड <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>को लागि बायाँ।"</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"तपाईंलाई अलार्म, रिमाइन्डर, कार्यक्रम र तपाईंले निर्दिष्ट गर्नुभएका कलरहरू बाहेकका ध्वनि र कम्पनहरूले बाधा पुऱ्याउने छैनन्। तपाईंले अझै संगीत, भिडियो र खेलहरू लगायत आफूले प्ले गर्न छनौट गरेका जुनसुकै कुरा सुन्न सक्नुहुन्छ।"</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"तपाईंलाई अलार्म बाहेकका ध्वनि र कम्पनहरूले बाधा पुऱ्याउने छैनन्। तपाईंले अझै संगीत, भिडियो र खेलहरू लगायत आफूले प्ले गर्न छनौट गरेका जुनसुकै कुरा पनि सुन्न सक्नुहुन्छ।"</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"अलार्म, रिमाइन्डर, घटना, र तपाईंले निर्दिष्ट गर्नुहुने कलरहरू देखि बाहेक, आवाज र कम्पनले तपाईं लाई वाधा गर्ने छैन।"</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"आफू अनुकूल बनाउनुहोस्"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"यसले अलार्म, संगीत, भिडियो, र खेलहरू लगायतका सबै ध्वनि र कम्पन रोक्छ। तपाईं अझै पनि फोन कल गर्न सक्षम हुनुहुन्छ।"</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"यसले अलार्म, संगीत, भिडियोहरू र खेलहरूसहित सबै ध्वनिहरू र कम्पनहरूलाई रोक्छ।"</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"तपाईं <xliff:g id="APPLICATION">%1$s</xliff:g> मा जोडिनुभएको छ जसले इमेल, अनुप्रयोग र वेबसाइटहरू लगायतको तपाईंको व्यक्तिगत नेटवर्क सम्बन्धी गतिविधिको अनुगमन गर्न सक्छ।"</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> ले तपाईंको कार्य प्रोफाइलको व्यवस्थापन गर्छ। उक्त प्रोफाइल तपाईंका इमेल, अनुप्रयोग र वेवसाइटहरू लगायत तपाईंको नेटवर्कको गतिविधिको अनुगमन गर्नसक्ने <xliff:g id="APPLICATION">%2$s</xliff:g> मा जडान छ।\n\nथप जानकारीका लागि, आफ्ना प्रशासकलाई सम्पर्क गर्नुहोस्।"</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> ले तपाईंको कार्य प्रोफाइलको व्यवस्थापन गर्छ। उक्त प्रोफाइल तपाईंका इमेल, अनुप्रयोग र वेवसाइटहरू लगायत तपाईंको नेटवर्कको गतिविधिको अनुगमन गर्नसक्ने <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> मा जडान छ। \n\nतपाईं आफ्नो व्यक्तिगत नेटवर्कको गतिविधिको अनुगमन गर्नसक्ने <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> मा पनि जडान हुनुहुन्छ।"</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> का लागि अनलक गरियो"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> चल्दै छ"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"तपाईँले नखोले सम्म उपकरण बन्द रहनेछ"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"छिटो सूचनाहरू प्राप्त गर्नुहोस्"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"तपाईँले अनलक गर्नअघि तिनीहरूलाई हेर्नुहोस्"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"धन्यवाद पर्दैन"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"सेटअप गर्नुहोस्"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"अहिले नै निष्क्रिय पार्नुहोस्"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"अहिल्यै अन्त्य गर्नुहोस्"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"विस्तार गर्नुहोस्"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"संक्षिप्त पार्नुहोस्"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"पर्दा राखेका छ"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"निष्क्रिय"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"सशक्त सूचना नियन्त्रणहरू मार्फत तपाईं अनुप्रयाेगका सूचनाहरूका लागि ० देखि ५ सम्मको महत्व सम्बन्धी स्तर सेट गर्न सक्नुहुन्छ। \n\n"<b>"स्तर ५"</b>" \n- सूचनाको सूचीको माथिल्लो भागमा देखाउने \n- पूर्ण स्क्रिनमा अवरोधका लागि अनुमति दिने \n- सधैँ चियाउने \n\n"<b>"स्तर ४"</b>" \n- पूर्ण स्क्रिनमा अवरोधलाई रोक्ने \n- सधैँ चियाउने \n\n"<b>"स्तर ३"</b>" \n- पूर्ण स्क्रिनमा अवरोधलाई रोक्ने \n- कहिल्यै नचियाउने \n\n"<b>"स्तर २"</b>" \n- पूर्ण स्क्रिनमा अवरोधलाई रोक्ने \n- कहिल्यै नचियाउने \n- कहिल्यै पनि आवाज ननिकाल्ने र कम्पन नगर्ने \n\n"<b>"स्तर १"</b>" \n- पूर्ण स्क्रिनमा अवरोध रोक्ने \n- कहिल्यै नचियाउने \n- कहिल्यै पनि आवाज ननिकाल्ने वा कम्पन नगर्ने \n- लक स्क्रिन र वस्तुस्थिति पट्टीबाट लुकाउने \n- सूचनाको सूचीको तल्लो भागमा देखाउने \n\n"<b>"स्तर ०"</b>" \n- अनुप्रयोगका सबै सूचनाहरूलाई रोक्ने"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"सूचनाहरू"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"तपाईंले अबदेखि यी सूचनाहरू प्राप्त गर्नुहुने छैन।"</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"तपाईंले अब उप्रान्त यी सूचनाहरू प्राप्त गर्नुहुने छैन"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> सूचनाका कोटिहरू"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"यस अनुप्रयोगमा सूचना सम्बन्धी कोटीहरू छैनन्"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">यस अनुप्रयोगका <xliff:g id="NUMBER_1">%d</xliff:g> सूचना कोटिहरू मध्ये १</item>
<item quantity="one"> यस अनुप्रयोगको <xliff:g id="NUMBER_0">%d</xliff:g> सूचना कोटी मध्ये १</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"सूचना सम्बन्धी नियन्त्रणहरू"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"सूचना स्नुज गर्ने विकल्पहरू"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"१५ मिनेट"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"३० मिनेट"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"१ घन्टा"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"२ घन्टा"</string>
<string name="snooze_undo" msgid="6074877317002985129">"अनडू गर्नुहोस्"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> का लागि स्नुज गरियो"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"ब्याट्री उपयोग"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"चार्ज गर्ने समयमा ब्याट्री सेभर उपलब्ध छैन"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"ब्याट्री सेभर"</string>
diff --git a/packages/SystemUI/res/values-ne/strings_car.xml b/packages/SystemUI/res/values-ne/strings_car.xml
index 1a4f8ef9..d81a488 100644
--- a/packages/SystemUI/res/values-ne/strings_car.xml
+++ b/packages/SystemUI/res/values-ne/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"सुरक्षित तरिकाले सवारी चलाउनुहोस्"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"ड्राइभिङ सर्तहरूका बारे पूर्ण सजग रहनुहोस् र जहिले पनि लागू हुने नियमहरूको पालना गर्नुहोस्। दिशा निर्देशनहरू अशुद्ध, अपूर्ण, खतरनाक, अनुपयुक्त, निषेधित वा प्रशासनिक क्षेत्र पार गर्ने हुन सक्छ। व्यवसायिक जानकारी अशुद्ध वा अपूर्ण पनि हुन सक्छ। डेटा वास्तविक समयको हुँदैन र स्थान सटीकताको ग्यारेन्टी गर्न सकिँदैन। आफ्नो मोबाइल यन्त्र ह्याण्डल गर्ने वा सवारी चलाएको बेलामा Android स्वतःको लागि लक्षित नगरिएका अनुप्रयोगहरूको प्रयोग गर्ने नगर्नुहोस्।"</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"अज्ञात"</string>
+ <string name="start_driving" msgid="864023351402918991">"ड्राइभिङ सुरू गर्नुहोस्"</string>
</resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 040f0a8..91a953d 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Zoeken"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Veeg omhoog voor <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Veeg naar links voor <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Je wordt niet gestoord door geluiden en trillingen, behalve bij alarmen, herinneringen, afspraken en specifieke bellers die je selecteert. Je kunt nog steeds alles horen wat je wilt afspelen, waaronder muziek, video\'s en games."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Je wordt niet gestoord door geluiden en trillingen, behalve bij alarmen. Je kunt nog steeds alles horen wat je wilt afspelen, waaronder muziek, video\'s en games."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"U wordt niet gestoord door geluiden en trillingen, behalve voor alarmen, herinneringen, afspraken en bellers die u specificeert."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Aanpassen"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Hiermee worden ALLE geluiden en trillingen geblokkeerd, waaronder die voor alarmen, muziek, video\'s en games. U kunt wel nog steeds bellen."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Hiermee worden ALLE geluiden en trillingen geblokkeerd, waaronder die voor alarmen, muziek, video\'s en games."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Je bent verbonden met <xliff:g id="APPLICATION">%1$s</xliff:g>, waarmee je persoonlijke netwerkactiviteit kan worden gecontroleerd, inclusief e-mails, apps en websites."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Je werkprofiel wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Het profiel is verbonden met <xliff:g id="APPLICATION">%2$s</xliff:g>, waarmee je werkgerelateerde netwerkactiviteit (waaronder e-mails, apps en websites) kan worden bijgehouden.\n\nNeem contact op met je beheerder voor meer informatie."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Je werkprofiel wordt beheerd door <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Het profiel is verbonden met <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, waarmee je werkgerelateerde netwerkactiviteit (waaronder e-mails, apps en websites) kan worden bijgehouden.\n\nJe bent ook verbonden met <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, waarmee je persoonlijke netwerkactiviteit kan worden bijgehouden."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Ontgrendeld voor <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> is actief"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Het apparaat blijft vergrendeld totdat u het handmatig ontgrendelt"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Sneller meldingen ontvangen"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Weergeven voordat u ontgrendelt"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Nee, bedankt"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Configureren"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Nu uitschakelen"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Nu uitschakelen"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Uitvouwen"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Samenvouwen"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Scherm is vastgezet"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Uit"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Met beheeropties voor meldingen met betrekking tot stroomverbruik kun je een belangrijkheidsniveau van 0 tot 5 instellen voor de meldingen van een app. \n\n"<b>"Niveau 5"</b>" \n- Boven aan de lijst met meldingen weergeven \n- Onderbreking op volledig scherm toestaan \n- Altijd korte weergave \n\n"<b>"Niveau 4"</b>" \n- Geen onderbreking op volledig scherm \n- Altijd korte weergave \n\n"<b>"Niveau 3"</b>" \n- Geen onderbreking op volledig scherm \n- Nooit korte weergave \n\n"<b>"Niveau 2"</b>" \n- Geen onderbreking op volledig scherm \n- Nooit korte weergave \n- Nooit geluid laten horen of trillen \n\n"<b>"Niveau 1"</b>" \n- Geen onderbreking op volledig scherm \n- Nooit korte weergave \n- Nooit geluid laten horen of trillen \n- Verbergen op vergrendelingsscherm en statusbalk \n- Onder aan de lijst met meldingen weergeven \n\n"<b>"Niveau 0"</b>" \n- Alle meldingen van de app blokkeren"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Meldingen"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Je ontvangt deze meldingen niet meer."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Je ontvangt deze meldingen niet meer"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> meldingscategorieën"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Deze app heeft geen meldingscategorieën"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 van <xliff:g id="NUMBER_1">%d</xliff:g> meldingscategorieën van deze app</item>
<item quantity="one">1 van <xliff:g id="NUMBER_0">%d</xliff:g> meldingscategorie van deze app</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"beheeropties voor meldingen"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"snooze-opties voor meldingen"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minuten"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minuten"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 uur"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 uur"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ONGEDAAN MAKEN"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Snoozefunctie <xliff:g id="TIME_AMOUNT">%1$s</xliff:g> actief"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Accugebruik"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Batterijbesparing niet beschikbaar tijdens opladen"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Batterijbesparing"</string>
diff --git a/packages/SystemUI/res/values-nl/strings_car.xml b/packages/SystemUI/res/values-nl/strings_car.xml
index be66e01..32582e5 100644
--- a/packages/SystemUI/res/values-nl/strings_car.xml
+++ b/packages/SystemUI/res/values-nl/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Rijd veilig"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Blijf je volledig bewust van de rijomstandigheden en houd je altijd aan de wet. Aanwijzingen kunnen onnauwkeurig, onvolledig, gevaarlijk, ongeschikt of verboden zijn of het doorkruisen van overheidszones vereisen. Bedrijfsinformatie kan ook onnauwkeurg of onvolledig zijn. De gegevens worden niet in realtime verstrekt en de nauwkeurigheid van een locatie kan niet worden gegarandeerd. Bedien tijdens het rijden je mobiele apparaat niet en gebruik geen apps die niet bedoeld zijn voor Android Auto."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Onbekend"</string>
+ <string name="start_driving" msgid="864023351402918991">"Beginnen met rijden"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 12b1dcb..5fc0b1b 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"ਖੋਜੋ"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ਲਈ ਉੱਪਰ ਸਲਾਈਡ ਕਰੋ।"</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ਤੱਕ ਖੱਬੇ ਪਾਸੇ ਸਲਾਈਡ ਕਰੋ।"</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"ਧੁਨੀਆਂ ਅਤੇ ਥਰਥਰਾਹਟਾਂ ਤੁਹਾਨੂੰ ਪਰੇਸ਼ਾਨ ਨਹੀਂ ਕਰਨਗੀਆਂ, ਸਿਵਾਏ ਅਲਾਰਮਾਂ, ਯਾਦ-ਦਹਾਨੀਆਂ, ਵਰਤਾਰਿਆਂ, ਅਤੇ ਤੁਹਾਡੇ ਵੱਲੋਂ ਨਿਰਧਾਰਤ ਕੀਤੇ ਕਾਲਰਾਂ ਦੀ ਸੂਰਤ ਵਿੱਚ। ਤੁਸੀਂ ਅਜੇ ਵੀ ਸੰਗੀਤ, ਵੀਡੀਓ ਅਤੇ ਗੇਮਾਂ ਸਮੇਤ ਆਪਣੀ ਚੋਣ ਅਨੁਸਾਰ ਕੁਝ ਵੀ ਸੁਣ ਸਕਦੇ ਹੋ।"</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"ਧੁਨੀਆਂ ਅਤੇ ਥਰਥਰਾਹਟਾਂ ਤੁਹਾਨੂੰ ਪਰੇਸ਼ਾਨ ਨਹੀਂ ਕਰਨਗੀਆਂ, ਸਿਵਾਏ ਅਲਾਰਮਾਂ ਦੀ ਸੂਰਤ ਵਿੱਚ। ਤੁਸੀਂ ਅਜੇ ਵੀ ਸੰਗੀਤ, ਵੀਡੀਓ ਅਤੇ ਗੇਮਾਂ ਸਮੇਤ ਆਪਣੀ ਚੋਣ ਅਨੁਸਾਰ ਕੁਝ ਵੀ ਸੁਣ ਸਕਦੇ ਹੋ।"</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"ਤੁਹਾਡੇ ਦੁਆਰਾ ਤੈਅ ਕੀਤੇ ਅਲਾਰਮਾਂ, ਯਾਦ-ਦਹਾਨੀਆਂ, ਵਰਤਾਰਿਆਂ, ਅਤੇ ਕਾਲ ਕਰਨ ਵਾਲਿਆਂ ਨੂੰ ਛੱਡਕੇ, ਤੁਹਾਨੂੰ ਧੁਨੀਆਂ ਅਤੇ ਥਰਥਰਾਹਟਾਂ ਵੱਲੋਂ ਪਰੇਸ਼ਾਨ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ।"</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"ਵਿਸ਼ੇਸ਼-ਵਿਉਂਤਬੱਧ ਕਰੋ"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"ਇਹ ਅਲਾਰਮ, ਸੰਗੀਤ, ਵੀਡੀਓ, ਅਤੇ ਗੇਮਾਂ ਸਮੇਤ, ਸਾਰੀਆਂ ਧੁਨੀਆਂ ਅਤੇ ਥਰਥਰਾਹਟ ਨੂੰ ਬਲੌਕ ਕਰਦਾ ਹੈ। ਤੁਸੀਂ ਅਜੇ ਵੀ ਫ਼ੋਨ ਕਾਲ ਕਰਨ ਦੇ ਯੋਗ ਹੋਵੋਂਗੇ।"</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"ਇਹ ਅਲਾਰਮ, ਸੰਗੀਤ, ਵੀਡੀਓਜ਼, ਅਤੇ ਗੇਮਸ ਸਮੇਤ, ਸਾਰੀਆਂ ਧੁਨੀਆਂ ਅਤੇ ਵਾਇਬ੍ਰੇਸ਼ਨ ਨੂੰ ਬਲੌਕ ਕਰਦਾ ਹੈ।"</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"ਤੁਸੀਂ <xliff:g id="APPLICATION">%1$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਹੋ, ਜੋ ਈਮੇਲਾਂ, ਐਪਾਂ, ਅਤੇ ਵੈੱਬਸਾਈਟਾਂ ਸਮੇਤ ਤੁਹਾਡੀ ਨਿੱਜੀ ਨੈੱਟਵਰਕ ਸਰਗਰਮੀ ਦੀ ਨਿਗਰਾਨੀ ਕਰ ਸਕਦੀ ਹੈ।"</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"ਤੁਹਾਡੇ ਕਾਰਜ ਪ੍ਰੋਫ਼ਾਈਲ ਦਾ ਪ੍ਰਬੰਧਨ <xliff:g id="ORGANIZATION">%1$s</xliff:g> ਵੱਲੋਂ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਇਹ ਪ੍ਰੋਫ਼ਾਈਲ <xliff:g id="APPLICATION">%2$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਹੈ, ਜੋ ਈਮੇਲਾਂ, ਐਪਾਂ, ਅਤੇ ਵੈੱਬਸਾਈਟਾਂ ਸਮੇਤ ਤੁਹਾਡੀ ਕਾਰਜ ਨੈੱਟਵਰਕ ਸਰਗਰਮੀ ਦੀ ਨਿਗਰਾਨੀ ਕਰ ਸਕਦੀ ਹੈ।\n\nਹੋਰ ਜਾਣਕਾਰੀ ਲਈ, ਆਪਣੇ ਪ੍ਰਸ਼ਾਸਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।"</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"ਤੁਹਾਡੇ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਦਾ ਪ੍ਰਬੰਧਨ <xliff:g id="ORGANIZATION">%1$s</xliff:g> ਵੱਲੋਂ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਪ੍ਰੋਫਾਈਲ <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ ਹੈ, ਜੋ ਈਮੇਲਾਂ, ਐਪਾਂ, ਅਤੇ ਵੈੱਬਸਾਈਟਾਂ ਸਮੇਤ ਤੁਹਾਡੀ ਕਾਰਜ ਨੈੱਟਵਰਕ ਸਰਗਰਮੀ ਦੀ ਨਿਗਰਾਨੀ ਕਰ ਸਕਦੀ ਹੈ।\n\nਤੁਸੀਂ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> ਨਾਲ ਵੀ ਕਨੈਕਟ ਹੋਂ, ਜੋ ਤੁਹਾਡੀ ਨਿੱਜੀ ਨੈੱਟਵਰਕ ਸਰਗਰਮੀ ਦੀ ਨਿਗਰਾਨੀ ਕਰ ਸਕਦੀ ਹੈ।"</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> ਲਈ ਅਨਲੌਕ ਕੀਤੀ ਗਈ"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> ਚੱਲ ਰਿਹਾ ਹੈ"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"ਡੀਵਾਈਸ ਲੌਕ ਰਹੇਗੀ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਮੈਨੂਅਲੀ ਅਨਲੌਕ ਨਹੀਂ ਕਰਦੇ"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"ਤੇਜ਼ੀ ਨਾਲ ਸੂਚਨਾਵਾਂ ਪ੍ਰਾਪਤ ਕਰੋ"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"ਅਨਲੌਕ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਉਹਨਾਂ ਨੂੰ ਦੇਖੋ"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"ਨਹੀਂ ਧੰਨਵਾਦ"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"ਸਥਾਪਤ ਕਰੋ"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"ਹੁਣੇ ਬੰਦ ਕਰੋ"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"ਹੁਣੇ ਸਮਾਪਤ ਕਰੋ"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"ਵਿਸਤਾਰ ਕਰੋ"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"ਨਸ਼ਟ ਕਰੋ"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"ਸਕ੍ਰੀਨ ਪਿੰਨ ਕੀਤੀ"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"ਬੰਦ"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"ਪਾਵਰ ਸੂਚਨਾ ਕੰਟਰੋਲਾਂ ਨਾਲ, ਤੁਸੀਂ ਕਿਸੇ ਐਪ ਦੀਆਂ ਸੂਚਨਾਵਾਂ ਲਈ ਮਹੱਤਤਾ ਪੱਧਰ ਨੂੰ 0 ਤੋਂ 5 ਤੱਕ ਸੈੱਟ ਕਰ ਸਕਦੇ ਹੋ। \n\n"<b>"ਪੱਧਰ 5"</b>" \n- ਸੂਚਨਾ ਸੂਚੀ ਦੇ ਸਿਖਰ \'ਤੇ ਵਿਖਾਓ \n- ਪੂਰੀ ਸਕ੍ਰੀਨ ਰੁਕਾਵਟ ਦੀ ਆਗਿਆ ਦਿਓ \n- ਹਮੇਸ਼ਾਂ ਝਲਕ ਵਿਖਾਓ \n\n"<b>"ਪੱਧਰ 4"</b>" \n- ਪੂਰੀ ਸਕ੍ਰੀਨ ਰੁਕਾਵਟ ਨੂੰ ਰੋਕੋ \n- ਹਮੇਸ਼ਾਂ ਝਲਕ ਵਿਖਾਓ \n\n"<b>"ਪੱਧਰ 3"</b>" \n- ਪੂਰੀ ਸਕ੍ਰੀਨ ਰੁਕਾਵਟ ਨੂੰ ਰੋਕੋ \n- ਕਦੇ ਝਲਕ ਨਾ ਵਿਖਾਓ \n\n"<b>"ਪੱਧਰ 2"</b>" \n- ਪੂਰੀ ਸਕ੍ਰੀਨ ਰੁਕਾਵਟ ਰੋਕੋ \n- ਕਦੇ ਝਲਕ ਨਾ ਵਿਖਾਓ \n- ਕਦੇ ਵੀ ਧੁਨੀ ਜਾਂ ਥਰਥਰਾਹਟ ਨਾ ਕਰੋ \n\n"<b>"ਪੱੱਧਰ 1"</b>" \n- ਪੂਰੀ ਸਕ੍ਰੀਨ ਰੁਕਾਵਟ ਨੂੰ ਰੋਕੋ \n- ਕਦੇ ਝਲਕ ਨਾ ਵਿਖਾਓ \n- ਕਦੇ ਧੁਨੀ ਜਾਂ ਥਰਥਰਾਹਟ ਨਾ ਕਰੋ \n- ਲੌਕ ਸਕ੍ਰੀਨ ਅਤੇ ਸਥਿਤੀ ਪੱਟੀ ਤੋਂ ਲੁਕਾਓ \n- ਸੂਚਨਾ ਸੂਚੀ ਦੇ ਹੇਠਾਂ ਵਿਖਾਓ \n\n"<b>"ਪੱਧਰ 0"</b>" \n- ਐਪ ਤੋਂ ਸਾਰੀਆਂ ਸੂਚਨਾਵਾਂ ਨੂੰ ਬਲੌਕ ਕਰੋ"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"ਸੂਚਨਾਵਾਂ"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"ਤੁਸੀਂ ਹੁਣ ਇਹ ਸੂਚਨਾਵਾਂ ਪ੍ਰਾਪਤ ਨਹੀਂ ਕਰੋਂਗੇ।"</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"ਤੁਹਾਨੂੰ ਹੁਣ ਇਹ ਸੂਚਨਾਵਾਂ ਪ੍ਰਾਪਤ ਨਹੀਂ ਹੋਣਗੀਆਂ"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> ਸੂਚਨਾ ਸ਼੍ਰੇਣੀਆਂ"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"ਇਸ ਐਪ ਵਿੱਚ ਸੂਚਨਾ ਸ਼੍ਰੇਣੀਆਂ ਨਹੀਂ ਹਨ"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">ਇਸ ਐਪ ਤੋਂ <xliff:g id="NUMBER_1">%d</xliff:g> ਸੂਚਨਾ ਸ਼੍ਰੇਣੀ ਵਿੱਚੋਂ 1</item>
<item quantity="other">ਇਸ ਐਪ ਤੋਂ <xliff:g id="NUMBER_1">%d</xliff:g> ਸੂਚਨਾ ਸ਼੍ਰੇਣੀਆਂ ਵਿੱਚੋਂ 1</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"ਸੂਚਨਾ ਕੰਟਰੋਲ"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"ਸੂਚਨਾ ਸਨੂਜ਼ ਵਿਕਲਪ"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 ਮਿੰਟ"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 ਮਿੰਟ"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 ਘੰਟਾ"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 ਘੰਟੇ"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ਅਣਕੀਤਾ ਕਰੋ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> ਲਈ ਸਨੂਜ਼ ਕੀਤਾ ਗਿਆ"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"ਬੈਟਰੀ ਵਰਤੋਂ"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"ਬੈਟਰੀ ਸੇਵਰ ਚਾਰਜਿੰਗ ਦੌਰਾਨ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"ਬੈਟਰੀ ਸੇਵਰ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings_car.xml b/packages/SystemUI/res/values-pa/strings_car.xml
index ac38625d..1c023a3 100644
--- a/packages/SystemUI/res/values-pa/strings_car.xml
+++ b/packages/SystemUI/res/values-pa/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"ਸੁਰੱਖਿਅਤ ਢੰਗ ਨਾਲ ਗੱਡੀ ਚਲਾਓ"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"ਗੱਡੀ ਚਲਾਉਣ ਦੇ ਹਾਲਾਤਾਂ ਤੋਂ ਪੂਰੀ ਤਰ੍ਹਾਂ ਜਾਣੂ ਰਹੋ ਅਤੇ ਸਦਾ ਲਾਗੂ ਕਾਨੂੰਨਾਂ ਦੀ ਪਾਲਣਾ ਕਰੋ। ਦਿਸ਼ਾਵਾਂ ਗਲਤ, ਅਧੂਰੀਆਂ, ਖ਼ਤਰਨਾਕ, ਢੁੱਕਵੀਆਂ ਨਹੀਂ, ਪ੍ਰਤਿਬੰਧਿਤ ਹੋ ਸਕਦੀਆਂ ਹਨ ਜਾਂ ਪ੍ਰਸ਼ਾਸਕੀ ਖੇਤਰਾਂ ਵਿੱਚੋਂ ਲੰਘਣਾ ਸ਼ਾਮਲ ਹੋ ਸਕਦਾ ਹੈ। ਵਪਾਰਕ ਜਾਣਕਾਰੀ ਵੀ ਗਲਤ ਜਾਂ ਅਧੂਰੀ ਹੋ ਸਕਦੀ ਹੈ। ਡੈਟਾ ਰੀਅਲ-ਟਾਈਮ ਨਹੀਂ ਹੈ ਅਤੇ ਟਿਕਾਣਾ ਸਟੀਕਤਾ ਗਾਰੰਟੀਸ਼ੁਦਾ ਨਹੀਂ ਹੋ ਸਕਦੀ ਹੈ। ਗੱਡੀ ਚਲਾਉਣ ਦੌਰਾਨ ਆਪਣੀ ਮੋਬਾਈਲ ਡੀਵਾਈਸ ਜਾਂ ਉਹਨਾਂ ਐਪਾਂ ਦੀ ਵਰਤੋਂ ਨਾ ਕਰੋ ਜੋ ਕਿ Android Auto ਲਈ ਨਹੀਂ ਬਣੀਆਂ ਹਨ।"</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"ਅਗਿਆਤ"</string>
+ <string name="start_driving" msgid="864023351402918991">"ਗੱਡੀ ਚਲਾਉਣਾ ਸ਼ੁਰੂ ਕਰੋ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index fd59150..86a6272 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -356,8 +356,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Szukaj"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Przesuń w górę: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Przesuń w lewo: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Nie będą Cię niepokoić żadne dźwięki ani wibracje z wyjątkiem alarmów, przypomnień, wydarzeń i połączeń od wybranych osób. Będziesz słyszeć wszystkie odtwarzane treści, takie jak muzyka, filmy czy gry."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Nie będą Cię niepokoić żadne dźwięki ani wibracje z wyjątkiem alarmów. Będziesz słyszeć wszystkie odtwarzane treści, takie jak muzyka, filmy czy gry."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Urządzenie nie będzie odtwarzać dźwięków ani włączać wibracji, z wyjątkiem wybranych przez Ciebie alarmów, przypomnień, wydarzeń i kontaktów."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Dostosuj"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"To zablokuje WSZYSTKIE dźwięki i wibracje, w tym alarmy, muzykę, filmy i gry. Nadal będzie można wykonywać połączenia."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"To zablokuje WSZYSTKIE dźwięki i wibracje – w tym alarmy, muzykę, filmy i gry."</string>
@@ -471,13 +470,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Masz połączenie z aplikacją <xliff:g id="APPLICATION">%1$s</xliff:g>, która może monitorować Twoją prywatną aktywność w sieci, w tym e-maile, aplikacje i strony internetowe."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Organizacja <xliff:g id="ORGANIZATION">%1$s</xliff:g> zarządza Twoim profilem do pracy. Profil jest połączony z aplikacją <xliff:g id="APPLICATION">%2$s</xliff:g>, która może monitorować Twoją aktywność w sieci, w tym e-maile, aplikacje i strony internetowe.\n\nSkontaktuj się z administratorem, aby uzyskać więcej informacji."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Organizacja <xliff:g id="ORGANIZATION">%1$s</xliff:g> zarządza Twoim profilem do pracy. Profil jest połączony z aplikacją <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, która może monitorować Twoją aktywność w sieci, w tym e-maile, aplikacje i strony internetowe.\n\nMasz też połączenie z aplikacją <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, która może monitorować Twoją osobistą aktywność w sieci."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Odblokowano dla: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"Aplikacja <xliff:g id="TRUST_AGENT">%1$s</xliff:g> jest uruchomiona"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Urządzenie pozostanie zablokowane, aż odblokujesz je ręcznie"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Szybszy dostęp do powiadomień"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Zobacz powiadomienia, jeszcze zanim odblokujesz ekran"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Nie, dziękuję"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Skonfiguruj"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Wyłącz teraz"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Zakończ teraz"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Rozwiń"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Zwiń"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekran jest przypięty"</string>
@@ -552,9 +553,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Wył."</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Dzięki zaawansowanym ustawieniom możesz określić poziom ważności powiadomień z aplikacji w skali od 0 do 5. \n\n"<b>"Poziom 5"</b>" \n– Pokazuj u góry listy powiadomień \n– Zezwalaj na powiadomienia na pełnym ekranie \n– Zawsze pokazuj podgląd \n\n"<b>"Poziom 4"</b>" \n– Wyłącz powiadomienia na pełnym ekranie \n– Zawsze pokazuj podgląd \n\n"<b>"Poziom 3"</b>" \n– Wyłącz powiadomienia na pełnym ekranie \n– Nigdy nie pokazuj podglądu \n\n"<b>"Poziom 2"</b>" \n– Wyłącz powiadomienia na pełnym ekranie \n– Nigdy nie pokazuj podglądu \n– NIgdy nie powiadamiaj dźwiękiem ani wibracjami \n\n"<b>"Poziom 1"</b>" \n– Wyłącz powiadomienia na pełnym ekranie \n– Nigdy nie pokazuj podglądu \n– NIgdy nie powiadamiaj dźwiękiem ani wibracjami \n– Ukrywaj na ekranie blokady i pasku stanu \n– Pokazuj u dołu listy powiadomień \n\n"<b>"Poziom 0"</b>" \n– Blokuj wszystkie powiadomienia aplikacji"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Powiadomienia"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Nie będziesz już otrzymywać tych powiadomień."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Nie będziesz już otrzymywać tych powiadomień"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"Kategorie powiadomień: <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Ta aplikacja nie ma kategorii powiadomień"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="few">1 z <xliff:g id="NUMBER_1">%d</xliff:g> kategorii powiadomień z tej aplikacji</item>
<item quantity="many">1 z <xliff:g id="NUMBER_1">%d</xliff:g> kategorii powiadomień z tej aplikacji</item>
@@ -578,12 +581,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"sterowanie powiadomieniami"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"opcje odkładania powiadomień"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 min"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 min"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 godz."</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 godziny"</string>
<string name="snooze_undo" msgid="6074877317002985129">"COFNIJ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Odłożono na <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Wykorzystanie baterii"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Oszczędzanie baterii nie jest dostępne podczas ładowania"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Oszczędzanie baterii"</string>
diff --git a/packages/SystemUI/res/values-pl/strings_car.xml b/packages/SystemUI/res/values-pl/strings_car.xml
index dbb0373..0841e27 100644
--- a/packages/SystemUI/res/values-pl/strings_car.xml
+++ b/packages/SystemUI/res/values-pl/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Jedź bezpiecznie"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Uważnie obserwuj warunki na drodze i zawsze przestrzegaj obowiązującego prawa. Wskazówki mogą być niedokładne, niekompletne, niebezpieczne, nieprzydatne lub niezgodne z prawem, mogą też obejmować przekraczanie obszarów administracyjnych. Informacje o firmach również mogą być niedokładne lub niekompletne. Dane nie są podawane w czasie rzeczywistym. Nie ma gwarancji, że dane o lokalizacji są dokładne. Podczas prowadzenia samochodu nie obsługuj urządzenia mobilnego ani nie używaj aplikacji nieprzeznaczonych na Androida Auto."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Brak informacji"</string>
+ <string name="start_driving" msgid="864023351402918991">"Uruchom tryb Jazda samochodem"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 7cd023c..ba62df4 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Pesquisar"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>, deslize para cima."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>, deslize para a esquerda."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Você não será perturbado por sons e vibrações, exceto alarmes, lembretes, eventos e chamadas de pessoas especificadas. No entanto, você ouvirá tudo o que decidir reproduzir, como músicas, vídeos e jogos."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Você não será perturbado por sons e vibrações, exceto alarmes. No entanto, você ouvirá tudo o que decidir reproduzir, como músicas, vídeos e jogos."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Você não será perturbado por sons e vibrações, com exceção de alarmes, lembretes, eventos e autores de chamadas que você especificar."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Personalizar"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Isso bloqueia TODOS os sons e vibrações, incluindo alarmes, músicas, vídeos e jogos. Você ainda poderá fazer chamadas telefônicas."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Isso bloqueia TODOS os sons e vibrações, incluindo alarmes, músicas, vídeos e jogos."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Você está conectado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorar sua atividade pessoal na rede, incluindo e-mails, apps e websites."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Seu perfil de trabalho é gerenciado por <xliff:g id="ORGANIZATION">%1$s</xliff:g>. O perfil está conectado a <xliff:g id="APPLICATION">%2$s</xliff:g>, que pode monitorar sua atividade profissional de rede, incluindo e-mails, apps e websites.\n\nPara saber mais informações, entre em contato com seu administrador."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Seu perfil de trabalho é gerenciado por <xliff:g id="ORGANIZATION">%1$s</xliff:g>. O perfil está conectado a <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, que pode monitorar sua atividade profissional de rede, incluindo e-mails, apps e websites.\n\nVocê também está conectado a <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, que pode monitorar sua atividade pessoal de rede."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Desbloqueado para <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> está em execução"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"O dispositivo permanecerá bloqueado até que você o desbloqueie manualmente"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Receba notificações mais rápido"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Veja-as antes de desbloquear"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Não, obrigado"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Configurar"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Desligar agora"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Finalizar agora"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Expandir"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Recolher"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"A tela está fixada"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Desativado"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Com controles de ativação de notificações, é possível definir o nível de importância de 0 a 5 para as notificações de um app. \n\n"<b>"Nível 5"</b>" \n- Exibir na parte superior da lista de notificações \n- Permitir interrupção em tela cheia \n- Sempre exibir \n\n"<b>"Nível 4"</b>" \n- Impedir interrupções em tela cheia \n- Sempre exibir \n\n"<b>"Nível 3"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n\n"<b>"Nível 2"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n- Nunca emitir som ou vibrar \n\n"<b>"Nível 1"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n- Nunca emitir som ou vibrar \n- Ocultar da tela de bloqueio e barra de status \n- Exibir na parte inferior da lista de notificações \n\n"<b>"Nível 0"</b>" \n- Bloquear todas as notificações do app"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Notificações"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Você deixará de receber essas notificações."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Você deixará de receber essas notificações"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> categorias de notificação"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Este app não tem categorias de notificação"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 de <xliff:g id="NUMBER_1">%d</xliff:g> categoria de notificação deste app</item>
<item quantity="other">1 de <xliff:g id="NUMBER_1">%d</xliff:g> categorias de notificação deste app</item>
@@ -572,12 +575,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> do <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"controles de notificação"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"opções de adiamento de notificação"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minutos"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minutos"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"Uma hora"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 horas"</string>
<string name="snooze_undo" msgid="6074877317002985129">"DESFAZER"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Adiada para <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Uso da bateria"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"A Economia de bateria não fica disponível durante o carregamento"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Economia de bateria"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings_car.xml b/packages/SystemUI/res/values-pt-rBR/strings_car.xml
index 5d754ef..b113ed7 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings_car.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Dirija com segurança"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Mantenha-se atento às condições da estrada e sempre obedeça às leis aplicáveis. As instruções podem ser imprecisas, incompletas, perigosas, inadequadas, proibidas ou envolver o cruzamento de áreas administrativas. As informações comerciais também podem ser imprecisas ou incompletas. Os dados não são exibidos em tempo real, e a precisão da localização não pode ser garantida. Enquanto dirige, não manuseie seu dispositivo móvel nem use apps que não tenham sido criados para o Android Auto."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Desconhecido"</string>
+ <string name="start_driving" msgid="864023351402918991">"Começar a dirigir"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index fe0859e..796e0d3 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Pesquisar"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Deslize para cima para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Deslize para a esquerda para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Não é incomodado por sons e vibrações, exceto de alarmes, lembretes, eventos e autores de chamadas que especificar. Continua a ouvir tudo o que optar por reproduzir, incluindo música, vídeos e jogos."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Não é incomodado por sons e vibrações, exceto de alarmes. Continua a ouvir tudo o que optar por reproduzir, incluindo música, vídeos e jogos."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Não é incomodado por sons e vibrações, exceto de alarmes, lembretes, eventos e autores de chamadas que especificar."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Personalizar"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Esta ação bloqueia TODOS os sons e as vibrações, incluindo de alarmes, de músicas, de vídeos e de jogos. Continua a ser possível telefonar."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Esta ação bloqueia TODOS os sons e as vibrações, incluindo de alarmes, de músicas, de vídeos e de jogos."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Está ligado ao <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorizar a atividade da rede pessoal, incluindo emails, aplicações e Sites."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"O seu perfil de trabalho é gerido pela <xliff:g id="ORGANIZATION">%1$s</xliff:g>. O perfil está associado à aplicação <xliff:g id="APPLICATION">%2$s</xliff:g>, que pode monitorizar a atividade da rede de trabalho, incluindo emails, aplicações e Sites.\n\nContacte o administrador para obter mais informações."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"O seu perfil de trabalho é gerido pela <xliff:g id="ORGANIZATION">%1$s</xliff:g>. O perfil está associado à aplicação <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, que pode monitorizar a atividade da rede de trabalho, incluindo emails, aplicações e Sites.\n\nTambém está associado à aplicação <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, que pode monitorizar a atividade da rede pessoal."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Desbloqueado para <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> em execução"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"O dispositivo permanecerá bloqueado até ser desbloqueado manualmente"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Receber notificações mais rapidamente"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Ver antes de desbloquear"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Não, obrigado"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Configurar"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Desativar agora"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Terminar agora"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Expandir"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Reduzir"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"O ecrã está fixado"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Desativado"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Com os controlos de notificações do consumo de energia, pode definir um nível de importância de 0 a 5 para as notificações de aplicações. \n\n"<b>"Nível 5"</b>" \n- Mostrar no início da lista de notificações \n- Permitir a interrupção do ecrã inteiro \n- Aparecer rapidamente sempre \n\n"<b>"Nível 4"</b>" \n- Impedir a interrupção do ecrã inteiro \n- Aparecer rapidamente sempre\n\n"<b>"Nível 3"</b>" \n- Impedir a interrupção do ecrã inteiro \n- Nunca aparecer rapidamente \n\n"<b>"Nível 2"</b>" \n- Impedir a interrupção do ecrã inteiro \n- Nunca aparecer rapidamente \n- Nunca tocar nem vibrar \n\n"<b>"Nível 1"</b>" \n- Impedir a interrupção do ecrã inteiro \n- Nunca aparecer rapidamente \n- Nunca tocar nem vibrar \n- Ocultar do ecrã de bloqueio e da barra de estado \n- Mostrar no fim da lista de notificações \n\n"<b>"Nível 0"</b>" \n- Bloquear todas as notificações da aplicação"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Notificações"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Já não recebe estas notificações."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Deixará de receber estas notificações"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> categorias de notificação"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Esta aplicação não tem categorias de notificação"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 out of <xliff:g id="NUMBER_1">%d</xliff:g> notification categories from this app</item>
<item quantity="other">1 de <xliff:g id="NUMBER_1">%d</xliff:g> categorias de notificação desta aplicação</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> do <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"controlos de notificação"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"opções de suspensão de notificações"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minutos"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minutos"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 hora"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 horas"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ANULAR"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Suspensa por <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Utiliz. da bateria"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Poupança de bateria não disponível durante o carregamento"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Poupança de bateria"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings_car.xml b/packages/SystemUI/res/values-pt-rPT/strings_car.xml
index afb030a..6a8b40d 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings_car.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Conduza com segurança"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Mantenha-se completamente atento às condições de condução e respeite sempre as leis aplicáveis. As direções podem ser imprecisas, incompletas, perigosas, inadequadas, proibidas ou envolver a travessia de áreas administrativas. Os dados das empresas também podem ser imprecisos ou incompletos. Os dados não são fornecidos em tempo real e não é possível garantir a precisão da localização. Não manuseie o dispositivo móvel nem utilize aplicações não destinadas ao Android Auto durante a condução."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Desconhecido"</string>
+ <string name="start_driving" msgid="864023351402918991">"Começar a conduzir"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 7cd023c..ba62df4 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Pesquisar"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>, deslize para cima."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Para <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>, deslize para a esquerda."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Você não será perturbado por sons e vibrações, exceto alarmes, lembretes, eventos e chamadas de pessoas especificadas. No entanto, você ouvirá tudo o que decidir reproduzir, como músicas, vídeos e jogos."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Você não será perturbado por sons e vibrações, exceto alarmes. No entanto, você ouvirá tudo o que decidir reproduzir, como músicas, vídeos e jogos."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Você não será perturbado por sons e vibrações, com exceção de alarmes, lembretes, eventos e autores de chamadas que você especificar."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Personalizar"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Isso bloqueia TODOS os sons e vibrações, incluindo alarmes, músicas, vídeos e jogos. Você ainda poderá fazer chamadas telefônicas."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Isso bloqueia TODOS os sons e vibrações, incluindo alarmes, músicas, vídeos e jogos."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Você está conectado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorar sua atividade pessoal na rede, incluindo e-mails, apps e websites."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Seu perfil de trabalho é gerenciado por <xliff:g id="ORGANIZATION">%1$s</xliff:g>. O perfil está conectado a <xliff:g id="APPLICATION">%2$s</xliff:g>, que pode monitorar sua atividade profissional de rede, incluindo e-mails, apps e websites.\n\nPara saber mais informações, entre em contato com seu administrador."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Seu perfil de trabalho é gerenciado por <xliff:g id="ORGANIZATION">%1$s</xliff:g>. O perfil está conectado a <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, que pode monitorar sua atividade profissional de rede, incluindo e-mails, apps e websites.\n\nVocê também está conectado a <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, que pode monitorar sua atividade pessoal de rede."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Desbloqueado para <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> está em execução"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"O dispositivo permanecerá bloqueado até que você o desbloqueie manualmente"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Receba notificações mais rápido"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Veja-as antes de desbloquear"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Não, obrigado"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Configurar"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Desligar agora"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Finalizar agora"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Expandir"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Recolher"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"A tela está fixada"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Desativado"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Com controles de ativação de notificações, é possível definir o nível de importância de 0 a 5 para as notificações de um app. \n\n"<b>"Nível 5"</b>" \n- Exibir na parte superior da lista de notificações \n- Permitir interrupção em tela cheia \n- Sempre exibir \n\n"<b>"Nível 4"</b>" \n- Impedir interrupções em tela cheia \n- Sempre exibir \n\n"<b>"Nível 3"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n\n"<b>"Nível 2"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n- Nunca emitir som ou vibrar \n\n"<b>"Nível 1"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n- Nunca emitir som ou vibrar \n- Ocultar da tela de bloqueio e barra de status \n- Exibir na parte inferior da lista de notificações \n\n"<b>"Nível 0"</b>" \n- Bloquear todas as notificações do app"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Notificações"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Você deixará de receber essas notificações."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Você deixará de receber essas notificações"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> categorias de notificação"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Este app não tem categorias de notificação"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 de <xliff:g id="NUMBER_1">%d</xliff:g> categoria de notificação deste app</item>
<item quantity="other">1 de <xliff:g id="NUMBER_1">%d</xliff:g> categorias de notificação deste app</item>
@@ -572,12 +575,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> do <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"controles de notificação"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"opções de adiamento de notificação"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minutos"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minutos"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"Uma hora"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 horas"</string>
<string name="snooze_undo" msgid="6074877317002985129">"DESFAZER"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Adiada para <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Uso da bateria"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"A Economia de bateria não fica disponível durante o carregamento"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Economia de bateria"</string>
diff --git a/packages/SystemUI/res/values-pt/strings_car.xml b/packages/SystemUI/res/values-pt/strings_car.xml
index 5d754ef..b113ed7 100644
--- a/packages/SystemUI/res/values-pt/strings_car.xml
+++ b/packages/SystemUI/res/values-pt/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Dirija com segurança"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Mantenha-se atento às condições da estrada e sempre obedeça às leis aplicáveis. As instruções podem ser imprecisas, incompletas, perigosas, inadequadas, proibidas ou envolver o cruzamento de áreas administrativas. As informações comerciais também podem ser imprecisas ou incompletas. Os dados não são exibidos em tempo real, e a precisão da localização não pode ser garantida. Enquanto dirige, não manuseie seu dispositivo móvel nem use apps que não tenham sido criados para o Android Auto."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Desconhecido"</string>
+ <string name="start_driving" msgid="864023351402918991">"Começar a dirigir"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 3fd6b9b..8c39954 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -356,8 +356,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Căutați"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Glisați în sus pentru <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Glisați spre stânga pentru <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Se vor anunța prin sunete și vibrații numai alarmele, mementourile, evenimentele și apelanții specificați de dvs. Totuși, veți auzi tot ce alegeți să redați, inclusiv muzică, videoclipuri și jocuri."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Se vor anunța prin sunete și vibrații numai alarmele. Totuși, veți auzi tot ce alegeți să redați, inclusiv muzică, videoclipuri și jocuri."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Nu veți fi deranjat(ă) de sunete și vibrații, exceptând alarmele, mementourile, evenimentele și apelanții pe care îi menționați."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Personalizați"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Această opțiune blochează TOATE sunetele și vibrațiile, inclusiv cele ale alarmelor, muzicii, videoclipurilor și jocurilor. Totuși, veți putea iniția apeluri."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Această opțiune blochează TOATE sunetele și vibrațiile, inclusiv cele ale alarmelor, muzicii, videoclipurilor și jocurilor."</string>
@@ -471,13 +470,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"V-ați conectat la aplicația <xliff:g id="APPLICATION">%1$s</xliff:g>, care vă poate monitoriza activitatea personală în rețea, inclusiv e-mailurile, aplicațiile și site-urile accesate."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Profilul de serviciu este gestionat de <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profilul este conectat la <xliff:g id="APPLICATION">%2$s</xliff:g>, care vă poate monitoriza activitatea în rețeaua de serviciu, inclusiv e-mailurile, aplicațiile și site-urile accesate.\n\nPentru mai multe informații, contactați administratorul."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Profilul de serviciu este gestionat de <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profilul este conectat la <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, care vă poate monitoriza activitatea în rețeaua de serviciu, inclusiv e-mailurile, aplicațiile și site-urile accesate.\n\nDe asemenea, v-ați conectat la <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, care vă poate monitoriza activitatea în rețeaua personală."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Deblocat pentru <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> rulează"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Dispozitivul va rămâne blocat până când îl deblocați manual"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Obțineți notificări mai rapid"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Doresc să se afișeze înainte de deblocare"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Nu, mulț."</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Config."</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Dezactivați acum"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Opriți acum"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Extindeți"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Restrângeți"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ecranul este fixat"</string>
@@ -552,9 +553,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Dezactivate"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Folosind comenzile de gestionare a notificărilor, puteți să setați un nivel de importanță de la 0 la 5 pentru notificările unei aplicații. \n\n"<b>"Nivelul 5"</b>" \n– Se afișează la începutul listei de notificări \n– Se permite întreruperea pe ecranul complet \n– Se afișează întotdeauna scurt \n\n"<b>"Nivelul 4"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Se afișează întotdeauna scurt \n\n"<b>"Nivelul 3"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n\n"<b>"Nivelul 2"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n– Nu se emit sunete și nu vibrează niciodată \n\n"<b>"Nivelul 1"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n– Nu se emit sunete și nu vibrează niciodată \n– Se ascunde în ecranul de blocare și în bara de stare \n– Se afișează la finalul listei de notificări \n\n"<b>"Nivelul 0"</b>" \n– Se blochează toate notificările din aplicație"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Notificări"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Nu veți mai primi aceste notificări."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Nu veți mai primi aceste notificări"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> categorii de notificări"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Această aplicație nu are categorii de notificare"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="few">1 din <xliff:g id="NUMBER_1">%d</xliff:g> categorii de notificare din această aplicație</item>
<item quantity="other">1 din <xliff:g id="NUMBER_1">%d</xliff:g> de categorii de notificare din această aplicație</item>
@@ -576,12 +579,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"comenzile notificării"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"opțiuni de amânare a notificării"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minute"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 de minute"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 oră"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 ore"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ANULAȚI"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Amânată <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Utilizarea bateriei"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Economisirea bateriei nu este disponibilă pe durata încărcării"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Economisirea bateriei"</string>
diff --git a/packages/SystemUI/res/values-ro/strings_car.xml b/packages/SystemUI/res/values-ro/strings_car.xml
index 54d8585..27751f6 100644
--- a/packages/SystemUI/res/values-ro/strings_car.xml
+++ b/packages/SystemUI/res/values-ro/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Conduceți atent"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Aflați mereu condițiile de trafic și respectați întotdeauna legislația aplicabilă. Indicațiile de orientare pot fi inexacte, incomplete, periculoase, neadecvate, interzise sau pot implica trecerea prin zone administrative. Informațiile despre companii pot fi, de asemenea, inexacte sau incomplete. Datele nu sunt în timp real și nu se poate garanta exactitatea locației. Nu manipulați dispozitivul mobil și nu folosiți aplicații neconcepute pentru Android Auto când sunteți la volan."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Necunoscut"</string>
+ <string name="start_driving" msgid="864023351402918991">"Începeți să conduceți"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 2fa3ad2..956179e 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -358,8 +358,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Поиск"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Проведите вверх, чтобы <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Проведите влево, чтобы <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Вас не будут отвлекать звуки и вибрация, за исключением сигналов будильника, напоминаний, уведомлений о мероприятиях и звонков от помеченных контактов. Вы по-прежнему будете слышать включенную вами музыку, видео, игры и т. д."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Вас не будут отвлекать звуки и вибрация, за исключением сигналов будильника. Вы по-прежнему будете слышать включенную вами музыку, видео, игры и т. д."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Вас не будут отвлекать звуки и вибрация, за исключением сигналов будильника, напоминаний, уведомлений о мероприятиях и звонков от помеченных контактов."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Настроить"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"В этом режиме будут отключены вибросигнал и все звуки (в том числе для будильника, музыкального проигрывателя, игр и видео). При этом вы сможете разговаривать по телефону."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"В этом режиме будут отключены вибросигнал и все звуки (в том числе для будильника, музыкального проигрывателя, игр и видео)."</string>
@@ -473,13 +472,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Запущено приложение \"<xliff:g id="APPLICATION">%1$s</xliff:g>\", которое может отслеживать ваши действия в сети, включая работу с электронной почтой, приложениями и веб-сайтами."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Вашим рабочим профилем управляет организация \"<xliff:g id="ORGANIZATION">%1$s</xliff:g>\". Приложение \"<xliff:g id="APPLICATION">%2$s</xliff:g>\" может отслеживать ваши действия в корпоративной сети, включая работу с электронной почтой, приложениями и веб-сайтами.\n\nЧтобы получить подробную информацию, обратитесь к администратору."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Вашим рабочим профилем управляет организация \"<xliff:g id="ORGANIZATION">%1$s</xliff:g>\". Приложение \"<xliff:g id="APPLICATION_WORK">%2$s</xliff:g>\" может отслеживать ваши действия в корпоративной сети, включая работу с электронной почтой, приложениями и веб-сайтами.\n\nТакже запущено приложение \"<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>\", которое может отслеживать ваши действия в сети, выполняемые в личном профиле."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Разблокировано для пользователя <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"Агент \"<xliff:g id="TRUST_AGENT">%1$s</xliff:g>\" работает"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Устройство необходимо будет разблокировать вручную"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Быстрый доступ к уведомлениям"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Просматривайте уведомления на заблокированном экране."</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Закрыть"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Настроить"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>."</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Отключить"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Завершить"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Развернуть"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Свернуть"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Блокировка в приложении включена"</string>
@@ -554,9 +555,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Отключено"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"С помощью этой функции вы можете устанавливать уровень важности уведомлений от 0 до 5 для каждого приложения.\n\n"<b>"Уровень 5"</b>\n"‒ Помещать уведомления в начало списка.\n‒ Показывать полноэкранные уведомления.\n‒ Показывать всплывающие уведомления.\nУровень 4\n"<b></b>\n"‒ Не показывать полноэкранные уведомления.\n‒ Показывать всплывающие уведомления.\nУровень 3\n"<b></b>\n"‒ Не показывать полноэкранные уведомления.\n‒ Не показывать всплывающие уведомления.\nУровень 2\n"<b></b>\n"‒ Не показывать полноэкранные уведомления.\n‒ Не показывать всплывающие уведомления.\n‒ Не использовать звук и вибрацию.\nУровень 1\n"<b></b>\n"‒ Не показывать полноэкранные уведомления.\n‒ Не показывать всплывающие уведомления.\n‒ Не использовать звук и вибрацию.\n‒ Не показывать на экране блокировки и в строке состояния.\n‒ Помещать уведомления в конец списка.\nУровень 0\n"<b></b>\n"‒ Блокировать все уведомления приложения."</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Уведомления"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Вы больше не будете получать эти уведомления."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Вы больше не будете получать эти уведомления"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"Категорий оповещений: <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"В приложении нет категорий уведомлений"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 из <xliff:g id="NUMBER_1">%d</xliff:g> категории уведомлений этого приложения</item>
<item quantity="few">1 из <xliff:g id="NUMBER_1">%d</xliff:g> категорий уведомлений этого приложения</item>
@@ -580,12 +583,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g>: <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"настройки уведомлений"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"параметры отсрочки уведомлений"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 минут"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 минут"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 час"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 часа"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ОТМЕНИТЬ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Отложено на <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Уровень заряда"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Режим энергосбережения нельзя включить во время зарядки"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Режим энергосбережения"</string>
diff --git a/packages/SystemUI/res/values-ru/strings_car.xml b/packages/SystemUI/res/values-ru/strings_car.xml
index cbdb8d7..f697cbd 100644
--- a/packages/SystemUI/res/values-ru/strings_car.xml
+++ b/packages/SystemUI/res/values-ru/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Безопасность движения"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Всегда учитывайте дорожную обстановку и соблюдайте правила. Маршруты могут быть неточными, неполными, проходить по опасным, неподходящим или запрещенным для движения участкам или пересекать границы. Сведения об организациях также могут быть неточными и неполными. Данные не обновляются в режиме реального времени, и точность определения местоположения не гарантируется. Не пользуйтесь мобильным устройством и не используйте приложения, не предназначенные для Android Auto, управляя автомобилем."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Неизвестный пользователь"</string>
+ <string name="start_driving" msgid="864023351402918991">"Запустить навигацию"</string>
</resources>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index c8865ae..b6743e1 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"සෙවීම"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> සඳහා උඩට සර්පණය කරන්න."</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> සඳහා වමට සර්පණය කරන්න."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"එලාම සිහිකැඳවීම්, සිදුවීම්, සහ ඔබ සඳහන් කළ අමතන්නන් හැර, ශබ්ද සහ කම්පනවලින් ඔබට බාධා නොවනු ඇත. සංගීතය, වීඩියෝ, සහ ක්රීඩා ඇතුළු ඔබ වාදනය කිරීමට තෝරන ලද සියලු දේ ඔබට තවම ඇසෙනු ඇත."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"එලාම හැර, ශබ්ද සහ කම්පනවලින් ඔබට බාධා නොවනු ඇත. සංගීතය, වීඩියෝ, සහ ක්රීඩා ඇතුළු ඔබ වාදනය කිරීමට තෝරන ලද සියලු දේ ඔබට තවම ඇසෙනු ඇත."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"එලාම, සිහිකැඳවීම්, සිදුවීම් සහ ඔබ සඳහන් කරන අමතන්නන් වෙතින් හැර, ශබ්ද සහ කම්පනවලින් ඔබට බාධා නොවනු ඇත."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"අභිරුචිකරණය"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"මෙය එලාම්, සංගීතය, වීඩියෝ, සහ ක්රීඩා ඇතුළු, සියලු ශබ්ද සහ කම්පන අවහිර කරයි. ඔබට තවමත් දුරකථන ඇමතුම් ගැනීමට හැකියාව ඇත."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"මෙය එලාම්, සංගීතය, වීඩියෝ, සහ ක්රීඩා ඇතුළු, සියලු ශබ්ද සහ කම්පන අවහිර කරයි."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"ඊ-තැපැල්, යෙදුම් සහ වෙබ් අඩවි ඇතුළු ඔබේ පෞද්ගලික ජාල ක්රියාකාරකම් නිරීක්ෂණය කළ හැකි, <xliff:g id="APPLICATION">%1$s</xliff:g> වෙත ඔබ සම්බන්ධ වී ඇත."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"ඔබේ කාර්ය පැතිකඩ කළමනාකරණය කරන්නේ <xliff:g id="ORGANIZATION">%1$s</xliff:g> මගිනි. ඔබේ ඊ-තැපැල්, යෙදුම්, සහ වෙබ් අඩවි ඇතුළු, ඔබේ ජාල ක්රියාකාරකම් නිරීක්ෂණය කළ හැකි, <xliff:g id="APPLICATION">%2$s</xliff:g> වෙත පැතිකඩ සම්බන්ධය.\n\nවැඩිදුර තොරතුරු සඳහා, ඔබගේ පරිපාලක අමතන්න."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"ඔබේ කාර්යාල පැතිකඩ කළමනාකරණය කරන්නේ <xliff:g id="ORGANIZATION">%1$s</xliff:g> විසිනි. ඊ-තැපැල්, යෙදුම් සහ වෙබ් අඩවි ඇතුළු ඔබේ කාර්යාල ජාල ක්රියාකාරකම් නිරීක්ෂණය කළ හැකි, <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, වෙත පැතිකඩ සම්බන්ධ වී ඇත.\n\nඔබ ඔබේ පෞද්ගලික ජාල ක්රියාකාරකම් නිරීක්ෂණය කළ හැකි, <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> වෙතද සම්බන්ධ වී ඇත."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> සඳහා අගුලු හරින ලදී"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> ධාවනය වේ"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"ඔබ අතින් අගුළු අරින තුරු උපකරණය අගුළු වැටි තිබේ"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"දැනුම්දීම් ඉක්මනින් ලබාගන්න"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"ඔබ අඟුළු හැරීමට කලින් ඒවා බලන්න"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"එපා ස්තූතියි"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"සකසන්න"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"දැන් ක්රියාවිරහිත කරන්න"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"දැන් අවසන් කරන්න"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"දිග හරින්න"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"හකුළන්න"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"තීරය අමුණන ලදි"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"ක්රියාවිරහිතයි"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"බල දැනුම්දීම් පාලන සමගින්, ඔබට යෙදුමක දැනුම්දීම් සඳහා වැදගත්කම 0 සිට 5 දක්වා සැකසිය හැකිය. \n\n"<b>"5 මට්ටම"</b>" \n- දැනුම්දීම් ලැයිස්තුවේ ඉහළින්ම පෙන්වන්න \n- පූර්ණ තිර බාධාවට ඉඩ දෙන්න \n- සැම විට එබී බලන්න \n\n"<b>"4 මට්ටම"</b>" \n- පූර්ණ තිර බාධාව වළක්වන්න \n- සැම විට එබී බලන්න \n\n"<b>"3 මට්ටම"</b>" \n- පූර්ණ තිර බාධාව වළක්වන්න \n- කිසි විටක එබී නොබලන්න \n\n"<b>"2 මට්ටම"</b>" \n- පූර්ණ තිර බාධාව වළක්වන්න \n- කිසි විටක එබී නොබලන්න \n- කිසි විටක හඬ සහ කම්පනය සිදු නොකරන්න \n\n"<b>"1 මට්ටම"</b>" \n- පූර්ණ තිර බාධාව වළක්වන්න \n- කිසි විටක එබී නොබලන්න \n- කිසි විටක හඬ සහ කම්පනය සිදු නොකරන්න \n- අගුලු තිරය සහ තත්ත්ව තීරුව වෙතින් සඟවන්න \n- දැනුම්දීම් ලැයිස්තුවේ පහළින්ම පෙන්වන්න \n\n"<b>"0 මට්ටම"</b>" \n- යෙදුම වෙතින් වන සියලු දැනුම් දීම් සඟවන්න."</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"දැනුම් දීම්"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"ඔබ තවදුරටත් මෙම දැනුම්දීම් නොලැබෙනු ඇත."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"ඔබට තවදුරටත් මෙම දැනුම්දීම් නොලැබෙනු ඇත"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"දැනුම්දීම් කාණ්ඩ <xliff:g id="NUMBER">%d</xliff:g>ක්"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"මෙම යෙදුම හට දැනුම්දීම් ප්රවර්ග නොමැත"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">මෙම යෙදුමෙන් දැනුම්දීම් ප්රවර්ග <xliff:g id="NUMBER_1">%d</xliff:g>න් 1ක්</item>
<item quantity="other">මෙම යෙදුමෙන් දැනුම්දීම් ප්රවර්ග <xliff:g id="NUMBER_1">%d</xliff:g>න් 1ක්</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"දැනුම්දීම් පාලන"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"දැනුම්දීම් මදක් නතර කිරීමේ විකල්ප"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"මිනිත්තු 15"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"මිනිත්තු 30"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"පැය 1"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"පැය 2"</string>
<string name="snooze_undo" msgid="6074877317002985129">"අස් කරන්න"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g>ක් මදක් නතර කරන ලදී"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"බැටරි භාවිතය"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"ආරෝපණය අතරතුර බැටරි සුරැකුම ලබා ගත නොහැකිය."</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"බැටරි සුරැකුම"</string>
diff --git a/packages/SystemUI/res/values-si/strings_car.xml b/packages/SystemUI/res/values-si/strings_car.xml
index 549e89b..14ec53b 100644
--- a/packages/SystemUI/res/values-si/strings_car.xml
+++ b/packages/SystemUI/res/values-si/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"ආරක්ෂිතව රිය පදවන්න"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"රිය පැදවීම් තත්ත්වයන් ගැන පූර්ණ අවධානයෙන් සිටින්න, සැම විටම අදාළ සියලු නීතිවලට ගරු කරන්න. මග පෙන්වීම් නිවැරදි නොවීමට, අසම්පූර්ණ වීමට, භයානක වීමට, නුසුදුසු වීමට, තහනම් ඒවා වීමට, හෝ පරිපාලන ප්රදේශ තරණයට අදාළ ඒවා වීමට හැකිය. දත්ත තථ්ය කාල නොවීමට හැකි අතර, ස්ථාන නිරවද්යතාව සහතික කළ නොහැකිය. රිය පදවන අතරතුර ඔබේ ජංගම උපාංගය භාවිත කිරීම හෝ Android Auto සඳහා නිපදවා නැති යෙදුම් භාවිත කිරීම නොකරන්න."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"නොදනී"</string>
+ <string name="start_driving" msgid="864023351402918991">"රිය පැදවීම ආරම්භ කරන්න"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 9d5eb79..d102dda 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -358,8 +358,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Vyhľadávanie"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Prejdite prstom nahor: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Prejdite prstom doľava: <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Nebudú vás vyrušovať zvuky ani vibrácie, iba budíky, pripomenutia, udalosti a volajúci, ktorých určíte. Budete naďalej počuť všetko, čo sa rozhodnete prehrať, ako napríklad hudbu, videá a hry."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Nebudú vás vyrušovať zvuky ani vibrácie, iba budíky. Budete naďalej počuť všetko, čo sa rozhodnete prehrať, ako napríklad hudbu, videá a hry."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Nebudú vás rušiť zvuky ani vibrácie s výnimkou budíkov, pripomenutí, udalostí a vybraných volajúcich."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Prispôsobiť"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Budú zablokované všetky zvuky a vibrácie vrátane budíkov, hudby, videí a hier. Stále však budete môcť telefonovať."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Zablokujú sa tým VŠETKY zvuky a vibrácie vrátane zvukov budíkov, hudby, videí a hier."</string>
@@ -473,13 +472,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Ste pripojený/-á k aplikácii <xliff:g id="APPLICATION">%1$s</xliff:g>, ktorá môže sledovať vašu osobnú aktivitu v sieti vrátane e-mailových správ, aplikácií a webových stránok."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Váš pracovný profil spravuje organizácia <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Je pripojený k aplikácii <xliff:g id="APPLICATION">%2$s</xliff:g>, ktorá môže sledovať vašu pracovnú aktivitu v sieti vrátane správ, aplikácií a webových stránok.\n\nĎalšie informácie vám poskytne správca."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Váš pracovný profil spravuje organizácia <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Je pripojený k aplikácii <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, ktorá môže sledovať vašu pracovnú aktivitu v sieti vrátane správ, aplikácií a webových stránok.\n\nPripojili ste sa k aplikácii <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, ktorá môže sledovať vašu osobnú aktivitu v sieti."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Odomknuté pre používateľa <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"Agent <xliff:g id="TRUST_AGENT">%1$s</xliff:g> je spustený"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Zariadenie zostane uzamknuté, dokým ho ručne neodomknete."</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Získavať upozornenia rýchlejšie"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Zobraziť pred odomknutím"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Nie, vďaka"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Nastaviť"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Vypnúť"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Skončiť"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Rozbaliť"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Zbaliť"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Obrazovka je pripnutá"</string>
@@ -554,9 +555,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Vypnuté"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Pomocou ovládacích prvkov zobrazovania upozornení môžete nastaviť pre upozornenia aplikácie úroveň dôležitosti od 0 do 5. \n\n"<b>"Úroveň 5"</b>" \n– Zobrazovať v hornej časti zoznamu upozornení. \n– Povoliť prerušenia na celú obrazovku. \n– Vždy zobrazovať čiastočne. \n\n"<b>"Úroveň 4"</b>" \n– Zabrániť prerušeniam na celú obrazovku. \n– Vždy zobrazovať čiastočne. \n\n"<b>"Úroveň 3"</b>" \n– Zabrániť prerušeniam na celú obrazovku. \n– Nikdy nezobrazovať čiastočne. \n\n"<b>"Úroveň 2"</b>" \n– Zabrániť prerušeniam na celú obrazovku. \n– Nikdy nezobrazovať čiastočne. \n– Nikdy nespúšťať zvuk ani vibrácie. \n\n"<b>"Úroveň 1"</b>" \n– Zabrániť prerušeniam na celú obrazovku. \n– Nikdy nezobrazovať čiastočne. \n– Nikdy nespúšťať zvuk ani vibrácie. \n– Skryť na uzamknutej obrazovke a v stavovom riadku. \n– Zobraziť v dolnej časti zoznamu upozornení. \n\n"<b>"Úroveň 0"</b>" \n– Blokovať všetky upozornenia z aplikácie."</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Upozornenia"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Takéto upozornenia už nebudete dostávať."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Tieto upozornenia už nebudete dostávať"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"Počet kategórií upozornení: <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Táto aplikácia nemá kategórie upozornení"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="few">1 z <xliff:g id="NUMBER_1">%d</xliff:g> kategórií upozornení z tejto aplikácie</item>
<item quantity="many">1 z <xliff:g id="NUMBER_1">%d</xliff:g> kategórie upozornení z tejto aplikácie</item>
@@ -580,12 +583,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"ovládacie prvky pre upozornenia"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"možnosti stlmenia upozornení"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minút"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minút"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 hod."</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 hodiny"</string>
<string name="snooze_undo" msgid="6074877317002985129">"SPÄŤ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Stlmené na <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Využitie batérie"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Počas nabíjania nie je Šetrič batérie k dispozícii"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Šetrič batérie"</string>
diff --git a/packages/SystemUI/res/values-sk/strings_car.xml b/packages/SystemUI/res/values-sk/strings_car.xml
index bd9410d..6512065 100644
--- a/packages/SystemUI/res/values-sk/strings_car.xml
+++ b/packages/SystemUI/res/values-sk/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Jazdite bezpečne"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Zachovajte si úplný prehľad o jazdných podmienkach a vždy dodržiavajte príslušné zákony. Trasy môžu byť nepresné, neúplné, nebezpečné, nevhodné, zakázané alebo môžu zahŕňať cestu cez rôzne správne oblasti. Obchodné informácie môžu byť tiež nepresné alebo neúplné. Údaje nie sú aktualizované v reálnom čase a presnosť polohy nemôžeme zaručiť. Počas jazdy nemanipulujte s mobilným zariadením ani nepoužívajte aplikácie, ktoré nie sú určené pre funkciu Android Auto."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Neznáme"</string>
+ <string name="start_driving" msgid="864023351402918991">"Začať šoférovať"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 8289516..f61049d 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -358,8 +358,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Iskanje"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Povlecite navzgor za <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Povlecite v levo za <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Ne bodo vas motili zvoki ali tresenje, razen od alarmov, opomnikov, dogodkov in klicateljev, ki jih določite. Še vedno pa boste slišali vse, kar se boste odločili predvajati, vključno z glasbo, videoposnetki in igrami."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Ne bodo vas motili zvoki ali tresenje, razen alarmov. Še vedno pa boste slišali vse, kar se boste odločili predvajati, vključno z glasbo, videoposnetki in igrami."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Ne bodo vas motili zvoki in vibriranje, razen od alarmov, opomnikov, dogodkov in klicateljev, ki jih določite."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Prilagodi"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"S tem so blokirani VSI zvoki in vibriranje – tudi od alarmov, glasbe, videoposnetkov in iger. Še vedno boste lahko opravljali telefonske klice."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"S tem so blokirani VSI zvoki in vibriranje – tudi od alarmov, glasbe, videoposnetkov in iger."</string>
@@ -473,13 +472,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Povezani ste z aplikacijo <xliff:g id="APPLICATION">%1$s</xliff:g>, ki lahko nadzira vašo osebno omrežno dejavnost, vključno z e-pošto, aplikacijami in spletnimi mesti."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Delovni profil upravlja organizacija <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profil je povezan z aplikacijo <xliff:g id="APPLICATION">%2$s</xliff:g>, ki lahko nadzira vašo delovno omrežno dejavnost, vključno z e-pošto, aplikacijami in spletnimi mesti.\n\nZa več informacij se obrnite na skrbnika."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Delovni profil upravlja organizacija <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profil je povezan z aplikacijo <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, ki lahko nadzira vašo delovno omrežno dejavnost, vključno z e-pošto, aplikacijami in spletnimi mesti.\n\nPovezani ste tudi z aplikacijo <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, ki lahko nadzira vašo osebno omrežno dejavnost."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Odklenjeno za uporabnika <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> se izvaja"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Naprava bo ostala zaklenjena, dokler je ročno ne odklenete."</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Hitrejše prejemanje obvestil"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Oglejte si jih pred odklepanjem"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Ne, hvala"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Nastavitev"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Izklopi"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Končaj zdaj"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Razširi"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Strni"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Zaslon je pripet"</string>
@@ -554,9 +555,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Izklopljeno"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"S kontrolniki za pomebnost obvestila je mogoče za obvestila aplikacije nastaviti stopnjo pomembnosti od 0 do 5. \n\n"<b>"Stopnja 5"</b>" \n– Prikaz na vrhu seznama obvestil \n– Omogočanje prekinitev v celozaslonskem načinu \n– Vedno prikaži hitre predoglede \n\n"<b>"Stopnja 4"</b>" \n– Preprečevanje prekinitev v celozaslonskem načinu \n– Vedno prikaži hitre predoglede \n\n"<b>"Stopnja 3"</b>" \n– Preprečevanje prekinitev v celozaslonskem načinu \n– Nikoli ne prikaži hitrih predogledov \n\n"<b>"Stopnja 2"</b>" \n– Preprečevanje prekinitev v celozaslonskem načinu \n– Nikoli ne prikaži hitrih predogledov \n– Nikoli ne uporabi zvoka in vibriranja \n\n"<b>"Stopnja 1"</b>" \n– Preprečevanje prekinitev v celozaslonskem načinu \n– Nikoli ne prikaži hitrih predogledov \n– Nikoli ne uporabi zvoka in vibriranja \n– Skrivanje na zaklenjenem zaslonu in v vrstici stanja \n– Prikaz na dnu seznama obvestil \n\n"<b>"Stopnja 0"</b>" \n– Blokiranje vseh obvestil aplikacije"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Obvestila"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Teh obvestil ne boste več prejemali."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Teh obvestil ne boste več prejemali"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"Št. kategorij obvestil: <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Ta aplikacija nima kategorij obvestil"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 od <xliff:g id="NUMBER_1">%d</xliff:g> kategorije obvestil iz te aplikacije</item>
<item quantity="two">1 od <xliff:g id="NUMBER_1">%d</xliff:g> kategorij obvestil iz te aplikacije</item>
@@ -580,12 +583,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"kontrolniki obvestil"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"možnosti preložitve obvestil"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minut"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minut"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 ura"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 uri"</string>
<string name="snooze_undo" msgid="6074877317002985129">"RAZVELJAVI"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Preloženo za <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Poraba akumulatorja"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Varčevanje z energijo akumulatorja med polnjenjem ni na voljo"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Varčevanje z energijo akumulatorja"</string>
diff --git a/packages/SystemUI/res/values-sl/strings_car.xml b/packages/SystemUI/res/values-sl/strings_car.xml
index 0102799..8e05730 100644
--- a/packages/SystemUI/res/values-sl/strings_car.xml
+++ b/packages/SystemUI/res/values-sl/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Varna vožnja"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Bodite pozorni na vozne razmere in vselej upoštevajte veljavno zakonodajo. Navodila za pot so morda nenatančna, nepopolna, nevarna, neprimerna ali prepovedana oziroma vključujejo prečkanje upravnih območij. Prav tako nenatančni ali nepopolni so lahko podatki o podjetjih. Podatki niso sprotni in natančnosti lociranja ni mogoče zagotoviti. Med vožnjo ne uporabljajte mobilne naprave ali aplikacij, ki niso namenjene za Android Auto."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Neznano"</string>
+ <string name="start_driving" msgid="864023351402918991">"Začnite voziti"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 15cc542..1ad24eb 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Kërko"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Rrëshqit lart për <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Rrëshqit majtas për <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Nuk do të shqetësohesh nga tingujt dhe dridhjet, përveç alarmeve, alarmeve rikujtuese, ngjarjeve dhe telefonuesve që specifikon. Do të vazhdosh të dëgjosh gjithçka që zgjedh të luash duke përfshirë muzikën, videot dhe lojërat."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Nuk do të shqetësohesh nga tingujt dhe dridhjet, përveç alarmeve. Do të vazhdosh të dëgjosh gjithçka që zgjedh të luash duke përfshirë muzikën, videot dhe lojërat."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Nuk do të shqetësohesh nga tingujt dhe dridhjet, përveç atyre nga alarmet, rikujtesat, ngjarjet dhe telefonuesit që specifikon."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Personalizo"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Kjo bllokon TË GJITHË tingujt dhe dridhjet, duke përfshirë edhe nga alarmet, muzika, videot dhe lojërat. Përsëri do të mund të bësh telefonata."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Kjo bllokon TË GJITHË tingujt dhe dridhjet, duke përfshirë edhe nga alarmet, muzika, videot dhe lojërat."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Je i lidhur me aplikacionin <xliff:g id="APPLICATION">%1$s</xliff:g>, i cili mund të monitorojë aktivitetin tënd personal në rrjet, përfshirë mailet, aplikacionet dhe sajtet e uebit."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Profili yt i punës menaxhohet nga <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profili është i lidhur me <xliff:g id="APPLICATION">%2$s</xliff:g>, i cili mund të monitorojë aktivitetin tënd të punës në rrjet, duke përfshirë mail-et, aplikacionet dhe sajtet e uebit.\n\nPër më shumë informacione, kontakto me administratorin."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Profili yt i punës menaxhohet nga <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profili është i lidhur me <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, i cili mund të monitorojë aktivitetin tënd të punës në rrjet, duke përfshirë mail-et, aplikacionet dhe sajtet e uebit.\n\nJe lidhur gjithashtu edhe me <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, i cili mund të monitorojë aktivitetin tënd personal në rrjet."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Shkyçur për <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> po ekzekutohet"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Pajisje do të qëndrojë e kyçur derisa ta shkyçësh manualisht"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Merr njoftime më shpejt"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Shikoji para se t\'i shkyçësh"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Jo, faleminderit!"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Konfiguro"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Çaktivizoje tani"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Përfundo tani"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Zgjeroje"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Mbylle"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekrani u gozhdua"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Joaktiv"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Me kontrollet e njoftimit të energjisë, mund të caktosh një nivel rëndësie nga 0 në 5 për njoftimet e një aplikacioni. \n\n"<b>"Niveli 5"</b>" \n- Shfaq në krye të listës së njoftimeve \n- Lejo ndërprerjen e ekranit të plotë \n- Gjithmonë shfaq shpejt \n\n"<b>"Niveli 4"</b>" \n- Parandalo ndërprerjen e ekranit të plotë \n- Gijthmonë shfaq shpejt \n\n"<b>"Niveli 3"</b>" \n- Parandalo ndërprerjen e ekranit të plotë \n- Asnjëherë mos shfaq shpejt \n\n"<b>"Niveli 2"</b>" \n- Parandalo ndërprerjen e ekranit të plotë \n- Asnjëherë mos shfaq shpejt \n- Asnjëherë mos lësho tingull dhe dridhje \n\n"<b>"Niveli 1"</b>" \n- Parandalo ndërprerjen e ekranit të plotë \n- Asnjëherë mos shfaq shpejt \n- Asnjëherë mos lësho tingull ose dridhje \n- Fshih nga ekrani i kyçjes dhe shiriti i statusit \n- Shfaq në fund të listës së njoftimeve \n\n"<b>"Niveli 0"</b>" \n- Blloko të gjitha njoftimet nga aplikacioni"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Njoftime"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Këto njoftime nuk do t\'i marrësh më."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Këto njoftime nuk do t\'i marrësh më"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> kategori njoftimesh"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Ky aplikacion nuk ka kategori njoftimesh"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 nga <xliff:g id="NUMBER_1">%d</xliff:g> kategori njoftimi nga ky aplikacion</item>
<item quantity="one">1 nga <xliff:g id="NUMBER_0">%d</xliff:g> kategori njoftimi nga ky aplikacion</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"kontrollet e njoftimit"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"opsionet e shtyrjes së njoftimit"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minuta"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minuta"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 orë"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 orë"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ZHBËJ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"U shty për <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Përdorimi i baterisë"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"\"Kursyesi i baterisë\" nuk është i disponueshëm gjatë karikimit"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Kursyesi i baterisë"</string>
diff --git a/packages/SystemUI/res/values-sq/strings_car.xml b/packages/SystemUI/res/values-sq/strings_car.xml
index 2e63877..b3a52b8 100644
--- a/packages/SystemUI/res/values-sq/strings_car.xml
+++ b/packages/SystemUI/res/values-sq/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Drejtoje makinën në mënyrë të sigurt"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Ji plotësisht i vetëdijshëm për kushtet e lëvizjes me makinë dhe respekto gjithmonë ligjet përkatëse. Udhëzimet mund të jenë të pasakta, jo të plota, të rrezikshme, të papërshtatshme, të ndaluara ose mund të përfshijnë kalimin në zona administrative. Informacionet e biznesit mund të jenë po ashtu të pasakta ose jo të plota. Të dhënat nuk janë në kohë reale dhe saktësia e vendndodhjes nuk mund të garantohet. Gjatë drejtimit të makinës mos e përdor pajisjen celulare ose mos përdor aplikacionet që nuk janë të planifikuara për Android Auto."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"E panjohur"</string>
+ <string name="start_driving" msgid="864023351402918991">"Fillo të drejtosh makinën"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 4ef7267..163a09d 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Претрага"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Превуците нагоре за <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Превуците улево за <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Неће вас узнемиравати звукови и вибрације осим за аларме, подсетнике, догађаје и позиваоце које наведете. И даље ћете чути све што одаберете да пустите, укључујући музику, видео снимке и игре."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Неће вас узнемиравати звукови и вибрације осим за аларме. И даље ћете чути све што одаберете да пустите, укључујући музику, видео снимке и игре."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Неће вас узнемиравати звукови и вибрације, осим за аларме, подсетнике, догађаје и позиваоце које изаберете."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Прилагоди"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Ово блокира СВЕ звукове и вибрације укључујући аларме, музику, видео снимке и игре. И даље ћете моћи да упућујете позиве."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Ово блокира СВЕ звукове и вибрације укључујући аларме, музику, видео снимке и игре."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Повезани сте са апликацијом <xliff:g id="APPLICATION">%1$s</xliff:g>, која може да надгледа активности на личној мрежи, укључујући имејлове, апликације и веб-сајтове."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Профилом за Work управља <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Повезан је са апликацијом <xliff:g id="APPLICATION">%2$s</xliff:g>, која може да надгледа активности на пословној мрежи, укључујући имејлове, апликације и веб-сајтове.\n\nВише информација потражите од администратора."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Профилом за Work управља <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Повезан је са апликацијом <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, која може да надгледа активности на пословној мрежи, укључујући имејлове, апликације и веб-сајтове.\n\nПовезани сте и са апликацијом <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, која може да надгледа активности на личној мрежи."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Откључано за корисника <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"Функција <xliff:g id="TRUST_AGENT">%1$s</xliff:g> је покренута"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Уређај ће остати закључан док га не откључате ручно"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Брже добијајте обавештења"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Прегледајте их пре откључавања"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Не, хвала"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Активирај"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Искључи одмах"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Прекини одмах"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Прошири"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Скупи"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Екран је закачен"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Искључено"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Помоћу напредних контрола за обавештења можете да подесите ниво важности од 0. до 5. за обавештења апликације. \n\n"<b>"5. ниво"</b>" \n– Приказују се у врху листе обавештења \n- Дозволи прекид режима целог екрана \n– Увек завируј \n\n"<b>"4. ниво"</b>" \n– Спречи прекид режима целог екрана \n– Увек завируј \n\n"<b>"3. ниво"</b>" \n– Спречи прекид режима целог екрана \n– Никада не завируј \n\n"<b>"2. ниво"</b>" \n– Спречи прекид режима целог екрана \n– Никада не завируј \n– Никада не производи звук или вибрацију \n\n"<b>"1. ниво"</b>" \n– Спречи прекид режима целог екрана \n– Никада не завируј \n– Никада не производи звук или вибрацију \n– Сакриј на закључаном екрану и статусној траци \n– Приказују се у дну листе обавештења \n\n"<b>"0. ниво"</b>" \n– Блокирај сва обавештења из апликације"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Обавештења"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Више нећете да добијате ова обавештења."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Више нећете добијати ова обавештења"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"Категорија обавештења: <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Ова апликација нема категорије обавештења"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 од <xliff:g id="NUMBER_1">%d</xliff:g> категорије обавештења за ову апликацију</item>
<item quantity="few">1 од <xliff:g id="NUMBER_1">%d</xliff:g> категорије обавештења за ову апликацију</item>
@@ -574,12 +577,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"контроле обавештења"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"опције за одлагање обавештења"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 минута"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 минута"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 сат"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 сата"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ОПОЗОВИ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Одложено је за <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Потрошња батерије"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Уштеда батерије није доступна током пуњења"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Уштеда батерије"</string>
diff --git a/packages/SystemUI/res/values-sr/strings_car.xml b/packages/SystemUI/res/values-sr/strings_car.xml
index 7f99120..88e2714 100644
--- a/packages/SystemUI/res/values-sr/strings_car.xml
+++ b/packages/SystemUI/res/values-sr/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Возите безбедно"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Увек водите рачуна о условима вожње и увек поштујте примењиве законе. Упутства могу да буду нетачна, непотпуна, опасна, неприкладна или забрањена, односно да подразумевају прелазак између административних области. И подаци о предузећу могу да буду нетачни или непотпуни. Подаци не настају у реалном времену и не можемо да гарантујемо прецизност локације. Немојте да употребљавате мобилни уређај нити да користите апликације које нису намењене за Android Auto током вожње."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Непознато"</string>
+ <string name="start_driving" msgid="864023351402918991">"Почните да возите"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index a479bea..b6e5434 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Sök"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Dra uppåt för <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Dra åt vänster för <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Du blir inte störd av ljud och vibrationer, förutom från alarm, påminnelser, event och specifika samtal. Ljudet är fortfarande på för sådant du väljer att spela upp, till exempel musik, videor och spel."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Du blir inte störd av ljud och vibrationer, förutom från alarm. Ljudet är fortfarande på för sådant du väljer att spela upp, till exempel musik, videor och spel."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Du blir inte störd av ljud och vibrationer, förutom från alarm, påminnelser, event och specifika samtal."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Anpassa"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Detta blockerar ALLA ljud och vibrationer, inklusive alarm, musik, videor och spel. Du kan fortfarande ringa."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Detta blockerar ALLA ljud och vibrationer, inklusive alarm, musik, videor och spel."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Du är ansluten till <xliff:g id="APPLICATION">%1$s</xliff:g> som kan övervaka din privata aktivitet på nätverket, inklusive e-postmeddelanden, appar och webbplatser."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Jobbprofilen hanteras av <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profilen är ansluten till <xliff:g id="APPLICATION">%2$s</xliff:g> som kan bevaka din nätverksaktivitet, exempelvis e-post, appar och webbplatser.\n\nKontakta administratören om du vill veta mer."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Jobbprofilen hanteras av <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Profilen är ansluten till <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> som kan bevaka din nätverksaktivitet på jobbet, exempelvis e-post, appar och webbplatser.\n\nDu är även ansluten till <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> som kan bevaka din privata nätverksaktivitet."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Upplåst för <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> körs"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Enheten förblir låst tills du låser upp den manuellt"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Få aviseringar snabbare"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Visa dem innan du låser upp"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Nej tack"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Konfig."</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Inaktivera nu"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Sluta nu"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Utöka"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Komprimera"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Skärmen har fästs"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"På"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Med aviseringsinställningarna kan du ange prioritetsnivå från 0 till 5 för aviseringar från en app. \n\n"<b>"Nivå 5"</b>" \n– Visa högst upp i aviseringslistan\n– Tillåt avbrott i helskärmsläge \n– Snabbvisa alltid \n\n"<b>"Nivå 4"</b>" \n– Tillåt inte avbrott i helskärmsläge \n– Snabbvisa alltid \n\n"<b>"Nivå 3"</b>" \n- Tillåt inte avbrott i helskärmsläge \n– Snabbvisa aldrig \n\n"<b>"Nivå 2"</b>" \n– Tillåt inte avbrott i helskärmsläge \n– Snabbvisa aldrig \n– Aldrig med ljud eller vibration \n\n"<b>"Nivå 1"</b>" \n– Tillåt inte avbrott i helskärmsläge \n– Snabbvisa aldrig \n– Aldrig med ljud eller vibration \n– Visa inte på låsskärmen och i statusfältet \n– Visa längst ned i aviseringslistan \n\n"<b>"Nivå 0"</b>" \n– Blockera alla aviseringar från appen"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Aviseringar"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Inga fler aviseringar av det här slaget visas."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Inga fler aviseringar av det här slaget visas"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> aviseringskategorier"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Det finns inga aviseringskategorier i appen"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 av <xliff:g id="NUMBER_1">%d</xliff:g> aviseringskategorier från denna app</item>
<item quantity="one">1 av <xliff:g id="NUMBER_0">%d</xliff:g> aviseringskategorier från denna app</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"inställningar för aviseringar"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"alternativ för att snooza aviseringar"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minuter"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minuter"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 timme"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 timmar"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ÅNGRA"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Snoozad i <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Batteriförbrukning"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Batterisparläget är inte tillgängligt vid laddning"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Batterisparläge"</string>
diff --git a/packages/SystemUI/res/values-sv/strings_car.xml b/packages/SystemUI/res/values-sv/strings_car.xml
index 0c03fb6..25b1136 100644
--- a/packages/SystemUI/res/values-sv/strings_car.xml
+++ b/packages/SystemUI/res/values-sv/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Kör försiktigt"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Håll koll på trafik- och körförhållanden och följ gällande lagar. Det kan hända att vägbeskrivningen inte stämmer, att den är ofullständig, farlig, olämplig, förbjuden eller att den innebär att du måste korsa en administrativ enhet. Det kan även hända att företagsuppgifter är felaktiga eller ofullständiga. Uppgifter visas inte i realtid och det går inte att garantera att platsen är korrekt. Hantera inte din mobila enhet och använd inte appar som inte är ämnade för Android Auto medan du kör."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Okänt"</string>
+ <string name="start_driving" msgid="864023351402918991">"Börja köra"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index adbf29b..b51f442 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Tafuta"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Sogeza juu kwa <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Sogeza kushoto kwa <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Hutasumbuliwa na sauti na mitetemo, isipokuwa kengele, vikumbusho, matukio na watu wanaokupigia simu ambao umechagua. Bado utasikia chochote utakachochagua kucheza, ikiwa ni pamoja na muziki, video na michezo."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Hutasumbuliwa na sauti na mitetemo, isipokuwa kengele. Bado utasikia chochote utakachochagua kucheza, ikiwa ni pamoja na muziki, video na michezo."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Hutasumbuliwa na sauti na mitetemo, isipokuwa kengele, vikumbusho, matukio na wapigaji simu utakaobainisha."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Badilisha upendavyo"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Hatua hii huzuia sauti na mitetemo YOTE, ikiwa ni pamoja na ile inayotokana na kengele, muziki, video na michezo. Bado utaweza kupiga simu."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Hatua hii huzuia sauti na mitetemo YOTE, ikiwa ni pamoja na ile inayotokana na kengele, muziki, video na michezo."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Umeunganishwa kwenye <xliff:g id="APPLICATION">%1$s</xliff:g>, ambayo inaweza kufuatilia shughuli za mtandao wako, ikiwa ni pamoja na barua pepe, programu na tovuti."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Wasifu wako wa kazini unasimamiwa na <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Wasifu umeunganishwa kwenye <xliff:g id="APPLICATION">%2$s</xliff:g>, ambayo inaweza kufuatilia shughuli za mtandao wako wa kazini, ikiwa ni pamoja na barua pepe, programu na tovuti.\n\n Wasiliana na msimamizi wako kwa maelezo zaidi."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Wasifu wako wa kazini unasimamiwa na <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Wasifu umeunganishwa kwenye <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, ambayo inaweza kufuatilia shughuli zako kwenye mtandao wa kazini, ikiwa ni pamoja na barua pepe, programu na tovuti.\n\n Umeunganishwa pia kwenye <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, ambayo inaweza kufuatilia shughuli zako kwenye mtandao wa binafsi."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Imefunguliwa kwa ajili ya <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> inatumika"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Kifaa kitaendelea kuwa katika hali ya kufungwa hadi utakapokifungua mwenyewe"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Pata arifa kwa haraka"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Zitazame kabla hujafungua"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Hapana, asante"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Sanidi"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Zima sasa"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Komesha sasa"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Panua"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Kunja"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Skrini imebandikwa"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Imezimwa"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Ukiwa na udhibiti wa arifa, unaweza kuweka kiwango cha umuhimu wa arifa za programu kuanzia 0 hadi 5. \n\n"<b>"Kiwango cha 5"</b>" \n- Onyesha katika sehemu ya juu ya orodha ya arifa \n- Ruhusu ukatizaji wa skrini nzima \n- Ruhusu arifa za kuchungulia kila wakati\n\n"<b>"Kiwango cha 4"</b>" \n- Zuia ukatizaji wa skrini nzima\n- Ruhusu arifa za kuchungulia kila wakati \n\n"<b>"Kiwango cha 3"</b>" \n- Zuia ukatizaji wa skrini nzima\n- Usiruhusu kamwe arifa za kuchungulia\n\n"<b>"Kiwango cha 2"</b>" \n- Zuia ukatizaji wa skrini nzima\n- Usiruhusu kamwe arifa za kuchungulia \n- Usiruhusu kamwe sauti au mtetemo \n\n"<b>"Kiwango cha 1"</b>" \n- Zuia ukatizaji wa skrini nzima \n- Usiruhusu kamwe arifa za kuchungulia \n- Usiruhusu kamwe sauti na mtetemo \n- Usionyeshe skrini iliyofungwa na sehemu ya arifa \n- Onyesha katika sehemu ya chini ya orodha ya arifa \n\n"<b>"Kiwango cha 0"</b>" \n- Zuia arifa zote kutoka programu"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Arifa"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Hutapokea arifa hizi tena."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Hutapokea arifa hizi tena"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"Aina <xliff:g id="NUMBER">%d</xliff:g> za arifa"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Programu hii haina aina za arifa"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">Aina 1 ya arifa kati ya <xliff:g id="NUMBER_1">%d</xliff:g> kutoka kwenye kifaa hiki</item>
<item quantity="one">Aina 1 ya arifa kati ya <xliff:g id="NUMBER_0">%d</xliff:g> kutoka kwenye kifaa hiki</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"vidhibiti vya arifa"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"chaguo za kuahirisha arifa"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"Dakika 15"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"Dakika 30"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"Saa 1"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"Saa 2"</string>
<string name="snooze_undo" msgid="6074877317002985129">"TENDUA"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Imeahirishwa kwa <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Matumizi ya betri"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Kiokoa Betri hakipatikani unapochaji betri"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Kiokoa Betri"</string>
diff --git a/packages/SystemUI/res/values-sw/strings_car.xml b/packages/SystemUI/res/values-sw/strings_car.xml
index e4bb25c..319f882 100644
--- a/packages/SystemUI/res/values-sw/strings_car.xml
+++ b/packages/SystemUI/res/values-sw/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Endesha gari kwa usalama"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Jifahamishe kikamilifu kuhusu masharti ya kuendesha gari na utii kila wakati sheria zilizopo. Huenda maelekezo yasiwe sahihi, hayajakamilika, ni hatari, hayafai, hayaruhusiwi au yanahusisha kuvuka maeneo ya kiutawala. Huenda pia maelezo ya biashara yasiwe sahihi au hayajakamilika. Data si ya wakati halisi na hatuwezi kukupa hakikisho kuhusu usahihi wa mahali. Usitumie kifaa chako cha mkononi au programu zisizo za Android Auto wakati unapoendesha gari."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Haijulikani"</string>
+ <string name="start_driving" msgid="864023351402918991">"Anza Kuendesha Gari"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index ea75cec..57aa04a 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"தேடு"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> க்கு மேலாக இழுக்கவும்."</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> க்கு இடதுபக்கமாக இழுக்கவும்."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"அலாரங்கள், நினைவூட்டல்கள், நிகழ்வுகள் மற்றும் குறிப்பிட்ட அழைப்பாளர்களைத் தவிர்த்து, பிற ஒலிகள் மற்றும் அதிர்வுகளின் தொந்தரவு இருக்காது. எனினும், நீங்கள் எதையேனும் (இசை, வீடியோக்கள் மற்றும் கேம்கள் உட்பட) ஒலிக்கும்படி தேர்ந்தெடுத்திருந்தால், அவை வழக்கம் போல் ஒலிக்கும்."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"அலாரங்களைத் தவிர்த்து, பிற ஒலிகள் மற்றும் அதிர்வுகளின் தொந்தரவு இருக்காது. எனினும், நீங்கள் எதையேனும் (இசை, வீடியோக்கள் மற்றும் கேம்கள் உட்பட) ஒலிக்கும்படி தேர்ந்தெடுத்திருந்தால், அவை வழக்கம் போல் ஒலிக்கும்."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"அலாரங்கள், நினைவூட்டல்கள், நிகழ்வுகள், குறிப்பிட்ட அழைப்புகள் தவிர, ஒலிகளினாலும் அதிர்வினாலும் தொந்தரவு இருக்காது."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"தனிப்பயனாக்கு"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"இது அலாரங்கள், இசை, வீடியோக்கள் மற்றும் கேம்கள் உட்பட எல்லா ஒலிகளையும் அதிர்வுகளையும் தடுக்கும். இருப்பினும் நீங்கள் அழைப்புகளைச் செய்யலாம்."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"இது அலாரங்கள், இசை, வீடியோக்கள் மற்றும் கேம்கள் உட்பட எல்லா ஒலிகளையும் அதிர்வுகளையும் தடுக்கும்."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"<xliff:g id="APPLICATION">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள். இந்தப் பயன்பாட்டால் மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் தனிப்பட்ட நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"உங்கள் பணி விவரத்தை <xliff:g id="ORGANIZATION">%1$s</xliff:g> நிர்வகிக்கிறது. மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் பணி நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்கக்கூடிய <xliff:g id="APPLICATION">%2$s</xliff:g> உடன் அது இணைக்கப்பட்டுள்ளது.\n\nமேலும் தகவலுக்கு, நிர்வாகியைத் தொடர்புகொள்ளவும்."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"உங்கள் பணி விவரத்தை <xliff:g id="ORGANIZATION">%1$s</xliff:g> நிர்வகிக்கிறது. மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் பணி நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்கக்கூடிய <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> உடன் அது இணைக்கப்பட்டுள்ளது.\n\nஉங்கள் தனிப்பட்ட நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்கக்கூடிய <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> உடனும் இணைக்கப்பட்டுள்ளீர்கள்."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g>க்குத் திறக்கப்பட்டது"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> இயங்குகிறது"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"நீங்கள் கைமுறையாகத் திறக்கும் வரை, சாதனம் பூட்டப்பட்டிருக்கும்"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"விரைவாக அறிவிப்புகளைப் பெறுதல்"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"திறக்கும் முன் அவற்றைப் பார்க்கவும்"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"வேண்டாம்"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"அமை"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"இப்போதே முடக்கு"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"இப்போது முடி"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"விரிவாக்கு"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"சுருக்கு"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"திரை பொருத்தப்பட்டது"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"முடக்கத்தில்"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"ஆற்றல்மிக்க அறிவிப்புக் கட்டுப்பாடுகள் மூலம், பயன்பாட்டின் அறிவிப்புகளுக்கு முக்கியத்துவ நிலையை (0-5) அமைக்கலாம். \n\n"<b>"நிலை 5"</b>" \n- அறிவிப்புப் பட்டியலின் மேலே காட்டும் \n- முழுத் திரைக் குறுக்கீட்டை அனுமதிக்கும் \n- எப்போதும் நடப்புத் திரையின் மேல் பகுதியில் அறிவிப்புகளைக் காட்டும் \n\n"<b>"நிலை 4"</b>" \n- முழுத் திரைக் குறுக்கீட்டைத் தடுக்கும் \n- எப்போதும் நடப்புத் திரையின் மேல் பகுதியில் அறிவிப்புகளைக் காட்டும் \n\n"<b>"நிலை 3"</b>" \n- முழுத் திரைக் குறுக்கீட்டைத் தடுக்கும் \n- ஒருபோதும் நடப்புத் திரையின் மேல் பகுதியில் அறிவிப்புகளைக் காட்டாது \n\n"<b>"நிலை 2"</b>" \n- முழுத் திரைக் குறுக்கீட்டைத் தடுக்கும் \n- ஒருபோதும் நடப்புத் திரையின் மேல் பகுதியில் அறிவிப்புகளைக் காட்டாது \n- ஒருபோதும் ஒலி எழுப்பாது, அதிர்வுறாது \n\n"<b>"நிலை 1"</b>" \n- முழுத் திரைக் குறுக்கீட்டைத் தடுக்கும் \n- ஒருபோதும் நடப்புத் திரையின் மேல் பகுதியில் அறிவிப்புகளைக் காட்டாது \n- ஒருபோதும் ஒலி எழுப்பாது அல்லது அதிர்வுறாது \n- பூட்டுத்திரை மற்றும் நிலைப்பட்டியிலிருந்து மறைக்கும் \n- அறிவிப்புகள் பட்டியலின் கீழே காட்டும் \n\n"<b>"நிலை 0"</b>" \n- பயன்பாட்டின் எல்லா அறிவிப்புகளையும் தடுக்கும்"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"அறிவிப்புகள்"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"இந்த அறிவிப்புகளை இனி பெறமாட்டீர்கள்."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"இந்த அறிவிப்புகளை இனி பெறமாட்டீர்கள்"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> அறிவிப்பு வகைகள்"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"இந்தப் பயன்பாட்டில் அறிவிப்பு வகைகள் இல்லை"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">இந்தப் பயன்பாடு வழங்கும் <xliff:g id="NUMBER_1">%d</xliff:g> அறிவிப்பு வகைகளில் ஒரு அறிவிப்பு வகை</item>
<item quantity="one">இந்தப் பயன்பாடு வழங்கும் <xliff:g id="NUMBER_0">%d</xliff:g> அறிவிப்பு வகையில் ஒரு அறிவிப்பு வகை</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"அறிவிப்புக் கட்டுப்பாடுகள்"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"அறிவிப்பை உறக்கநிலையாக்கும் விருப்பங்கள்"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 நிமிடங்கள்"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 நிமிடங்கள்"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 மணிநேரம்"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 மணிநேரம்"</string>
<string name="snooze_undo" msgid="6074877317002985129">"செயல்தவிர்"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"உறக்கநிலையில் வைத்திருந்த நேரம்: <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"பேட்டரி உபயோகம்"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"சார்ஜ் செய்யும் போது பேட்டரி சேமிப்பானைப் பயன்படுத்த முடியாது"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"பேட்டரி சேமிப்பான்"</string>
diff --git a/packages/SystemUI/res/values-ta/strings_car.xml b/packages/SystemUI/res/values-ta/strings_car.xml
index 7ddd5e0..9a53db0 100644
--- a/packages/SystemUI/res/values-ta/strings_car.xml
+++ b/packages/SystemUI/res/values-ta/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"பாதுகாப்பாக வண்டியோட்டவும்"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"வாகனம் ஓட்டுவதற்கான சூழ்நிலைகளை முழுவதுமாகத் தெரிந்து வைத்திருக்கவும் மற்றும் பொருந்தும் விதிகளை எப்போதும் பின்பற்றவும். வழிகள் துல்லியமற்றதாக, முழுமையற்றதாக, ஆபத்துக்குரியதாக, பொருத்தமில்லாததாக, தடைசெய்யப்பட்டதாக அல்லது அரசின் கட்டுப்பாட்டில் உள்ள எல்லைகளைக் கடப்பதாக இருக்கலாம். வணிகத் தகவலும் துல்லியமற்றதாக அல்லது முழுமையற்றதாக இருக்கலாம். தரவு நிகழ்நேர அடிப்படையிலானது அல்ல மற்றும் இருப்பிடத் துல்லியத்திற்கு உத்தரவாதம் வழங்க முடியாது. வாகனம் ஓட்டும் போது மொபைல் சாதனத்தையோ அல்லது Android Auto மூலம் கட்டுப்படுத்த முடியாத பயன்பாடுகளையோ பயன்படுத்த வேண்டாம்."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"தெரியாதது"</string>
+ <string name="start_driving" msgid="864023351402918991">"வாகனம் ஓட்டத் தொடங்கு"</string>
</resources>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 97763d4..56b271f 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"శోధించండి"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> కోసం పైకి స్లైడ్ చేయండి."</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> కోసం ఎడమవైపుకు స్లైడ్ చేయండి."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"మీరు పేర్కొనే అలారాలు, రిమైండర్లు, ఈవెంట్లు మరియు కాలర్ల నుండి మినహా మరే ఇతర ధ్వనులు మరియు వైబ్రేషన్లతో మీకు అంతరాయం కలగదు. మీరు ఇప్పటికీ సంగీతం, వీడియోలు మరియు గేమ్లతో సహా మీరు ప్లే చేయడానికి ఎంచుకున్నవి ఏవైనా వింటారు."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"అలారాలు నుండి మినహా మరే ఇతర ధ్వనులు మరియు వైబ్రేషన్లతో మీకు అంతరాయం కలగదు. మీరు ఇప్పటికీ సంగీతం, వీడియోలు మరియు గేమ్లతో సహా మీరు ప్లే చేయడానికి ఎంచుకున్నవి ఏవైనా వింటారు."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"మీరు అలారాలు, రిమైండర్లు, ఈవెంట్లు మరియు పేర్కొనే కాలర్ల నుండి మినహా మరే ఇతర ధ్వనులు మరియు వైబ్రేషన్లతో మీకు అంతరాయం కలగదు."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"అనుకూలీకరించు"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"ఇది అలారాలు, సంగీతం, వీడియోలు మరియు గేమ్లతో సహా అన్ని ధ్వనులు మరియు వైబ్రేషన్లను బ్లాక్ చేస్తుంది. మీరు ఇప్పటికీ ఫోన్ కాల్లు చేయగలుగుతారు."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"ఇది అలారాలు, సంగీతం, వీడియోలు మరియు గేమ్లతో సహా అన్ని ధ్వనులు మరియు వైబ్రేషన్లను బ్లాక్ చేస్తుంది."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"మీరు <xliff:g id="APPLICATION">%1$s</xliff:g>కి కనెక్ట్ చేయబడ్డారు, ఇది ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ వ్యక్తిగత నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగలదు."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"మీ కార్యాలయ ప్రొఫైల్ <xliff:g id="ORGANIZATION">%1$s</xliff:g> నిర్వహణలో ఉంది. ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ కార్యాలయ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల <xliff:g id="APPLICATION">%2$s</xliff:g>కి ప్రొఫైల్ కనెక్ట్ చేయబడింది.\n\nమరింత సమాచారం కోసం, మీ నిర్వాహకులను సంప్రదించండి."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"మీ కార్యాలయ ప్రొఫైల్ <xliff:g id="ORGANIZATION">%1$s</xliff:g> నిర్వహణలో ఉంది. ఇమెయిల్లు, అనువర్తనాలు మరియు వెబ్సైట్లతో సహా మీ కార్యాలయ నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>కి ప్రొఫైల్ కనెక్ట్ చేయబడింది.\n\nమీ వ్యక్తిగత నెట్వర్క్ కార్యాచరణను పర్యవేక్షించగల <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>కి కూడా మీరు కనెక్ట్ చేయబడ్డారు."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> కోసం అన్లాక్ చేయబడింది"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> అమలులో ఉంది"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"మీరు మాన్యువల్గా అన్లాక్ చేస్తే మినహా పరికరం లాక్ చేయబడి ఉంటుంది"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"నోటిఫికేషన్లను వేగంగా పొందండి"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"వీటిని మీరు అన్లాక్ చేయకముందే చూడండి"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"వద్దు, ధన్యవాదాలు"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"సెటప్ చేయి"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"ఇప్పుడు ఆఫ్ చేయండి"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"ఇప్పుడే ముగించు"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"విస్తరింపజేయండి"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"కుదించండి"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"స్క్రీన్ పిన్ చేయబడింది"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"ఆఫ్లో ఉన్నాయి"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"పవర్ నోటిఫికేషన్ నియంత్రణలతో, మీరు అనువర్తన నోటిఫికేషన్ల కోసం ప్రాముఖ్యత స్థాయిని 0 నుండి 5 వరకు సెట్ చేయవచ్చు. \n\n"<b>"స్థాయి 5"</b>" \n- నోటిఫికేషన్ జాబితా పైభాగంలో చూపబడతాయి \n- పూర్తి స్క్రీన్ అంతరాయం అనుమతించబడుతుంది \n- ఎల్లప్పుడూ త్వరిత వీక్షణ అందించబడుతుంది \n\n"<b>"స్థాయి 4"</b>" \n- పూర్తి స్క్రీన్ అంతరాయం నిరోధించబడుతుంది \n- ఎల్లప్పుడూ త్వరిత వీక్షణ అందించబడుతుంది \n\n"<b>"స్థాయి 3"</b>" \n- పూర్తి స్క్రీన్ అంతరాయం నిరోధించబడుతుంది \n- ఎప్పుడూ త్వరిత వీక్షణ అందించబడదు \n\n"<b>"స్థాయి 2"</b>" \n- పూర్తి స్క్రీన్ అంతరాయం నిరోధించబడుతుంది \n- ఎప్పుడూ త్వరిత వీక్షణ అందించబడదు \n- ఎప్పుడూ శబ్దం మరియు వైబ్రేషన్ చేయవు \n\n"<b>"స్థాయి 1"</b>" \n- పూర్తి స్క్రీన్ అంతరాయం నిరోధించబడుతుంది \n- ఎప్పుడూ త్వరిత వీక్షణ అందించబడదు \n- ఎప్పుడూ శబ్దం లేదా వైబ్రేట్ చేయవు \n- లాక్ స్క్రీన్ మరియు స్థితి పట్టీ నుండి దాచబడతాయి \n- నోటిఫికేషన్ జాబితా దిగువ భాగంలో చూపబడతాయి \n\n"<b>"స్థాయి 0"</b>" \n- అనువర్తనం నుండి అన్ని నోటిఫికేషన్లు బ్లాక్ చేయబడతాయి"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"నోటిఫికేషన్లు"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"మీరు ఇకపై ఈ నోటిఫికేషన్లను పొందరు."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"మీరు ఇకపై ఈ నోటిఫికేషన్లను పొందరు"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> నోటిఫికేషన్ వర్గాలు"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"ఈ అనువర్తనానికి నోటిఫికేషన్ వర్గాలు లేవు"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">ఈ అనువర్తనం నుంచి <xliff:g id="NUMBER_1">%d</xliff:g> నోటిఫికేషన్ వర్గాలలో 1</item>
<item quantity="one">ఈ అనువర్తనం నుంచి <xliff:g id="NUMBER_0">%d</xliff:g> నోటిఫికేషన్ వర్గంలో 1</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"నోటిఫికేషన్ నియంత్రణలు"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"నోటిఫికేషన్ తాత్కాలిక ఆపివేత ఎంపికలు"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 నిమిషాలు"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 నిమిషాలు"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 గంట"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 గంటలు"</string>
<string name="snooze_undo" msgid="6074877317002985129">"చర్య రద్దు చేయి"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> వరకు తాత్కాలికంగా ఆపివేయబడింది"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"బ్యాటరీ వినియోగం"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"ఛార్జ్ అవుతున్న సమయంలో బ్యాటరీ సేవర్ అందుబాటులో ఉండదు"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"బ్యాటరీ సేవర్"</string>
diff --git a/packages/SystemUI/res/values-te/strings_car.xml b/packages/SystemUI/res/values-te/strings_car.xml
index 6f60b3a..1831422 100644
--- a/packages/SystemUI/res/values-te/strings_car.xml
+++ b/packages/SystemUI/res/values-te/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"సురక్షితంగా డ్రైవ్ చేయండి"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"డ్రైవింగ్ షరతులపై పూర్తి అవగాహనను కలిగి ఉండండి మరియు ఎల్లప్పుడూ వర్తించే చట్టాలకు అనుగుణంగా నడుచుకోండి. దిశలు నిర్దిష్టంగా ఉండకపోవచ్చు, అసంపూర్ణంగా ఉండవచ్చు, ప్రమాదకరంగా ఉండవచ్చు, అనుకూలంగా ఉండకపోవచ్చు, నిషేధించబడి ఉండవచ్చు లేదా పాలనా ప్రాంతాల మీదుగా వెళ్లే విధంగా ఉండవచ్చు. వ్యాపార సమాచారం కూడా నిర్దిష్టంగా లేదా సంపూర్ణంగా ఉండకపోవచ్చు. డేటా నిజ-సమయానికి సంబంధించినది కాదు మరియు స్థాన ఖచ్చితత్వానికి ఎలాంటి హామీ ఉండకపోవచ్చు. డ్రైవింగ్ చేస్తున్నప్పుడు మీ మొబైల్ పరికరాన్ని ఉపయోగించవద్దు లేదా Android Auto కోసం ఉద్దేశించబడని అనువర్తనాలను ఉపయోగించవద్దు."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"తెలియని"</string>
+ <string name="start_driving" msgid="864023351402918991">"డ్రైవింగ్ను ప్రారంభించండి"</string>
</resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 28957c5..2259681 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"ค้นหา"</string>
<string name="description_direction_up" msgid="7169032478259485180">"เลื่อนขึ้นเพื่อ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>"</string>
<string name="description_direction_left" msgid="7207478719805562165">"เลื่อนไปทางซ้ายเพื่อ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>"</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"คุณจะไม่ถูกรบกวนจากเสียงและการสั่น ยกเว้นเสียงนาฬิกาปลุก การช่วยเตือน กิจกรรม และผู้โทรที่ระบุไว้ คุณจะยังคงได้ยินสิ่งที่คุณเลือกเล่น เช่น เพลง วิดีโอ และเกม"</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"คุณจะไม่ถูกรบกวนจากเสียงและการสั่น ยกเว้นเสียงนาฬิกาปลุก คุณจะยังคงได้ยินสิ่งที่คุณเลือกเล่น เช่น เพลง วิดีโอ และเกม"</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"คุณจะไม่ถูกรบกวนจากเสียงและการสั่น ยกเว้นการปลุก การช่วยเตือน กิจกรรม และผู้โทรที่คุณระบุ"</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"กำหนดค่า"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"การใช้โหมดนี้จะบล็อกเสียงและการสั่นทั้งหมด ซึ่งรวมถึงเสียงปลุก เพลง วิดีโอ และเกม คุณจะยังโทรออกได้อยู่"</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"การใช้โหมดนี้จะบล็อกเสียงและการสั่นทั้งหมด ซึ่งรวมถึงเสียงปลุก เพลง วิดีโอ และเกม"</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"คุณเชื่อมต่อกับ <xliff:g id="APPLICATION">%1$s</xliff:g> ซึ่งสามารถตรวจสอบกิจกรรมในเครือข่ายส่วนตัวของคุณ รวมถึงอีเมล แอป และเว็บไซต์ได้"</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> เป็นผู้จัดการโปรไฟล์งานของคุณ โปรไฟล์ดังกล่าวเชื่อมต่ออยู่กับ <xliff:g id="APPLICATION">%2$s</xliff:g> ซึ่งสามารถตรวจสอบกิจกรรมในเครือข่ายของคุณ รวมถึงอีเมล แอป และเว็บไซต์\n\nโปรดติดต่อผู้ดูแลระบบของคุณสำหรับข้อมูลเพิ่มเติม"</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> เป็นผู้จัดการโปรไฟล์งานของคุณ โปรไฟล์ดังกล่าวเชื่อมต่ออยู่กับ <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> ซึ่งสามารถตรวจสอบกิจกรรมในเครือข่ายของคุณ รวมถึงอีเมล แอป และเว็บไซต์\n\nคุณยังเชื่อมต่ออยู่กับ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> ด้วย ซึ่งสามารถตรวจสอบกิจกรรมในเครือข่ายส่วนตัวของคุณ"</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"ปลดล็อกสำหรับ <xliff:g id="USER_NAME">%1$s</xliff:g> แล้ว"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> กำลังทำงาน"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"อุปกรณ์จะล็อกจนกว่าคุณจะปลดล็อกด้วยตนเอง"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"รับการแจ้งเตือนเร็วขึ้น"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"ดูก่อนปลดล็อก"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"ไม่เป็นไร"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"ตั้งค่า"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g> <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"ปิดเลย"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"ไม่ใช้แล้ว"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"ขยาย"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"ยุบ"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"ตรึงหน้าจอแล้ว"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"ปิด"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"ส่วนควบคุมการแจ้งเตือนแบบเปิด/ปิดช่วยให้คุณตั้งค่าระดับความสำคัญสำหรับการแจ้งเตือนของแอปได้ตั้งแต่ระดับ 0-5 \n\n"<b>"ระดับ 5"</b>" \n- แสดงที่ด้านบนของรายการแจ้งเตือน \n- อนุญาตให้รบกวนแบบเต็มหน้าจอ \n- อนุญาตให้แสดงชั่วครู่ \n\n"<b>"ระดับ 4"</b>" \n- ป้องกันการรบกวนแบบเต็มหน้าจอ \n- แสดงชั่วครู่เสมอ \n\n"<b>"ระดับ 3"</b>" \n- ป้องกันการรบกวนแบบเต็มหน้าจอ \n- ไม่แสดงชั่วครู่เลย \n\n"<b>"ระดับ 2"</b>" \n- ป้องกันการรบกวนแบบเต็มหน้าจอ \n- ไม่แสดงชั่วครู่เลย \n- ไม่ส่งเสียงหรือสั่นเลย \n\n"<b>"ระดับ 1"</b>" \n- ป้องกันการรบกวนแบบเต็มหน้าจอ \n- ไม่แสดงชั่วครู่เลย \n- ไม่ส่งเสียงหรือสั่นเลย \n- ซ่อนจากหน้าจอล็อกและแถบสถานะ \n- แสดงที่ด้านล่างของรายการแจ้งเตือน \n\n"<b>"ระดับ 0"</b>" \n- บล็อกการแจ้งเตือนทั้งหมดจากแอป"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"การแจ้งเตือน"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"คุณจะไม่ได้รับการแจ้งเตือนเหล่านี้อีก"</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"คุณจะไม่ได้รับการแจ้งเตือนเหล่านี้อีก"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"หมวดหมู่การแจ้งเตือน <xliff:g id="NUMBER">%d</xliff:g> หมวด"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"แอปนี้ไม่มีหมวดหมู่การแจ้งเตือน"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">การแจ้งเตือน 1 ใน <xliff:g id="NUMBER_1">%d</xliff:g> หมวดหมู่จากแอปนี้</item>
<item quantity="one">การแจ้งเตือน 1 ใน <xliff:g id="NUMBER_0">%d</xliff:g> หมวดหมู่จากแอปนี้</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"ส่วนควบคุมการแจ้งเตือน"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"ตัวเลือกการปิดเสียงแจ้งเตือนชั่วคราว"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 นาที"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 นาที"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 ชั่วโมง"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 ชั่วโมง"</string>
<string name="snooze_undo" msgid="6074877317002985129">"เลิกทำ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"ปิดเสียงเตือนชั่วคราวไว้เป็นเวลา <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"การใช้งานแบตเตอรี่"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"ไม่สามารถใช้โหมดประหยัดแบตเตอรี่ระหว่างการชาร์จ"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"โหมดประหยัดแบตเตอรี่"</string>
diff --git a/packages/SystemUI/res/values-th/strings_car.xml b/packages/SystemUI/res/values-th/strings_car.xml
index b40584a..d6cd225 100644
--- a/packages/SystemUI/res/values-th/strings_car.xml
+++ b/packages/SystemUI/res/values-th/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"ขับขี่อย่างปลอดภัย"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"มีสติรู้ตัวเต็มที่ในสภาพการขับขี่ต่างๆ เและปฏิบัติตามกฎหมายที่บังคับใช้เสมอ ทั้งนี้เส้นทางอาจไม่ถูกต้อง ไม่ครบถ้วน เป็นอันตราย ไม่เหมาะสม ห้ามใช้ หรือมีการข้ามเขตปกครอง นอกจากนี้ข้อมูลทางธุรกิจอาจไม่ถูกต้องหรือไม่สมบูรณ์ ข้อมูลไม่ใช่แบบเรียลไทม์ และไม่สามารถรับประกันความถูกต้องของตำแหน่ง อย่าใช้งานอุปกรณ์เคลื่อนที่หรือใช้แอปที่ไม่ได้ออกแบบมาสำหรับ Android Auto ขณะขับรถ"</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"ไม่ทราบ"</string>
+ <string name="start_driving" msgid="864023351402918991">"เริ่มขับรถ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 4788a0e..7b9135a 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Maghanap"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Mag-slide pataas para sa <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Mag-slide pakaliwa para sa <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Hindi ka maiistorbo ng mga tunog at pag-vibrate, maliban sa mga alarm, paalala, kaganapan, at tumatawag na tutukuyin mo. Maririnig mo pa rin ang anumang pipiliin mong i-play kabilang ang musika, mga video, at mga laro."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Hindi ka maiistorbo ng mga tunog at pag-vibrate, maliban sa mga alarm. Maririnig mo pa rin ang anumang pipiliin mong i-play kabilang ang musika, mga video, at mga laro."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Hindi ka magagambala ng mga tunog at pag-vibrate, maliban mula sa mga alarm, paalala, kaganapan at mga tinukoy mong tumatawag."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"I-customize"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Bina-block nito ang LAHAT ng tunog at pag-vibrate, kabilang ang mula sa mga alarm, musika, video at laro. Magagawa mo pa ring tumawag."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Bina-block nito ang LAHAT ng tunog at pag-vibrate, kabilang ang mula sa mga alarm, musika, video at laro."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Nakakonekta ka sa <xliff:g id="APPLICATION">%1$s</xliff:g>, na maaaring sumubaybay sa aktibidad sa iyong personal na network, kabilang ang mga email, app at website."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Pinamamahalaan ng <xliff:g id="ORGANIZATION">%1$s</xliff:g> ang iyong profile sa trabaho. Nakakonekta ang profile sa <xliff:g id="APPLICATION">%2$s</xliff:g>, na maaaring sumubaybay sa aktibidad sa iyong network sa trabaho, kasama ang mga email, app, at website.\n\nPara sa higit pang impormasyon, makipag-ugnayan sa iyong admin."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Pinamamahalaan ng <xliff:g id="ORGANIZATION">%1$s</xliff:g> ang iyong profile sa trabaho. Nakakonekta ang profile sa <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, na maaaring sumubaybay sa aktibidad sa iyong network sa trabaho, kasama ang mga email, app, at website.\n\nNakakonekta ka rin sa <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, na maaaring sumubaybay sa aktibidad sa iyong personal na network."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Na-unlock para kay <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"Gumagana ang <xliff:g id="TRUST_AGENT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Mananatiling naka-lock ang device hanggang sa manu-mano mong i-unlock"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Kunin ang notification nang mas mabilis"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Tingnan ang mga ito bago ka mag-unlock"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Hindi"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"I-set up"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"I-off na ngayon"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Tapusin ngayon"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Palawakin"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"I-collapse"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Naka-pin ang screen"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Naka-off"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Sa pamamagitan ng mga kontrol sa notification ng power, magagawa mong itakda ang antas ng kahalagahan ng mga notification ng isang app mula 0 hanggang 5. \n\n"<b>"Antas 5"</b>" \n- Ipakita sa itaas ng listahan ng notification \n- Payagan ang pag-istorbo kapag full screen \n- Palaging sumilip \n\n"<b>"Antas 4"</b>" \n- Pigilan ang pag-istorbo kapag full screen \n- Palaging sumilip \n\n"<b>"Antas 3"</b>" \n- Pigilan ang pag-istorbo kapag full screen \n- Huwag kailanman sumilip \n\n"<b>"Antas 2"</b>" \n- Pigilan ang pag-istorbo kapag full screen \n- Huwag kailanman sumilip \n- Huwag kailanman tumunog o mag-vibrate \n\n"<b>"Antas 1"</b>" \n- Pigilan ang pag-istorbo kapag full screen \n- Huwag kailanman sumilip \n- Huwag kailanman tumunog o mag-vibrate \n- Itago sa lock screen at status bar \n- Ipakita sa ibaba ng listahan ng notification \n\n"<b>"Antas 0"</b>" \n- I-block ang lahat ng notification mula sa app"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Mga Notification"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Hindi mo na matatanggap ang mga notification na ito."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Hindi ka na makakatanggap ng ganitong mga notification"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> (na) kategorya ng notification"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Walang kategorya ng notification ang app na ito"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 sa <xliff:g id="NUMBER_1">%d</xliff:g> kategorya ng notification mula sa app na ito</item>
<item quantity="other">1 sa <xliff:g id="NUMBER_1">%d</xliff:g> na kategorya ng notification mula sa app na ito</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"mga kontrol ng notification"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"mga opsyon sa pag-snooze ng notification"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 minuto"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 minuto"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 oras"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 oras"</string>
<string name="snooze_undo" msgid="6074877317002985129">"I-UNDO"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Na-snooze ng <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Paggamit ng baterya"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Hindi available ang Pangtipid sa Baterya kapag nagcha-charge"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Pangtipid sa Baterya"</string>
diff --git a/packages/SystemUI/res/values-tl/strings_car.xml b/packages/SystemUI/res/values-tl/strings_car.xml
index c6926ed..16442b85 100644
--- a/packages/SystemUI/res/values-tl/strings_car.xml
+++ b/packages/SystemUI/res/values-tl/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Magmaneho nang ligtas"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Manatiling lubos na nakakaalam sa mga kondisyon sa pagmamaneho at palaging sumunod sa mga naaangkop na batas. Ang mga direksyon ay maaaring hindi tumpak, hindi kumpleto, mapanganib, hindi naaangkop, ipinagbabawal o kinasasangkutan ng pagtawid sa mga administratibong lugar. Maaari ding hindi tumpak o hindi kumpleto ang impormasyon ng negosyo. Hindi real-time ang data at hindi magagarantiya ang katumpakan ng lokasyon. Huwag gamitin ang iyong mobile device o gumamit ng mga app na hindi ginawa para sa Android Auto habang nagmamaneho."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Hindi Alam"</string>
+ <string name="start_driving" msgid="864023351402918991">"Simulan ang Pagmamaneho"</string>
</resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 903f2c8..2743fcc 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Ara"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> için yukarı kaydırın."</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> için sola kaydırın."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Alarmlar, hatırlatıcılar, etkinlikler ve sizin seçtiğiniz arayan kişiler dışında hiçbir ses ve titreşimle rahatsız edilmeyeceksiniz. O sırada çaldığınız, seyrettiğiniz ya da oynadığınız müzik, video ve oyunların sesini duymaya devam edeceksiniz"</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Alarmlar dışında hiçbir ses ve titreşimle rahatsız edilmeyeceksiniz. O sırada çaldığınız, seyrettiğiniz ya da oynadığınız müzik, video ve oyunların sesini duymaya devam edeceksiniz."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Belirttiğiniz alarmlar, hatırlatıcılar, etkinlikler ve arayanlar hariç olmak üzere, sesler ve titreşimlerle rahatsız edilmeyeceksiniz."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Özelleştir"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Bu seçenek TÜM sesleri ve titreşimleri engeller. Buna alarmlar, müzik, videolar ve oyunlar dahildir. Telefon aramaları yapmaya devam edebileceksiniz."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Bu seçenek TÜM sesleri ve titreşimleri engeller. Buna alarmlar, müzik, videolar ve oyunlar dahildir."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"E-postalarınız, uygulamalarınız ve web siteleriniz dahil olmak üzere kişisel ağ etkinliğinizi izleyebilen <xliff:g id="APPLICATION">%1$s</xliff:g> uygulamasına bağlısınız."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"İş profiliniz <xliff:g id="ORGANIZATION">%1$s</xliff:g> tarafından yönetiliyor. Profil; e-postalar, uygulamalar ve web siteleri de dahil olmak üzere iş ağı etkinliğinizi izleyebilen <xliff:g id="APPLICATION">%2$s</xliff:g> uygulamasına bağlı.\n\nDaha fazla bilgi için yöneticinize başvurun."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"İş profiliniz <xliff:g id="ORGANIZATION">%1$s</xliff:g> tarafından yönetiliyor. Profil; e-postalar, uygulamalar ve web siteleri de dahil olmak üzere iş ağı etkinliğinizi izleyebilen <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> uygulamasına bağlı.\n\nAyrıca, kişisel ağ etkinliğinizi izleyebilen <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> uygulamasına bağlısınız."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> için kilit açıldı"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> çalışıyor"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Cihazınızın kilidini manuel olarak açmadıkça cihaz kilitli kalacak"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Bildirimleri daha hızlı alın"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Kilidi açmadan bildirimleri görün"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Hayır, teşekkürler"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Kur"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Şimdi kapat"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Şimdi sona erdir"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Genişlet"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Daralt"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekran sabitlendi"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Kapalı"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Güç bildirim kontrolleriyle, bir uygulamanın bildirimleri için 0 ile 5 arasında bir önem düzeyi ayarlayabilirsiniz. \n\n"<b>"5. Düzey"</b>" \n- Bildirim listesinin en üstünde gösterilsin \n- Tam ekran kesintisine izin verilsin \n- Ekranda her zaman kısaca belirsin \n\n"<b>"4. Düzey"</b>" \n- Tam ekran kesintisi engellensin \n- Ekranda her zaman kısaca belirsin \n\n"<b>"3. Düzey"</b>" \n- Tam ekran kesintisi engellensin \n- Ekranda hiçbir zaman kısaca belirmesin \n\n"<b>"2. Düzey"</b>" \n- Tam ekran kesintisi engellensin \n- Ekranda hiçbir zaman belirmesin \n- Hiçbir zaman ses çıkarmasın ve titreştirmesin \n\n"<b>"1. Düzey"</b>" \n- Tam ekran kesintisi engellensin \n- Ekranda hiçbir zaman kısaca belirmesin \n- Hiçbir zaman ses çıkarmasın veya titreştirmesin \n- Kilit ekranından ve durum çubuğundan gizlensin \n- Bildirim listesinin en altında gösterilsin \n\n"<b>"0. Düzey"</b>" \n- Uygulamadan gelen tüm bildirimler engellensin"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Bildirimler"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Bu bildirimleri artık almayacaksınız."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Bu bildirimleri artık almayacaksınız"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> bildirim kategorisi"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Bu uygulamanın bildirim kategorisi yok"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">Bu uygulamadaki <xliff:g id="NUMBER_1">%d</xliff:g> bildirim kategorisinden 1 tanesi</item>
<item quantity="one">Bu uygulamadaki <xliff:g id="NUMBER_0">%d</xliff:g> bildirim kategorisinden 1 tanesi</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"Bildirim kontrolleri"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"bildirim erteleme seçenekleri"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 dakika"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 dakika"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 saat"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 saat"</string>
<string name="snooze_undo" msgid="6074877317002985129">"GERİ AL"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> süreyle ertelendi"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Pil kullanımı"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Şarj sırasında Pil Tasarrufu özelliği kullanılamaz"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Pil Tasarrufu"</string>
diff --git a/packages/SystemUI/res/values-tr/strings_car.xml b/packages/SystemUI/res/values-tr/strings_car.xml
index 72018e2..126789f 100644
--- a/packages/SystemUI/res/values-tr/strings_car.xml
+++ b/packages/SystemUI/res/values-tr/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Güvenli bir şekilde sürün"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Sürüş koşullarına karşı dikkatli olun ve geçerli yasalara her zaman uyun. Yol tarifi yanlış, eksik, tehlikeli, uygunsuz, yasak olabilir veya idari bölgelerden geçişleri içerebilir. İşletme bilgileri de yanlış veya eksik olabilir. Veriler gerçek zamanlı değildir ve konum doğruluğu garanti edilemez. Sürüş sırasında mobil cihazınızı veya Android Auto için geliştirilmemiş uygulamaları kullanmayın."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Bilinmiyor"</string>
+ <string name="start_driving" msgid="864023351402918991">"Sürmeye Başla"</string>
</resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 937822c..a224545 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -358,8 +358,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Пошук"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Проведіть пальцем угору, щоб <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Проведіть пальцем ліворуч, щоб <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Ви отримуватиме звукові та вібросигнали лише для вибраних будильників, нагадувань, подій і абонентів. Однак ви чутимете все, що захочете відтворити, зокрема музику, відео й ігри."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Ви отримуватиме звукові та вібросигнали лише для будильників. Однак ви чутимете все, що захочете відтворити, зокрема музику, відео й ігри."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Ви отримуватимете звукові й вібросигнали лише для вибраних сповіщень, нагадувань, повідомлень про події та викликів абонентів."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Налаштувати"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Блокуватимуться ВСІ звукові та вібросигнали, зокрема будильники, музика, відео й ігри. Ви зможете телефонувати."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Блокуватимуться ВСІ звукові та вібросигнали, зокрема будильники, музика, відео й ігри."</string>
@@ -473,13 +472,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Ваш профіль під’єднано до додатка <xliff:g id="APPLICATION">%1$s</xliff:g>, який може відстежувати вашу особисту активність у мережі, зокрема доступ до електронної пошти, додатків і веб-сайтів."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Вашим робочим профілем керує адміністратор організації <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Цей профіль під’єднано до додатка <xliff:g id="APPLICATION">%2$s</xliff:g>, який може відстежувати вашу активність у мережі, зокрема в електронній пошті, додатках і на веб-сайтах.\n\nЩоб дізнатися більше, зв’яжіться з адміністратором."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Вашим робочим профілем керує адміністратор організації <xliff:g id="ORGANIZATION">%1$s</xliff:g>. Цей профіль під’єднано до додатка <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, який може відстежувати вашу активність у мережі, зокрема а електронній пошті, додатках і на веб-сайтах.\n\nВаш профіль також під’єднано до додатка <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, який може відстежувати вашу особисту активність у мережі."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Розблоковано для користувача <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> працює"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Пристрій залишатиметься заблокованим, доки ви не розблокуєте його вручну"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Швидше отримуйте сповіщення"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Переглядайте сповіщення, перш ніж розблокувати екран"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Ні, дякую"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Налаштув."</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Вимкнути"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Закрити"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Розгорнути"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Згорнути"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Екран закріплено"</string>
@@ -554,9 +555,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Вимк."</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"За допомогою елементів керування сповіщеннями ви можете налаштувати пріоритет сповіщень додатка – від 0 до 5 рівня. \n\n"<b>"Рівень 5"</b>\n"- Показувати сповіщення вгорі списку \n- Виводити на весь екран \n- Завжди показувати короткі сповіщення \n\n"<b>"Рівень 4"</b>\n"- Не виводити на весь екран \n- Завжди показувати короткі сповіщення \n\n"<b>"Рівень 3"</b>\n"- Не виводити на весь екран \n- Не показувати короткі сповіщення \n\n"<b>"Рівень 2"</b>\n"- Не виводити на весь екран \n- Не показувати короткі сповіщення \n- Вимкнути звук і вібросигнал \n\n"<b>"Рівень 1"</b>\n"- Не виводити на весь екран \n- Не показувати короткі сповіщення \n- Вимкнути звук і вібросигнал \n- Не показувати на заблокованому екрані та в рядку стану \n- Показувати сповіщення внизу списку \n\n"<b>"Рівень 0"</b>\n"- Блокувати всі сповіщення з додатка"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Сповіщення"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Ви більше не отримуватимете ці сповіщення."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Ви більше не отримуватимете ці сповіщення"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"Категорії сповіщень (<xliff:g id="NUMBER">%d</xliff:g>)"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"У цьому додатку немає категорій сповіщень"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 з <xliff:g id="NUMBER_1">%d</xliff:g> категорії сповіщень із цього додатка</item>
<item quantity="few">1 з <xliff:g id="NUMBER_1">%d</xliff:g> категорій сповіщень із цього додатка</item>
@@ -580,12 +583,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"елементи керування сповіщеннями"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"параметри відкладення сповіщень"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 хвилин"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 хвилин"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 годину"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 години"</string>
<string name="snooze_undo" msgid="6074877317002985129">"ВІДМІНИТИ"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Відкладено на <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Використання заряду"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Режим енергозбереження не можна ввімкнути під час заряджання"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Режим енергозбереження"</string>
diff --git a/packages/SystemUI/res/values-uk/strings_car.xml b/packages/SystemUI/res/values-uk/strings_car.xml
index 4fda0ab..852e1c6 100644
--- a/packages/SystemUI/res/values-uk/strings_car.xml
+++ b/packages/SystemUI/res/values-uk/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Будьте уважні за кермом"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Стежте умовами дорожнього руху та завжди дотримуйтеся відповідних законів. Маршрути можуть бути неточними, неповними, небезпечними, непридатними або перетинати межі адміністративних одиниць. Інформація про компанії також може бути неточною або неповною. Дані надаються не в реальному часі. Точність визначення місцезнаходжень не гарантується. Коли ви за кермом, не користуйтеся мобільним пристроєм або додатками, не призначеними для Android Auto."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Невідомо"</string>
+ <string name="start_driving" msgid="864023351402918991">"Почати рух"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 23a0626..2ad6bb8 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"تلاش کریں"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> کیلئے اوپر سلائیڈ کریں۔"</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> کیلئے بائیں سلائیڈ کریں۔"</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"الارمز، یاد دہانیوں، ایونٹس اور آپ کے متعین کردہ کالرز کے علاوہ، آپ آوازوں اور وائبریشنز سے ڈسٹرب نہیں ہوں گے۔ موسیقی، ویڈیوز اور گیمز سمیت آپ ابھی بھی ہر وہ چیز سنیں گے جسے چلانے کا آپ انتخاب کرتے ہیں۔"</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"الارمز کے علاوہ، آپ آوازوں اور وائبریشنز سے ڈسٹرب نہیں ہوں گے۔ موسیقی، ویڈیوز اور گیمز سمیت آپ ہر وہ چیز سنیں گے جسے چلانے کا آپ انتخاب کرتے ہیں۔"</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"الارمز، یاد دہانیوں، ایونٹس اور آپ کے متعین کردہ کالرز کے علاوہ، آپ آوازوں اور وائبریشنز سے ڈسٹرب نہیں ہوں گے۔"</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"حسب ضرورت بنائیں"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"یہ الارمز، موسیقی، ویڈیوز اور گیمز کی آوازوں اور وائبریشنز سمیت سبھی آوازیں اور وائبریشنز مسدود کر دیتا ہے۔ آپ ابھی بھی فون کالز کر سکیں گے۔"</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"یہ الارمز، موسیقی، ویڈیوز اور گیمز کی آوازوں اور وائبریشنز سمیت سبھی آوازیں اور وائبریشنز مسدود کر دیتا ہے۔"</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"آپ <xliff:g id="APPLICATION">%1$s</xliff:g> سے منسلک ہیں، جو ای میلز، ایپس اور ویب سائٹس سمیت آپ کے نجی نیٹ ورک کی سرگرمی مانیٹر کر سکتی ہے۔"</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"آپ کا دفتری پروفائل <xliff:g id="ORGANIZATION">%1$s</xliff:g> کے زیر انتظام ہے۔ پروفائل <xliff:g id="APPLICATION">%2$s</xliff:g> سے منسلک ہے جو ای میلز، ایپس اور ویب سائٹس سمیت آپ کے دفتری نیٹ ورک کی سرگرمی مانیٹر کر سکتی ہے۔\n\nمزید معلومات کیلئے اپنے منتظم سے رابطہ کریں۔"</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"آپ کا دفتری پروفائل <xliff:g id="ORGANIZATION">%1$s</xliff:g> کے زیر انتظام ہے۔ پروفائل <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> سے منسلک ہے جو ای میلز، ایپس اور ویب سائٹس سمیت آپ کے دفتری نیٹ ورک کی سرگرمی مانیٹر کر سکتی ہے۔\n\nآپ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> سے بھی منسلک ہیں، جو آپ کے ذاتی نیٹ ورک کی سرگرمی مانیٹر کر سکتی ہے۔"</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> کے لیے غیر مقفل کر دیا گیا"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> چل رہا ہے"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"آلہ اس وقت تک مقفل رہے گا جب تک آپ دستی طور پر اسے غیر مقفل نہ کریں"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"تیزی سے اطلاعات حاصل کریں"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"غیر مقفل کرنے سے پہلے انہیں دیکھیں"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"نہیں شکریہ"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"ترتیب دیں"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>۔ <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"ابھی بند کریں"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"ابھی ختم کریں"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"پھیلائیں"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"سکیڑیں"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"اسکرین پن کردہ ہے"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"آف"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"پاور اطلاع کنٹرولز کے ساتھ آپ کسی ایپ کی اطلاعات کیلئے 0 سے 5 تک اہمیت کی سطح سیٹ کر سکتے ہیں۔ \n\n"<b>"سطح 5"</b>\n"- اطلاعات کی فہرست کے اوپر دکھائیں \n- پوری اسکرین کی مداخلت کی اجازت دیں \n- ہمیشہ جھانکنا\n\n"<b>"سطح 4"</b>\n"- پوری اسکرین کی مداخلت کو روکیں \n- ہمیشہ جھانکنا\n\n"<b>"سطح 3"</b>\n"- پوری اسکرین کی مداخلت کو روکیں \n- کبھی نہ جھانکنا \n\n"<b>"سطح 2"</b>\n"- پوری اسکرین کی مداخلت کو روکیں \n- کبھی نہ جھانکنا \n- کبھی آواز اور ارتعاش پیدا نہ کرنا \n\n"<b>" سطح 1"</b>\n"- پوری اسکرین کی مداخلت کو روکنا \n- کبھی نہ جھانکنا \n- کبھی بھی آواز یا ارتعاش پیدا نہ کرنا\n- مقفل اسکرین اور اسٹیٹس بار سے چھپانا \n - اطلاع کی فہرست کی نیچے دکھانا \n\n"<b>"سطح 0"</b>\n"- ایپ سے تمام اطلاعات مسدود کریں"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"اطلاعات"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"آپ کو یہ اطلاعات مزید نہیں ملیں گی۔"</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"آپ کو یہ اطلاعات مزید نہیں ملیں گی"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"اطلاع کے <xliff:g id="NUMBER">%d</xliff:g> زمرے"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"اس ایپ میں اطلاعاتی زمرے نہیں ہیں"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">اس ایپ کے <xliff:g id="NUMBER_1">%d</xliff:g> اطلاعاتی زمروں میں سے 1</item>
<item quantity="one">اس ایپ کے <xliff:g id="NUMBER_0">%d</xliff:g> اطلاعاتی زمرے میں سے 1</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"اطلاع کے کنٹرولز"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"اطلاع اسنوز کرنے کے اختیارات"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 منٹ"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 منٹ"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 گھنٹہ"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 گھنٹے"</string>
<string name="snooze_undo" msgid="6074877317002985129">"کالعدم کریں"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> کیلئے اسنوز کیا گیا"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"بیٹری کا استعمال"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"چارجنگ کے دوران بیٹری سیور دستیاب نہیں ہے"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"بیٹری سیور"</string>
diff --git a/packages/SystemUI/res/values-ur/strings_car.xml b/packages/SystemUI/res/values-ur/strings_car.xml
index 151ca09..653a59b 100644
--- a/packages/SystemUI/res/values-ur/strings_car.xml
+++ b/packages/SystemUI/res/values-ur/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"احتیاط سے گاڑی چلائیں"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"گاڑی چلاتے وقت اپنے گردونواح سے مکمل طور پر باخبر رہیں اور ہمیشہ قابل اطلاق قوانین کی پابندی کریں۔ ہدایات غلط، نامکمل، خطرناک، موزوں نہیں، ممنوع یا انتظامی علاقوں میں سے گزرنے کے متعلق ہو سکتی ہیں۔ کاروباری معلومات بھی غلط یا نامکمل ہو سکتی ہے۔ گاڑی چلاتے وقت اپنا موبائل آلہ یا وہ ایپس استعمال نہ کریں جو Android Auto کیلئے نہیں ہیں۔"</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"نامعلوم"</string>
+ <string name="start_driving" msgid="864023351402918991">"ڈرائیونگ شروع کریں"</string>
</resources>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 530b317..72fca86 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Qidirish"</string>
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> uchun yuqoriga suring."</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> uchun chapga suring."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Turli ovoz va tebranishlar endi sizni bezovta qilmaydi. Biroq, signallar, eslatmalar, tadbirlar haqidagi bildirishnomalar va siz tanlagan abonentlardan kelgan chaqiruvlar bundan mustasno. Lekin, ijro etiladigan barcha narsalar, jumladan, musiqa, video va o‘yinlar eshitiladi."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Turli ovoz va tebranishlar endi sizni bezovta qilmaydi. Biroq, signallar bundan mustasno. Lekin, ijro etiladigan barcha narsalar, jumladan, musiqa, video va o‘yinlar eshitiladi."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Turli ovoz va tebranishlar endi sizni bezovta qilmaydi. Biroq signallar, eslatmalar, tadbirlar haqidagi bildirishnomalar va siz tanlagan abonentlardan kelgan qo‘ng‘iroqlar bundan mustasno."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Sozlash"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Bu BARCHA, jumladan signallar, musiqa, videolar va o‘yinlardan keladigan tovush va tebranishlarni to‘sib qo‘yadi. Siz telefon qo‘ng‘iroqlarini bemalol amalga oshirishingiz mumkin."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Bu BARCHA, jumladan, signallar, musiqa, videolar va o‘yinlardan keladigan tovush va tebranishlarni to‘sib qo‘yadi."</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"<xliff:g id="APPLICATION">%1$s</xliff:g> ilovasi ishga tushirilgan. U internetdagi harakatlaringiz, jumladan, e-pochta, ilova va veb-saytlardagi xatti-harakatlaringizni kuzatishi mumkin."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Ishchi profilingiz <xliff:g id="ORGANIZATION">%1$s</xliff:g> tomonidan boshqariladi. <xliff:g id="APPLICATION">%2$s</xliff:g> ilovasi ish tarmog‘idagi, jumladan, e-pochta, ilova va veb-saytlardagi xatti-harakatlaringizni kuzatishi mumkin.\n\nBatafsil axborot olish uchun administrator bilan bog‘laning."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Ishchi profilingiz <xliff:g id="ORGANIZATION">%1$s</xliff:g> tomonidan boshqariladi. <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> ilovasi ish tarmog‘idagi, jumladan, e-pochta, ilova va veb-saytlardagi xatti-harakatlaringizni kuzatishi mumkin.\n\nShuningdek, <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> ilovasi ham shaxsiy tarmoqdagi harakatlaringizni kuzatishi mumkin."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> uchun qulfdan chiqarilgan"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> ishlamoqda"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Qurilma qo‘lda qulfdan chiqarilmaguncha qulflangan holatda qoladi"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Bildirishnomalarni tezroq oling"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Ularni qulfdan chiqarishdan oldin ko‘ring"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Yo‘q, kerak emas"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Sozlash"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"O‘chiring"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Tugatish"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Yoyish"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Yig‘ish"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Ekran qadaldi"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"O‘chiq"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Bildirishnomalar uchun kengaytirilgan boshqaruv yordamida ilova bildirishnomalarining muhimlik darajasini (0-5) sozlash mumkin. \n\n"<b>"5-daraja"</b>" \n- Bildirishnomani ro‘yxatning boshida ko‘rsatish \n- To‘liq ekranli bildirishnomalarni ko‘rsatish \n- Qalqib chiquvchi bildirishnomalarni ko‘rsatish \n\n"<b>"4-daraja"</b>" \n- To‘liq ekranli bildirishnomalarni ko‘rsatmaslik \n- Qalqib chiquvchi bildirishnomalarni ko‘rsatish \n\n"<b>"3-daraja"</b>" \n- To‘liq ekranli bildirishnomalarni ko‘rsatmaslik \n- Qalqib chiquvchi bildirishnomalarni ko‘rsatmaslik \n\n"<b>"2-daraja"</b>" \n- To‘liq ekranli bildirishnomalarni ko‘rsatmaslik \n- Qalqib chiquvchi bildirishnomalarni ko‘rsatmaslik \n- Ovoz va tebranishdan foydalanmaslik \n\n"<b>"1-daraja"</b>" \n- To‘liq ekranli bildirishnomalarni ko‘rsatmaslik \n- Qalqib chiquvchi bildirishnomalarni ko‘rsatmaslik \n- Ovoz va tebranishdan foydalanmaslik \n- Ekran qulfi va holat qatorida ko‘rsatmaslik \n- Bildirishnomani ro‘yxatning oxirida ko‘rsatish \n\n"<b>"0-daraja"</b>" \n- Ilovadan keladigan barcha bildirishnomalarni bloklash"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Bildirishnomalar"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Ushbu bildirishnomalar endi ko‘rsatilmaydi."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Ushbu bildirishnomalar endi ko‘rsatilmaydi"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> ta bildirishnoma turkumi"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Bu ilovada bildirishnomalar turkumi yo‘q"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">Bu ilovadagi <xliff:g id="NUMBER_1">%d</xliff:g> ta bildirishnomalar turkumidan 1 tasi</item>
<item quantity="one">Bu ilovadagi <xliff:g id="NUMBER_0">%d</xliff:g> ta bildirishnomalar turkumidan 1 tasi</item>
@@ -572,12 +575,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"bildirishnoma sozlamalari"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"bildirishnomalarni kechiktirish parametrlari"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 daqiqa"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 daqiqa"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 soat"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 soat"</string>
<string name="snooze_undo" msgid="6074877317002985129">"BEKOR QILISH"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"<xliff:g id="TIME_AMOUNT">%1$s</xliff:g> muddatga kechiktirildi"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Batareya sarfi"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Quvvat tejash rejimidan quvvatlash vaqtida foydalanib bo‘lmaydi"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Quvvat tejash rejimi"</string>
diff --git a/packages/SystemUI/res/values-uz/strings_car.xml b/packages/SystemUI/res/values-uz/strings_car.xml
index 610dc1f..29951be 100644
--- a/packages/SystemUI/res/values-uz/strings_car.xml
+++ b/packages/SystemUI/res/values-uz/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Xavfsiz haydash"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Harakat xavfsizligi va amaldagi qonunchilikka doim rioya qiling. Yo‘l ko‘rsatmalari noaniq, chala, xavfli, mos emas, taqiqlangan yoki ma’muriy hududlarni kesib o‘tadigan bo‘lishi mumkin. Biznes ma’lumotlari ham noaniq yoki chala bo‘lishi mumkin. Ma’lumotlar real vaqt rejimida bo‘lmasligi hamda joylashuv ma’lumotining aniqligi kafolatlanmaydi. Avtomobilni haydash mobaynida Android Auto xizmati bilan bog‘liq bo‘lmagan hollarda mobil qurilma yoki ilovalardan foydalanmang."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Noma’lum"</string>
+ <string name="start_driving" msgid="864023351402918991">"Navigatsiyani boshlash"</string>
</resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 29c70c6..ad6d85b 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Tìm kiếm"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Trượt lên để <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Trượt sang trái để <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Bạn sẽ không bị làm phiền bởi âm thanh và tiếng rung, ngoại trừ báo thức, nhắc nhở, sự kiện và người gọi mà bạn chỉ định. Bạn sẽ vẫn nghe thấy mọi thứ bạn chọn phát, bao gồm nhạc, video và trò chơi."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Bạn sẽ không bị làm phiền bởi âm thanh và tiếng rung, ngoại trừ báo thức. Bạn sẽ vẫn nghe thấy mọi thứ bạn chọn phát, bao gồm nhạc, video và trò chơi."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Bạn sẽ không bị làm phiền bởi âm thanh và tiếng rung, ngoại trừ báo thức, nhắc nhở, sự kiện và người gọi mà bạn chỉ định."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Tùy chỉnh"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Chế độ này sẽ chặn TẤT CẢ âm thanh và tiếng rung, bao gồm báo thức, âm nhạc, video và trò chơi. Bạn vẫn có thể gọi điện thoại."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Chế độ này sẽ chặn TẤT CẢ âm thanh và tiếng rung, bao gồm báo thức, âm nhạc, video và trò chơi."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Bạn đang kết nối với <xliff:g id="APPLICATION">%1$s</xliff:g>. Ứng dụng này có thể giám sát hoạt động mạng cá nhân của bạn bao gồm email, ứng dụng và trang web."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Hồ sơ công việc của bạn do <xliff:g id="ORGANIZATION">%1$s</xliff:g> quản lý. Hồ sơ này được kết nối với <xliff:g id="APPLICATION">%2$s</xliff:g>, ứng dụng này có thể giám sát hoạt động mạng cơ quan của bạn, bao gồm email, ứng dụng và trang web.\n\nĐể biết thêm thông tin, hãy liên hệ với quản trị viên của bạn."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Hồ sơ công việc của bạn do <xliff:g id="ORGANIZATION">%1$s</xliff:g> quản lý. Hồ sơ này được kết nối với <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, ứng dụng này có thể giám sát hoạt động mạng của bạn, bao gồm email, ứng dụng và trang web.\n\nBạn cũng đang kết nối với <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, ứng dụng này có thể giám sát hoạt động mạng cá nhân của bạn."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Được mở khóa cho <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> đang chạy"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Thiết bị sẽ vẫn bị khóa cho tới khi bạn mở khóa theo cách thủ công"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Nhận thông báo nhanh hơn"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Xem thông báo trước khi bạn mở khóa"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Ko, cảm ơn"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Thiết lập"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Tắt ngay bây giờ"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Kết thúc bây giờ"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Mở rộng"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Thu gọn"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Màn hình được ghim"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Tắt"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Với các kiểm soát thông báo nguồn, bạn có thể đặt cấp độ quan trọng từ 0 đến 5 cho các thông báo của ứng dụng. \n\n"<b>"Cấp 5"</b>" \n- Hiển thị ở đầu danh sách thông báo \n- Cho phép gián đoạn ở chế độ toàn màn hình \n- Luôn xem nhanh \n\n"<b>"Cấp 4"</b>" \n- Ngăn gián đoạn ở chế độ toàn màn hình \n- Luôn xem nhanh \n\n"<b>"Cấp 3"</b>" \n- Ngăn gián đoạn ở chế độ toàn màn hình \n- Không bao giờ xem nhanh \n\n"<b>"Cấp 2"</b>" \n- Ngăn gián đoạn ở chế độ toàn màn hình \n- Không bao giờ xem nhanh \n- Không bao giờ có âm báo và rung \n\n"<b>"Cấp 1"</b>" \n- Ngăn gián đoạn ở chế độ toàn màn hình \n- Không bao giờ xem nhanh \n- Không bao giờ có âm báo và rung \n- Ẩn khỏi màn hình khóa và thanh trạng thái \n- Hiển thị ở cuối danh sách thông báo \n\n"<b>"Cấp 0"</b>" \n- Chặn tất cả các thông báo từ ứng dụng"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Thông báo"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Bạn sẽ không nhận được những thông báo này nữa."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Bạn sẽ không nhận được những thông báo này nữa"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> danh mục thông báo"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Ứng dụng này không có loại thông báo"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">1 trên tổng số <xliff:g id="NUMBER_1">%d</xliff:g> loại thông báo từ ứng dụng này</item>
<item quantity="one">1 trên tổng số <xliff:g id="NUMBER_0">%d</xliff:g> loại thông báo từ ứng dụng này</item>
@@ -572,12 +575,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"điều khiển thông báo"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"Tùy chọn báo lại thông báo"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 phút"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 phút"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 giờ"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 giờ"</string>
<string name="snooze_undo" msgid="6074877317002985129">"HOÀN TÁC"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Báo lại sau <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Mức sử dụng pin"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Trình tiết kiệm pin không khả dụng trong khi sạc"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Trình tiết kiệm pin"</string>
diff --git a/packages/SystemUI/res/values-vi/strings_car.xml b/packages/SystemUI/res/values-vi/strings_car.xml
index 2aee7d5..fb47969 100644
--- a/packages/SystemUI/res/values-vi/strings_car.xml
+++ b/packages/SystemUI/res/values-vi/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Lái xe an toàn"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Luôn biết rõ điều kiện lái xe và luôn tuân thủ luật pháp hiện hành. Chỉ đường có thể không chính xác, không hoàn chỉnh, nguy hiểm, không thích hợp, bị cấm hoặc bao gồm tuyến đường giao các khu vực hành chính. Thông tin doanh nghiệp cũng có thể không chính xác hoặc không hoàn chỉnh. Dữ liệu không ở thời gian thực và không thể đảm bảo độ chính xác của vị trí. Không sử dụng thiết bị di động của bạn hoặc sử dụng ứng dụng không dành cho Android Auto trong khi lái xe."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Không xác định"</string>
+ <string name="start_driving" msgid="864023351402918991">"Bắt đầu lái xe"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 33dc240..d41c251 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"搜索"</string>
<string name="description_direction_up" msgid="7169032478259485180">"向上滑动以<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
<string name="description_direction_left" msgid="7207478719805562165">"向左滑动以<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"您将不会受到声音和振动的打扰(闹钟、提醒、活动和所指定来电者的相关提示音除外)。您依然可以听到您选择播放的任何内容(包括音乐、视频和游戏)的相关音效。"</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"您将不会受到声音和振动的打扰(闹钟提示音除外)。您依然可以听到您选择播放的任何内容(包括音乐、视频和游戏)的相关音效。"</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"您将不会受声音和振动的打扰,但闹钟、提醒、活动和您指定的来电者除外。"</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"自定义"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"这会阻止所有声音和振动(包括闹钟、音乐、视频和游戏)打扰您。您仍然可以拨打电话。"</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"这会阻止所有声音和振动(包括闹钟、音乐、视频和游戏)打扰您。"</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"您已连接到<xliff:g id="APPLICATION">%1$s</xliff:g>,该应用可以监控您的个人网络活动,包括收发电子邮件、使用应用和浏览网站。"</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"您的工作资料由“<xliff:g id="ORGANIZATION">%1$s</xliff:g>”负责管理,且已连接到“<xliff:g id="APPLICATION">%2$s</xliff:g>”(该应用能够监控您的工作网络活动,其中包括收发电子邮件、使用应用和浏览网站)。\n\n如需更多信息,请与您的管理员联系。"</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"您的工作资料由“<xliff:g id="ORGANIZATION">%1$s</xliff:g>”负责管理,且已连接到“<xliff:g id="APPLICATION_WORK">%2$s</xliff:g>”(该应用能够监控您的工作网络活动,其中包括收发电子邮件、使用应用和浏览网站)。\n\n此外,您还连接到了“<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>”(该应用能够监控您的个人网络活动)。"</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"已为<xliff:g id="USER_NAME">%1$s</xliff:g>解锁"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"“<xliff:g id="TRUST_AGENT">%1$s</xliff:g>”正在运行"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"在您手动解锁之前,设备会保持锁定状态"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"更快捷地查看通知"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"无需解锁即可查看通知"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"不用了"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"设置"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>(<xliff:g id="EXIT_CONDITION">%2$s</xliff:g>)"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"立即关闭"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"立即结束"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"展开"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"收起"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"已固定屏幕"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"关闭"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"利用高级通知设置,您可以为应用通知设置从 0 级到 5 级的重要程度等级。\n\n"<b>"5 级"</b>" \n- 在通知列表顶部显示 \n- 允许全屏打扰 \n- 一律短暂显示通知 \n\n"<b>"4 级"</b>" \n- 禁止全屏打扰 \n- 一律短暂显示通知 \n\n"<b>"3 级"</b>" \n- 禁止全屏打扰 \n- 一律不短暂显示通知 \n\n"<b>"2 级"</b>" \n- 禁止全屏打扰 \n- 一律不短暂显示通知 \n- 一律不发出声音或振动 \n\n"<b>"1 级"</b>" \n- 禁止全屏打扰 \n- 一律不短暂显示通知 \n- 一律不发出声音或振动 \n- 不在锁定屏幕和状态栏中显示 \n- 在通知列表底部显示 \n\n"<b>"0 级"</b>" \n- 屏蔽应用的所有通知"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"通知"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"您将不会再收到这类通知。"</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"您将不会再收到这类通知"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> 个通知类别"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"此应用没有通知类别"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">此应用指定的 1 个通知类别(共 <xliff:g id="NUMBER_1">%d</xliff:g> 个)</item>
<item quantity="one">此应用指定的 1 个通知类别(共 <xliff:g id="NUMBER_0">%d</xliff:g> 个)</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g><xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"通知设置"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"通知延后选项"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 分钟"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 分钟"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 小时"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 小时"</string>
<string name="snooze_undo" msgid="6074877317002985129">"撤消"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"已延后 <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"电池使用情况"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"充电过程中无法使用省电模式"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"省电模式"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings_car.xml b/packages/SystemUI/res/values-zh-rCN/strings_car.xml
index 6a5a4e8..27dd755 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings_car.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"安全驾驶"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"请充分了解驾驶条件并务必遵守适用的法律。路线指示可能不准确,不完整,也可能包含危险、不适合通行、禁止通行或跨越行政区域的路段。商家信息也可能不准确或不完整。数据并非实时提供,因此无法保证位置信息的精确度。请勿在驾驶过程中操作您的移动设备或使用不支持 Android Auto 的应用。"</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"未知"</string>
+ <string name="start_driving" msgid="864023351402918991">"开始驾驶"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index cab3aea..fc923bc 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -354,8 +354,7 @@
<string name="description_target_search" msgid="3091587249776033139">"搜尋"</string>
<string name="description_direction_up" msgid="7169032478259485180">"向上滑動即可<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
<string name="description_direction_left" msgid="7207478719805562165">"向左滑動即可<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"您不會受音效和震動騷擾 (鬧鐘、提醒、活動,以及您指定的來電者鈴聲除外)。如果您選擇播放特定音樂、影片和遊戲等內容,仍可以聽到相關音訊。"</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"您不會受音效和震動騷擾 (鬧鐘除外)。如果您選擇播放特定音樂、影片和遊戲等內容,仍可以聽到相關音訊。"</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"除了指定的鬧鐘、提醒、活動及來電者,您將不會受到其他聲音和震動騷擾。"</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"自訂"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"這會封鎖所有聲音和震動,包括鬧鐘、音樂、影片和遊戲,但您仍可撥打電話。"</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"這會封鎖所有聲音和震動,包括鬧鐘、音樂、影片和遊戲。"</string>
@@ -469,13 +468,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"您已連接至「<xliff:g id="APPLICATION">%1$s</xliff:g>」,此應用程式可以監控您的個人網絡活動,包括電郵、應用程式及網站。"</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"您的工作設定檔由<xliff:g id="ORGANIZATION">%1$s</xliff:g>管理。設定檔已連結至「<xliff:g id="APPLICATION">%2$s</xliff:g>」,此應用程式可以監控您的工作網絡活動,包括電郵、應用程式和網站。\n\n如需瞭解詳情,請聯絡您的管理員。"</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"您的工作設定檔由<xliff:g id="ORGANIZATION">%1$s</xliff:g>管理。設定檔已連結至「<xliff:g id="APPLICATION_WORK">%2$s</xliff:g>」,此應用程式可以監控您的工作網絡活動,包括電郵、應用程式和網站。\n\n您亦已連結至「<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>」,此應用程式可以監控您的個人網絡活動。"</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"已為<xliff:g id="USER_NAME">%1$s</xliff:g>解鎖"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> 執行中"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"裝置將保持上鎖,直到您手動解鎖"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"更快取得通知"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"解鎖前顯示"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"不用了,謝謝"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"設定"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>。<xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"立即關閉"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"立即終止"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"展開"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"收合"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"螢幕已固定"</string>
@@ -550,9 +551,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"關閉"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"通知控制項讓您設定應用程式通知的重要性 (0 至 5 級)。\n\n"<b>"第 5 級"</b>" \n- 在通知清單頂部顯示 \n- 允許全螢幕騷擾 \n- 一律顯示通知 \n\n"<b>"第 4 級"</b>" \n- 阻止全螢幕騷擾 \n- 一律顯示通知 \n\n"<b>"第 3 級"</b>" \n- 阻止全螢幕騷擾 \n- 永不顯示通知 \n\n"<b>"第 2 級"</b>" \n- 阻止全螢幕騷擾 \n- 永不顯示通知 \n- 永不發出聲響和震動 \n\n"<b>"第 1 級"</b>" \n- 阻止全螢幕騷擾 \n- 永不顯示通知 \n- 永不發出聲響和震動 \n- 從上鎖畫面和狀態列中隱藏 \n- 在通知清單底部顯示 \n\n"<b>"第 0 級"</b>" \n- 封鎖所有應用程式通知"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"通知"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"您不會再收到這些通知。"</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"您不會再收到這些通知"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> 個通知類別"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"此應用程式沒有通知類別"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">此應用程式的 1 個通知類別 (共 <xliff:g id="NUMBER_1">%d</xliff:g> 個)</item>
<item quantity="one">此應用程式的 1 個通知類別 (共 <xliff:g id="NUMBER_0">%d</xliff:g> 個)</item>
@@ -572,12 +575,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"通知控制項"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"通知延後選項"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 分鐘"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 分鐘"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 小時"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 小時"</string>
<string name="snooze_undo" msgid="6074877317002985129">"復原"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"已延後 <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"電池用量"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"充電時無法使用「省電模式」"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"省電模式"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings_car.xml b/packages/SystemUI/res/values-zh-rHK/strings_car.xml
index 18f9e50..01f3b14 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings_car.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"請安全駕駛"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"請隨時留意路面情況,並遵守適用的交通規則。系統提供的路線可能不準確、不完整、存在危險、不適合、禁止通行,或涉及跨越行政區域的路段。此外,商家資訊也可能不準確或不完整。路況資料並非即時更新,也無法保證定位資訊的準確性。駕駛時請勿操作流動裝置,或使用不適用於 Android Auto 的應用程式。"</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"不明"</string>
+ <string name="start_driving" msgid="864023351402918991">"開始駕駛"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 9526426..f4d1fbd1 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"搜尋"</string>
<string name="description_direction_up" msgid="7169032478259485180">"向上滑動即可<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
<string name="description_direction_left" msgid="7207478719805562165">"向左滑動即可<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"裝置不會發出音效或震動造成干擾,但會保留鬧鐘、提醒、活動和你指定的來電者響鈴。如果你選擇播放特定音樂、影片和遊戲等內容,還是可以聽見相關音訊。"</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"裝置不會發出音效或震動造成干擾,但會保留鬧鐘響鈴。如果你選擇播放特定音樂、影片和遊戲等內容,還是可以聽見相關音訊。"</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"你不會受到聲音和震動干擾,但鬧鐘、提醒、活動和指定來電者除外。"</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"自訂"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"這會封鎖「所有」聲音和震動干擾,包括鬧鐘、音樂、影片和遊戲在內,但你仍然可以撥打電話。"</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"這會封鎖「所有」聲音和震動干擾,包括鬧鐘、音樂、影片和遊戲在內。"</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"由於你已連結至「<xliff:g id="APPLICATION">%1$s</xliff:g>」,你的個人網路活動 (包括收發電子郵件、使用應用程式及瀏覽網站) 可能會受到這個應用程式監控。"</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"你的 Work 設定檔是由「<xliff:g id="ORGANIZATION">%1$s</xliff:g>」所管理。由於該設定檔已連結至「<xliff:g id="APPLICATION">%2$s</xliff:g>」,因此你的網路活動 (包括收發電子郵件、使用應用程式和瀏覽網站) 可能會受到這個應用程式監控。\n\n如要瞭解詳情,請與你的管理員聯絡。"</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"你的 Work 設定檔是由「<xliff:g id="ORGANIZATION">%1$s</xliff:g>」所管理。由於該設定檔已連結至「<xliff:g id="APPLICATION_WORK">%2$s</xliff:g>」,因此你的網路活動 (包括收發電子郵件、使用應用程式和瀏覽網站) 可能會受到這個應用程式監控。\n\n此外,你還與「<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>」建立了連結,因此你的個人網路活動也可能會受到該應用程式監控。"</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"已為<xliff:g id="USER_NAME">%1$s</xliff:g>解鎖"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"「<xliff:g id="TRUST_AGENT">%1$s</xliff:g>」執行中"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"在你手動解鎖前,裝置將保持鎖定狀態"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"更快取得通知"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"解鎖前顯示"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"不用了,謝謝"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"設定"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>。<xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"立即停用"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"立刻結束"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"展開"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"收合"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"螢幕已固定"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"關閉"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"只要使用電源通知控制項,你就能為應用程式通知設定從 0 到 5 的重要性等級。\n\n"<b>"等級 5"</b>" \n- 顯示在通知清單頂端 \n- 允許全螢幕通知 \n- 一律允許短暫顯示通知 \n\n"<b>"等級 4"</b>" \n- 禁止全螢幕通知 \n- 一律允許短暫顯示通知 \n\n"<b>"等級 3"</b>" \n- 禁止全螢幕通知 \n- 一律不允許短暫顯示通知 \n\n"<b>"等級 2"</b>" \n- 禁止全螢幕通知 \n- 一律不允許短暫顯示通知 \n- 一律不發出音效或震動 \n\n"<b>"等級 1"</b>" \n- 禁止全螢幕通知 \n- 一律不允許短暫顯示通知 \n- 一律不發出音效或震動 \n- 在鎖定畫面和狀態列中隱藏 \n- 顯示在通知清單底端 \n\n"<b>"等級 0"</b>" \n- 封鎖應用程式的所有通知"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"通知"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"你不會再收到這類通知。"</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"你不會再收到這類通知"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> 個通知類別"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"這個應用程式沒有通知類別"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="other">在 <xliff:g id="NUMBER_1">%d</xliff:g> 個通知類別中,有 1 個類別是來自這個應用程式</item>
<item quantity="one">在 <xliff:g id="NUMBER_0">%d</xliff:g> 個通知類別中,有 1 個類別是來自這個應用程式</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"通知控制項"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"通知延後選項"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 分鐘"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 分鐘"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 小時"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 小時"</string>
<string name="snooze_undo" msgid="6074877317002985129">"復原"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"已延後 <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"電池用量"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"充電時無法使用節約耗電量模式"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"節約耗電量"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings_car.xml b/packages/SystemUI/res/values-zh-rTW/strings_car.xml
index e420fe7..01f3b14 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings_car.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"安全駕駛"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"請隨時注意周遭路況並遵守交通規則。系統所提供的路線可能有誤、不完整,或包含危險、不合適、禁止通行或管制區域的路段;此外,商家資訊也可能有誤或不完整。路況資料並非即時更新,也無法保證定位資訊必然準確無誤。駕駛時請勿操作你的行動裝置,或使用不適用於 Android Auto 的應用程式。"</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"不明"</string>
+ <string name="start_driving" msgid="864023351402918991">"開始駕駛"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 80ce905..bc82fc0 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -352,8 +352,7 @@
<string name="description_target_search" msgid="3091587249776033139">"Sesha"</string>
<string name="description_direction_up" msgid="7169032478259485180">"Shelelisela ngenhla ku-<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Shelelisela ngakwesokunxele ku-<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
- <string name="zen_priority_introduction" msgid="7577965386868311310">"Ngeke uphazanyiswe yimisindo nokudlidliza, ngaphandle kwama-alamu, izikhumbuzi, izehlakalo, nabashayi obacacisayo. Usazokuzwa okuthile okhetha ukudlala kufaka phakathi umculo, amavidiyo, namageyimu."</string>
- <string name="zen_alarms_introduction" msgid="7034415210361973827">"Ngeke uphazanyiswe yimisindo nokudlidliza, ngaphandle kwama-alamu. Usazokuzwa okuthile okhetha ukudlala kufaka phakathi umculo, amavidiyo, namageyimu."</string>
+ <string name="zen_priority_introduction" msgid="3070506961866919502">"Ngeke uze uphazanyiswe yimisindo nokudlidliza, ngaphandle kokuvela kuma-alamu, izikhumbuzi, imicimbi, nabashayi obacacisayo."</string>
<string name="zen_priority_customize_button" msgid="7948043278226955063">"Enza ngendlela oyifisayo"</string>
<string name="zen_silence_introduction_voice" msgid="2284540992298200729">"Lokhu kuvimbela YONKE imisindo nokudludliza, kufaka phakathi ama-alamu, umculo, amavidiyo, namageyimu. Usazokwazi ukwenza amkholi wefoni."</string>
<string name="zen_silence_introduction" msgid="3137882381093271568">"Lokhu kuvimbela YONKE imisindo nokudlidliza, kufaka phakathi kusuka kuma-alamu, umculo, amavidiyo, namageyimu."</string>
@@ -467,13 +466,15 @@
<string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Uxhumeke ku-<xliff:g id="APPLICATION">%1$s</xliff:g>, engaqapha umsebenzi wakho womuntu siqu wenethiwekhi, ofaka ama-imeyili, izinhlelo zokusebenza, namawebhusayithi."</string>
<string name="monitoring_description_app_work" msgid="4612997849787922906">"Iphrofayela yakho yomsebenzi iphethwe i-<xliff:g id="ORGANIZATION">%1$s</xliff:g>. Iphrofayela ixhumeke ku-<xliff:g id="APPLICATION">%2$s</xliff:g>, engaqapha umsebenzi wenethiwekhi yakho yokusebenza, ofaka ama-imeyili, izinhlelo zokusebenza, namawebhusayithi.\n\nUkuze uthole olunye ulwazi, xhumana nomlawuli wakho."</string>
<string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"Iphrofayela yakho yomsebenzi iphethwe i-<xliff:g id="ORGANIZATION">%1$s</xliff:g>. Iphrofayela ixhumeke ku-<xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, engaqapha umsebenzi wakho wenethiwekhi, ofaka ama-imeyili, izinhlelo zokusebenza, namawebhusayithi.\n\nFuthi uxhumeke ku-<xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, engaqapha umsebenzi wakho siqu wenethiwekhi."</string>
+ <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Kuvulelwe u-<xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
+ <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> iyasebenza"</string>
<string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"Idivayisi izohlala ikhiyekile uze uyivule ngokwenza"</string>
<string name="hidden_notifications_title" msgid="7139628534207443290">"Thola izaziso ngokushesha"</string>
<string name="hidden_notifications_text" msgid="2326409389088668981">"Ibone ngaphambi kokuthi uyivule"</string>
<string name="hidden_notifications_cancel" msgid="3690709735122344913">"Cha ngiyabonga"</string>
<string name="hidden_notifications_setup" msgid="41079514801976810">"Lungisa"</string>
<string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
- <string name="volume_zen_end_now" msgid="6930243045593601084">"Vala manje"</string>
+ <string name="volume_zen_end_now" msgid="3179845345429841822">"Qeda manje"</string>
<string name="accessibility_volume_expand" msgid="5946812790999244205">"Nweba"</string>
<string name="accessibility_volume_collapse" msgid="3609549593031810875">"Goqa"</string>
<string name="screen_pinning_title" msgid="3273740381976175811">"Isikrini siphiniwe"</string>
@@ -548,9 +549,11 @@
<string name="tuner_full_importance_settings_off" msgid="8208165412614935229">"Valiwe"</string>
<string name="power_notification_controls_description" msgid="4372459941671353358">"Ngezilawuli zesaziso zamandla, ungasetha ileveli ebalulekile kusuka ku-0 kuya ku-5 kusuka kuzaziso zohlelo lokusebenza. \n\n"<b>"Ileveli 5"</b>" \n- Ibonisa phezulu kuhlu lwesaziso \n- Vumela ukuphazamiseka kwesikrini esigcwele \n- Ukuhlola njalo \n\n"<b>"Ileveli 4"</b>" \n- Gwema ukuphazamiseka kwesikrini esigcwele \n- Ukuhlola njalo \n\n"<b>"Ileveli 3"</b>" \n- Gwema ukuphazamiseka kwesikrini esigcwele \n- Ukungahloli \n\n"<b>"Ileveli 2"</b>" \n- Gwema ukuphazamiseka kwesikrini esigcwele \n- Ukungahloli \n- Ungenzi umsindo nokudlidliza \n\n"<b>"Ileveli 1"</b>" \n- Gwema ukuphazamiseka kwesikrini esigcwele \n- Ukungahloli \n- Ungenzi umsindo noma ukudlidliza \n- Fihla kusuka kusikrini sokukhiya nebha yesimo \n- Bonisa phansi kohlu lwesaziso \n\n"<b>"Ileveli 0"</b>" \n- Vimbela zonke izaziso kusuka kuhlelo lokusebenza"</string>
<string name="notification_header_default_channel" msgid="7506845022070889909">"Izaziso"</string>
- <string name="notification_channel_disabled" msgid="5805874247999578073">"Ngeke usathola lezi zaziso."</string>
+ <string name="notification_channel_disabled" msgid="2139193533791840539">"Ngeke usathola izaziso"</string>
<string name="notification_num_channels" msgid="2048144408999179471">"<xliff:g id="NUMBER">%d</xliff:g> izigaba zesaziso"</string>
<string name="notification_default_channel_desc" msgid="2506053815870808359">"Lolu hlelo lokusebenza alunazo izigaba zesaziso"</string>
+ <!-- no translation found for notification_unblockable_desc (3561016061737896906) -->
+ <skip />
<plurals name="notification_num_channels_desc" formatted="false" msgid="5492793452274077663">
<item quantity="one">1 isigaba kwezingu-<xliff:g id="NUMBER_1">%d</xliff:g> sezaziso kusukela kulolu hlelo lokusebenza</item>
<item quantity="other">1 isigaba kwezingu-<xliff:g id="NUMBER_1">%d</xliff:g> sezaziso kusukela kulolu hlelo lokusebenza</item>
@@ -570,12 +573,10 @@
<string name="notification_menu_accessibility" msgid="2046162834248888553">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
<string name="notification_menu_gear_description" msgid="2204480013726775108">"izilawuli zesaziso"</string>
<string name="notification_menu_snooze_description" msgid="3653669438131034525">"izinketho zokusnuza zesaziso"</string>
- <string name="snooze_option_15_min" msgid="1068727451405610715">"15 amaminithi"</string>
- <string name="snooze_option_30_min" msgid="867081342535195788">"30 amaminithi"</string>
- <string name="snooze_option_1_hour" msgid="1098086401880077154">"1 ihora"</string>
- <string name="snooze_option_2_hour" msgid="8332218255658969475">"2 amahora"</string>
<string name="snooze_undo" msgid="6074877317002985129">"HLEHLISA"</string>
<string name="snoozed_for_time" msgid="2390718332980204462">"Kusnuzwe u-<xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
+ <!-- no translation found for snoozeHourOptions (2124335842674413030) -->
+ <!-- no translation found for snoozeMinuteOptions (4127251700591510196) -->
<string name="battery_panel_title" msgid="7944156115535366613">"Ukusetshenziswa kwebhethri"</string>
<string name="battery_detail_charging_summary" msgid="1279095653533044008">"Isilondolozi sebhethri asitholakali ngesikhathi sokushaja"</string>
<string name="battery_detail_switch_title" msgid="6285872470260795421">"Isilondolozi sebhethri"</string>
diff --git a/packages/SystemUI/res/values-zu/strings_car.xml b/packages/SystemUI/res/values-zu/strings_car.xml
index 83301ac..3eddfd4 100644
--- a/packages/SystemUI/res/values-zu/strings_car.xml
+++ b/packages/SystemUI/res/values-zu/strings_car.xml
@@ -19,6 +19,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Shayela ngokuqophelela"</string>
- <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"Hlala wazi ngokugcwele izimo zokushayela uphinde uthobele yonke imithetho esebenzayo. Izikhombisi-ndlela kungenzeka azilungile, aziphelele, ziyingozi, azifanelekile, zivinjiwe, noma zifaka ukweqa izindawo zokulawula. Ulwazi lwebhizinisi kungenzeka lungalungi noma lungapheleli. Idatha akuyona isikhathi sangempela, futhi ukunemba kwendawo akukwazi ukuqinisekiswa. Ungaphathi idivayisi yakho yeselula noma usebenzise izinhlelo zokusebenza ezihloselwe i-Android Auto ngenkathi ushayela."</string>
+ <string name="unknown_user_label" msgid="4323896111737677955">"Akwaziwa"</string>
+ <string name="start_driving" msgid="864023351402918991">"Qala ukushayela"</string>
</resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 06b945d..81f0138 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -124,12 +124,10 @@
<declare-styleable name="PluginInflateContainer">
<attr name="viewType" format="string" />
</declare-styleable>
- <declare-styleable name="ScrimView">
- <!-- The initial color for the scrim. -->
- <attr name="scrimColor" format="color" />
- </declare-styleable>
<attr name="lightIconTheme" format="reference" />
<attr name="darkIconTheme" format="reference" />
+ <attr name="bgProtectTextColor" format="color" />
+ <attr name="bgProtectSecondaryTextColor" format="color" />
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 83a0b7b..3ef28bd 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -32,7 +32,6 @@
<color name="qs_detail_button">@*android:color/quaternary_device_default_settings</color>
<color name="qs_detail_button_white">#B3FFFFFF</color><!-- 70% white -->
<color name="qs_detail_transition">#66FFFFFF</color>
- <color name="scrim_behind_color">@android:color/black</color>
<color name="status_bar_clock_color">#FFFFFFFF</color>
<color name="qs_user_detail_icon_muted">#FFFFFFFF</color> <!-- not so muted after all -->
<color name="qs_tile_disabled_color">#9E9E9E</color> <!-- 38% black -->
@@ -78,6 +77,9 @@
<!-- The color of the material notification background when low priority -->
<color name="notification_material_background_low_priority_color">#fff5f5f5</color>
+ <!-- The color of the dividing line between grouped notifications. -->
+ <color name="notification_divider_color">#FF616161</color>
+
<!-- The background color of the notification shade -->
<color name="notification_shade_background_color">#ffeeeeee</color>
diff --git a/packages/SystemUI/res/values/colors_car.xml b/packages/SystemUI/res/values/colors_car.xml
new file mode 100644
index 0000000..4faf252
--- /dev/null
+++ b/packages/SystemUI/res/values/colors_car.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+ <color name="car_user_switcher_progress_bgcolor">#00000000</color> <!-- Transparent -->
+ <color name="car_user_switcher_progress_fgcolor">#80CBC4</color> <!-- Teal 200 -->
+ <color name="car_start_driving_background">#FAFAFA</color> <!-- Grey 50 -->
+ <color name="car_start_driving_text">#212121</color> <!-- Grey 900 -->
+</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index fd7e165..d3cbe4a 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -210,9 +210,12 @@
<!-- Set to true to enable the user switcher on the keyguard. -->
<bool name="config_keyguardUserSwitcher">false</bool>
- <!-- Doze: does this device support STATE_DOZE and STATE_DOZE_SUSPEND? -->
+ <!-- Doze: does this device support STATE_DOZE? -->
<bool name="doze_display_state_supported">false</bool>
+ <!-- Doze: does this device support STATE_DOZE_SUSPEND? -->
+ <bool name="doze_suspend_display_state_supported">false</bool>
+
<!-- Doze: should the significant motion sensor be used as a pulse signal? -->
<bool name="doze_pulse_on_significant_motion">false</bool>
@@ -336,4 +339,60 @@
<!-- Whether to show activity indicators in the status bar -->
<bool name="config_showActivity">false</bool>
+ <!-- Whether or not the button to clear all notifications will be shown. -->
+ <bool name="config_enableNotificationsClearAll">true</bool>
+
+ <!-- Whether or not to show the notification shelf that houses the icons of notifications that
+ have been scrolled off-screen. -->
+ <bool name="config_showNotificationShelf">true</bool>
+
+ <!-- Whether or not the notifications should always fade as they are dismissed. -->
+ <bool name="config_fadeNotificationsOnDismiss">false</bool>
+
+ <!-- Whether or not the parent of the notification row itself is being translated when swiped or
+ its children views. If true, then the contents are translated and vice versa. -->
+ <bool name="config_translateNotificationContentsOnSwipe">true</bool>
+
+ <!-- Whether or not the fade on the notification is based on the amount that it has been swiped
+ off-screen. -->
+ <bool name="config_fadeDependingOnAmountSwiped">false</bool>
+
+ <!-- Whether or not to show the expand button at the end of the notification header. -->
+ <bool name="config_showNotificationExpandButtonAtEnd">false</bool>
+
+ <!-- Whether or the notifications should be clipped to be reduced in height if it has been
+ scrolled to the top of the screen. -->
+ <bool name="config_clipNotificationScrollToTop">true</bool>
+
+ <!-- Whether or not the notification contents should be clipped to any background that is
+ set on the notification container. For example, if this value is true and the background
+ has rounded corners, then the contents will be clipped to those corners. -->
+ <bool name="config_clipNotificationsToOutline">false</bool>
+
+ <!-- Whether or not notifications that can be expanded will always be in their expanded state.
+ This value only affects notifications that are not a group of notifications from the same
+ applications. If this value is false, then only the first notification will be expanded;
+ the other notifications need to be manually expanded by the user. -->
+ <bool name="config_alwaysExpandNonGroupedNotifications">false</bool>
+
+ <!-- Whether or not an expandable notification can be manually expanded or collapsed by the
+ user. Grouped notifications are still expandable even if this value is false. -->
+ <bool name="config_enableNonGroupedNotificationExpand">true</bool>
+
+ <!-- Whether or not there should be dividing lines between child notifications when the
+ group has been expanded. -->
+ <bool name="config_showDividersWhenGroupNotificationExpanded">false</bool>
+
+ <!-- Whether or not the dividing lines should be shown when the container is expanding and
+ collapsing. If this value is true, then the lines will only show when the container has
+ been completely expanded. -->
+ <bool name="config_hideDividersDuringExpand">false</bool>
+
+ <!-- Whether or not child notifications that are part of a group will have shadows. -->
+ <bool name="config_enableShadowOnChildNotifications">true</bool>
+
+ <!-- Whether or not a view containing child notifications will have a custom background when
+ it has been expanded to reveal its children. -->
+ <bool name="config_showGroupNotificationBgWhenExpanded">false</bool>
+
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 4245b11..fc9e585 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -204,9 +204,17 @@
<!-- Width for the notification panel and related windows -->
<dimen name="match_parent">-1px</dimen>
<dimen name="standard_notification_panel_width">416dp</dimen>
+
+ <!-- The top margin of the panel that holds the list of notifications. -->
+ <dimen name="notification_panel_margin_top">0dp</dimen>
+
+ <!-- The bottom margin of the panel that holds the list of notifications. -->
+ <dimen name="notification_panel_margin_bottom">0dp</dimen>
+
<dimen name="notification_panel_width">@dimen/match_parent</dimen>
- <dimen name="volume_dialog_panel_width">@dimen/standard_notification_panel_width</dimen>
+ <!-- The width of the panel that holds the quick settings. -->
+ <dimen name="qs_panel_width">@dimen/notification_panel_width</dimen>
<!-- Gravity for the notification panel -->
<integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top -->
@@ -323,6 +331,22 @@
<!-- The height of the divider between the individual notifications. -->
<dimen name="notification_divider_height">0.5dp</dimen>
+ <!-- The corner radius of the shadow behind the notification. -->
+ <dimen name="notification_shadow_radius">0dp</dimen>
+
+ <!-- The alpha of the dividing line between child notifications of a notification group. -->
+ <item name="notification_divider_alpha" format="float" type="dimen">0.5</item>
+
+ <!-- The height of the divider between the individual notifications in a notification
+ group. -->
+ <dimen name="notification_children_container_divider_height">@dimen/notification_divider_height</dimen>
+
+ <!-- The height of the header for a container containing child notifications. -->
+ <dimen name="notification_children_container_header_height">53dp</dimen>
+
+ <!-- The top margin for the notification children container in its non-expanded form. -->
+ <dimen name="notification_children_container_margin_top">@*android:dimen/notification_content_margin_top</dimen>
+
<!-- The height of a notification header -->
<dimen name="notification_header_height">53dp</dimen>
@@ -774,6 +798,30 @@
<dimen name="signal_icon_size">17dp</dimen>
- <dimen name="qs_gutter_height">6dp</dimen>
+ <dimen name="hwui_edge_margin">16dp</dimen>
+
+ <dimen name="volume_dialog_panel_width">315dp</dimen>
+ <dimen name="global_actions_panel_width">125dp</dimen>
+
+ <dimen name="global_actions_top_padding">100dp</dimen>
+
+ <!-- the maximum offset in either direction that elements are moved horizontally to prevent
+ burn-in on AOD -->
+ <dimen name="burn_in_prevention_offset_x">8dp</dimen>
+
+ <!-- the maximum offset in either direction that elements are moved vertically to prevent
+ burn-in on AOD -->
+ <dimen name="burn_in_prevention_offset_y">50dp</dimen>
+
+ <dimen name="corner_size">16dp</dimen>
+ <dimen name="top_padding">0dp</dimen>
+ <dimen name="bottom_padding">48dp</dimen>
+ <dimen name="edge_margin">16dp</dimen>
+
+ <dimen name="rounded_corner_radius">0dp</dimen>
+ <dimen name="rounded_corner_content_padding">0dp</dimen>
+
+ <!-- Intended corner radius when drawing the mobile signal -->
+ <dimen name="stat_sys_mobile_signal_corner_radius">0.75dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/dimens_car.xml b/packages/SystemUI/res/values/dimens_car.xml
index 988caf5..d5d4e10 100644
--- a/packages/SystemUI/res/values/dimens_car.xml
+++ b/packages/SystemUI/res/values/dimens_car.xml
@@ -16,18 +16,24 @@
*/
-->
<resources>
- <dimen name="car_lockscreen_disclaimer_title_size">48sp</dimen>
- <dimen name="car_lockscreen_disclaimer_title_padding_start">96dp</dimen>
- <dimen name="car_lockscreen_disclaimer_title_padding_top">82dp</dimen>
- <dimen name="car_lockscreen_disclaimer_text_size">28sp</dimen>
- <dimen name="car_lockscreen_disclaimer_text_padding_start">96dp</dimen>
- <dimen name="car_lockscreen_disclaimer_text_padding_end">96dp</dimen>
- <dimen name="car_lockscreen_disclaimer_text_padding_top">8dp</dimen>
- <dimen name="car_lockscreen_user_grid_view_padding_start">10dp</dimen>
- <dimen name="car_lockscreen_user_grid_view_padding_end">10dp</dimen>
- <dimen name="car_fullscreen_user_pod_image_avatar_width">128dp</dimen>
- <dimen name="car_fullscreen_user_pod_image_avatar_height">128dp</dimen>
- <dimen name="car_fullscreen_user_pod_text_size">24sp</dimen>
+ <dimen name="car_margin">148dp</dimen>
+
+ <dimen name="car_fullscreen_user_pod_margin_side">44dp</dimen>
+ <dimen name="car_fullscreen_user_pod_margin_above_text">24dp</dimen>
+ <dimen name="car_fullscreen_user_pod_image_avatar_width">192dp</dimen>
+ <dimen name="car_fullscreen_user_pod_image_avatar_height">192dp</dimen>
+ <dimen name="car_fullscreen_user_pod_text_size">40sp</dimen> <!-- B1 -->
+
<dimen name="car_navigation_button_width">64dp</dimen>
<dimen name="car_navigation_bar_width">760dp</dimen>
+
+ <dimen name="car_page_indicator_dot_diameter">12dp</dimen>
+ <dimen name="car_page_indicator_margin_top">32dp</dimen>
+
+ <dimen name="car_user_switcher_progress_bar_height">6dp</dimen>
+ <dimen name="car_user_switcher_progress_bar_margin_top">@dimen/status_bar_height</dimen>
+ <dimen name="car_start_driving_corner_radius">16dp</dimen>
+ <dimen name="car_start_driving_padding_side">30dp</dimen>
+ <dimen name="car_start_driving_height">80dp</dimen>
+ <dimen name="car_start_driving_text_size">32sp</dimen> <!-- B2 -->
</resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index fc0c82c..3c6ad23 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -45,6 +45,7 @@
<item type="id" name="shadow_alpha_animator_start_value_tag"/>
<item type="id" name="doze_saved_filter_tag"/>
<item type="id" name="qs_icon_tag"/>
+ <item type="id" name="qs_slash_tag"/>
<item type="id" name="scrim"/>
<item type="id" name="scrim_target"/>
<item type="id" name="scrim_alpha_start"/>
diff --git a/packages/SystemUI/res/values/integers_car.xml b/packages/SystemUI/res/values/integers_car.xml
new file mode 100644
index 0000000..320ee9f
--- /dev/null
+++ b/packages/SystemUI/res/values/integers_car.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (c) 2017, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <integer name="car_user_switcher_timeout_ms">15000</integer>
+ <!-- This values less than ProgressBar.PROGRESS_ANIM_DURATION for a smooth animation. -->
+ <integer name="car_user_switcher_anim_update_ms">60</integer>
+</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 994a566..570438a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1195,6 +1195,12 @@
<xliff:g id="application_personal">%3$s</xliff:g>, which can monitor your personal network
activity.</string>
+ <!-- Indication on the keyguard that appears when a trust agents unlocks the device. [CHAR LIMIT=40] -->
+ <string name="keyguard_indication_trust_granted">Unlocked for <xliff:g id="user_name" example="John Doe">%1$s</xliff:g></string>
+
+ <!-- Indication on the keyguard that appears when a trust agent is active and available. [CHAR LIMIT=40] -->
+ <string name="keyguard_indication_trust_managed"><xliff:g id="trust_agent" example="Smart Lock">%1$s</xliff:g> is running</string>
+
<!-- Indication on the keyguard that appears when the user disables trust agents until the next time they unlock manually. [CHAR LIMIT=NONE] -->
<string name="keyguard_indication_trust_disabled">Device will stay locked until you manually unlock</string>
@@ -1442,7 +1448,7 @@
<string name="notification_header_default_channel">Notifications</string>
<!-- Notification Inline Controls: Shown when a channel's notifications are currently blocked -->
- <string name="notification_channel_disabled">You won\'t get these notifications anymore.</string>
+ <string name="notification_channel_disabled">You won\'t get these notifications anymore</string>
<!-- Notification: Control panel: Label that shows how many channels are included in this bundle
of notifications. Replaces the channel name and only appears when there is more than one channel. -->
@@ -1452,6 +1458,9 @@
Hints that the user's only option is to block all of the app's notifications. -->
<string name="notification_default_channel_desc">This app doesn\'t have notification categories</string>
+ <!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. -->
+ <string name="notification_unblockable_desc">Notifications from this app can\'t be turned off</string>
+
<!-- Notification: Control panel: Label that shows how many channels this application has
defined, describing the current notification channel as "1 out of n notification categories from this app". -->
<plurals name="notification_num_channels_desc">
@@ -1502,20 +1511,27 @@
<!-- Notification: Menu row: Content description for the snooze icon. [CHAR LIMIT=NONE] -->
<string name="notification_menu_snooze_description">notification snooze options</string>
- <!-- Notification: Menu row: Snooze options: 15 minute option. [CHAR LIMIT=50]-->
- <string name="snooze_option_15_min">15 minutes</string>
- <!-- Notification: Menu row: Snooze options: 30 minute option. [CHAR LIMIT=50]-->
- <string name="snooze_option_30_min">30 minutes</string>
- <!-- Notification: Menu row: Snooze options: 1 hour option. [CHAR LIMIT=50]-->
- <string name="snooze_option_1_hour">1 hour</string>
- <!-- Notification: Menu row: Snooze options: 1 hour option. [CHAR LIMIT=50]-->
- <string name="snooze_option_2_hour">2 hours</string>
- <!-- Notification: Menu row: Snooze undo button label. [CHAR LIMIT=50]-->
+ <!-- Notification: Snooze panel: Snooze undo button label. [CHAR LIMIT=50]-->
<string name="snooze_undo">UNDO</string>
- <!-- Notification: Menu row: Snooze: message indicating how long the notification was snoozed for. [CHAR LIMIT=100]-->
+ <!-- Notification: Snooze panel: message indicating how long the notification was snoozed for. [CHAR LIMIT=100]-->
<string name="snoozed_for_time">Snoozed for <xliff:g id="time_amount" example="15 minutes">%1$s</xliff:g></string>
+ <!-- Notification:Snooze panel: Snooze options for hours -->
+ <plurals name="snoozeHourOptions">
+ <item quantity="one">%d hour</item>
+ <item quantity="two">%d hours</item>
+ <item quantity="few">%d hours</item>
+ <item quantity="other">%d hours</item>
+ </plurals>
+
+ <!-- Notification: Snooze panel: Snooze options for minutes -->
+ <plurals name="snoozeMinuteOptions">
+ <item quantity="one">%d minute</item>
+ <item quantity="few">%d minutes</item>
+ <item quantity="other">%d minutes</item>
+ </plurals>
+
<!-- Title of the battery settings detail panel [CHAR LIMIT=20] -->
<string name="battery_panel_title">Battery usage</string>
diff --git a/packages/SystemUI/res/values/strings_car.xml b/packages/SystemUI/res/values/strings_car.xml
index 882773a..658eb4f 100644
--- a/packages/SystemUI/res/values/strings_car.xml
+++ b/packages/SystemUI/res/values/strings_car.xml
@@ -17,13 +17,6 @@
*/
-->
<resources>
- <string name="car_lockscreen_disclaimer_title">Drive safely</string>
- <string name="car_lockscreen_disclaimer_text">
- Stay fully aware of driving conditions and always obey applicable laws. Directions may be
- inaccurate, incomplete, dangerous, not suitable, prohibited, or involve crossing
- administrative areas. Business information may also be inaccurate or incomplete. Data is
- not real-time, and location accuracy cannot be guaranteed. Do not handle your mobile device
- or use apps not intended for Android Auto while driving.
- </string>
-
+ <string name="unknown_user_label">Unknown</string>
+ <string name="start_driving">Start Driving</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index d98b789..f942be6 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -293,7 +293,18 @@
<style name="systemui_theme" parent="@*android:style/Theme.DeviceDefault.QuickSettings">
<item name="lightIconTheme">@style/DualToneLightTheme</item>
<item name="darkIconTheme">@style/DualToneDarkTheme</item>
+ <item name="bgProtectTextColor">?android:attr/textColorPrimaryInverse</item>
+ <item name="bgProtectSecondaryTextColor">?android:attr/textColorSecondaryInverse</item>
+ <item name="pinDividerColor">@color/pin_divider_color</item>
+ <item name="pinDeleteColor">@color/pin_delete_color</item>
+ <item name="*android:lockPatternStyle">@style/LockPatternStyle</item>
</style>
+
+ <style name="LockPatternStyle">
+ <item name="*android:regularColor">?android:attr/textColorPrimaryInverse</item>
+ <item name="*android:successColor">?android:attr/textColorPrimaryInverse</item>
+ </style>
+
<style name="qs_theme" parent="systemui_theme">
<item name="lightIconTheme">@style/QSIconTheme</item>
<item name="darkIconTheme">@style/QSIconTheme</item>
@@ -307,6 +318,8 @@
<style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
+ <style name="Theme.SystemUI.Dialog.GlobalActions" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen" />
+
<style name="QSBorderlessButton">
<item name="android:padding">12dp</item>
<item name="android:background">@drawable/qs_btn_borderless_rect</item>
diff --git a/packages/SystemUI/res/values/styles_car.xml b/packages/SystemUI/res/values/styles_car.xml
new file mode 100644
index 0000000..c66792c
--- /dev/null
+++ b/packages/SystemUI/res/values/styles_car.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (c) 2017, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="CarUserSwitcher.ProgressBar" parent="@android:style/Widget.ProgressBar.Horizontal">
+ <item name="android:progressDrawable">@drawable/car_progress_bar</item>
+ <item name="android:min">0</item>
+ <item name="android:max">@integer/car_user_switcher_timeout_ms</item>
+ <item name="android:progress">0</item>
+ <item name="android:interpolator">@android:anim/linear_interpolator</item>
+ </style>
+
+ <style name="CarUserSwitcher.StartDrivingButton" parent="@android:style/Widget.Material.Button">
+ <item name="android:background">@drawable/car_round_button</item>
+ <item name="android:textSize">@dimen/car_start_driving_text_size</item>
+ <item name="android:textColor">@color/car_start_driving_text</item>
+ <item name="android:paddingLeft">@dimen/car_start_driving_padding_side</item>
+ <item name="android:paddingRight">@dimen/car_start_driving_padding_side</item>
+ </style>
+</resources>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 223dafd..32f502b 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -128,7 +128,7 @@
<com.android.systemui.tuner.TunerSwitch
android:key="doze_always_on"
android:title="@string/tuner_doze_always_on"
- sysui:defValue="false" />
+ sysui:defValue="true" />
</PreferenceScreen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
new file mode 100644
index 0000000..6a86bb2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+import android.telephony.SubscriptionManager;
+import android.telephony.euicc.EuiccManager;
+
+import java.lang.ref.WeakReference;
+
+/***
+ * This button is used by the device with embedded SIM card to disable current carrier to unlock
+ * the device with no cellular service.
+ */
+class KeyguardEsimArea extends Button implements View.OnClickListener {
+ private EuiccManager mEuiccManager;
+
+ public KeyguardEsimArea(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardEsimArea(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public KeyguardEsimArea(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, android.R.style.Widget_Material_Button_Borderless);
+ }
+
+ public KeyguardEsimArea(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ // STOPSHIP(b/37353596): use EuiccManager API to disable current carrier.
+ }
+
+ public static boolean isEsimLocked(Context context, int subId) {
+ EuiccManager euiccManager =
+ (EuiccManager) context.getSystemService(Context.EUICC_SERVICE);
+ return euiccManager.isEnabled()
+ && SubscriptionManager.from(context).getActiveSubscriptionInfo(subId).isEmbedded();
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 616e5b9..165ca2a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -75,8 +75,7 @@
}
public KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle) {
- super(new ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault), attrs,
- defStyle);
+ super(context, attrs, defStyle);
mSecurityModel = new KeyguardSecurityModel(context);
mLockPatternUtils = new LockPatternUtils(context);
mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index 0cf8900..4c7b48d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -35,8 +35,10 @@
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.euicc.EuiccManager;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
@@ -77,10 +79,11 @@
if (DEBUG) Log.v(TAG, "Resetting state");
KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
mSubId = monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED);
+ boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
if (SubscriptionManager.isValidSubscriptionId(mSubId)) {
int count = TelephonyManager.getDefault().getSimCount();
Resources rez = getResources();
- final String msg;
+ String msg;
int color = Color.WHITE;
if (count < 2) {
msg = rez.getString(R.string.kg_sim_pin_instructions);
@@ -92,9 +95,14 @@
color = info.getIconTint();
}
}
+ if (isEsimLocked) {
+ msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim);
+ }
mSecurityMessageDisplay.setMessage(msg);
mSimImageView.setImageTintList(ColorStateList.valueOf(color));
}
+ KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area);
+ esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index fb3cee7..d8163ba 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -29,8 +29,10 @@
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.euicc.EuiccManager;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
@@ -118,10 +120,11 @@
state = ENTER_PUK;
KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
mSubId = monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED);
+ boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
if (SubscriptionManager.isValidSubscriptionId(mSubId)) {
int count = TelephonyManager.getDefault().getSimCount();
Resources rez = getResources();
- final String msg;
+ String msg;
int color = Color.WHITE;
if (count < 2) {
msg = rez.getString(R.string.kg_puk_enter_puk_hint);
@@ -133,11 +136,18 @@
color = info.getIconTint();
}
}
+ if (isEsimLocked) {
+ msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim);
+ }
mSecurityMessageDisplay.setMessage(msg);
mSimImageView.setImageTintList(ColorStateList.valueOf(color));
}
+ KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area);
+ esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE);
mPasswordEntry.requestFocus();
}
+
+
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index d214d55..2262869 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -21,7 +21,9 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Color;
import android.os.UserHandle;
+import android.support.v4.graphics.ColorUtils;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.AttributeSet;
@@ -57,7 +59,8 @@
private View[] mVisibleInDoze;
private boolean mPulsing;
- private boolean mDark;
+ private float mDarkAmount;
+ private int mTextColor;
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@@ -124,6 +127,7 @@
mOwnerInfo = findViewById(R.id.owner_info);
mBatteryDoze = findViewById(R.id.battery_doze);
mVisibleInDoze = new View[]{mBatteryDoze, mClockView};
+ mTextColor = mClockView.getCurrentTextColor();
boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
setEnableMarquee(shouldMarquee);
@@ -186,6 +190,10 @@
((MarginLayoutParams) mClockView.getLayoutParams()).bottomMargin;
}
+ public float getClockTextSize() {
+ return mClockView.getTextSize();
+ }
+
public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) {
if (info == null) {
return "";
@@ -278,12 +286,13 @@
}
}
- public void setDark(boolean dark) {
- if (mDark == dark) {
+ public void setDark(float darkAmount) {
+ if (mDarkAmount == darkAmount) {
return;
}
- mDark = dark;
+ mDarkAmount = darkAmount;
+ boolean dark = darkAmount == 1;
final int N = mClockContainer.getChildCount();
for (int i = 0; i < N; i++) {
View child = mClockContainer.getChildAt(i);
@@ -294,6 +303,7 @@
}
updateDozeVisibleViews();
mBatteryDoze.setDark(dark);
+ mClockView.setTextColor(ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount));
}
public void setPulsing(boolean pulsing) {
@@ -303,7 +313,7 @@
private void updateDozeVisibleViews() {
for (View child : mVisibleInDoze) {
- child.setAlpha(mDark && mPulsing ? 0.8f : 1);
+ child.setAlpha(mDarkAmount == 1 && mPulsing ? 0.8f : 1);
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index ad82840..7c0aa07 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -52,7 +52,6 @@
import android.os.IRemoteCallback;
import android.os.Message;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
@@ -62,7 +61,6 @@
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
-import android.util.ArraySet;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
@@ -74,6 +72,8 @@
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -131,6 +131,7 @@
private static final int MSG_SCREEN_TURNED_OFF = 332;
private static final int MSG_DREAMING_STATE_CHANGED = 333;
private static final int MSG_USER_UNLOCKED = 334;
+ private static final int MSG_ASSISTANT_STACK_CHANGED = 335;
/** Fingerprint state: Not listening to fingerprint. */
private static final int FINGERPRINT_STATE_STOPPED = 0;
@@ -170,6 +171,8 @@
private boolean mBootCompleted;
private boolean mNeedsSlowUnlockTransition;
private boolean mHasLockscreenWallpaper;
+ private boolean mAssistantVisible;
+ private boolean mKeyguardOccluded;
// Device provisioning state
private boolean mDeviceProvisioned;
@@ -287,6 +290,10 @@
case MSG_USER_UNLOCKED:
handleUserUnlocked();
break;
+ case MSG_ASSISTANT_STACK_CHANGED:
+ mAssistantVisible = (boolean)msg.obj;
+ updateFingerprintListeningState();
+ break;
}
}
};
@@ -414,9 +421,22 @@
mKeyguardGoingAway = goingAway;
}
+ /**
+ * Updates KeyguardUpdateMonitor's internal state to know if keyguard is occluded
+ * @param occluded
+ */
+ public void setKeyguardOccluded(boolean occluded) {
+ mKeyguardOccluded = occluded;
+ updateFingerprintListeningState();
+ }
+
private void onFingerprintAuthenticated(int userId) {
Trace.beginSection("KeyGuardUpdateMonitor#onFingerPrintAuthenticated");
mUserFingerprintAuthenticated.put(userId, true);
+ // Update/refresh trust state only if user can skip bouncer
+ if (getUserCanSkipBouncer(userId)) {
+ mTrustManager.unlockedByFingerprintForUser(userId);
+ }
// Don't send cancel if authentication succeeds
mFingerprintCancelSignal = null;
for (int i = 0; i < mCallbacks.size(); i++) {
@@ -425,6 +445,11 @@
cb.onFingerprintAuthenticated(userId);
}
}
+
+ // Only authenticate fingerprint once when assistant is visible
+ mAssistantVisible = false;
+ updateFingerprintListeningState();
+
Trace.endSection();
}
@@ -1106,6 +1131,7 @@
mFpm.addLockoutResetCallback(mLockoutResetCallback);
}
+ SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
mUserManager = context.getSystemService(UserManager.class);
}
@@ -1122,7 +1148,8 @@
private boolean shouldListenForFingerprint() {
return (mKeyguardIsVisible || !mDeviceInteractive ||
- (mBouncer && !mKeyguardGoingAway) || mGoingToSleep)
+ (mBouncer && !mKeyguardGoingAway) || mGoingToSleep ||
+ (mAssistantVisible && mKeyguardOccluded))
&& !mSwitchingUser && !isFingerprintDisabled(getCurrentUser());
}
@@ -1151,8 +1178,10 @@
private void stopListeningForFingerprint() {
if (DEBUG) Log.v(TAG, "stopListeningForFingerprint()");
if (mFingerprintRunningState == FINGERPRINT_STATE_RUNNING) {
- mFingerprintCancelSignal.cancel();
- mFingerprintCancelSignal = null;
+ if (mFingerprintCancelSignal != null) {
+ mFingerprintCancelSignal.cancel();
+ mFingerprintCancelSignal = null;
+ }
setFingerprintRunningState(FINGERPRINT_STATE_CANCELLING);
}
if (mFingerprintRunningState == FINGERPRINT_STATE_CANCELLING_RESTARTING) {
@@ -1651,6 +1680,7 @@
public void clearFingerprintRecognized() {
mUserFingerprintAuthenticated.clear();
+ mTrustManager.clearAllFingerprints();
}
public boolean isSimPinVoiceSecure() {
@@ -1674,6 +1704,23 @@
}
}
+ private final TaskStackListener mTaskStackListener = new TaskStackListener() {
+ @Override
+ public void onTaskStackChangedBackground() {
+ try {
+ ActivityManager.StackInfo info = ActivityManager.getService().getStackInfo(
+ ActivityManager.StackId.ASSISTANT_STACK_ID);
+ if (info == null) {
+ return;
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_ASSISTANT_STACK_CHANGED,
+ info.visible));
+ } catch (RemoteException e) {
+ Log.e(TAG, "unable to check task stack", e);
+ }
+ }
+ };
+
/**
* @return true if and only if the state has changed for the specified {@code slotId}
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index c43820d..7e81173 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
@@ -127,12 +128,13 @@
mCharPadding = a.getDimensionPixelSize(R.styleable.PasswordTextView_charPadding,
getContext().getResources().getDimensionPixelSize(
R.dimen.password_char_padding));
+ int textColor = a.getColor(R.styleable.PasswordTextView_android_textColor, Color.WHITE);
+ mDrawPaint.setColor(textColor);
} finally {
a.recycle();
}
mDrawPaint.setFlags(Paint.SUBPIXEL_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
mDrawPaint.setTextAlign(Paint.Align.CENTER);
- mDrawPaint.setColor(0xffffffff);
mDrawPaint.setTypeface(Typeface.create("sans-serif-light", 0));
mShowPassword = Settings.System.getInt(mContext.getContentResolver(),
Settings.System.TEXT_SHOW_PASSWORD, 1) == 1;
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index cf8747e..6476416 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -71,6 +71,7 @@
private int mLightModeBackgroundColor;
private int mLightModeFillColor;
+ private float mDarkIntensity;
public BatteryMeterView(Context context) {
this(context, null, 0);
@@ -238,6 +239,7 @@
@Override
public void onDarkChanged(Rect area, float darkIntensity, int tint) {
+ mDarkIntensity = darkIntensity;
float intensity = DarkIconDispatcher.isInArea(area, this) ? darkIntensity : 0;
int foreground = getColorForDarkIntensity(intensity, mLightModeFillColor,
mDarkModeFillColor);
@@ -254,6 +256,14 @@
}
}
+ public void setFillColor(int color) {
+ if (mLightModeFillColor == color) {
+ return;
+ }
+ mLightModeFillColor = color;
+ onDarkChanged(new Rect(), mDarkIntensity, DarkIconDispatcher.DEFAULT_ICON_TINT);
+ }
+
private int getColorForDarkIntensity(float darkIntensity, int lightColor, int darkColor) {
return (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, lightColor, darkColor);
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 8ba33c3..73a2a43 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -77,6 +77,8 @@
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
+import com.android.systemui.tuner.TunablePadding;
+import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerServiceImpl;
import com.android.systemui.util.leak.GarbageMonitor;
@@ -84,6 +86,8 @@
import com.android.systemui.util.leak.LeakReporter;
import com.android.systemui.volume.VolumeDialogControllerImpl;
+import com.google.android.colorextraction.ColorExtractor;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;
@@ -248,7 +252,7 @@
new FragmentService(mContext));
mProviders.put(ExtensionController.class, () ->
- new ExtensionControllerImpl());
+ new ExtensionControllerImpl(mContext));
mProviders.put(PluginDependencyProvider.class, () ->
new PluginDependencyProvider(get(PluginManager.class)));
@@ -264,6 +268,10 @@
mProviders.put(AccessibilityManagerWrapper.class,
() -> new AccessibilityManagerWrapper(mContext));
+ mProviders.put(ColorExtractor.class, () -> new ColorExtractor(mContext));
+
+ mProviders.put(TunablePaddingService.class, () -> new TunablePaddingService());
+
mProviders.put(ForegroundServiceController.class,
() -> new ForegroundServiceControllerImpl(mContext));
diff --git a/packages/SystemUI/src/com/android/systemui/HardwareBgDrawable.java b/packages/SystemUI/src/com/android/systemui/HardwareBgDrawable.java
new file mode 100644
index 0000000..6417e29
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/HardwareBgDrawable.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.util.Log;
+
+public class HardwareBgDrawable extends LayerDrawable {
+
+ private final Paint mPaint = new Paint();
+ private final Drawable[] mLayers;
+ private final boolean mRoundTop;
+ private int mPoint;
+ private boolean mRotatedBackground;
+
+ public HardwareBgDrawable(boolean roundTop, boolean roundEnd, Context context) {
+ this(roundTop, getLayers(context, roundTop, roundEnd));
+ }
+
+ public HardwareBgDrawable(boolean roundTop, Drawable[] layers) {
+ super(layers);
+ if (layers.length != 2) {
+ throw new IllegalArgumentException("Need 2 layers");
+ }
+ mRoundTop = roundTop;
+ mLayers = layers;
+ mLayers[1].setTint(0xffeeeeee);
+ }
+
+ private static Drawable[] getLayers(Context context, boolean roundTop, boolean roundEnd) {
+ int drawable = roundEnd ? R.drawable.rounded_bg_full : R.drawable.rounded_bg;
+ if (roundTop) {
+ return new Drawable[]{
+ context.getDrawable(drawable).mutate(),
+ context.getDrawable(drawable).mutate(),
+ };
+ }
+ return new Drawable[]{
+ context.getDrawable(drawable).mutate(),
+ context.getDrawable(roundEnd ? R.drawable.rounded_full_bg_bottom
+ : R.drawable.rounded_bg_bottom).mutate(),
+ };
+ }
+
+ public void setCutPoint(int point) {
+ mPoint = point;
+ invalidateSelf();
+ }
+
+ public int getCutPoint() {
+ return mPoint;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mPoint >= 0 && !mRotatedBackground) {
+ Rect bounds = getBounds();
+ int top = bounds.top + mPoint;
+ if (top > bounds.bottom) top = bounds.bottom;
+ if (mRoundTop) {
+ mLayers[0].setBounds(bounds.left, bounds.top, bounds.right, top);
+ } else {
+ mLayers[1].setBounds(bounds.left, top, bounds.right, bounds.bottom);
+ }
+ if (mRoundTop) {
+ mLayers[1].draw(canvas);
+ mLayers[0].draw(canvas);
+ } else {
+ mLayers[0].draw(canvas);
+ mLayers[1].draw(canvas);
+ }
+ } else {
+ mLayers[0].draw(canvas);
+ }
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mPaint.setAlpha(alpha);
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ mPaint.setColorFilter(colorFilter);
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.OPAQUE;
+ }
+
+ public void setRotatedBackground(boolean rotatedBackground) {
+ mRotatedBackground = rotatedBackground;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
new file mode 100644
index 0000000..bb0f2f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+public class HardwareUiLayout extends FrameLayout implements Tunable {
+
+ private static final String EDGE_BLEED = "sysui_hwui_edge_bleed";
+ private static final String ROUNDED_DIVIDER = "sysui_hwui_rounded_divider";
+ private final int[] mTmp2 = new int[2];
+ private View mChild;
+ private int mOldHeight;
+ private boolean mAnimating;
+ private AnimatorSet mAnimation;
+ private View mDivision;
+ private boolean mHasOutsideTouch;
+ private HardwareBgDrawable mBackground;
+ private Animator mAnimator;
+ private boolean mCollapse;
+ private int mEndPoint;
+ private boolean mEdgeBleed;
+ private boolean mRoundedDivider;
+ private boolean mLandscape;
+ private boolean mRotatedBackground;
+
+ public HardwareUiLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ updateSettings();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ updateSettings();
+ Dependency.get(TunerService.class).addTunable(this, EDGE_BLEED, ROUNDED_DIVIDER);
+ getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ getViewTreeObserver().removeOnComputeInternalInsetsListener(mInsetsListener);
+ Dependency.get(TunerService.class).removeTunable(this);
+ }
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ updateSettings();
+ }
+
+ private void updateSettings() {
+ mEdgeBleed = Settings.Secure.getInt(getContext().getContentResolver(),
+ EDGE_BLEED, 0) != 0;
+ mRoundedDivider = Settings.Secure.getInt(getContext().getContentResolver(),
+ ROUNDED_DIVIDER, 1) != 0;
+ updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding());
+ mBackground = new HardwareBgDrawable(mRoundedDivider, !mEdgeBleed, getContext());
+ if (mChild != null) {
+ mChild.setBackground(mBackground);
+ requestLayout();
+ }
+ }
+
+ private void updateEdgeMargin(int edge) {
+ if (mChild != null) {
+ MarginLayoutParams params = (MarginLayoutParams) mChild.getLayoutParams();
+ if (mLandscape) {
+ params.topMargin = edge;
+ } else {
+ params.rightMargin = edge;
+ }
+ mChild.setLayoutParams(params);
+ }
+ }
+
+ private int getEdgePadding() {
+ return getContext().getResources().getDimensionPixelSize(R.dimen.edge_margin);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (mChild == null) {
+ if (getChildCount() != 0) {
+ mChild = getChildAt(0);
+ mChild.setBackground(mBackground);
+ updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding());
+ mOldHeight = mChild.getMeasuredHeight();
+ mChild.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+ updatePosition());
+ } else {
+ return;
+ }
+ }
+ int newHeight = mChild.getMeasuredHeight();
+ if (newHeight != mOldHeight) {
+ animateChild(mOldHeight, newHeight);
+ }
+ post(() -> updatePosition());
+ boolean landscape = getMeasuredWidth() > getMeasuredHeight();
+ if (landscape != mLandscape) {
+ mLandscape = landscape;
+ if (mLandscape) {
+ toLandscape();
+ if (mChild instanceof LinearLayout) {
+ mRotatedBackground = true;
+ mBackground.setRotatedBackground(true);
+ ((LinearLayout) mChild).setOrientation(LinearLayout.HORIZONTAL);
+ swapDimens(mChild);
+ }
+ } else {
+ fromLandscape();
+ if (mChild instanceof LinearLayout) {
+ mRotatedBackground = false;
+ mBackground.setRotatedBackground(false);
+ ((LinearLayout) mChild).setOrientation(LinearLayout.VERTICAL);
+ swapDimens(mChild);
+ }
+ }
+ }
+ }
+
+ private void fromLandscape() {
+ rotateRight(this);
+ rotateRight(mChild);
+ swapDimens(this);
+
+ LayoutParams p = (LayoutParams) mChild.getLayoutParams();
+ p.gravity = rotateGravityRight(p.gravity);
+ mChild.setLayoutParams(p);
+ }
+
+ private void swapDimens(View v) {
+ ViewGroup.LayoutParams params = v.getLayoutParams();
+ int h = params.width;
+ params.width = params.height;
+ params.height = h;
+ v.setLayoutParams(params);
+ }
+
+ private int rotateGravityRight(int gravity) {
+ int retGravity = 0;
+ int layoutDirection = getLayoutDirection();
+ final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
+ final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.CENTER_HORIZONTAL:
+ retGravity |= Gravity.CENTER_VERTICAL;
+ break;
+ case Gravity.RIGHT:
+ retGravity |= Gravity.BOTTOM;
+ break;
+ case Gravity.LEFT:
+ default:
+ retGravity |= Gravity.TOP;
+ break;
+ }
+
+ switch (verticalGravity) {
+ case Gravity.CENTER_VERTICAL:
+ retGravity |= Gravity.CENTER_HORIZONTAL;
+ break;
+ case Gravity.BOTTOM:
+ retGravity |= Gravity.LEFT;
+ break;
+ case Gravity.TOP:
+ default:
+ retGravity |= Gravity.RIGHT;
+ break;
+ }
+ return retGravity;
+ }
+
+ private void toLandscape() {
+ rotateLeft(this);
+ rotateLeft(mChild);
+ swapDimens(this);
+
+ LayoutParams p = (LayoutParams) mChild.getLayoutParams();
+ p.gravity = rotateGravityLeft(p.gravity);
+ mChild.setLayoutParams(p);
+ }
+
+ private int rotateGravityLeft(int gravity) {
+ if (gravity == -1) {
+ gravity = Gravity.TOP | Gravity.START;
+ }
+ int retGravity = 0;
+ int layoutDirection = getLayoutDirection();
+ final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
+ final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.CENTER_HORIZONTAL:
+ retGravity |= Gravity.CENTER_VERTICAL;
+ break;
+ case Gravity.RIGHT:
+ retGravity |= Gravity.TOP;
+ break;
+ case Gravity.LEFT:
+ default:
+ retGravity |= Gravity.BOTTOM;
+ break;
+ }
+
+ switch (verticalGravity) {
+ case Gravity.CENTER_VERTICAL:
+ retGravity |= Gravity.CENTER_HORIZONTAL;
+ break;
+ case Gravity.BOTTOM:
+ retGravity |= Gravity.RIGHT;
+ break;
+ case Gravity.TOP:
+ default:
+ retGravity |= Gravity.LEFT;
+ break;
+ }
+ return retGravity;
+ }
+
+ private void rotateLeft(View v) {
+ v.setPadding(v.getPaddingTop(), v.getPaddingRight(), v.getPaddingBottom(),
+ v.getPaddingLeft());
+ MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams();
+ params.setMargins(params.topMargin, params.rightMargin, params.bottomMargin,
+ params.leftMargin);
+ v.setLayoutParams(params);
+ }
+
+ private void rotateRight(View v) {
+ v.setPadding(v.getPaddingBottom(), v.getPaddingLeft(), v.getPaddingTop(),
+ v.getPaddingRight());
+ MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams();
+ params.setMargins(params.bottomMargin, params.leftMargin, params.topMargin,
+ params.rightMargin);
+ v.setLayoutParams(params);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ post(() -> updatePosition());
+ }
+
+ private void animateChild(int oldHeight, int newHeight) {
+ if (true) return;
+ if (mAnimating) {
+ mAnimation.cancel();
+ }
+ mAnimating = true;
+ mAnimation = new AnimatorSet();
+ mAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimating = false;
+ }
+ });
+ int fromTop = mChild.getTop();
+ int fromBottom = mChild.getBottom();
+ int toTop = fromTop - ((newHeight - oldHeight) / 2);
+ int toBottom = fromBottom + ((newHeight - oldHeight) / 2);
+ ObjectAnimator top = ObjectAnimator.ofInt(mChild, "top", fromTop, toTop);
+ top.addUpdateListener(animation -> mBackground.invalidateSelf());
+ mAnimation.playTogether(top,
+ ObjectAnimator.ofInt(mChild, "bottom", fromBottom, toBottom));
+ }
+
+ public void setDivisionView(View v) {
+ mDivision = v;
+ if (mDivision != null) {
+ mDivision.addOnLayoutChangeListener(
+ (v1, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+ updatePosition());
+ }
+ updatePosition();
+ }
+
+ private void updatePosition() {
+ if (mChild == null) return;
+ if (mDivision != null && mDivision.getVisibility() == VISIBLE) {
+ int index = mRotatedBackground ? 0 : 1;
+ mDivision.getLocationOnScreen(mTmp2);
+ float trans = mRotatedBackground ? mDivision.getTranslationX()
+ : mDivision.getTranslationY();
+ int viewTop = (int) (mTmp2[index] + trans);
+ mChild.getLocationOnScreen(mTmp2);
+ viewTop -= mTmp2[index];
+ setCutPoint(viewTop);
+ } else {
+ setCutPoint(mChild.getMeasuredHeight());
+ }
+ }
+
+ private void setCutPoint(int point) {
+ int curPoint = mBackground.getCutPoint();
+ if (curPoint == point) return;
+ if (getAlpha() == 0 || curPoint == 0) {
+ mBackground.setCutPoint(point);
+ return;
+ }
+ if (mAnimator != null) {
+ if (mEndPoint == point) {
+ return;
+ }
+ mAnimator.cancel();
+ }
+ mEndPoint = point;
+ mAnimator = ObjectAnimator.ofInt(mBackground, "cutPoint", curPoint, point);
+ if (mCollapse) {
+ mAnimator.setStartDelay(300);
+ mCollapse = false;
+ }
+ mAnimator.start();
+ }
+
+ @Override
+ public ViewOutlineProvider getOutlineProvider() {
+ return super.getOutlineProvider();
+ }
+
+ public void setOutsideTouchListener(OnClickListener onClickListener) {
+ mHasOutsideTouch = true;
+ requestLayout();
+ setOnClickListener(onClickListener);
+ setClickable(true);
+ setFocusable(true);
+ }
+
+ public void setCollapse() {
+ mCollapse = true;
+ }
+
+ public static HardwareUiLayout get(View v) {
+ if (v instanceof HardwareUiLayout) return (HardwareUiLayout) v;
+ if (v.getParent() instanceof View) {
+ return get((View) v.getParent());
+ }
+ return null;
+ }
+
+ private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener = inoutInfo -> {
+ if (mHasOutsideTouch || (mChild == null)) {
+ inoutInfo.setTouchableInsets(
+ ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+ return;
+ }
+ inoutInfo.setTouchableInsets(
+ ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT);
+ inoutInfo.contentInsets.set(mChild.getLeft(), mChild.getTop(),
+ 0, getBottom() - mChild.getBottom());
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 9a4179f..6571294 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -405,17 +405,17 @@
}
} else {
drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
+ if (FIXED_SIZED_SURFACE) {
+ // If the surface is fixed-size, we should only need to
+ // draw it once and then we'll let the window manager
+ // position it appropriately. As such, we no longer needed
+ // the loaded bitmap. Yay!
+ // hw-accelerated renderer retains bitmap for faster rotation
+ unloadWallpaper(false /* forgetSize */);
+ }
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- if (FIXED_SIZED_SURFACE && !mIsHwAccelerated) {
- // If the surface is fixed-size, we should only need to
- // draw it once and then we'll let the window manager
- // position it appropriately. As such, we no longer needed
- // the loaded bitmap. Yay!
- // hw-accelerated renderer retains bitmap for faster rotation
- unloadWallpaper(false /* forgetSize */);
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
new file mode 100644
index 0000000..c4740af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui;
+
+import static com.android.systemui.tuner.TunablePadding.FLAG_START;
+import static com.android.systemui.tuner.TunablePadding.FLAG_END;
+
+import android.app.Fragment;
+import android.graphics.PixelFormat;
+import android.support.annotation.VisibleForTesting;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+
+import com.android.systemui.R.id;
+import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
+import com.android.systemui.statusbar.phone.NavigationBarFragment;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.tuner.TunablePadding;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+public class RoundedCorners extends SystemUI implements Tunable {
+ public static final String SIZE = "sysui_rounded_size";
+ public static final String PADDING = "sysui_rounded_content_padding";
+
+ private int mRoundedDefault;
+ private View mOverlay;
+ private View mBottomOverlay;
+ private float mDensity;
+ private TunablePadding mQsPadding;
+ private TunablePadding mStatusBarPadding;
+ private TunablePadding mNavBarPadding;
+
+ @Override
+ public void start() {
+ mRoundedDefault = mContext.getResources().getDimensionPixelSize(
+ R.dimen.rounded_corner_radius);
+ if (mRoundedDefault == 0) {
+ // No rounded corners on this device.
+ return;
+ }
+
+ mOverlay = LayoutInflater.from(mContext)
+ .inflate(R.layout.rounded_corners, null);
+ mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ mOverlay.findViewById(R.id.right).setRotation(90);
+
+ mContext.getSystemService(WindowManager.class)
+ .addView(mOverlay, getWindowLayoutParams());
+ mBottomOverlay = LayoutInflater.from(mContext)
+ .inflate(R.layout.rounded_corners, null);
+ mBottomOverlay.findViewById(R.id.right).setRotation(180);
+ mBottomOverlay.findViewById(R.id.left).setRotation(270);
+ WindowManager.LayoutParams layoutParams = getWindowLayoutParams();
+ layoutParams.gravity = Gravity.BOTTOM;
+ mContext.getSystemService(WindowManager.class)
+ .addView(mBottomOverlay, layoutParams);
+
+ DisplayMetrics metrics = new DisplayMetrics();
+ mContext.getSystemService(WindowManager.class)
+ .getDefaultDisplay().getMetrics(metrics);
+ mDensity = metrics.density;
+
+ Dependency.get(TunerService.class).addTunable(this, SIZE);
+
+ // Add some padding to all the content near the edge of the screen.
+ int padding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.rounded_corner_content_padding);
+ StatusBar sb = getComponent(StatusBar.class);
+ View statusBar = sb.getStatusBarWindow();
+
+ TunablePadding.addTunablePadding(statusBar.findViewById(R.id.keyguard_header), PADDING,
+ padding, FLAG_END);
+
+ FragmentHostManager.get(sb.getNavigationBarWindow()).addTagListener(
+ NavigationBarFragment.TAG,
+ new TunablePaddingTagListener(padding, 0));
+
+ FragmentHostManager fragmentHostManager = FragmentHostManager.get(statusBar);
+ fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG,
+ new TunablePaddingTagListener(padding, R.id.status_bar));
+ fragmentHostManager.addTagListener(QS.TAG,
+ new TunablePaddingTagListener(padding, R.id.header));
+ }
+
+ private WindowManager.LayoutParams getWindowLayoutParams() {
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ 0
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+ | WindowManager.LayoutParams.FLAG_SLIPPERY
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+ ,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.setTitle("RoundedOverlay");
+ lp.gravity = Gravity.TOP;
+ return lp;
+ }
+
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ if (mOverlay == null) return;
+ if (SIZE.equals(key)) {
+ int size = mRoundedDefault;
+ try {
+ size = (int) (Integer.parseInt(newValue) * mDensity);
+ } catch (Exception e) {
+ }
+ setSize(mOverlay.findViewById(R.id.left), size);
+ setSize(mOverlay.findViewById(R.id.right), size);
+ setSize(mBottomOverlay.findViewById(R.id.left), size);
+ setSize(mBottomOverlay.findViewById(R.id.right), size);
+ }
+ }
+
+ private void setSize(View view, int pixelSize) {
+ LayoutParams params = view.getLayoutParams();
+ params.width = pixelSize;
+ params.height = pixelSize;
+ view.setLayoutParams(params);
+ }
+
+ @VisibleForTesting
+ static class TunablePaddingTagListener implements FragmentListener {
+
+ private final int mPadding;
+ private final int mId;
+ private TunablePadding mTunablePadding;
+
+ public TunablePaddingTagListener(int padding, int id) {
+ mPadding = padding;
+ mId = id;
+ }
+
+ @Override
+ public void onFragmentViewCreated(String tag, Fragment fragment) {
+ if (mTunablePadding != null) {
+ mTunablePadding.destroy();
+ }
+ View view = fragment.getView();
+ if (mId != 0) {
+ view = view.findViewById(mId);
+ }
+ mTunablePadding = TunablePadding.addTunablePadding(view, PADDING, mPadding,
+ FLAG_START | FLAG_END);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 74b745f..93d661d 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -22,6 +22,7 @@
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.RectF;
import android.os.Handler;
import android.util.Log;
@@ -30,9 +31,7 @@
import android.view.View;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
-
import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -91,6 +90,7 @@
private int mFalsingThreshold;
private boolean mTouchAboveFalsingThreshold;
private boolean mDisableHwLayers;
+ private boolean mFadeDependingOnAmountSwiped;
private Context mContext;
private HashMap<View, Animator> mDismissPendingMap = new HashMap<>();
@@ -101,12 +101,15 @@
mHandler = new Handler();
mSwipeDirection = swipeDirection;
mVelocityTracker = VelocityTracker.obtain();
- mDensityScale = context.getResources().getDisplayMetrics().density;
mPagingTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop();
- mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f); // extra long-press!
- mFalsingThreshold = context.getResources().getDimensionPixelSize(
- R.dimen.swipe_helper_falsing_threshold);
+ // Extra long-press!
+ mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f);
+
+ Resources res = context.getResources();
+ mDensityScale = res.getDisplayMetrics().density;
+ mFalsingThreshold = res.getDimensionPixelSize(R.dimen.swipe_helper_falsing_threshold);
+ mFadeDependingOnAmountSwiped = res.getBoolean(R.bool.config_fadeDependingOnAmountSwiped);
mFalsingManager = FalsingManager.getInstance(context);
mFlingAnimationUtils = new FlingAnimationUtils(context, getMaxEscapeAnimDuration() / 1000f);
}
@@ -176,8 +179,7 @@
}
protected float getSize(View v) {
- return mSwipeDirection == X ? v.getMeasuredWidth() :
- v.getMeasuredHeight();
+ return mSwipeDirection == X ? v.getMeasuredWidth() : v.getMeasuredHeight();
}
public void setMinSwipeProgress(float minSwipeProgress) {
@@ -195,6 +197,11 @@
}
private float getSwipeAlpha(float progress) {
+ if (mFadeDependingOnAmountSwiped) {
+ // The more progress has been fade, the lower the alpha value so that the view fades.
+ return Math.max(1 - progress, 0);
+ }
+
return Math.min(0, Math.max(1, progress / SWIPE_PROGRESS_FADE_END));
}
@@ -207,9 +214,8 @@
float swipeProgress = getSwipeProgressForOffset(animView, translation);
if (!mCallback.updateSwipeProgress(animView, dismissable, swipeProgress)) {
if (FADE_OUT_DURING_SWIPE && dismissable) {
- float alpha = swipeProgress;
if (!mDisableHwLayers) {
- if (alpha != 0f && alpha != 1f) {
+ if (swipeProgress != 0f && swipeProgress != 1f) {
animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
} else {
animView.setLayerType(View.LAYER_TYPE_NONE, null);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 0eb469f..4a45997 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -87,6 +87,7 @@
GarbageMonitor.Service.class,
LatencyTester.class,
GlobalActionsComponent.class,
+ RoundedCorners.class,
};
/**
diff --git a/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java b/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java
index 965ded5..931a994 100644
--- a/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java
@@ -25,6 +25,7 @@
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;
+import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
@@ -56,7 +57,7 @@
private static final long TIMEOUT_MILLIS = 11000; // 11 seconds.
public static final boolean DEBUG = false;
- private final Handler mHandler = new Handler();
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Context mContext;
// Err on the side of caution, so logging is not started after a crash even tough the screen
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
index a129ace..8506734 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
@@ -24,6 +24,7 @@
import android.hardware.SensorManager;
import android.net.Uri;
import android.os.Handler;
+import android.os.Looper;
import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.Settings;
@@ -59,7 +60,7 @@
Sensor.TYPE_ROTATION_VECTOR,
};
- private final Handler mHandler = new Handler();
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Context mContext;
private final SensorManager mSensorManager;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
index 851ab77..2e9ba56 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
@@ -20,6 +20,7 @@
import android.database.ContentObserver;
import android.hardware.SensorEvent;
import android.os.Handler;
+import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.DisplayMetrics;
@@ -41,7 +42,7 @@
private static HumanInteractionClassifier sInstance = null;
- private final Handler mHandler = new Handler();
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Context mContext;
private final StrokeClassifier[] mStrokeClassifiers;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index eea09df..1cc10c2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -25,6 +25,7 @@
import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
public class DozeFactory {
@@ -42,10 +43,12 @@
AmbientDisplayConfiguration config = new AmbientDisplayConfiguration(context);
DozeParameters params = new DozeParameters(context);
Handler handler = new Handler();
- WakeLock wakeLock = WakeLock.createPartial(context, "Doze");
+ WakeLock wakeLock = new DelayedWakeLock(handler,
+ WakeLock.createPartial(context, "Doze"));
DozeMachine machine = new DozeMachine(
- DozeScreenStatePreventingAdapter.wrapIfNeeded(dozeService, params),
+ DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(
+ DozeScreenStatePreventingAdapter.wrapIfNeeded(dozeService, params), params),
config,
wakeLock);
machine.setParts(new DozeMachine.Part[]{
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 44bb33a..5526e6b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -91,8 +91,9 @@
case DOZE_AOD_PAUSED:
return Display.STATE_OFF;
case DOZE_PULSING:
+ return Display.STATE_ON;
case DOZE_AOD:
- return Display.STATE_DOZE; // TODO: use STATE_ON if appropriate.
+ return Display.STATE_DOZE_SUSPEND;
default:
return Display.STATE_UNKNOWN;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 5241266..3271caf 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -57,6 +57,8 @@
super.onDreamingStarted();
mDozeMachine.requestState(DozeMachine.State.INITIALIZED);
startDozing();
+ setDozeScreenBrightness(getResources().getInteger(
+ com.android.internal.R.integer.config_screenBrightnessDoze));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java
new file mode 100644
index 0000000..1e06797
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.doze;
+
+import android.support.annotation.VisibleForTesting;
+import android.view.Display;
+
+import com.android.systemui.statusbar.phone.DozeParameters;
+
+/**
+ * Prevents usage of doze screen states on devices that don't support them.
+ */
+public class DozeSuspendScreenStatePreventingAdapter implements DozeMachine.Service {
+
+ private final DozeMachine.Service mInner;
+
+ @VisibleForTesting
+ DozeSuspendScreenStatePreventingAdapter(DozeMachine.Service inner) {
+ mInner = inner;
+ }
+
+ @Override
+ public void finish() {
+ mInner.finish();
+ }
+
+ @Override
+ public void setDozeScreenState(int state) {
+ if (state == Display.STATE_DOZE_SUSPEND) {
+ state = Display.STATE_DOZE;
+ }
+ mInner.setDozeScreenState(state);
+ }
+
+ @Override
+ public void requestWakeUp() {
+ mInner.requestWakeUp();
+ }
+
+ /**
+ * If the device supports the doze display state, return {@code inner}. Otherwise
+ * return a new instance of {@link DozeSuspendScreenStatePreventingAdapter} wrapping {@code inner}.
+ */
+ public static DozeMachine.Service wrapIfNeeded(DozeMachine.Service inner,
+ DozeParameters params) {
+ return isNeeded(params) ? new DozeSuspendScreenStatePreventingAdapter(inner) : inner;
+ }
+
+ private static boolean isNeeded(DozeParameters params) {
+ return !params.getDozeSuspendDisplayStateSupported();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 563b8fe..693a690 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -99,9 +99,20 @@
}
private void onSensor(int pulseReason, boolean sensorPerformedProxCheck) {
- requestPulse(pulseReason, sensorPerformedProxCheck);
+ boolean isDoubleTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
+ boolean isPickup = pulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP;
- if (pulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP) {
+ if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
+ if (isDoubleTap) {
+ mMachine.wakeUp();
+ } else {
+ mDozeHost.extendPulse();
+ }
+ } else {
+ requestPulse(pulseReason, sensorPerformedProxCheck);
+ }
+
+ if (isPickup) {
final long timeSinceNotification =
SystemClock.elapsedRealtime() - mNotificationPulseTime;
final boolean withinVibrationThreshold =
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index ea33ebf..cf87fca 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -87,6 +87,7 @@
break;
case DOZE_PULSE_DONE:
mHost.abortPulsing();
+ break;
case INITIALIZED:
mHost.startDozing();
break;
@@ -129,6 +130,7 @@
}
verifyLastTimeTick();
mAlarmManager.cancel(mTimeTick);
+ mTimeTickScheduled = false;
}
private void verifyLastTimeTick() {
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
new file mode 100644
index 0000000..8809510
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.fragments;
+
+import android.app.Fragment;
+import android.util.Log;
+import android.view.View;
+
+import com.android.systemui.plugins.FragmentBase;
+import com.android.systemui.statusbar.policy.ExtensionController.Extension;
+
+import java.util.function.Consumer;
+
+/**
+ * Wires up an Extension to a Fragment tag/id so that it always contains the class
+ * selected by the extension.
+ */
+public class ExtensionFragmentListener<T extends FragmentBase> implements Consumer<T> {
+
+ private static final String TAG = "ExtensionFragmentListener";
+
+ private final FragmentHostManager mFragmentHostManager;
+ private final String mTag;
+ private final Extension<T> mExtension;
+ private String mOldClass;
+
+ private ExtensionFragmentListener(View view, String tag, int id, Extension<T> extension) {
+ mTag = tag;
+ mFragmentHostManager = FragmentHostManager.get(view);
+ mExtension = extension;
+ mFragmentHostManager.getFragmentManager().beginTransaction()
+ .replace(id, (Fragment) mExtension.get(), mTag)
+ .commit();
+ }
+
+ @Override
+ public void accept(T extension) {
+ try {
+ Fragment.class.cast(extension);
+ mFragmentHostManager.getExtensionManager().setCurrentExtension(mTag,
+ mOldClass, extension.getClass().getName(), mExtension.getContext());
+ mOldClass = extension.getClass().getName();
+ } catch (ClassCastException e) {
+ Log.e(TAG, extension.getClass().getName() + " must be a Fragment", e);
+ }
+ }
+
+ public static <T> void attachExtensonToFragment(View view, String tag, int id,
+ Extension<T> extension) {
+ extension.addCallback(new ExtensionFragmentListener(view, tag, id, extension));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index ee155d9..871f113 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -28,6 +28,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Parcelable;
+import android.support.annotation.NonNull;
import android.util.ArrayMap;
import android.view.LayoutInflater;
import android.view.View;
@@ -52,7 +53,7 @@
ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
| ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS);
private final FragmentService mManager;
- private final PluginFragmentManager mPlugins = new PluginFragmentManager();
+ private final ExtensionFragmentManager mPlugins = new ExtensionFragmentManager();
private FragmentController mFragments;
private FragmentLifecycleCallbacks mLifecycleCallbacks;
@@ -175,7 +176,7 @@
return mFragments.getFragmentManager();
}
- PluginFragmentManager getPluginManager() {
+ ExtensionFragmentManager getExtensionManager() {
return mPlugins;
}
@@ -262,22 +263,16 @@
}
}
- class PluginFragmentManager {
- private final ArrayMap<String, Context> mPluginLookup = new ArrayMap<>();
+ class ExtensionFragmentManager {
+ private final ArrayMap<String, Context> mExtensionLookup = new ArrayMap<>();
- public void removePlugin(String tag, String currentClass, String defaultClass) {
+ public void setCurrentExtension(@NonNull String tag, @Nullable String oldClass,
+ @NonNull String currentClass, @Nullable Context context) {
Fragment fragment = getFragmentManager().findFragmentByTag(tag);
- mPluginLookup.remove(currentClass);
- getFragmentManager().beginTransaction()
- .replace(((View) fragment.getView().getParent()).getId(),
- instantiate(mContext, defaultClass, null), tag)
- .commit();
- reloadFragments();
- }
-
- public void setCurrentPlugin(String tag, String currentClass, Context context) {
- Fragment fragment = getFragmentManager().findFragmentByTag(tag);
- mPluginLookup.put(currentClass, context);
+ if (oldClass != null) {
+ mExtensionLookup.remove(oldClass);
+ }
+ mExtensionLookup.put(currentClass, context);
getFragmentManager().beginTransaction()
.replace(((View) fragment.getView().getParent()).getId(),
instantiate(context, currentClass, null), tag)
@@ -293,11 +288,11 @@
}
Fragment instantiate(Context context, String className, Bundle arguments) {
- Context pluginContext = mPluginLookup.get(className);
- if (pluginContext != null) {
- Fragment f = Fragment.instantiate(pluginContext, className, arguments);
+ Context extensionContext = mExtensionLookup.get(className);
+ if (extensionContext != null) {
+ Fragment f = Fragment.instantiate(extensionContext, className, arguments);
if (f instanceof Plugin) {
- ((Plugin) f).onCreate(mContext, pluginContext);
+ ((Plugin) f).onCreate(mContext, extensionContext);
}
return f;
}
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
deleted file mode 100644
index 03bb73d..0000000
--- a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.fragments;
-
-import android.app.Fragment;
-import android.content.Context;
-import android.util.Log;
-import android.view.View;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.plugins.FragmentBase;
-import com.android.systemui.plugins.Plugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.PluginManager;
-
-public class PluginFragmentListener implements PluginListener<Plugin> {
-
- private static final String TAG = "PluginFragmentListener";
-
- private final FragmentHostManager mFragmentHostManager;
- private final PluginManager mPluginManager;
- private final Class<? extends Fragment> mDefaultClass;
- private final Class<? extends FragmentBase> mExpectedInterface;
- private final String mTag;
-
- public PluginFragmentListener(View view, String tag, Class<? extends Fragment> defaultFragment,
- Class<? extends FragmentBase> expectedInterface) {
- mTag = tag;
- mFragmentHostManager = FragmentHostManager.get(view);
- mPluginManager = Dependency.get(PluginManager.class);
- mExpectedInterface = expectedInterface;
- mDefaultClass = defaultFragment;
- }
-
- public void startListening() {
- mPluginManager.addPluginListener(this, mExpectedInterface,
- false /* Only allow one */);
- }
-
- public void stopListening() {
- mPluginManager.removePluginListener(this);
- }
-
- @Override
- public void onPluginConnected(Plugin plugin, Context pluginContext) {
- try {
- mExpectedInterface.cast(plugin);
- Fragment.class.cast(plugin);
- mFragmentHostManager.getPluginManager().setCurrentPlugin(mTag,
- plugin.getClass().getName(), pluginContext);
- } catch (ClassCastException e) {
- Log.e(TAG, plugin.getClass().getName() + " must be a Fragment and implement "
- + mExpectedInterface.getName(), e);
- }
- }
-
- @Override
- public void onPluginDisconnected(Plugin plugin) {
- mFragmentHostManager.getPluginManager().removePlugin(mTag,
- plugin.getClass().getName(), mDefaultClass.getName());
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 206342e..e8dcf6c 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -14,19 +14,26 @@
package com.android.systemui.globalactions;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
import com.android.internal.R;
-import com.android.internal.app.AlertController;
-import com.android.internal.app.AlertController.AlertParams;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.HardwareUiLayout;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.volume.VolumeDialogImpl;
+import com.android.systemui.volume.VolumeDialogMotion.LogAccelerateInterpolator;
+import com.android.systemui.volume.VolumeDialogMotion.LogDecelerateInterpolator;
import android.app.ActivityManager;
import android.app.Dialog;
+import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
@@ -34,11 +41,15 @@
import android.content.IntentFilter;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
+import android.database.DataSetObserver;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.os.Build;
-import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
@@ -56,21 +67,27 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
-import android.util.TypedValue;
-import android.view.KeyEvent;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
+import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
-import android.widget.ListView;
+import android.widget.LinearLayout;
import android.widget.TextView;
+import com.google.android.colorextraction.ColorExtractor;
+import com.google.android.colorextraction.ColorExtractor.GradientColors;
+import com.google.android.colorextraction.drawable.GradientDrawable;
+
import java.util.ArrayList;
import java.util.List;
@@ -79,7 +96,7 @@
* may show depending on whether the keyguard is showing, and whether the device
* is provisioned.
*/
-class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
+class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
static public final String SYSTEM_DIALOG_REASON_KEY = "reason";
static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
@@ -162,6 +179,7 @@
/**
* Show the global actions dialog (creating if necessary)
+ *
* @param keyguardShowing True if keyguard is showing
*/
public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
@@ -205,12 +223,12 @@
mDialog.getWindow().setAttributes(attrs);
mDialog.show();
mWindowManagerFuncs.onGlobalActionsShown();
- mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
}
}
/**
* Create the global actions dialog.
+ *
* @return A new dialog.
*/
private ActionsDialog createDialog() {
@@ -314,29 +332,20 @@
mAdapter = new MyAdapter();
- AlertParams params = new AlertParams(mContext);
- params.mAdapter = mAdapter;
- params.mOnClickListener = this;
- params.mForceInverseBackground = true;
-
- ActionsDialog dialog = new ActionsDialog(mContext, params);
+ OnItemLongClickListener onItemLongClickListener = new OnItemLongClickListener() {
+ @Override
+ public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
+ long id) {
+ final Action action = mAdapter.getItem(position);
+ if (action instanceof LongPressAction) {
+ return ((LongPressAction) action).onLongPress();
+ }
+ return false;
+ }
+ };
+ ActionsDialog dialog = new ActionsDialog(mContext, this, mAdapter, onItemLongClickListener);
dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
-
- dialog.getListView().setItemsCanFocus(true);
- dialog.getListView().setLongClickable(true);
- dialog.getListView().setOnItemLongClickListener(
- new AdapterView.OnItemLongClickListener() {
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
- long id) {
- final Action action = mAdapter.getItem(position);
- if (action instanceof LongPressAction) {
- return ((LongPressAction) action).onLongPress();
- }
- return false;
- }
- });
- dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ dialog.setKeyguardShowing(mKeyguardShowing);
dialog.setOnDismissListener(this);
@@ -346,7 +355,7 @@
private final class PowerAction extends SinglePressAction implements LongPressAction {
private PowerAction() {
super(R.drawable.ic_lock_power_off,
- R.string.global_action_power_off);
+ R.string.global_action_power_off);
}
@Override
@@ -614,7 +623,7 @@
SinglePressAction switchToUser = new SinglePressAction(
R.drawable.ic_menu_cc, icon,
(user.name != null ? user.name : "Primary")
- + (isCurrentUser ? " \u2714" : "")) {
+ + (isCurrentUser ? " \u2714" : "")) {
public void onPress() {
try {
ActivityManager.getService().switchUser(user.id);
@@ -641,7 +650,6 @@
refreshSilentMode();
mAirplaneModeOn.updateState(mAirplaneState);
mAdapter.notifyDataSetChanged();
- mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
if (mShowSilentToggle) {
IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
mContext.registerReceiver(mRingerModeReceiver, filter);
@@ -652,7 +660,7 @@
if (!mHasVibrator) {
final boolean silentModeOn =
mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
- ((ToggleAction)mSilentModeAction).updateState(
+ ((ToggleAction) mSilentModeAction).updateState(
silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off);
}
}
@@ -681,7 +689,8 @@
/**
* The adapter used for the list within the global actions dialog, taking
* into account whether the keyguard is showing via
- * {@link com.android.systemui.globalactions.GlobalActionsDialog#mKeyguardShowing} and whether the device is provisioned
+ * {@link com.android.systemui.globalactions.GlobalActionsDialog#mKeyguardShowing} and whether
+ * the device is provisioned
* via {@link com.android.systemui.globalactions.GlobalActionsDialog#mDeviceProvisioned}.
*/
private class MyAdapter extends BaseAdapter {
@@ -744,7 +753,11 @@
public View getView(int position, View convertView, ViewGroup parent) {
Action action = getItem(position);
- return action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
+ View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
+ if (position == 2) {
+ HardwareUiLayout.get(parent).setDivisionView(view);
+ }
+ return view;
}
}
@@ -760,7 +773,7 @@
private interface Action {
/**
* @return Text that will be announced when dialog is created. null
- * for none.
+ * for none.
*/
CharSequence getLabelForAccessibility(Context context);
@@ -770,13 +783,13 @@
/**
* @return whether this action should appear in the dialog when the keygaurd
- * is showing.
+ * is showing.
*/
boolean showDuringKeyguard();
/**
* @return whether this action should appear in the dialog before the
- * device is provisioned.
+ * device is provisioned.
*/
boolean showBeforeProvisioning();
@@ -834,7 +847,8 @@
public View create(
Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
- View v = inflater.inflate(R.layout.global_actions_item, parent, false);
+ View v = inflater.inflate(com.android.systemui.R.layout.global_actions_item, parent,
+ false);
ImageView icon = (ImageView) v.findViewById(R.id.icon);
TextView messageView = (TextView) v.findViewById(R.id.message);
@@ -895,10 +909,10 @@
protected int mDisabledStatusMessageResId;
/**
- * @param enabledIconResId The icon for when this action is on.
- * @param disabledIconResid The icon for when this action is off.
- * @param message The general information message, e.g 'Silent Mode'
- * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
+ * @param enabledIconResId The icon for when this action is on.
+ * @param disabledIconResid The icon for when this action is off.
+ * @param message The general information message, e.g 'Silent Mode'
+ * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
* @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
*/
public ToggleAction(int enabledIconResId,
@@ -931,7 +945,7 @@
willCreate();
View v = inflater.inflate(R
- .layout.global_actions_item, parent, false);
+ .layout.global_actions_item, parent, false);
ImageView icon = (ImageView) v.findViewById(R.id.icon);
TextView messageView = (TextView) v.findViewById(R.id.message);
@@ -979,6 +993,7 @@
* Implementations may override this if their state can be in on of the intermediate
* states until some notification is received (e.g airplane mode is 'turning off' until
* we know the wireless connections are back online
+ *
* @param buttonOn Whether the button was turned on or off
*/
protected void changeStateFromPress(boolean buttonOn) {
@@ -1020,7 +1035,7 @@
private static class SilentModeTriStateAction implements Action, View.OnClickListener {
- private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 };
+ private final int[] ITEM_IDS = {R.id.option1, R.id.option2, R.id.option3};
private final AudioManager mAudioManager;
private final Handler mHandler;
@@ -1145,19 +1160,19 @@
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
- case MESSAGE_DISMISS:
- if (mDialog != null) {
- mDialog.dismiss();
- mDialog = null;
- }
- break;
- case MESSAGE_REFRESH:
- refreshSilentMode();
- mAdapter.notifyDataSetChanged();
- break;
- case MESSAGE_SHOW:
- handleShow();
- break;
+ case MESSAGE_DISMISS:
+ if (mDialog != null) {
+ mDialog.dismiss();
+ mDialog = null;
+ }
+ break;
+ case MESSAGE_REFRESH:
+ refreshSilentMode();
+ mAdapter.notifyDataSetChanged();
+ break;
+ case MESSAGE_SHOW:
+ handleShow();
+ break;
}
}
};
@@ -1191,40 +1206,122 @@
}
}
- private static final class ActionsDialog extends Dialog implements DialogInterface {
- private final Context mContext;
- private final AlertController mAlert;
- private final MyAdapter mAdapter;
+ private static final class ActionsDialog extends Dialog implements DialogInterface,
+ ColorExtractor.OnColorsChangedListener {
- public ActionsDialog(Context context, AlertParams params) {
- super(context, getDialogTheme(context));
+ private final Context mContext;
+ private final MyAdapter mAdapter;
+ private final LinearLayout mListView;
+ private final HardwareUiLayout mHardwareLayout;
+ private final OnClickListener mClickListener;
+ private final OnItemLongClickListener mLongClickListener;
+ private final GradientDrawable mGradientDrawable;
+ private final ColorExtractor mColorExtractor;
+ private boolean mKeyguardShowing;
+
+ public ActionsDialog(Context context, OnClickListener clickListener, MyAdapter adapter,
+ OnItemLongClickListener longClickListener) {
+ super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
mContext = getContext();
- mAlert = AlertController.create(mContext, this, getWindow());
- mAdapter = (MyAdapter) params.mAdapter;
- params.apply(mAlert);
+ mAdapter = adapter;
+ mClickListener = clickListener;
+ mLongClickListener = longClickListener;
+ mGradientDrawable = new GradientDrawable(mContext);
+ mColorExtractor = Dependency.get(ColorExtractor.class);
+
+ // Window initialization
+ Window window = getWindow();
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
+ window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ window.setBackgroundDrawable(mGradientDrawable);
+ window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+
+ setContentView(com.android.systemui.R.layout.global_actions_wrapped);
+ mListView = findViewById(android.R.id.list);
+ mHardwareLayout = HardwareUiLayout.get(mListView);
+ mHardwareLayout.setOutsideTouchListener(view -> dismiss());
}
- private static int getDialogTheme(Context context) {
- TypedValue outValue = new TypedValue();
- context.getTheme().resolveAttribute(R.attr.alertDialogTheme,
- outValue, true);
- return outValue.resourceId;
+ private void updateList() {
+ mListView.removeAllViews();
+ for (int i = 0; i < mAdapter.getCount(); i++) {
+ View v = mAdapter.getView(i, null, mListView);
+ final int pos = i;
+ v.setOnClickListener(view -> mClickListener.onClick(this, pos));
+ v.setOnLongClickListener(view ->
+ mLongClickListener.onItemLongClick(null, v, pos, 0));
+ mListView.addView(v);
+ }
}
@Override
protected void onStart() {
super.setCanceledOnTouchOutside(true);
super.onStart();
- }
+ updateList();
- public ListView getListView() {
- return mAlert.getListView();
+ Point displaySize = new Point();
+ mContext.getDisplay().getRealSize(displaySize);
+ mColorExtractor.addOnColorsChangedListener(this);
+ mGradientDrawable.setScreenSize(displaySize.x, displaySize.y);
+ GradientColors colors = mColorExtractor.getColors(mKeyguardShowing ?
+ WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM);
+ mGradientDrawable.setColors(colors, false);
}
@Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mAlert.installContent();
+ protected void onStop() {
+ super.onStop();
+ mColorExtractor.removeOnColorsChangedListener(this);
+ }
+
+ @Override
+ public void show() {
+ super.show();
+ mGradientDrawable.setAlpha(0);
+ mHardwareLayout.setTranslationX(getAnimTranslation());
+ mHardwareLayout.setAlpha(0);
+ mHardwareLayout.animate()
+ .alpha(1)
+ .translationX(0)
+ .setDuration(300)
+ .setInterpolator(new LogDecelerateInterpolator())
+ .setUpdateListener(animation -> {
+ int alpha = (int) ((Float) animation.getAnimatedValue()
+ * ScrimController.GRADIENT_SCRIM_ALPHA * 255);
+ mGradientDrawable.setAlpha(alpha);
+ })
+ .start();
+ }
+
+ @Override
+ public void dismiss() {
+ mHardwareLayout.setTranslationX(0);
+ mHardwareLayout.setAlpha(1);
+ mHardwareLayout.animate()
+ .alpha(0)
+ .translationX(getAnimTranslation())
+ .setDuration(300)
+ .withEndAction(() -> super.dismiss())
+ .setInterpolator(new LogAccelerateInterpolator())
+ .setUpdateListener(animation -> {
+ int alpha = (int) ((1f - (Float) animation.getAnimatedValue())
+ * ScrimController.GRADIENT_SCRIM_ALPHA * 255);
+ mGradientDrawable.setAlpha(alpha);
+ })
+ .start();
+ }
+
+ private float getAnimTranslation() {
+ return getContext().getResources().getDimension(
+ com.android.systemui.R.dimen.global_actions_panel_width) / 2;
}
@Override
@@ -1242,19 +1339,20 @@
}
@Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (mAlert.onKeyDown(keyCode, event)) {
- return true;
+ public void onColorsChanged(ColorExtractor extractor, int which) {
+ if (mKeyguardShowing) {
+ if ((WallpaperManager.FLAG_LOCK & which) != 0) {
+ mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_LOCK));
+ }
+ } else {
+ if ((WallpaperManager.FLAG_SYSTEM & which) != 0) {
+ mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM));
+ }
}
- return super.onKeyDown(keyCode, event);
}
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (mAlert.onKeyUp(keyCode, event)) {
- return true;
- }
- return super.onKeyUp(keyCode, event);
+ public void setKeyguardShowing(boolean keyguardShowing) {
+ mKeyguardShowing = keyguardShowing;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index f745956..b8de9f3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -364,7 +364,10 @@
public void onUserSwitchComplete(int userId) {
if (userId != UserHandle.USER_SYSTEM) {
UserInfo info = UserManager.get(mContext).getUserInfo(userId);
- if (info != null && (info.isGuest() || info.isDemo())) {
+ // Don't try to dismiss if the user has Pin/Patter/Password set
+ if (info == null || mLockPatternUtils.isSecure(userId)) {
+ return;
+ } else if (info.isGuest() || info.isDemo()) {
// If we just switched to a guest, try to dismiss keyguard.
dismiss(null /* callback */);
}
@@ -1173,6 +1176,7 @@
if (mOccluded != isOccluded) {
mOccluded = isOccluded;
+ mUpdateMonitor.setKeyguardOccluded(isOccluded);
mStatusBarKeyguardViewManager.setOccluded(isOccluded, animate
&& mDeviceInteractive);
adjustStatusBarLocked();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 854696e..ddaeb04 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -376,10 +376,7 @@
if (!mSendingHoverAccessibilityEvents) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
- AccessibilityNodeInfo info =
- PipAccessibilityInteractionConnection.obtainRootAccessibilityNodeInfo();
- event.setSource(info);
- info.recycle();
+ event.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID);
mAccessibilityManager.sendAccessibilityEvent(event);
mSendingHoverAccessibilityEvents = true;
}
@@ -389,10 +386,7 @@
if (mSendingHoverAccessibilityEvents) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
- AccessibilityNodeInfo info =
- PipAccessibilityInteractionConnection.obtainRootAccessibilityNodeInfo();
- event.setSource(info);
- info.recycle();
+ event.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID);
mAccessibilityManager.sendAccessibilityEvent(event);
mSendingHoverAccessibilityEvents = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
index 7fe28c3..eaf715f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
@@ -15,39 +15,56 @@
package com.android.systemui.qs;
import android.content.Context;
-import android.content.res.ColorStateList;
-import android.widget.FrameLayout;
-import android.widget.FrameLayout.LayoutParams;
+import android.graphics.drawable.Drawable;
import android.widget.ImageView;
import com.android.settingslib.Utils;
import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QSTile.Icon;
import com.android.systemui.plugins.qs.QSTile.State;
-import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon;
+import com.android.systemui.statusbar.phone.SignalDrawable;
+
+import java.util.Objects;
// Exists to provide easy way to add sim icon to cell tile
// TODO Find a better way to handle this and remove it.
public class CellTileView extends SignalTileView {
- private final ImageView mOverlay;
+ private final SignalDrawable mSignalDrawable;
public CellTileView(Context context) {
super(context);
- mOverlay = new ImageView(mContext);
- mOverlay.setImageTintList(ColorStateList.valueOf(Utils.getColorAttr(context,
- android.R.attr.colorPrimary)));
- FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT);
- int padding = context.getResources().getDimensionPixelOffset(R.dimen.cell_overlay_padding);
- params.leftMargin = params.rightMargin = padding;
- mIconFrame.addView(mOverlay, params);
+ mSignalDrawable = new SignalDrawable(mContext);
+ float dark = Utils.getColorAttr(context, android.R.attr.colorForeground) == 0xff000000
+ ? 1 : 0;
+ mSignalDrawable.setDarkIntensity(dark);
+ mSignalDrawable.setIntrinsicSize(context.getResources().getDimensionPixelSize(
+ R.dimen.qs_tile_icon_size));
}
- @Override
- public void setIcon(State state) {
- State s = state.copy();
- updateIcon(mOverlay, state);
- s.icon = ResourceIcon.get(R.drawable.ic_sim);
- super.setIcon(s);
+ protected void updateIcon(ImageView iv, State state) {
+ if (!Objects.equals(state.icon, iv.getTag(R.id.qs_icon_tag))) {
+ mSignalDrawable.setLevel(((SignalIcon) state.icon).getState());
+ iv.setImageDrawable(mSignalDrawable);
+ iv.setTag(R.id.qs_icon_tag, state.icon);
+ }
+ }
+
+ public static class SignalIcon extends Icon {
+
+ private final int mState;
+
+ public SignalIcon(int state) {
+ mState = state;
+ }
+
+ public int getState() {
+ return mState;
+ }
+
+ @Override
+ public Drawable getDrawable(Context context) {
+ return null;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 0d74b7a..ed57c04 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -39,8 +39,6 @@
protected float mQsExpansion;
private QSCustomizer mQSCustomizer;
private QSFooter mQSFooter;
- private int mGutterHeight;
- private View mBackground;
private float mFullElevation;
public QSContainerImpl(Context context, AttributeSet attrs) {
@@ -55,8 +53,6 @@
mHeader = findViewById(R.id.header);
mQSCustomizer = findViewById(R.id.qs_customize);
mQSFooter = findViewById(R.id.qs_footer);
- mBackground = findViewById(R.id.qs_background);
- mGutterHeight = getContext().getResources().getDimensionPixelSize(R.dimen.qs_gutter_height);
mFullElevation = mQSPanel.getElevation();
setClickable(true);
@@ -110,18 +106,10 @@
public void updateExpansion() {
int height = calculateContainerHeight();
- int gutterHeight = Math.round(mQsExpansion * mGutterHeight);
- setBottom(getTop() + height + gutterHeight);
+ setBottom(getTop() + height);
mQSDetail.setBottom(getTop() + height);
- mBackground.setBottom(getTop() + height);
// Pin QS Footer to the bottom of the panel.
mQSFooter.setTranslationY(height - mQSFooter.getHeight());
-
- float elevation = mQsExpansion * mFullElevation;
- mQSDetail.setElevation(elevation);
- mBackground.setElevation(elevation);
- mQSFooter.setElevation(elevation);
- mQSPanel.setElevation(elevation);
}
protected int calculateContainerHeight() {
@@ -135,17 +123,4 @@
mQsExpansion = expansion;
updateExpansion();
}
-
- public void setGutterEnabled(boolean gutterEnabled) {
- if (gutterEnabled == (mGutterHeight != 0)) {
- return;
- }
- if (gutterEnabled) {
- mGutterHeight = getContext().getResources().getDimensionPixelSize(
- R.dimen.qs_gutter_height);
- } else {
- mGutterHeight = 0;
- }
- updateExpansion();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index c9c3a7f..3f00276 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -63,7 +63,6 @@
private QSContainerImpl mContainer;
private int mLayoutDirection;
private QSFooter mFooter;
- private int mGutterHeight;
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@@ -80,7 +79,6 @@
mHeader = view.findViewById(R.id.header);
mFooter = view.findViewById(R.id.qs_footer);
mContainer = view.findViewById(id.quick_settings_container);
- mGutterHeight = getContext().getResources().getDimensionPixelSize(R.dimen.qs_gutter_height);
mQSDetail.setQsPanel(mQSPanel, mHeader);
@@ -131,7 +129,6 @@
@Override
public void setHasNotifications(boolean hasNotifications) {
- mContainer.setGutterEnabled(hasNotifications);
}
public void setPanelView(HeightListener panelView) {
@@ -250,7 +247,7 @@
mContainer.setExpansion(expansion);
final float translationScaleY = expansion - 1;
if (!mHeaderAnimating) {
- int height = mHeader.getHeight() + mGutterHeight;
+ int height = mHeader.getHeight();
getView().setTranslationY(mKeyguardShowing ? (translationScaleY * height)
: headerTranslation);
}
@@ -333,15 +330,15 @@
LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams();
int panelHeight = layoutParams.topMargin + layoutParams.bottomMargin +
+ mQSPanel.getMeasuredHeight();
- return panelHeight + getView().getPaddingBottom() + mGutterHeight;
+ return panelHeight + getView().getPaddingBottom();
} else {
- return getView().getMeasuredHeight() + mGutterHeight;
+ return getView().getMeasuredHeight();
}
}
@Override
public void setHeightOverride(int desiredHeight) {
- mContainer.setHeightOverride(desiredHeight - mGutterHeight);
+ mContainer.setHeightOverride(desiredHeight);
}
public int getQsMinExpansionHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index abafd64..7ec0760 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -23,11 +23,13 @@
import android.util.AttributeSet;
import android.view.View;
import android.widget.RelativeLayout;
+import android.widget.TextClock;
import com.android.settingslib.Utils;
import com.android.systemui.BatteryMeterView;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.R.id;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSDetail.Callback;
import com.android.systemui.statusbar.SignalClusterView;
@@ -69,15 +71,11 @@
float intensity = colorForeground == Color.WHITE ? 0 : 1;
Rect tintArea = new Rect(0, 0, 0, 0);
- applyDarkness(R.id.signal_cluster, tintArea, intensity, colorForeground);
applyDarkness(R.id.battery, tintArea, intensity, colorForeground);
applyDarkness(R.id.clock, tintArea, intensity, colorForeground);
BatteryMeterView battery = findViewById(R.id.battery);
battery.setForceShowPercent(true);
- // Don't show the Wi-Fi indicator here, because it is shown just below in the tile.
- SignalClusterView signalCluster = findViewById(R.id.signal_cluster);
- signalCluster.setQsSignalCluster();
mActivityStarter = Dependency.get(ActivityStarter.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
index 01f8169..b300e4a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
@@ -27,6 +27,7 @@
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.SignalState;
import com.android.systemui.qs.tileimpl.QSIconViewImpl;
+import com.android.systemui.qs.tileimpl.SlashImageView;
/** View that represents a custom quick settings tile for displaying signal info (wifi/cell). **/
public class SignalTileView extends QSIconViewImpl {
@@ -35,6 +36,7 @@
protected FrameLayout mIconFrame;
protected ImageView mSignal;
+ private ImageView mOverlay;
private ImageView mIn;
private ImageView mOut;
@@ -61,8 +63,10 @@
@Override
protected View createIcon() {
mIconFrame = new FrameLayout(mContext);
- mSignal = new ImageView(mContext);
+ mSignal = new SlashImageView(mContext);
mIconFrame.addView(mSignal);
+ mOverlay = new ImageView(mContext);
+ mIconFrame.addView(mOverlay, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
return mIconFrame;
}
@@ -108,7 +112,18 @@
public void setIcon(QSTile.State state) {
final SignalState s = (SignalState) state;
setIcon(mSignal, s);
- Drawable drawable = mSignal.getDrawable();
+
+ if (s.overlayIconId > 0) {
+ mOverlay.setVisibility(VISIBLE);
+ mOverlay.setImageResource(s.overlayIconId);
+ } else {
+ mOverlay.setVisibility(GONE);
+ }
+ if (s.overlayIconId > 0 && s.isOverlayIconWide) {
+ mSignal.setPaddingRelative(mWideOverlayIconStartPadding, 0, 0, 0);
+ } else {
+ mSignal.setPaddingRelative(0, 0, 0, 0);
+ }
final boolean shown = isShown();
setVisibility(mIn, shown, s.activityIn);
setVisibility(mOut, shown, s.activityOut);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SlashDrawable.java b/packages/SystemUI/src/com/android/systemui/qs/SlashDrawable.java
new file mode 100644
index 0000000..06adbbf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/SlashDrawable.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import android.annotation.ColorInt;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+public class SlashDrawable extends Drawable {
+
+ private final Path mPath = new Path();
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+ private static float[] OFFSET = {
+ 1.3f / 24f,-1.3f / 24f
+ };
+ private static float[][] PATH = {
+ {21.9f / 24f, 21.9f / 24f},
+ {2.1f / 24f, 2.1f / 24f},
+ {0.8f / 24f, 3.4f / 24f},
+ {20.6f / 24f, 23.2f / 24f},
+ };
+ private Drawable mDrawable;
+ private float mRotation;
+ private boolean mSlashed;
+ private Mode mTintMode;
+ private ColorStateList mTintList;
+
+ public SlashDrawable(Drawable d) {
+ mDrawable = d;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mDrawable != null ? mDrawable.getIntrinsicHeight(): 0;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mDrawable != null ? mDrawable.getIntrinsicWidth(): 0;
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ mDrawable.setBounds(bounds);
+ }
+
+ public void setDrawable(Drawable d) {
+ mDrawable = d;
+ mDrawable.setCallback(getCallback());
+ mDrawable.setBounds(getBounds());
+ if (mTintMode != null) mDrawable.setTintMode(mTintMode);
+ if (mTintList != null) mDrawable.setTintList(mTintList);
+ invalidateSelf();
+ }
+
+ public void setRotation(float rotation) {
+ if (mRotation == rotation) return;
+ mRotation = rotation;
+ invalidateSelf();
+ }
+
+ public void setSlashed(boolean slashed) {
+ if (mSlashed == slashed) return;
+ // TODO: Animate.
+ mSlashed = slashed;
+ invalidateSelf();
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas) {
+ canvas.save();
+ if (mSlashed) {
+ Matrix m = new Matrix();
+ int width = getBounds().width();
+ int height = getBounds().height();
+ mPath.reset();
+ mPath.moveTo(scale(PATH[0][0], width), scale(PATH[0][1], height));
+ mPath.lineTo(scale(PATH[1][0], width), scale(PATH[1][1], height));
+ mPath.lineTo(scale(PATH[2][0], width), scale(PATH[2][1], height));
+ mPath.lineTo(scale(PATH[3][0], width), scale(PATH[3][1], height));
+ mPath.close();
+ m = new Matrix();
+ m.setRotate(mRotation, width / 2, height / 2);
+ mPath.transform(m);
+ canvas.drawPath(mPath, mPaint);
+ m = new Matrix();
+ m.setRotate(-mRotation, width / 2, height / 2);
+ mPath.transform(m);
+
+ m = new Matrix();
+ m.setTranslate(scale(OFFSET[0], width), scale(OFFSET[1], height));
+ mPath.transform(m);
+ mPath.moveTo(scale(PATH[0][0], width), scale(PATH[0][1], height));
+ mPath.lineTo(scale(PATH[1][0], width), scale(PATH[1][1], height));
+ mPath.lineTo(scale(PATH[2][0], width), scale(PATH[2][1], height));
+ mPath.lineTo(scale(PATH[3][0], width), scale(PATH[3][1], height));
+ mPath.close();
+ m = new Matrix();
+ m.setRotate(mRotation, width / 2, height / 2);
+ mPath.transform(m);
+ canvas.clipOutPath(mPath);
+ }
+
+ mDrawable.draw(canvas);
+ canvas.restore();
+ }
+
+ private float scale(float frac, int width) {
+ return frac * width;
+ }
+
+ @Override
+ public void setTint(@ColorInt int tintColor) {
+ super.setTint(tintColor);
+ mDrawable.setTint(tintColor);
+ mPaint.setColor(tintColor);
+ }
+
+ @Override
+ public void setTintList(@Nullable ColorStateList tint) {
+ mTintList = tint;
+ super.setTintList(tint);
+ mDrawable.setTintList(tint);
+ mPaint.setColor(tint.getDefaultColor());
+ invalidateSelf();
+ }
+
+ @Override
+ public void setTintMode(@NonNull Mode tintMode) {
+ mTintMode = tintMode;
+ super.setTintMode(tintMode);
+ mDrawable.setTintMode(tintMode);
+ }
+
+ @Override
+ public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
+ mDrawable.setAlpha(alpha);
+ mPaint.setAlpha(alpha);
+ }
+
+ @Override
+ public void setColorFilter(@Nullable ColorFilter colorFilter) {
+ mDrawable.setColorFilter(colorFilter);
+ mPaint.setColorFilter(colorFilter);
+ }
+
+ @Override
+ public int getOpacity() {
+ return 255;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 661c921..b39fc11 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -21,7 +21,6 @@
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
-import android.graphics.drawable.Animatable;
import android.graphics.drawable.Animatable2;
import android.graphics.drawable.Animatable2.AnimationCallback;
import android.graphics.drawable.Drawable;
@@ -85,7 +84,8 @@
}
protected void updateIcon(ImageView iv, State state) {
- if (!Objects.equals(state.icon, iv.getTag(R.id.qs_icon_tag))) {
+ if (!Objects.equals(state.icon, iv.getTag(R.id.qs_icon_tag))
+ || !Objects.equals(state.slash, iv.getTag(R.id.qs_slash_tag))) {
boolean shouldAnimate = iv.isShown() && mAnimationEnabled
&& iv.getDrawable() != null;
Drawable d = state.icon != null
@@ -96,7 +96,11 @@
d.setAutoMirrored(false);
}
iv.setImageDrawable(d);
+ if (state.slash != null && iv instanceof SlashImageView) {
+ ((SlashImageView) iv).setState(state.slash);
+ }
iv.setTag(R.id.qs_icon_tag, state.icon);
+ iv.setTag(R.id.qs_slash_tag, state.slash);
iv.setPadding(0, padding, 0, padding);
if (d instanceof Animatable2) {
Animatable2 a = (Animatable2) d;
@@ -166,7 +170,7 @@
}
protected View createIcon() {
- final ImageView icon = new ImageView(mContext);
+ final ImageView icon = new SlashImageView(mContext);
icon.setId(android.R.id.icon);
icon.setScaleType(ScaleType.FIT_CENTER);
return icon;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index d12b16b..672f2c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -317,7 +317,9 @@
protected abstract void setListening(boolean listening);
protected void handleDestroy() {
- setListening(false);
+ if (mListeners.size() != 0) {
+ setListening(false);
+ }
mCallbacks.clear();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java
new file mode 100644
index 0000000..603f66b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.widget.ImageView;
+
+import com.android.systemui.plugins.qs.QSTile.SlashState;
+import com.android.systemui.qs.SlashDrawable;
+
+public class SlashImageView extends ImageView {
+
+ private SlashDrawable mSlash;
+
+ public SlashImageView(Context context) {
+ super(context);
+ }
+
+ private void ensureSlashDrawable() {
+ if (mSlash == null) {
+ mSlash = new SlashDrawable(getDrawable());
+ super.setImageDrawable(mSlash);
+ }
+ }
+
+ @Override
+ public void setImageDrawable(Drawable drawable) {
+ if (mSlash != null) {
+ mSlash.setDrawable(drawable);
+ } else {
+ super.setImageDrawable(drawable);
+ }
+ }
+
+ public void setState(SlashState slashState) {
+ ensureSlashDrawable();
+ mSlash.setRotation(slashState.rotation);
+ mSlash.setSlashed(slashState.isSlashed);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index c5f798e..2e7012e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -29,19 +29,15 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.qs.GlobalSetting;
import com.android.systemui.qs.QSHost;
-import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.qs.tileimpl.QSTileImpl;
/** Quick settings tile: Airplane mode **/
public class AirplaneModeTile extends QSTileImpl<BooleanState> {
- private final AnimationIcon mEnable =
- new AnimationIcon(R.drawable.ic_signal_airplane_enable_animation,
- R.drawable.ic_signal_airplane_disable);
- private final AnimationIcon mDisable =
- new AnimationIcon(R.drawable.ic_signal_airplane_disable_animation,
- R.drawable.ic_signal_airplane_enable);
+ private final Icon mIcon =
+ ResourceIcon.get(R.drawable.ic_signal_airplane);
private final GlobalSetting mSetting;
private boolean mListening;
@@ -90,11 +86,11 @@
final boolean airplaneMode = value != 0;
state.value = airplaneMode;
state.label = mContext.getString(R.string.airplane_mode);
- if (airplaneMode) {
- state.icon = mEnable;
- } else {
- state.icon = mDisable;
+ state.icon = mIcon;
+ if (state.slash == null) {
+ state.slash = new SlashState();
}
+ state.slash.isSlashed = !airplaneMode;
state.state = airplaneMode ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.contentDescription = state.label;
state.expandedAccessibilityClassName = Switch.class.getName();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index 74cc0ec..918c00c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -15,32 +15,12 @@
*/
package com.android.systemui.qs.tiles;
-import android.content.BroadcastReceiver;
-import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.service.quicksettings.Tile;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.style.RelativeSizeSpan;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnAttachStateChangeListener;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.Checkable;
-import android.widget.ImageView;
import android.widget.Switch;
-import android.widget.TextView;
-
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settingslib.BatteryInfo;
-import com.android.settingslib.graph.BatteryMeterDrawableBase;
-import com.android.settingslib.graph.UsageView;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -50,12 +30,10 @@
BatteryController.BatteryStateChangeCallback {
private final BatteryController mBatteryController;
- private final BatteryDetail mBatteryDetail = new BatteryDetail();
private int mLevel;
private boolean mPowerSave;
private boolean mCharging;
- private boolean mDetailShown;
private boolean mPluggedIn;
public BatterySaverTile(QSHost host) {
@@ -83,14 +61,6 @@
}
@Override
- public void setDetailListening(boolean listening) {
- super.setDetailListening(listening);
- if (!listening) {
- mBatteryDetail.mCurrentView = null;
- }
- }
-
- @Override
public Intent getLongClickIntent() {
return new Intent(Intent.ACTION_POWER_USAGE_SUMMARY);
}
@@ -122,160 +92,11 @@
mPluggedIn = pluggedIn;
mCharging = charging;
refreshState(level);
- if (mDetailShown) {
- mBatteryDetail.postBindView();
- }
}
@Override
public void onPowerSaveChanged(boolean isPowerSave) {
mPowerSave = isPowerSave;
refreshState(null);
- if (mDetailShown) {
- mBatteryDetail.postBindView();
- }
}
-
- private final class BatteryDetail implements DetailAdapter, OnClickListener,
- OnAttachStateChangeListener {
- private final BatteryMeterDrawableBase mDrawable
- = new BatteryMeterDrawableBase(
- mHost.getContext(),
- mHost.getContext().getColor(R.color.meter_background_color));
- private View mCurrentView;
-
- @Override
- public CharSequence getTitle() {
- return mContext.getString(R.string.battery_panel_title, mLevel);
- }
-
- @Override
- public Boolean getToggleState() {
- return null;
- }
-
- @Override
- public View createDetailView(Context context, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = LayoutInflater.from(mContext).inflate(R.layout.battery_detail, parent,
- false);
- }
- mCurrentView = convertView;
- mCurrentView.addOnAttachStateChangeListener(this);
- bindView();
- return convertView;
- }
-
- private void postBindView() {
- if (mCurrentView == null) return;
- mCurrentView.post(new Runnable() {
- @Override
- public void run() {
- bindView();
- }
- });
- }
-
- private void bindView() {
- if (mCurrentView == null) {
- return;
- }
- mDrawable.setBatteryLevel(100);
- mDrawable.setCharging(false);
- mDrawable.setPowerSave(true);
- mDrawable.setShowPercent(false);
- ((ImageView) mCurrentView.findViewById(android.R.id.icon)).setImageDrawable(mDrawable);
- Checkable checkbox = (Checkable) mCurrentView.findViewById(android.R.id.toggle);
- checkbox.setChecked(mPowerSave);
- BatteryInfo.getBatteryInfo(mContext, new BatteryInfo.Callback() {
- @Override
- public void onBatteryInfoLoaded(BatteryInfo info) {
- if (mCurrentView != null) {
- bindBatteryInfo(info);
- }
- }
- });
- final TextView batterySaverTitle =
- (TextView) mCurrentView.findViewById(android.R.id.title);
- final TextView batterySaverSummary =
- (TextView) mCurrentView.findViewById(android.R.id.summary);
- if (mCharging) {
- mCurrentView.findViewById(R.id.switch_container).setAlpha(.7f);
- batterySaverTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
- batterySaverTitle.setText(R.string.battery_detail_charging_summary);
- mCurrentView.findViewById(android.R.id.toggle).setVisibility(View.GONE);
- mCurrentView.findViewById(R.id.switch_container).setClickable(false);
- } else {
- mCurrentView.findViewById(R.id.switch_container).setAlpha(1);
- batterySaverTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
- batterySaverTitle.setText(R.string.battery_detail_switch_title);
- batterySaverSummary.setText(R.string.battery_detail_switch_summary);
- mCurrentView.findViewById(android.R.id.toggle).setVisibility(View.VISIBLE);
- mCurrentView.findViewById(R.id.switch_container).setClickable(true);
- mCurrentView.findViewById(R.id.switch_container).setOnClickListener(this);
- }
- }
-
- private void bindBatteryInfo(BatteryInfo info) {
- SpannableStringBuilder builder = new SpannableStringBuilder();
- builder.append(info.batteryPercentString, new RelativeSizeSpan(2.6f),
- Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- if (info.remainingLabel != null) {
- if (mContext.getResources().getBoolean(R.bool.quick_settings_wide)) {
- builder.append(' ');
- } else {
- builder.append('\n');
- }
- builder.append(info.remainingLabel);
- }
- ((TextView) mCurrentView.findViewById(R.id.charge_and_estimation)).setText(builder);
-
- info.bindHistory((UsageView) mCurrentView.findViewById(R.id.battery_usage));
- }
-
- @Override
- public void onClick(View v) {
- mBatteryController.setPowerSaveMode(!mPowerSave);
- }
-
- @Override
- public Intent getSettingsIntent() {
- return new Intent(Intent.ACTION_POWER_USAGE_SUMMARY);
- }
-
- @Override
- public void setToggleState(boolean state) {
- // No toggle state.
- }
-
- @Override
- public int getMetricsCategory() {
- return MetricsEvent.QS_BATTERY_DETAIL;
- }
-
- @Override
- public void onViewAttachedToWindow(View v) {
- if (!mDetailShown) {
- mDetailShown = true;
- v.getContext().registerReceiver(mReceiver,
- new IntentFilter(Intent.ACTION_TIME_TICK), null,
- Dependency.get(Dependency.TIME_TICK_HANDLER));
- }
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- if (mDetailShown) {
- mDetailShown = false;
- v.getContext().unregisterReceiver(mReceiver);
- }
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- postBindView();
- }
- };
- }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 9b20a7a..703be27 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -114,6 +114,10 @@
|| mController.getBluetoothState() == BluetoothAdapter.STATE_TURNING_ON;
state.dualTarget = true;
state.value = enabled;
+ if (state.slash == null) {
+ state.slash = new SlashState();
+ }
+ state.slash.isSlashed = !enabled;
if (enabled) {
state.label = null;
if (connected) {
@@ -137,7 +141,7 @@
}
state.state = Tile.STATE_ACTIVE;
} else {
- state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_off);
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on);
state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_bluetooth_off);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 92ff17a1..e6c3520 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -35,6 +35,8 @@
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile.SignalState;
+import com.android.systemui.qs.CellTileView;
+import com.android.systemui.qs.CellTileView.SignalIcon;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.SignalTileView;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -83,7 +85,7 @@
@Override
public QSIconView createTileView(Context context) {
- return new SignalTileView(context);
+ return new CellTileView(context);
}
@Override
@@ -120,23 +122,26 @@
final Resources r = mContext.getResources();
state.activityIn = cb.enabled && cb.activityIn;
state.activityOut = cb.enabled && cb.activityOut;
+ state.isOverlayIconWide = cb.isDataTypeIconWide;
+ state.overlayIconId = cb.dataTypeIconId;
state.label = r.getString(R.string.mobile_data);
- state.contentDescription = state.label;
+ final String signalContentDesc = cb.enabled && (cb.mobileSignalIconId > 0)
+ ? cb.signalContentDescription
+ : r.getString(R.string.accessibility_no_signal);
+ if (cb.noSim) {
+ state.contentDescription = state.label;
+ } else {
+ state.contentDescription = signalContentDesc + ", " + state.label;
+ }
+
state.expandedAccessibilityClassName = Switch.class.getName();
state.value = mDataController.isMobileDataSupported()
&& mDataController.isMobileDataEnabled();
- state.icon = ResourceIcon.get(R.drawable.ic_data_unavailable);
- state.state = cb.airplaneModeEnabled || !cb.enabled ? Tile.STATE_UNAVAILABLE
- : state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
- if (state.state == Tile.STATE_ACTIVE) {
- state.icon = ResourceIcon.get(R.drawable.ic_data_on);
- } else if (state.state == Tile.STATE_INACTIVE) {
- state.icon = ResourceIcon.get(R.drawable.ic_data_off);
- } else {
- state.icon = ResourceIcon.get(R.drawable.ic_data_unavailable);
- }
+
+ state.icon = new SignalIcon(cb.mobileSignalIconId);
+ state.state = Tile.STATE_ACTIVE;
}
@Override
@@ -163,6 +168,7 @@
boolean enabled;
boolean wifiEnabled;
boolean airplaneModeEnabled;
+ int mobileSignalIconId;
String signalContentDescription;
int dataTypeIconId;
String dataContentDescription;
@@ -192,6 +198,7 @@
return;
}
mInfo.enabled = qsIcon.visible;
+ mInfo.mobileSignalIconId = qsIcon.icon;
mInfo.signalContentDescription = qsIcon.contentDescription;
mInfo.dataTypeIconId = qsType;
mInfo.dataContentDescription = typeContentDescription;
@@ -208,6 +215,7 @@
mInfo.noSim = show;
if (mInfo.noSim) {
// Make sure signal gets cleared out when no sims.
+ mInfo.mobileSignalIconId = 0;
mInfo.dataTypeIconId = 0;
// Show a No SIMs description to avoid emergency calls message.
mInfo.enabled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 5b374b1..40fe484 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -25,6 +25,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
+import com.android.systemui.R.drawable;
import com.android.systemui.qs.QSHost;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -33,12 +34,7 @@
/** Quick settings tile: Invert colors **/
public class ColorInversionTile extends QSTileImpl<BooleanState> {
- private final AnimationIcon mEnable
- = new AnimationIcon(R.drawable.ic_invert_colors_enable_animation,
- R.drawable.ic_invert_colors_disable);
- private final AnimationIcon mDisable
- = new AnimationIcon(R.drawable.ic_invert_colors_disable_animation,
- R.drawable.ic_invert_colors_enable);
+ private final Icon mIcon = ResourceIcon.get(drawable.ic_invert_colors);
private final SecureSetting mSetting;
private boolean mListening;
@@ -96,10 +92,14 @@
protected void handleUpdateState(BooleanState state, Object arg) {
final int value = arg instanceof Integer ? (Integer) arg : mSetting.getValue();
final boolean enabled = value != 0;
+ if (state.slash == null) {
+ state.slash = new SlashState();
+ }
state.value = enabled;
+ state.slash.isSlashed = !state.value;
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.label = mContext.getString(R.string.quick_settings_inversion_label);
- state.icon = enabled ? mEnable : mDisable;
+ state.icon = mIcon;
state.expandedAccessibilityClassName = Switch.class.getName();
state.contentDescription = state.label;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index ecbc4f7..6c31cef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -27,7 +27,6 @@
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.net.Uri;
import android.os.UserManager;
import android.provider.Settings;
import android.provider.Settings.Global;
@@ -72,13 +71,6 @@
private static final QSTile.Icon TOTAL_SILENCE =
ResourceIcon.get(R.drawable.ic_qs_dnd_on_total_silence);
- private final AnimationIcon mDisable =
- new AnimationIcon(R.drawable.ic_dnd_disable_animation,
- R.drawable.ic_qs_dnd_off);
- private final AnimationIcon mDisableTotalSilence =
- new AnimationIcon(R.drawable.ic_dnd_total_silence_disable_animation,
- R.drawable.ic_qs_dnd_off);
-
private final ZenModeController mController;
private final DndDetailAdapter mDetailAdapter;
@@ -140,7 +132,8 @@
if (mState.value) {
mController.setZen(ZEN_MODE_OFF, null, TAG);
} else {
- mController.setZen(ZEN_MODE_ALARMS, null, TAG);
+ int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, Global.ZEN_MODE_ALARMS);
+ mController.setZen(zen, null, TAG);
}
}
@@ -167,9 +160,11 @@
final int zen = arg instanceof Integer ? (Integer) arg : mController.getZen();
final boolean newValue = zen != ZEN_MODE_OFF;
final boolean valueChanged = state.value != newValue;
+ if (state.slash == null) state.slash = new SlashState();
state.dualTarget = true;
state.value = newValue;
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+ state.slash.isSlashed = !state.value;
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME);
switch (zen) {
case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
@@ -191,7 +186,7 @@
R.string.accessibility_quick_settings_dnd_alarms_on);
break;
default:
- state.icon = TOTAL_SILENCE.equals(state.icon) ? mDisableTotalSilence : mDisable;
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
state.label = mContext.getString(R.string.quick_settings_dnd_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_dnd);
@@ -321,7 +316,7 @@
mZenPanel.init(mController);
mZenPanel.addOnAttachStateChangeListener(this);
mZenPanel.setCallback(mZenModePanelCallback);
- mZenPanel.setEmptyState(R.drawable.ic_qs_dnd_off, R.string.dnd_is_off);
+ mZenPanel.setEmptyState(R.drawable.ic_qs_dnd_detail_empty, R.string.dnd_is_off);
}
updatePanel();
return mZenPanel;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index 6d2aa90..e6ac908 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -23,12 +23,11 @@
import android.service.quicksettings.Tile;
import android.widget.Switch;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.qs.QSHost;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.FlashlightController;
@@ -36,12 +35,7 @@
public class FlashlightTile extends QSTileImpl<BooleanState> implements
FlashlightController.FlashlightListener {
- private final AnimationIcon mEnable
- = new AnimationIcon(R.drawable.ic_signal_flashlight_enable_animation,
- R.drawable.ic_signal_flashlight_disable);
- private final AnimationIcon mDisable
- = new AnimationIcon(R.drawable.ic_signal_flashlight_disable_animation,
- R.drawable.ic_signal_flashlight_enable);
+ private final Icon mIcon = ResourceIcon.get(R.drawable.ic_signal_flashlight);
private final FlashlightController mFlashlightController;
public FlashlightTile(QSHost host) {
@@ -104,11 +98,13 @@
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
+ if (state.slash == null) {
+ state.slash = new SlashState();
+ }
state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label);
if (!mFlashlightController.isAvailable()) {
- Drawable icon = mHost.getContext().getDrawable(R.drawable.ic_signal_flashlight_enable)
- .mutate();
- state.icon = new DrawableIcon(icon);
+ state.icon = mIcon;
+ state.slash.isSlashed = true;
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_flashlight_unavailable);
state.state = Tile.STATE_UNAVAILABLE;
@@ -123,8 +119,8 @@
} else {
state.value = mFlashlightController.isEnabled();
}
- final AnimationIcon icon = state.value ? mEnable : mDisable;
- state.icon = icon;
+ state.icon = mIcon;
+ state.slash.isSlashed = !state.value;
state.contentDescription = mContext.getString(R.string.quick_settings_flashlight_label);
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 40f2c4b..c17573d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -39,14 +39,7 @@
static final Intent TETHER_SETTINGS = new Intent().setComponent(new ComponentName(
"com.android.settings", "com.android.settings.TetherSettings"));
- private final AnimationIcon mEnable =
- new AnimationIcon(R.drawable.ic_hotspot_enable_animation,
- R.drawable.ic_hotspot_disable);
- private final Icon mEnabledStatic = ResourceIcon.get(R.drawable.ic_hotspot_disable);
- private final AnimationIcon mDisable =
- new AnimationIcon(R.drawable.ic_hotspot_disable_animation,
- R.drawable.ic_hotspot_enable);
- private final Icon mDisableNoAnimation = ResourceIcon.get(R.drawable.ic_hotspot_enable);
+ private final Icon mEnabledStatic = ResourceIcon.get(R.drawable.ic_hotspot);
private final Icon mUnavailable = ResourceIcon.get(R.drawable.ic_hotspot_unavailable);
private final HotspotController mController;
@@ -116,6 +109,9 @@
@Override
protected void handleUpdateState(AirplaneBooleanState state, Object arg) {
+ if (state.slash == null) {
+ state.slash = new SlashState();
+ }
state.label = mContext.getString(R.string.quick_settings_hotspot_label);
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_CONFIG_TETHERING);
@@ -124,18 +120,12 @@
} else {
state.value = mController.isHotspotEnabled();
}
- state.icon = !state.value ? mDisable
- : state.isTransient ? mEnabledStatic
- : mEnable;
- boolean wasAirplane = state.isAirplaneMode;
+ state.icon = mEnabledStatic;
state.isAirplaneMode = mAirplaneMode.getValue() != 0;
state.isTransient = mController.isHotspotTransient();
+ state.slash.isSlashed = !state.value && !state.isTransient;
if (state.isTransient) {
state.icon = ResourceIcon.get(R.drawable.ic_hotspot_transient_animation);
- } else if (state.isAirplaneMode) {
- state.icon = mUnavailable;
- } else if (wasAirplane) {
- state.icon = mDisableNoAnimation;
}
state.expandedAccessibilityClassName = Switch.class.getName();
state.contentDescription = state.label;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index 6522e10..5e66334 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -25,6 +25,7 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.R.drawable;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSHost;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
@@ -36,12 +37,7 @@
/** Quick settings tile: Location **/
public class LocationTile extends QSTileImpl<BooleanState> {
- private final AnimationIcon mEnable =
- new AnimationIcon(R.drawable.ic_signal_location_enable_animation,
- R.drawable.ic_signal_location_disable);
- private final AnimationIcon mDisable =
- new AnimationIcon(R.drawable.ic_signal_location_disable_animation,
- R.drawable.ic_signal_location_enable);
+ private final Icon mIcon = ResourceIcon.get(drawable.ic_signal_location);
private final LocationController mController;
private final KeyguardMonitor mKeyguard;
@@ -95,6 +91,9 @@
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
+ if (state.slash == null) {
+ state.slash = new SlashState();
+ }
final boolean locationEnabled = mController.isLocationEnabled();
// Work around for bug 15916487: don't show location tile on top of lock screen. After the
@@ -102,13 +101,13 @@
// state.visible = !(mKeyguard.isSecure() && mKeyguard.isShowing());
state.value = locationEnabled;
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_SHARE_LOCATION);
+ state.icon = mIcon;
+ state.slash.isSlashed = !state.value;
if (locationEnabled) {
- state.icon = mEnable;
state.label = mContext.getString(R.string.quick_settings_location_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_location_on);
} else {
- state.icon = mDisable;
state.label = mContext.getString(R.string.quick_settings_location_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_location_off);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 8aa1e43..2a12769 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -79,8 +79,7 @@
state.value = isActivated;
state.label = state.contentDescription =
mContext.getString(R.string.quick_settings_night_display_label);
- state.icon = ResourceIcon.get(isActivated ? R.drawable.ic_qs_night_display_on
- : R.drawable.ic_qs_night_display_off);
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_night_display_on);
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 76f6e7d..95a9132 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -150,6 +150,12 @@
mDetailAdapter.setItemsVisible(cb.enabled);
fireToggleStateChanged(cb.enabled);
}
+ if (state.slash == null) {
+ state.slash = new SlashState();
+ state.slash.rotation = 8;
+ }
+ state.slash.isSlashed = false;
+ state.state = Tile.STATE_ACTIVE;
state.dualTarget = true;
state.value = cb.enabled;
state.activityIn = cb.enabled && cb.activityIn;
@@ -160,6 +166,8 @@
state.icon = ResourceIcon.get(R.drawable.ic_signal_wifi_transient_animation);
state.label = r.getString(R.string.quick_settings_wifi_label);
} else if (!state.value) {
+ state.slash.isSlashed = true;
+ state.state = Tile.STATE_INACTIVE;
state.icon = ResourceIcon.get(R.drawable.ic_qs_wifi_disabled);
state.label = r.getString(R.string.quick_settings_wifi_label);
} else if (wifiConnected) {
@@ -184,7 +192,6 @@
state.dualLabelContentDescription = r.getString(
R.string.accessibility_quick_settings_open_settings, getTileLabel());
state.expandedAccessibilityClassName = Switch.class.getName();
- state.state = Tile.STATE_ACTIVE;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 6c89241..38821f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -33,12 +33,7 @@
/** Quick settings tile: Work profile on/off */
public class WorkModeTile extends QSTileImpl<BooleanState> implements
ManagedProfileController.Callback {
- private final AnimationIcon mEnable =
- new AnimationIcon(R.drawable.ic_signal_workmode_enable_animation,
- R.drawable.ic_signal_workmode_disable);
- private final AnimationIcon mDisable =
- new AnimationIcon(R.drawable.ic_signal_workmode_disable_animation,
- R.drawable.ic_signal_workmode_enable);
+ private final Icon mIcon = ResourceIcon.get(R.drawable.ic_signal_workmode_disable);
private final ManagedProfileController mProfileController;
@@ -93,6 +88,10 @@
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
+ if (state.slash == null) {
+ state.slash = new SlashState();
+ }
+
if (arg instanceof Boolean) {
state.value = (Boolean) arg;
} else {
@@ -100,12 +99,13 @@
}
state.label = mContext.getString(R.string.quick_settings_work_mode_label);
+ state.icon = mIcon;
if (state.value) {
- state.icon = mEnable;
+ state.slash.isSlashed = false;
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_work_mode_on);
} else {
- state.icon = mDisable;
+ state.slash.isSlashed = true;
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_work_mode_off);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 7697061..86b1d3b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -379,6 +379,8 @@
EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, true));
MetricsLogger.visible(this, MetricsEvent.OVERVIEW_ACTIVITY);
+ // Make sure we have the right gradient and we're listening for update events
+ mRecentsView.onStart();
// Notify of the next draw
mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener);
}
@@ -506,6 +508,9 @@
MetricsLogger.hidden(this, MetricsEvent.OVERVIEW_ACTIVITY);
Recents.getTaskLoader().getHighResThumbnailLoader().setVisible(false);
+ // We don't need to update the gradient when we're not visible
+ mRecentsView.onStop();
+
if (!isChangingConfigurations()) {
// Workaround for b/22542869, if the RecentsActivity is started again, but without going
// through SystemUI, we need to reset the config launch flags to ensure that we do not
@@ -514,6 +519,9 @@
RecentsActivityLaunchState launchState = config.getLaunchState();
launchState.reset();
}
+
+ // Force a gc to attempt to clean up bitmap references more quickly (b/38258699)
+ Recents.getSystemServices().gc();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index cbfa0e5..1f13830 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -362,6 +362,19 @@
}
/**
+ * Requests a gc() from the background thread.
+ */
+ public void gc() {
+ BackgroundThread.getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ System.gc();
+ System.runFinalization();
+ }
+ });
+ }
+
+ /**
* @return whether the provided {@param className} is blacklisted
*/
public boolean isBlackListedActivity(String className) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 15e1739..1da2d74 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -100,7 +100,6 @@
TaskResourceLoadQueue mLoadQueue;
TaskKeyLruCache<Drawable> mIconCache;
- Bitmap mDefaultThumbnail;
BitmapDrawable mDefaultIcon;
boolean mStarted;
@@ -111,11 +110,10 @@
/** Constructor, creates a new loading thread that loads task resources in the background */
public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
- TaskKeyLruCache<Drawable> iconCache, Bitmap defaultThumbnail,
- BitmapDrawable defaultIcon, OnIdleChangedListener onIdleChangedListener) {
+ TaskKeyLruCache<Drawable> iconCache, BitmapDrawable defaultIcon,
+ OnIdleChangedListener onIdleChangedListener) {
mLoadQueue = loadQueue;
mIconCache = iconCache;
- mDefaultThumbnail = defaultThumbnail;
mDefaultIcon = defaultIcon;
mMainThreadHandler = new Handler();
mOnIdleChangedListener = onIdleChangedListener;
@@ -230,18 +228,14 @@
}
if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
- final ThumbnailData cachedThumbnailData = ssp.getTaskThumbnail(t.key.id,
+ final ThumbnailData thumbnailData = ssp.getTaskThumbnail(t.key.id,
true /* reducedResolution */);
- if (cachedThumbnailData.thumbnail == null) {
- cachedThumbnailData.thumbnail = mDefaultThumbnail;
- }
-
if (!mCancelled) {
// Notify that the task data has changed
final Drawable finalIcon = cachedIcon;
mMainThreadHandler.post(
- () -> t.notifyTaskDataLoaded(cachedThumbnailData, finalIcon));
+ () -> t.notifyTaskDataLoaded(thumbnailData, finalIcon));
}
}
}
@@ -282,7 +276,6 @@
int mDefaultTaskBarBackgroundColor;
int mDefaultTaskViewBackgroundColor;
BitmapDrawable mDefaultIcon;
- Bitmap mDefaultThumbnail;
private TaskKeyLruCache.EvictionCallback mClearActivityInfoOnEviction =
new TaskKeyLruCache.EvictionCallback() {
@@ -304,15 +297,10 @@
mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count);
int iconCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
mMaxIconCacheSize;
- int thumbnailCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
- mMaxThumbnailCacheSize;
// Create the default assets
Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
icon.eraseColor(0);
- mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
- mDefaultThumbnail.setHasAlpha(false);
- mDefaultThumbnail.eraseColor(0xFFffffff);
mDefaultIcon = new BitmapDrawable(context.getResources(), icon);
// Initialize the proxy, cache and loaders
@@ -325,7 +313,7 @@
mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks,
mClearActivityInfoOnEviction);
mActivityInfoCache = new LruCache(numRecentTasks);
- mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultThumbnail, mDefaultIcon,
+ mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultIcon,
mHighResThumbnailLoader::setTaskLoadQueueIdle);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index a0ad782..319b463 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -21,6 +21,8 @@
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.app.ActivityOptions.OnAnimationStartedListener;
+import android.app.WallpaperColors;
+import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -41,6 +43,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
@@ -76,6 +79,9 @@
import com.android.systemui.stackdivider.WindowManagerProxy;
import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.google.android.colorextraction.ColorExtractor;
+import com.google.android.colorextraction.drawable.GradientDrawable;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -84,13 +90,12 @@
* This view is the the top level layout that contains TaskStacks (which are laid out according
* to their SpaceNode bounds.
*/
-public class RecentsView extends FrameLayout {
+public class RecentsView extends FrameLayout implements ColorExtractor.OnColorsChangedListener {
private static final String TAG = "RecentsView";
private static final int DEFAULT_UPDATE_SCRIM_DURATION = 200;
- private static final float DEFAULT_SCRIM_ALPHA = 0.33f;
- private static final float GRID_LAYOUT_SCRIM_ALPHA = 0.45f;
+ private static final float DEFAULT_SCRIM_ALPHA = 0.8f;
private static final int SHOW_STACK_ACTION_BUTTON_DURATION = 134;
private static final int HIDE_STACK_ACTION_BUTTON_DURATION = 100;
@@ -107,7 +112,8 @@
private int mDividerSize;
private final float mScrimAlpha;
- private final Drawable mBackgroundScrim;
+ private GradientDrawable mBackgroundScrim;
+ private final ColorExtractor mColorExtractor;
private Animator mBackgroundScrimAnimator;
private RecentsTransitionHelper mTransitionHelper;
@@ -136,10 +142,11 @@
mDividerSize = ssp.getDockedDividerSize(context);
mTouchHandler = new RecentsViewTouchHandler(this);
mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
- mScrimAlpha = Recents.getConfiguration().isGridEnabled
- ? GRID_LAYOUT_SCRIM_ALPHA : DEFAULT_SCRIM_ALPHA;
- mBackgroundScrim = new ColorDrawable(
- Color.argb((int) (mScrimAlpha * 255), 0, 0, 0)).mutate();
+ mScrimAlpha = DEFAULT_SCRIM_ALPHA;
+ mBackgroundScrim = new GradientDrawable(context);
+ mBackgroundScrim.setCallback(this);
+ mBackgroundScrim.setAlpha((int) (mScrimAlpha * 255));
+ mColorExtractor = Dependency.get(ColorExtractor.class);
LayoutInflater inflater = LayoutInflater.from(context);
if (RecentsDebugFlags.Static.EnableStackActionButton) {
@@ -187,7 +194,7 @@
// the tasks for the home animation.
if (launchState.launchedViaDockGesture || launchState.launchedFromApp
|| isTaskStackEmpty) {
- mBackgroundScrim.setAlpha(255);
+ mBackgroundScrim.setAlpha((int) (mScrimAlpha * 255));
} else {
mBackgroundScrim.setAlpha(0);
}
@@ -378,6 +385,11 @@
mEmptyView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
}
+ // Needs to know the screen size since the gradient never scales up or down
+ // even when bounds change.
+ mBackgroundScrim.setScreenSize(right - left, bottom - top);
+ mBackgroundScrim.setBounds(left, top, right, bottom);
+
if (RecentsDebugFlags.Static.EnableStackActionButton) {
// Layout the stack action button such that its drawable is start-aligned with the
// stack, vertically centered in the available space above the stack
@@ -771,8 +783,8 @@
private void animateBackgroundScrim(float alpha, int duration) {
Utilities.cancelAnimationWithoutCallbacks(mBackgroundScrimAnimator);
// Calculate the absolute alpha to animate from
- int fromAlpha = (int) ((mBackgroundScrim.getAlpha() / (mScrimAlpha * 255)) * 255);
- int toAlpha = (int) (alpha * 255);
+ int fromAlpha = mBackgroundScrim.getAlpha();
+ int toAlpha = (int) (alpha * mScrimAlpha * 255);
mBackgroundScrimAnimator = ObjectAnimator.ofInt(mBackgroundScrim, Utilities.DRAWABLE_ALPHA,
fromAlpha, toAlpha);
mBackgroundScrimAnimator.setDuration(duration);
@@ -815,4 +827,22 @@
mTaskStackView.dump(innerPrefix, writer);
}
}
+
+ @Override
+ public void onColorsChanged(ColorExtractor extractor, int which) {
+ if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
+ mBackgroundScrim.setColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM));
+ }
+ }
+
+ public void onStart() {
+ mColorExtractor.addOnColorsChangedListener(this);
+ // We don't want to interpolate colors because we're defining the initial state.
+ // Gradient should be set/ready when you open "Recents".
+ mBackgroundScrim.setColors(mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM), false);
+ }
+
+ public void onStop() {
+ mColorExtractor.removeOnColorsChangedListener(this);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 4b614ed..17204a4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -133,6 +133,7 @@
@ViewDebug.ExportedProperty(deepExport=true, prefix="task_")
private Task mTask;
+ private boolean mTaskBound;
@ViewDebug.ExportedProperty(category="recents")
private boolean mClipViewInStack = true;
@ViewDebug.ExportedProperty(category="recents")
@@ -607,6 +608,7 @@
SystemServicesProxy ssp = Recents.getSystemServices();
mTouchExplorationEnabled = touchExplorationEnabled;
mTask = t;
+ mTaskBound = true;
mTask.addCallback(this);
mIsDisabledInSafeMode = !mTask.isSystemApp && ssp.isInSafeMode();
mThumbnailView.bindToTask(mTask, mIsDisabledInSafeMode, displayOrientation, displayRect);
@@ -627,9 +629,11 @@
@Override
public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
- // Update each of the views to the new task data
- mThumbnailView.onTaskDataLoaded(thumbnailData);
- mHeaderView.onTaskDataLoaded();
+ if (mTaskBound) {
+ // Update each of the views to the new task data
+ mThumbnailView.onTaskDataLoaded(thumbnailData);
+ mHeaderView.onTaskDataLoaded();
+ }
}
@Override
@@ -638,6 +642,7 @@
mTask.removeCallback(this);
mThumbnailView.unbindFromTask();
mHeaderView.unbindFromTask(mTouchExplorationEnabled);
+ mTaskBound = false;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 2d47c7b..a7b845e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -578,7 +578,8 @@
if (requiresRotation) {
// Rotate the screenshot to the current orientation
Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
- mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
+ mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888,
+ mScreenBitmap.hasAlpha(), mScreenBitmap.getColorSpace());
Canvas c = new Canvas(ss);
c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
c.rotate(degrees);
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index 3b37437..4022718 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -79,6 +79,7 @@
private void addDivider(Configuration configuration) {
mView = (DividerView)
LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
+ mView.injectDependencies(mWindowManager, mDividerState);
mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
mView.setMinimizedDockStack(mMinimized, mHomeStackResizable);
final int size = mContext.getResources().getDimensionPixelSize(
@@ -87,7 +88,6 @@
final int width = landscape ? size : MATCH_PARENT;
final int height = landscape ? MATCH_PARENT : size;
mWindowManager.add(mView, width, height);
- mView.injectDependencies(mWindowManager, mDividerState);
}
private void removeDivider() {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerState.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerState.java
index 353a974..3a5c61e 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerState.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerState.java
@@ -22,4 +22,5 @@
public class DividerState {
public boolean animateAfterRecentsDrawn;
public boolean growAfterRecentsDrawn;
+ public float mRatioPositionBeforeMinimized;
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 6dc7870..09f459b 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -372,6 +372,17 @@
public void injectDependencies(DividerWindowManager windowManager, DividerState dividerState) {
mWindowManager = windowManager;
mState = dividerState;
+
+ // Set the previous position ratio before minimized state after attaching this divider
+ if (mStableInsets.isEmpty()) {
+ SystemServicesProxy.getInstance(mContext).getStableInsets(mStableInsets);
+ }
+ int position = (int) (mState.mRatioPositionBeforeMinimized *
+ (isHorizontalDivision() ? mDisplayHeight : mDisplayWidth));
+ mSnapAlgorithm = null;
+ initializeSnapAlgorithm();
+ mDividerPositionBeforeMinimized = mSnapAlgorithm.calculateNonDismissingSnapTarget(position)
+ .position;
}
public WindowManagerProxy getWindowManagerProxy() {
@@ -727,19 +738,12 @@
mHandle.setAlpha(minimized ? 0f : 1f);
mDockedStackMinimized = minimized;
} else if (mDockedStackMinimized != minimized) {
- if (mStableInsets.isEmpty()) {
- SystemServicesProxy.getInstance(mContext).getStableInsets(mStableInsets);
- }
mMinimizedSnapAlgorithm = null;
mDockedStackMinimized = minimized;
initializeSnapAlgorithm();
if (mIsInMinimizeInteraction != minimized) {
if (minimized) {
mIsInMinimizeInteraction = true;
- mDividerPositionBeforeMinimized = DockedDividerUtils.calculateMiddlePosition(
- isHorizontalDivision(), mStableInsets, mDisplayWidth, mDisplayHeight,
- mDividerSize);
-
int position = mMinimizedSnapAlgorithm.getMiddleTarget().position;
resizeStack(position, position, mMinimizedSnapAlgorithm.getMiddleTarget());
} else {
@@ -784,7 +788,7 @@
mIsInMinimizeInteraction = true;
if (minimized && (mCurrentAnimator == null || !mCurrentAnimator.isRunning())
&& (mDividerPositionBeforeMinimized <= 0 || !mAdjustedForIme)) {
- mDividerPositionBeforeMinimized = getCurrentPosition();
+ savePositionBeforeMinimized();
}
mMinimizedSnapAlgorithm = null;
mDockedStackMinimized = minimized;
@@ -844,10 +848,16 @@
// Only get new position if home stack is resizable, ime is open and not minimized
// (including the animation)
if (mHomeStackResizable && adjustedForIme && !mIsInMinimizeInteraction) {
- mDividerPositionBeforeMinimized = getCurrentPosition();
+ savePositionBeforeMinimized();
}
}
+ private void savePositionBeforeMinimized() {
+ mDividerPositionBeforeMinimized = getCurrentPosition();
+ mState.mRatioPositionBeforeMinimized = (float) mDividerPositionBeforeMinimized /
+ (isHorizontalDivision() ? mDisplayHeight : mDisplayWidth);
+ }
+
private void resetBackground() {
mBackground.setPivotX(mBackground.getWidth() / 2);
mBackground.setPivotY(mBackground.getHeight() / 2);
@@ -1210,14 +1220,6 @@
mDockSide, mDividerSize);
mEntranceAnimationRunning = true;
- // Insets might not have been fetched yet, so fetch manually if needed.
- if (mStableInsets.isEmpty()) {
- SystemServicesProxy.getInstance(mContext).getStableInsets(mStableInsets);
- mSnapAlgorithm = null;
- mMinimizedSnapAlgorithm = null;
- initializeSnapAlgorithm();
- }
-
resizeStack(position, mSnapAlgorithm.getMiddleTarget().position,
mSnapAlgorithm.getMiddleTarget());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 6fb5d6b..9402c5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -63,6 +63,8 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.statusbar.NotificationGuts.GutsContent;
+import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
+import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationInflater;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -162,6 +164,7 @@
private boolean mIsSystemChildExpanded;
private boolean mIsPinned;
private FalsingManager mFalsingManager;
+ private AboveShelfChangedListener mAboveShelfChangedListener;
private HeadsUpManager mHeadsUpManager;
private boolean mJustClicked;
@@ -171,6 +174,18 @@
private OnExpandClickListener mOnExpandClickListener;
private boolean mGroupExpansionChanging;
+ /**
+ * Whether or not a notification that is not part of a group of notifications can be manually
+ * expanded by the user.
+ */
+ private boolean mEnableNonGroupedNotificationExpand;
+
+ /**
+ * Whether or not to update the background of the header of the notification when its expanded.
+ * If {@code true}, the header background will disappear when expanded.
+ */
+ private boolean mShowGroupBackgroundWhenExpanded;
+
private OnClickListener mExpandClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
@@ -183,7 +198,7 @@
MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER,
nowExpanded);
onExpansionChanged(true /* userAction */, wasExpanded);
- } else {
+ } else if (mEnableNonGroupedNotificationExpand) {
if (v.isAccessibilityFocused()) {
mPrivateLayout.setFocusOnVisibilityChange();
}
@@ -376,6 +391,10 @@
expandedIcon.setStaticDrawableColor(color);
}
+ public void setAboveShelfChangedListener(AboveShelfChangedListener aboveShelfChangedListener) {
+ mAboveShelfChangedListener = aboveShelfChangedListener;
+ }
+
@Override
public boolean isDimmable() {
if (!getShowingLayout().isDimmable()) {
@@ -430,6 +449,7 @@
}
public void setHeadsUp(boolean isHeadsUp) {
+ boolean wasAboveShelf = isAboveShelf();
int intrinsicBefore = getIntrinsicHeight();
mIsHeadsUp = isHeadsUp;
mPrivateLayout.setHeadsUp(isHeadsUp);
@@ -442,6 +462,8 @@
}
if (isHeadsUp) {
setAboveShelf(true);
+ } else if (isAboveShelf() != wasAboveShelf) {
+ mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
}
}
@@ -622,6 +644,7 @@
*/
public void setPinned(boolean pinned) {
int intrinsicHeight = getIntrinsicHeight();
+ boolean wasAboveShelf = isAboveShelf();
mIsPinned = pinned;
if (intrinsicHeight != getIntrinsicHeight()) {
notifyHeightChanged(false /* needsAnimation */);
@@ -633,6 +656,9 @@
setUserExpanded(true);
}
setChronometerRunning(mLastChronometerRunning);
+ if (isAboveShelf() != wasAboveShelf) {
+ mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
+ }
}
public boolean isPinned() {
@@ -818,7 +844,9 @@
return mMenuRow;
}
+ @Override
public void onDensityOrFontScaleChanged() {
+ super.onDensityOrFontScaleChanged();
initDimens();
// Let's update our childrencontainer. This is intentionally not guarded with
// mIsSummaryWithChildren since we might have had children but not anymore.
@@ -979,8 +1007,12 @@
}
public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+ boolean wasAboveShelf = isAboveShelf();
mHeadsupDisappearRunning = headsUpAnimatingAway;
mPrivateLayout.setHeadsUpAnimatingAway(headsUpAnimatingAway);
+ if (isAboveShelf() != wasAboveShelf) {
+ mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
+ }
}
/**
@@ -1173,7 +1205,7 @@
}
public interface ExpansionLogger {
- public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
+ void logNotificationExpansion(String key, boolean userAction, boolean expanded);
}
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
@@ -1196,10 +1228,16 @@
mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height);
mMaxHeadsUpHeightIncreased = getFontScaledHeight(
R.dimen.notification_max_heads_up_height_increased);
- mIncreasedPaddingBetweenElements = getResources()
- .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
- mIconTransformContentShiftNoIcon = getResources().getDimensionPixelSize(
+
+ Resources res = getResources();
+ mIncreasedPaddingBetweenElements = res.getDimensionPixelSize(
+ R.dimen.notification_divider_height_increased);
+ mIconTransformContentShiftNoIcon = res.getDimensionPixelSize(
R.dimen.notification_icon_transform_content_shift);
+ mEnableNonGroupedNotificationExpand =
+ res.getBoolean(R.bool.config_enableNonGroupedNotificationExpand);
+ mShowGroupBackgroundWhenExpanded =
+ res.getBoolean(R.bool.config_showGroupNotificationBgWhenExpanded);
}
/**
@@ -1252,30 +1290,39 @@
mChildrenContainer.setIsLowPriority(mIsLowPriority);
mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
mChildrenContainer.onNotificationUpdated();
- mTranslateableViews.add(mChildrenContainer);
+
+ if (mShouldTranslateContents) {
+ mTranslateableViews.add(mChildrenContainer);
+ }
}
});
- // Add the views that we translate to reveal the menu
- mTranslateableViews = new ArrayList<View>();
- for (int i = 0; i < getChildCount(); i++) {
- mTranslateableViews.add(getChildAt(i));
+ if (mShouldTranslateContents) {
+ // Add the views that we translate to reveal the menu
+ mTranslateableViews = new ArrayList<>();
+ for (int i = 0; i < getChildCount(); i++) {
+ mTranslateableViews.add(getChildAt(i));
+ }
+ // Remove views that don't translate
+ mTranslateableViews.remove(mChildrenContainerStub);
+ mTranslateableViews.remove(mGutsStub);
}
- // Remove views that don't translate
- mTranslateableViews.remove(mChildrenContainerStub);
- mTranslateableViews.remove(mGutsStub);
}
public void resetTranslation() {
if (mTranslateAnim != null) {
mTranslateAnim.cancel();
}
- if (mTranslateableViews != null) {
+
+ if (!mShouldTranslateContents) {
+ setTranslationX(0);
+ } else if (mTranslateableViews != null) {
for (int i = 0; i < mTranslateableViews.size(); i++) {
mTranslateableViews.get(i).setTranslationX(0);
}
+ invalidateOutline();
}
- invalidateOutline();
+
mMenuRow.resetMenu();
}
@@ -1295,13 +1342,17 @@
// Don't translate if guts are showing.
return;
}
- // Translate the group of views
- for (int i = 0; i < mTranslateableViews.size(); i++) {
- if (mTranslateableViews.get(i) != null) {
- mTranslateableViews.get(i).setTranslationX(translationX);
+ if (!mShouldTranslateContents) {
+ setTranslationX(translationX);
+ } else if (mTranslateableViews != null) {
+ // Translate the group of views
+ for (int i = 0; i < mTranslateableViews.size(); i++) {
+ if (mTranslateableViews.get(i) != null) {
+ mTranslateableViews.get(i).setTranslationX(translationX);
+ }
}
+ invalidateOutline();
}
- invalidateOutline();
if (mMenuRow.getMenuView() != null) {
mMenuRow.onTranslationUpdate(translationX);
}
@@ -1309,10 +1360,15 @@
@Override
public float getTranslation() {
+ if (!mShouldTranslateContents) {
+ return getTranslationX();
+ }
+
if (mTranslateableViews != null && mTranslateableViews.size() > 0) {
// All of the views in the list should have same translation, just use first one.
return mTranslateableViews.get(0).getTranslationX();
}
+
return 0;
}
@@ -1395,13 +1451,14 @@
if (mIsSummaryWithChildren) {
mChildrenContainer.setDark(dark, fade, delay);
}
+ updateShelfIconColor();
}
public boolean isExpandable() {
if (mIsSummaryWithChildren && !mShowingPublic) {
return !mChildrenExpanded;
}
- return mExpandable;
+ return mEnableNonGroupedNotificationExpand && mExpandable;
}
public void setExpandable(boolean expandable) {
@@ -1455,6 +1512,10 @@
mHasUserChangedExpansion = true;
mUserExpanded = userExpanded;
onExpansionChanged(true /* userAction */, wasExpanded);
+ if (!wasExpanded && isExpanded()
+ && getActualHeight() != getIntrinsicHeight()) {
+ notifyHeightChanged(true /* needsAnimation */);
+ }
}
public void resetUserExpansion() {
@@ -1513,6 +1574,7 @@
*/
public void setOnKeyguard(boolean onKeyguard) {
if (onKeyguard != mOnKeyguard) {
+ boolean wasAboveShelf = isAboveShelf();
final boolean wasExpanded = isExpanded();
mOnKeyguard = onKeyguard;
onExpansionChanged(false /* userAction */, wasExpanded);
@@ -1522,6 +1584,9 @@
}
notifyHeightChanged(false /* needsAnimation */);
}
+ if (isAboveShelf() != wasAboveShelf) {
+ mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
+ }
}
}
@@ -2000,7 +2065,8 @@
public void updateBackgroundForGroupState() {
if (mIsSummaryWithChildren) {
// Only when the group has finished expanding do we hide its background.
- mShowNoBackground = isGroupExpanded() && !isGroupExpansionChanging() && !isUserLocked();
+ mShowNoBackground = !mShowGroupBackgroundWhenExpanded && isGroupExpanded()
+ && !isGroupExpansionChanging() && !isUserLocked();
mChildrenContainer.updateHeaderForExpansion(mShowNoBackground);
List<ExpandableNotificationRow> children = mChildrenContainer.getNotificationChildren();
for (int i = 0; i < children.size(); i++) {
@@ -2009,10 +2075,10 @@
} else if (isChildInGroup()) {
final int childColor = getShowingLayout().getBackgroundColorForExpansionState();
// Only show a background if the group is expanded OR if it is expanding / collapsing
- // and has a custom background color
+ // and has a custom background color.
final boolean showBackground = isGroupExpanded()
|| ((mNotificationParent.isGroupExpansionChanging()
- || mNotificationParent.isUserLocked()) && childColor != 0);
+ || mNotificationParent.isUserLocked()) && childColor != 0);
mShowNoBackground = !showBackground;
} else {
// Only children or parents ever need no background.
@@ -2065,8 +2131,12 @@
float x = event.getX();
float y = event.getY();
NotificationHeaderView header = getVisibleNotificationHeader();
- if (header != null) {
- return header.isInTouchRect(x - getTranslation(), y);
+ if (header != null && header.isInTouchRect(x - getTranslation(), y)) {
+ return true;
+ }
+ if ((!mIsSummaryWithChildren || mShowingPublic)
+ && getShowingLayout().disallowSingleClick(x, y)) {
+ return true;
}
return super.disallowSingleClick(event);
}
@@ -2167,7 +2237,11 @@
}
public void setAboveShelf(boolean aboveShelf) {
+ boolean wasAboveShelf = isAboveShelf();
mAboveShelf = aboveShelf;
+ if (isAboveShelf() != wasAboveShelf) {
+ mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
+ }
}
public static class NotificationViewState extends ExpandableViewState {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
index 91abc87..2556890 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -17,12 +17,14 @@
package com.android.systemui.statusbar;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Outline;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
+import com.android.systemui.R;
/**
* Like {@link ExpandableView}, but setting an outline for the height and clipping.
@@ -32,18 +34,26 @@
private final Rect mOutlineRect = new Rect();
private boolean mCustomOutline;
private float mOutlineAlpha = -1f;
+ private float mOutlineRadius;
- ViewOutlineProvider mProvider = new ViewOutlineProvider() {
+ /**
+ * {@code true} if the children views of the {@link ExpandableOutlineView} are translated when
+ * it is moved. Otherwise, the translation is set on the {@code ExpandableOutlineView} itself.
+ */
+ protected boolean mShouldTranslateContents;
+
+ private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
- int translation = (int) getTranslation();
+ int translation = mShouldTranslateContents ? (int) getTranslation() : 0;
if (!mCustomOutline) {
- outline.setRect(translation,
+ outline.setRoundRect(translation,
mClipTopAmount,
getWidth() + translation,
- Math.max(getActualHeight() - mClipBottomAmount, mClipTopAmount));
+ Math.max(getActualHeight() - mClipBottomAmount, mClipTopAmount),
+ mOutlineRadius);
} else {
- outline.setRect(mOutlineRect);
+ outline.setRoundRect(mOutlineRect, mOutlineRadius);
}
outline.setAlpha(mOutlineAlpha);
}
@@ -52,6 +62,20 @@
public ExpandableOutlineView(Context context, AttributeSet attrs) {
super(context, attrs);
setOutlineProvider(mProvider);
+ initDimens();
+ }
+
+ private void initDimens() {
+ Resources res = getResources();
+ mShouldTranslateContents =
+ res.getBoolean(R.bool.config_translateNotificationContentsOnSwipe);
+ mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius);
+ setClipToOutline(res.getBoolean(R.bool.config_clipNotificationsToOutline));
+ }
+
+ public void onDensityOrFontScaleChanged() {
+ initDimens();
+ invalidateOutline();
}
@Override
@@ -108,8 +132,8 @@
}
/**
- * @return whether the view currently needs an outline. This is usually false in case it doesn't
- * have a background.
+ * @return Whether the view currently needs an outline. This is usually {@code false} in case
+ * it doesn't have a background.
*/
protected boolean needsOutline() {
if (isChildInGroup()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index 6a1f470..efe5e0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -24,8 +24,8 @@
import android.view.ViewGroup;
import android.widget.FrameLayout;
-import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.ExpandableViewState;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.StackScrollState;
import java.util.ArrayList;
@@ -355,7 +355,7 @@
if (mClipToActualHeight) {
int top = getClipTopAmount();
mClipRect.set(0, top, getWidth(), Math.max(getActualHeight() + getExtraBottomPadding()
- - mClipBottomAmount, top));
+ - mClipBottomAmount, top));
setClipBounds(mClipRect);
} else {
setClipBounds(null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
index b15f090..e12b574 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
@@ -23,6 +23,7 @@
import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.CanvasProperty;
import android.graphics.Color;
@@ -55,7 +56,7 @@
private final int mMinBackgroundRadius;
private final Paint mCirclePaint;
- private final int mInverseColor;
+ private final int mDarkIconColor;
private final int mNormalColor;
private final ArgbEvaluator mColorInterpolator;
private final FlingAnimationUtils mFlingAnimationUtils;
@@ -126,17 +127,21 @@
public KeyguardAffordanceView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ TypedArray a = context.obtainStyledAttributes(attrs, android.R.styleable.ImageView);
+
mCirclePaint = new Paint();
mCirclePaint.setAntiAlias(true);
mCircleColor = 0xffffffff;
mCirclePaint.setColor(mCircleColor);
- mNormalColor = 0xffffffff;
- mInverseColor = 0xff000000;
+ mNormalColor = a.getColor(android.R.styleable.ImageView_tint, 0xffffffff);
+ mDarkIconColor = 0xff000000;
mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_affordance_min_background_radius);
mColorInterpolator = new ArgbEvaluator();
mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.3f);
+
+ a.recycle();
}
public void setImageDrawable(@Nullable Drawable drawable, boolean tint) {
@@ -177,7 +182,7 @@
Drawable drawable = getDrawable().mutate();
float alpha = mCircleRadius / mMinBackgroundRadius;
alpha = Math.min(1.0f, alpha);
- int color = (int) mColorInterpolator.evaluate(alpha, mNormalColor, mInverseColor);
+ int color = (int) mColorInterpolator.evaluate(alpha, mNormalColor, mDarkIconColor);
drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 1691e135..25ef478 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar;
-import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -66,21 +65,22 @@
private static final long TRANSIENT_FP_ERROR_TIMEOUT = 1300;
private final Context mContext;
- private final ViewGroup mIndicationArea;
- private final KeyguardIndicationTextView mTextView;
- private final KeyguardIndicationTextView mDisclosure;
+ private ViewGroup mIndicationArea;
+ private KeyguardIndicationTextView mTextView;
+ private KeyguardIndicationTextView mDisclosure;
private final UserManager mUserManager;
private final IBatteryStats mBatteryInfo;
private final SettableWakeLock mWakeLock;
private final int mSlowThreshold;
private final int mFastThreshold;
- private final LockIcon mLockIcon;
+ private LockIcon mLockIcon;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private String mRestingIndication;
private String mTransientIndication;
private int mTransientTextColor;
+ private int mInitialTextColor;
private boolean mVisible;
private boolean mPowerPluggedIn;
@@ -89,7 +89,7 @@
private int mChargingWattage;
private String mMessageToShowOnScreenOn;
- private KeyguardUpdateMonitorCallback mUpdateMonitor;
+ private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
private final DevicePolicyManager mDevicePolicyManager;
private boolean mDozing;
@@ -115,6 +115,7 @@
mIndicationArea = indicationArea;
mTextView = (KeyguardIndicationTextView) indicationArea.findViewById(
R.id.keyguard_indication_text);
+ mInitialTextColor = mTextView != null ? mTextView.getCurrentTextColor() : Color.WHITE;
mDisclosure = (KeyguardIndicationTextView) indicationArea.findViewById(
R.id.keyguard_indication_enterprise_disclosure);
mLockIcon = lockIcon;
@@ -153,10 +154,10 @@
* same instance.
*/
protected KeyguardUpdateMonitorCallback getKeyguardCallback() {
- if (mUpdateMonitor == null) {
- mUpdateMonitor = new BaseKeyguardCallback();
+ if (mUpdateMonitorCallback == null) {
+ mUpdateMonitorCallback = new BaseKeyguardCallback();
}
- return mUpdateMonitor;
+ return mUpdateMonitorCallback;
}
private void updateDisclosure() {
@@ -203,6 +204,24 @@
}
/**
+ * Returns the indication text indicating that trust has been granted.
+ *
+ * @return {@code null} or an empty string if a trust indication text should not be shown.
+ */
+ protected String getTrustGrantedIndication() {
+ return null;
+ }
+
+ /**
+ * Returns the indication text indicating that trust is currently being managed.
+ *
+ * @return {@code null} or an empty string if a trust managed text should not be shown.
+ */
+ protected String getTrustManagedIndication() {
+ return null;
+ }
+
+ /**
* Hides transient indication in {@param delayMs}.
*/
public void hideTransientIndicationDelayed(long delayMs) {
@@ -221,7 +240,7 @@
* Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
*/
public void showTransientIndication(String transientIndication) {
- showTransientIndication(transientIndication, Color.WHITE);
+ showTransientIndication(transientIndication, mInitialTextColor);
}
/**
@@ -250,19 +269,23 @@
}
}
- private void updateIndication() {
+ protected final void updateIndication() {
if (TextUtils.isEmpty(mTransientIndication)) {
mWakeLock.setAcquired(false);
}
if (mVisible) {
- // Walk down a precedence-ordered list of what should indication
+ // Walk down a precedence-ordered list of what indication
// should be shown based on user or device state
if (mDozing) {
// If we're dozing, never show a persistent indication.
if (!TextUtils.isEmpty(mTransientIndication)) {
+ // When dozing we ignore the initial text color and use white instead.
+ // We don't wait to draw black text on a black background.
+ int color = mTransientTextColor == mInitialTextColor ?
+ Color.WHITE : mTransientTextColor;
mTextView.switchIndication(mTransientIndication);
- mTextView.setTextColor(mTransientTextColor);
+ mTextView.setTextColor(color);
} else {
mTextView.switchIndication(null);
@@ -270,25 +293,35 @@
return;
}
- if (!mUserManager.isUserUnlocked(KeyguardUpdateMonitor.getCurrentUser())) {
+ KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ int userId = KeyguardUpdateMonitor.getCurrentUser();
+ String trustGrantedIndication = getTrustGrantedIndication();
+ String trustManagedIndication = getTrustManagedIndication();
+ if (!mUserManager.isUserUnlocked(userId)) {
mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked);
- mTextView.setTextColor(Color.WHITE);
-
+ mTextView.setTextColor(mInitialTextColor);
} else if (!TextUtils.isEmpty(mTransientIndication)) {
mTextView.switchIndication(mTransientIndication);
mTextView.setTextColor(mTransientTextColor);
-
+ } else if (!TextUtils.isEmpty(trustGrantedIndication)
+ && updateMonitor.getUserHasTrust(userId)) {
+ mTextView.switchIndication(trustGrantedIndication);
+ mTextView.setTextColor(mInitialTextColor);
} else if (mPowerPluggedIn) {
String indication = computePowerIndication();
if (DEBUG_CHARGING_SPEED) {
indication += ", " + (mChargingWattage / 1000) + " mW";
}
mTextView.switchIndication(indication);
- mTextView.setTextColor(Color.WHITE);
-
+ mTextView.setTextColor(mInitialTextColor);
+ } else if (!TextUtils.isEmpty(trustManagedIndication)
+ && updateMonitor.getUserTrustIsManaged(userId)
+ && !updateMonitor.getUserHasTrust(userId)) {
+ mTextView.switchIndication(trustManagedIndication);
+ mTextView.setTextColor(mInitialTextColor);
} else {
mTextView.switchIndication(mRestingIndication);
- mTextView.setTextColor(Color.WHITE);
+ mTextView.setTextColor(mInitialTextColor);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index baf0c2c..75704b16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -49,11 +49,11 @@
*/
public class NotificationContentView extends FrameLayout {
- private static final int VISIBLE_TYPE_CONTRACTED = 0;
- private static final int VISIBLE_TYPE_EXPANDED = 1;
- private static final int VISIBLE_TYPE_HEADSUP = 2;
+ public static final int VISIBLE_TYPE_CONTRACTED = 0;
+ public static final int VISIBLE_TYPE_EXPANDED = 1;
+ public static final int VISIBLE_TYPE_HEADSUP = 2;
private static final int VISIBLE_TYPE_SINGLELINE = 3;
- private static final int VISIBLE_TYPE_AMBIENT = 4;
+ public static final int VISIBLE_TYPE_AMBIENT = 4;
private static final int VISIBLE_TYPE_AMBIENT_SINGLELINE = 5;
public static final int UNDEFINED = -1;
@@ -928,7 +928,7 @@
}
}
- private NotificationViewWrapper getVisibleWrapper(int visibleType) {
+ public NotificationViewWrapper getVisibleWrapper(int visibleType) {
switch (visibleType) {
case VISIBLE_TYPE_EXPANDED:
return mExpandedWrapper;
@@ -1085,16 +1085,16 @@
mBeforeN = entry.targetSdk < Build.VERSION_CODES.N;
updateAllSingleLineViews();
if (mContractedChild != null) {
- mContractedWrapper.notifyContentUpdated(entry.row);
+ mContractedWrapper.onContentUpdated(entry.row);
}
if (mExpandedChild != null) {
- mExpandedWrapper.notifyContentUpdated(entry.row);
+ mExpandedWrapper.onContentUpdated(entry.row);
}
if (mHeadsUpChild != null) {
- mHeadsUpWrapper.notifyContentUpdated(entry.row);
+ mHeadsUpWrapper.onContentUpdated(entry.row);
}
if (mAmbientChild != null) {
- mAmbientWrapper.notifyContentUpdated(entry.row);
+ mAmbientWrapper.onContentUpdated(entry.row);
}
applyRemoteInput(entry);
updateLegacy();
@@ -1440,4 +1440,15 @@
}
return true;
}
+
+ /**
+ * Should a single click be disallowed on this view when on the keyguard?
+ */
+ public boolean disallowSingleClick(float x, float y) {
+ NotificationViewWrapper visibleWrapper = getVisibleWrapper(getVisibleType());
+ if (visibleWrapper != null) {
+ return visibleWrapper.disallowSingleClick(x, y);
+ }
+ return false;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 531437d..c0691c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -146,12 +146,12 @@
// Construct the icon.
icon = new StatusBarIconView(context,
- sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n);
+ sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
// Construct the expanded icon.
expandedIcon = new StatusBarIconView(context,
- sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n);
+ sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
final StatusBarIcon ic = new StatusBarIcon(
sbn.getUser(),
@@ -187,9 +187,11 @@
* @param n the notification to read the icon from.
* @throws InflationException
*/
- public void updateIcons(Context context, Notification n) throws InflationException {
+ public void updateIcons(Context context, StatusBarNotification sbn)
+ throws InflationException {
if (icon != null) {
// Update the icon
+ Notification n = sbn.getNotification();
final StatusBarIcon ic = new StatusBarIcon(
notification.getUser(),
notification.getPackageName(),
@@ -197,8 +199,8 @@
n.iconLevel,
n.number,
StatusBarIconView.contentDescForNotification(context, n));
- icon.setNotification(n);
- expandedIcon.setNotification(n);
+ icon.setNotification(sbn);
+ expandedIcon.setNotification(sbn);
if (!icon.set(ic) || !expandedIcon.set(ic)) {
throw new InflationException("Couldn't update icon: " + ic);
}
@@ -436,7 +438,7 @@
final int N = mEntries.size();
for (int i = 0; i < N; i++) {
Entry entry = mEntries.valueAt(i);
- final StatusBarNotification oldSbn = entry.notification.clone();
+ final StatusBarNotification oldSbn = entry.notification.cloneLight();
final String overrideGroupKey = getOverrideGroupKey(entry.key);
if (!Objects.equals(oldSbn.getOverrideGroupKey(), overrideGroupKey)) {
entry.notification.setOverrideGroupKey(overrideGroupKey);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 427708b..1ffb3b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -154,9 +154,32 @@
}
}
+ boolean nonBlockable = false;
+ try {
+ final PackageInfo pkgInfo = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
+ if (Utils.isSystemPackage(getResources(), pm, pkgInfo)) {
+ final int numChannels = mNotificationChannels.size();
+ for (int i = 0; i < numChannels; i++) {
+ final NotificationChannel notificationChannel = mNotificationChannels.get(i);
+ // If any of the system channels is not blockable, the bundle is nonblockable
+ if (!notificationChannel.isBlockableSystem()) {
+ nonBlockable = true;
+ break;
+ }
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // unlikely.
+ }
+ if (nonBlockablePkgs != null) {
+ nonBlockable |= nonBlockablePkgs.contains(pkg);
+ }
+
String channelsDescText;
mNumChannelsView = findViewById(R.id.num_channels_desc);
- if (mIsSingleDefaultChannel) {
+ if (nonBlockable) {
+ channelsDescText = mContext.getString(R.string.notification_unblockable_desc);
+ } else if (mIsSingleDefaultChannel) {
channelsDescText = mContext.getString(R.string.notification_default_channel_desc);
} else {
switch (mNotificationChannels.size()) {
@@ -188,8 +211,9 @@
// Multiple channels don't use a channel name for the title.
channelNameText = mContext.getString(R.string.notification_num_channels,
mNotificationChannels.size());
- } else if (mIsSingleDefaultChannel) {
- // If this is the default channel, don't use our channel-specific text.
+ } else if (mIsSingleDefaultChannel || nonBlockable) {
+ // If this is the default channel or the app is unblockable,
+ // don't use our channel-specific text.
channelNameText = mContext.getString(R.string.notification_header_default_channel);
} else {
channelNameText = mSingleNotificationChannel.getName();
@@ -218,19 +242,6 @@
groupDividerView.setVisibility(View.GONE);
}
- boolean nonBlockable = false;
- try {
- final PackageInfo pkgInfo = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
- nonBlockable = Utils.isSystemPackage(getResources(), pm, pkgInfo)
- && (mSingleNotificationChannel == null
- || !mSingleNotificationChannel.isBlockableSystem());
- } catch (PackageManager.NameNotFoundException e) {
- // unlikely.
- }
- if (nonBlockablePkgs != null) {
- nonBlockable |= nonBlockablePkgs.contains(pkg);
- }
-
bindButtons(nonBlockable);
// Top-level importance group
@@ -246,12 +257,11 @@
(View view) -> {
onSettingsClick.onClick(view, mSingleNotificationChannel, appUidF);
});
- if (numTotalChannels > 1) {
- settingsButton.setText(R.string.notification_all_categories);
- } else {
+ if (numTotalChannels <= 1 || nonBlockable) {
settingsButton.setText(R.string.notification_more_settings);
+ } else {
+ settingsButton.setText(R.string.notification_all_categories);
}
-
} else {
settingsButton.setVisibility(View.GONE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
index 570de18..c09da21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -67,7 +67,6 @@
private Context mContext;
private FrameLayout mMenuContainer;
- private MenuItem mSnoozeItem;
private MenuItem mInfoItem;
private ArrayList<MenuItem> mMenuItems;
private OnMenuEventListener mMenuListener;
@@ -86,9 +85,9 @@
private int[] mIconLocation = new int[2];
private int[] mParentLocation = new int[2];
- private float mHorizSpaceForIcon;
- private int mVertSpaceForIcons;
- private int mIconPadding;
+ private float mHorizSpaceForIcon = -1;
+ private int mVertSpaceForIcons = -1;
+ private int mIconPadding = -1;
private float mAlpha = 0f;
private float mPrevX;
@@ -104,17 +103,9 @@
public NotificationMenuRow(Context context) {
mContext = context;
- final Resources res = context.getResources();
- mShouldShowMenu = res.getBoolean(R.bool.config_showNotificationGear);
- mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
- mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
- mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
+ mShouldShowMenu = context.getResources().getBoolean(R.bool.config_showNotificationGear);
mHandler = new Handler(Looper.getMainLooper());
mMenuItems = new ArrayList<>();
- mSnoozeItem = createSnoozeItem(context);
- mInfoItem = createInfoItem(context);
- mMenuItems.add(mSnoozeItem);
- mMenuItems.add(mInfoItem);
}
@Override
@@ -180,19 +171,24 @@
}
private void createMenuViews(boolean resetState) {
- // Filter the menu items based on the notification
+ final Resources res = mContext.getResources();
+ mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
+ mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
+ mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
+ mMenuItems.clear();
+ // Construct the menu items based on the notification
if (mParent != null && mParent.getStatusBarNotification() != null) {
int flags = mParent.getStatusBarNotification().getNotification().flags;
boolean isForeground = (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
- if (isForeground) {
- // Don't show snooze for foreground services
- mMenuItems.remove(mSnoozeItem);
- } else if (!mMenuItems.contains(mSnoozeItem)) {
- // Was a foreground service but is no longer, add snooze back
- mMenuItems.add(mSnoozeItem);
+ if (!isForeground) {
+ // Only show snooze for non-foreground notifications
+ mMenuItems.add(createSnoozeItem(mContext));
}
}
- // Recreate the menu
+ mInfoItem = createInfoItem(mContext);
+ mMenuItems.add(mInfoItem);
+
+ // Construct the menu views
if (mMenuContainer != null) {
mMenuContainer.removeAllViews();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index bbcbfd6..67ea258 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -18,12 +18,13 @@
import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.os.SystemProperties;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
-
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
@@ -73,6 +74,7 @@
private float mOpenedAmount;
private boolean mNoAnimationsInThisFrame;
private boolean mAnimationsEnabled = true;
+ private boolean mShowNotificationShelf;
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -101,21 +103,24 @@
}
private void initDimens() {
- mIconAppearTopPadding = getResources().getDimensionPixelSize(
- R.dimen.notification_icon_appear_padding);
- mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height);
- mStatusBarPaddingStart = getResources().getDimensionPixelOffset(
- R.dimen.status_bar_padding_start);
- mPaddingBetweenElements = getResources().getDimensionPixelSize(
- R.dimen.notification_divider_height);
+ Resources res = getResources();
+ mIconAppearTopPadding = res.getDimensionPixelSize(R.dimen.notification_icon_appear_padding);
+ mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
+ mStatusBarPaddingStart = res.getDimensionPixelOffset(R.dimen.status_bar_padding_start);
+ mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
+
ViewGroup.LayoutParams layoutParams = getLayoutParams();
- layoutParams.height = getResources().getDimensionPixelOffset(
- R.dimen.notification_shelf_height);
+ layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
setLayoutParams(layoutParams);
- int padding = getResources().getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
+
+ int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
mShelfIcons.setPadding(padding, 0, padding, 0);
- mScrollFastThreshold = getResources().getDimensionPixelOffset(
- R.dimen.scroll_fast_threshold);
+ mScrollFastThreshold = res.getDimensionPixelOffset(R.dimen.scroll_fast_threshold);
+ mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf);
+
+ if (!mShowNotificationShelf) {
+ setVisibility(GONE);
+ }
}
@Override
@@ -150,7 +155,7 @@
public void updateState(StackScrollState resultState,
AmbientState ambientState) {
View lastView = ambientState.getLastVisibleBackgroundChild();
- if (lastView != null) {
+ if (mShowNotificationShelf && lastView != null) {
float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding()
+ ambientState.getStackTranslation();
ExpandableViewState lastViewState = resultState.getViewStateForView(lastView);
@@ -174,7 +179,8 @@
mShelfState.notGoneIndex = Math.min(mShelfState.notGoneIndex, mNotGoneIndex);
}
mShelfState.hasItemsInStableShelf = lastViewState.inShelf;
- mShelfState.hidden = !mAmbientState.isShadeExpanded();
+ mShelfState.hidden = !mAmbientState.isShadeExpanded()
+ || mAmbientState.isQsCustomizerShowing();
mShelfState.maxShelfEnd = maxShelfEnd;
} else {
mShelfState.hidden = true;
@@ -188,6 +194,11 @@
* the icons from the notification area into the shelf.
*/
public void updateAppearance() {
+ // If the shelf should not be shown, then there is no need to update anything.
+ if (!mShowNotificationShelf) {
+ return;
+ }
+
mShelfIcons.resetViewStates();
float shelfStart = getTranslationY();
float numViewsInShelf = 0.0f;
@@ -639,6 +650,10 @@
updateRelativeOffset();
}
+ public void setDarkOffsetX(int offsetX) {
+ mShelfIcons.setDarkOffsetX(offsetX);
+ }
+
private class ShelfState extends ExpandableViewState {
private float openedAmount;
private boolean hasItemsInStableShelf;
@@ -646,6 +661,10 @@
@Override
public void applyToView(View view) {
+ if (!mShowNotificationShelf) {
+ return;
+ }
+
super.applyToView(view);
setMaxShelfEnd(maxShelfEnd);
setOpenedAmount(openedAmount);
@@ -656,6 +675,10 @@
@Override
public void animateTo(View child, AnimationProperties properties) {
+ if (!mShowNotificationShelf) {
+ return;
+ }
+
super.animateTo(child, properties);
setMaxShelfEnd(maxShelfEnd);
setOpenedAmount(openedAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
index 8af1628..0e5e416 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
@@ -117,22 +117,27 @@
private ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
ArrayList<SnoozeOption> options = new ArrayList<>();
- options.add(createOption(R.string.snooze_option_15_min, 15));
- options.add(createOption(R.string.snooze_option_30_min, 30));
- mDefaultOption = createOption(R.string.snooze_option_1_hour, 60);
+ options.add(createOption(15 /* minutes */));
+ options.add(createOption(30 /* minutes */));
+ mDefaultOption = createOption(60 /* minutes */);
options.add(mDefaultOption);
- options.add(createOption(R.string.snooze_option_2_hour, 60 * 2));
+ options.add(createOption(60 * 2 /* minutes */));
return options;
}
- private SnoozeOption createOption(int descriptionResId, int minutes) {
+ private SnoozeOption createOption(int minutes) {
Resources res = getResources();
- final String description = res.getString(descriptionResId);
+ boolean showInHours = minutes >= 60;
+ int pluralResId = showInHours
+ ? R.plurals.snoozeHourOptions
+ : R.plurals.snoozeMinuteOptions;
+ int count = showInHours ? (minutes / 60) : minutes;
+ String description = res.getQuantityString(pluralResId, count, count);
String resultText = String.format(res.getString(R.string.snoozed_for_time), description);
SpannableString string = new SpannableString(resultText);
string.setSpan(new StyleSpan(Typeface.BOLD),
resultText.length() - description.length(), resultText.length(), 0 /* flags */);
- return new SnoozeOption(null, minutes, res.getString(descriptionResId), string);
+ return new SnoozeOption(null, minutes, description, string);
}
private void createOptionViews() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
index f9d0cd6..ba34ac1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
@@ -19,39 +19,52 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.NonNull;
import android.content.Context;
-import android.content.res.TypedArray;
+import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.Paint;
+import android.graphics.Point;
import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.v4.graphics.ColorUtils;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Display;
import android.view.View;
+import android.view.WindowManager;
import android.view.animation.Interpolator;
-import com.android.systemui.R;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+
+import com.google.android.colorextraction.ColorExtractor;
+import com.google.android.colorextraction.drawable.GradientDrawable;
/**
* A view which can draw a scrim
*/
-public class ScrimView extends View
-{
- private final Paint mPaint = new Paint();
- private int mScrimColor;
- private boolean mIsEmpty = true;
+public class ScrimView extends View implements ConfigurationController.ConfigurationListener {
+ private static final String TAG = "ScrimView";
+ private final ColorExtractor.GradientColors mColors;
private boolean mDrawAsSrc;
private float mViewAlpha = 1.0f;
private ValueAnimator mAlphaAnimator;
private Rect mExcludedRect = new Rect();
private boolean mHasExcludedArea;
- private ValueAnimator.AnimatorUpdateListener mAlphaUpdateListener
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mViewAlpha = (float) animation.getAnimatedValue();
- invalidate();
+ private Drawable mDrawable;
+ private PorterDuffColorFilter mColorFilter;
+ private int mTintColor;
+ private ValueAnimator.AnimatorUpdateListener mAlphaUpdateListener = animation -> {
+ if (mDrawable == null) {
+ Log.w(TAG, "Trying to animate null drawable");
+ return;
}
+ mDrawable.setAlpha((int) (255 * (float) animation.getAnimatedValue()));
};
private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() {
@Override
@@ -76,72 +89,151 @@
public ScrimView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ScrimView);
+ mDrawable = new GradientDrawable(context);
+ mDrawable.setCallback(this);
+ mColors = new ColorExtractor.GradientColors();
+ updateScreenSize();
+ updateColorWithTint(false);
- try {
- mScrimColor = ta.getColor(R.styleable.ScrimView_scrimColor, Color.BLACK);
- } finally {
- ta.recycle();
- }
+ // We need to know about configuration changes to update the gradient size
+ // since it's independent from view bounds.
+ ConfigurationController config = Dependency.get(ConfigurationController.class);
+ config.addCallback(this);
}
@Override
protected void onDraw(Canvas canvas) {
- if (mDrawAsSrc || (!mIsEmpty && mViewAlpha > 0f)) {
- PorterDuff.Mode mode = mDrawAsSrc ? PorterDuff.Mode.SRC : PorterDuff.Mode.SRC_OVER;
- int color = getScrimColorWithAlpha();
+ if (mDrawAsSrc || mDrawable.getAlpha() > 0) {
if (!mHasExcludedArea) {
- canvas.drawColor(color, mode);
+ mDrawable.draw(canvas);
} else {
- mPaint.setColor(color);
if (mExcludedRect.top > 0) {
- canvas.drawRect(0, 0, getWidth(), mExcludedRect.top, mPaint);
+ canvas.save();
+ canvas.clipRect(0, 0, getWidth(), mExcludedRect.top);
+ mDrawable.draw(canvas);
+ canvas.restore();
}
if (mExcludedRect.left > 0) {
- canvas.drawRect(0, mExcludedRect.top, mExcludedRect.left, mExcludedRect.bottom,
- mPaint);
+ canvas.save();
+ canvas.clipRect(0, mExcludedRect.top, mExcludedRect.left,
+ mExcludedRect.bottom);
+ mDrawable.draw(canvas);
+ canvas.restore();
}
if (mExcludedRect.right < getWidth()) {
- canvas.drawRect(mExcludedRect.right,
- mExcludedRect.top,
- getWidth(),
- mExcludedRect.bottom,
- mPaint);
+ canvas.save();
+ canvas.clipRect(mExcludedRect.right, mExcludedRect.top, getWidth(),
+ mExcludedRect.bottom);
+ mDrawable.draw(canvas);
+ canvas.restore();
}
if (mExcludedRect.bottom < getHeight()) {
- canvas.drawRect(0, mExcludedRect.bottom, getWidth(), getHeight(), mPaint);
+ canvas.save();
+ canvas.clipRect(0, mExcludedRect.bottom, getWidth(), getHeight());
+ mDrawable.draw(canvas);
+ canvas.restore();
}
}
}
}
- public int getScrimColorWithAlpha() {
- int color = mScrimColor;
- color = Color.argb((int) (Color.alpha(color) * mViewAlpha), Color.red(color),
- Color.green(color), Color.blue(color));
- return color;
+ public void setDrawable(Drawable drawable) {
+ mDrawable = drawable;
+ mDrawable.setCallback(this);
+ mDrawable.setBounds(getLeft(), getTop(), getRight(), getBottom());
+ mDrawable.setAlpha((int) (255 * mViewAlpha));
+ setDrawAsSrc(mDrawAsSrc);
+ updateScreenSize();
+ invalidate();
+ }
+
+ @Override
+ public void invalidateDrawable(@NonNull Drawable drawable) {
+ super.invalidateDrawable(drawable);
+ if (drawable == mDrawable) {
+ invalidate();
+ }
}
public void setDrawAsSrc(boolean asSrc) {
mDrawAsSrc = asSrc;
- mPaint.setXfermode(new PorterDuffXfermode(mDrawAsSrc ? PorterDuff.Mode.SRC
- : PorterDuff.Mode.SRC_OVER));
- invalidate();
+ PorterDuff.Mode mode = asSrc ? PorterDuff.Mode.SRC : PorterDuff.Mode.SRC_OVER;
+ mDrawable.setXfermode(new PorterDuffXfermode(mode));
}
- public void setScrimColor(int color) {
- if (color != mScrimColor) {
- mIsEmpty = Color.alpha(color) == 0;
- mScrimColor = color;
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (changed) {
+ mDrawable.setBounds(left, top, right, bottom);
invalidate();
- if (mChangeRunnable != null) {
- mChangeRunnable.run();
- }
}
}
- public int getScrimColor() {
- return mScrimColor;
+ public void setColors(@NonNull ColorExtractor.GradientColors colors) {
+ setColors(colors, false);
+ }
+
+ public void setColors(@NonNull ColorExtractor.GradientColors colors, boolean animated) {
+ if (colors == null) {
+ throw new IllegalArgumentException("Colors cannot be null");
+ }
+ if (mColors.equals(colors)) {
+ return;
+ }
+ mColors.set(colors);
+ updateColorWithTint(animated);
+ }
+
+ @VisibleForTesting
+ Drawable getDrawable() {
+ return mDrawable;
+ }
+
+ @VisibleForTesting
+ ColorExtractor.GradientColors getColors() {
+ return mColors;
+ }
+
+ public void setTint(int color) {
+ setTint(color, false);
+ }
+
+ public void setTint(int color, boolean animated) {
+ if (mTintColor == color) {
+ return;
+ }
+ mTintColor = color;
+ updateColorWithTint(animated);
+ }
+
+ private void updateColorWithTint(boolean animated) {
+ if (mDrawable instanceof GradientDrawable) {
+ // Optimization to blend colors and avoid a color filter
+ GradientDrawable drawable = (GradientDrawable) mDrawable;
+ float tintAmount = Color.alpha(mTintColor) / 255f;
+ int mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), mTintColor,
+ tintAmount);
+ int secondaryTinted = ColorUtils.blendARGB(mColors.getSecondaryColor(), mTintColor,
+ tintAmount);
+ drawable.setColors(mainTinted, secondaryTinted, animated);
+ } else {
+ if (mColorFilter == null) {
+ mColorFilter = new PorterDuffColorFilter(mTintColor, PorterDuff.Mode.SRC_OVER);
+ } else {
+ mColorFilter.setColor(mTintColor);
+ }
+ mDrawable.setColorFilter(Color.alpha(mTintColor) == 0 ? null : mColorFilter);
+ mDrawable.invalidateSelf();
+ }
+
+ if (mChangeRunnable != null) {
+ mChangeRunnable.run();
+ }
+ }
+
+ public int getTint() {
+ return mTintColor;
}
@Override
@@ -150,23 +242,29 @@
}
public void setViewAlpha(float alpha) {
- if (mAlphaAnimator != null) {
- mAlphaAnimator.cancel();
- }
if (alpha != mViewAlpha) {
mViewAlpha = alpha;
- invalidate();
+
+ if (mAlphaAnimator != null) {
+ mAlphaAnimator.cancel();
+ }
+
+ mDrawable.setAlpha((int) (255 * alpha));
if (mChangeRunnable != null) {
mChangeRunnable.run();
}
}
}
+ public float getViewAlpha() {
+ return mViewAlpha;
+ }
+
public void animateViewAlpha(float alpha, long durationOut, Interpolator interpolator) {
if (mAlphaAnimator != null) {
mAlphaAnimator.cancel();
}
- mAlphaAnimator = ValueAnimator.ofFloat(mViewAlpha, alpha);
+ mAlphaAnimator = ValueAnimator.ofFloat(getViewAlpha(), alpha);
mAlphaAnimator.addUpdateListener(mAlphaUpdateListener);
mAlphaAnimator.addListener(mClearAnimatorListener);
mAlphaAnimator.setInterpolator(interpolator);
@@ -193,4 +291,25 @@
public void setChangeRunnable(Runnable changeRunnable) {
mChangeRunnable = changeRunnable;
}
+
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ updateScreenSize();
+ }
+
+ private void updateScreenSize() {
+ if (mDrawable instanceof GradientDrawable) {
+ WindowManager wm = mContext.getSystemService(WindowManager.class);
+ if (wm == null) {
+ Log.w(TAG, "Can't resize gradient drawable to fit the screen");
+ return;
+ }
+ Display display = wm.getDefaultDisplay();
+ if (display != null) {
+ Point size = new Point();
+ display.getRealSize(size);
+ ((GradientDrawable) mDrawable).setScreenSize(size.x, size.y);
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 18cc8721..169019f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -28,6 +28,7 @@
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
+import android.net.NetworkBadging;
import android.telephony.SubscriptionInfo;
import android.util.ArraySet;
import android.util.AttributeSet;
@@ -121,7 +122,6 @@
private boolean mBlockEthernet;
private boolean mActivityEnabled;
private boolean mForceBlockWifi;
- private boolean mQsSignal;
public SignalClusterView(Context context) {
this(context, null);
@@ -153,10 +153,9 @@
updateActivityEnabled();
}
- public void setQsSignalCluster() {
+ public void setForceBlockWifi() {
mForceBlockWifi = true;
mBlockWifi = true;
- mQsSignal = true;
if (isAttachedToWindow()) {
// Re-register to get new callbacks.
mNetworkController.removeCallback(this);
@@ -301,23 +300,19 @@
}
@Override
- public void setMobileDataIndicators(IconState icon, IconState qsIcon, int type,
+ public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
String description, boolean isWide, int subId, boolean roaming) {
PhoneState state = getState(subId);
if (state == null) {
return;
}
- if (mQsSignal && qsIcon != null) {
- icon = qsIcon;
- type = qsType;
- }
- state.mMobileVisible = icon.visible && !mBlockMobile;
- state.mMobileStrengthId = icon.icon;
- state.mMobileTypeId = type;
- state.mMobileDescription = icon.contentDescription;
+ state.mMobileVisible = statusIcon.visible && !mBlockMobile;
+ state.mMobileStrengthId = statusIcon.icon;
+ state.mMobileTypeId = statusType;
+ state.mMobileDescription = statusIcon.contentDescription;
state.mMobileTypeDescription = typeContentDescription;
- state.mIsMobileTypeIconWide = type != 0 && isWide;
+ state.mIsMobileTypeIconWide = statusType != 0 && isWide;
state.mRoaming = roaming;
state.mActivityIn = activityIn && mActivityEnabled;
state.mActivityOut = activityOut && mActivityEnabled;
@@ -581,10 +576,11 @@
*/
private void setBadgedWifiIconForView(ImageView imageView, @DrawableRes int wifiPieId,
@DrawableRes int badgeId) {
+ // TODO(sghuman): Delete this method and revert to N badging logic
// Using the imageView's context to retrieve the Drawable so that theme is preserved.;
LayerDrawable icon = new LayerDrawable(new Drawable[] {
imageView.getContext().getDrawable(wifiPieId),
- imageView.getContext().getDrawable(badgeId)});
+ imageView.getContext().getDrawable(NetworkBadging.BADGING_NONE)});
// The LayerDrawable shares an underlying state so we must mutate the object to change the
// color between the light and dark themes.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 85475b6..3c7ddb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -34,6 +34,7 @@
import android.graphics.drawable.Icon;
import android.os.Parcelable;
import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.FloatProperty;
@@ -99,7 +100,7 @@
private int mNumberX;
private int mNumberY;
private String mNumberText;
- private Notification mNotification;
+ private StatusBarNotification mNotification;
private final boolean mBlocked;
private int mDensity;
private float mIconScale = 1.0f;
@@ -127,11 +128,11 @@
};
private final NotificationIconDozeHelper mDozer;
- public StatusBarIconView(Context context, String slot, Notification notification) {
- this(context, slot, notification, false);
+ public StatusBarIconView(Context context, String slot, StatusBarNotification sbn) {
+ this(context, slot, sbn, false);
}
- public StatusBarIconView(Context context, String slot, Notification notification,
+ public StatusBarIconView(Context context, String slot, StatusBarNotification sbn,
boolean blocked) {
super(context);
mDozer = new NotificationIconDozeHelper(context);
@@ -141,7 +142,7 @@
mNumberPain.setTextAlign(Paint.Align.CENTER);
mNumberPain.setColor(context.getColor(R.drawable.notification_number_text_color));
mNumberPain.setAntiAlias(true);
- setNotification(notification);
+ setNotification(sbn);
maybeUpdateIconScaleDimens();
setScaleType(ScaleType.CENTER);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
@@ -207,9 +208,11 @@
}
}
- public void setNotification(Notification notification) {
+ public void setNotification(StatusBarNotification notification) {
mNotification = notification;
- setContentDescription(notification);
+ if (notification != null) {
+ setContentDescription(notification.getNotification());
+ }
}
public StatusBarIconView(Context context, AttributeSet attrs) {
@@ -315,6 +318,10 @@
return true;
}
+ public Icon getSourceIcon() {
+ return mIcon.icon;
+ }
+
private Drawable getIcon(StatusBarIcon icon) {
return getIcon(getContext(), icon);
}
@@ -354,7 +361,7 @@
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
if (mNotification != null) {
- event.setParcelableData(mNotification);
+ event.setParcelableData(mNotification.getNotification());
}
}
@@ -456,6 +463,10 @@
+ " notification=" + mNotification + ")";
}
+ public StatusBarNotification getNotification() {
+ return mNotification;
+ }
+
public String getSlot() {
return mSlot;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 7162b31..76177a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -23,9 +23,11 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -33,6 +35,7 @@
import android.view.ViewStub;
import android.view.WindowManager;
import android.widget.LinearLayout;
+
import com.android.systemui.BatteryMeterView;
import com.android.systemui.Dependency;
import com.android.systemui.R;
@@ -41,6 +44,7 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.NavigationBarView;
@@ -62,6 +66,7 @@
private CarBatteryController mCarBatteryController;
private BatteryMeterView mBatteryMeterView;
+ private Drawable mNotificationPanelBackground;
private ConnectedDeviceSignalController mConnectedDeviceSignalController;
private CarNavigationBarView mNavigationBarView;
@@ -91,17 +96,18 @@
protected void makeStatusBarView() {
super.makeStatusBarView();
+ mNotificationPanelBackground = getDefaultWallpaper();
+ mScrimController.setScrimBehindDrawable(mNotificationPanelBackground);
+
FragmentHostManager manager = FragmentHostManager.get(mStatusBarWindow);
manager.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
- mBatteryMeterView = ((BatteryMeterView) fragment.getView().findViewById(
- R.id.battery));
+ mBatteryMeterView = fragment.getView().findViewById(R.id.battery);
// By default, the BatteryMeterView should not be visible. It will be toggled
// when a device has connected by bluetooth.
mBatteryMeterView.setVisibility(View.GONE);
- ViewStub stub = (ViewStub) fragment.getView().findViewById(
- R.id.connected_device_signals_stub);
+ ViewStub stub = fragment.getView().findViewById(R.id.connected_device_signals_stub);
View signalsView = stub.inflate();
// When a ViewStub if inflated, it does not respect the margins on the
@@ -256,7 +262,7 @@
if (userSwitcherController.useFullscreenUserSwitcher()) {
mFullscreenUserSwitcher = new FullscreenUserSwitcher(this,
userSwitcherController,
- (ViewStub) mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub));
+ mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub));
} else {
super.createUserSwitcher();
}
@@ -314,13 +320,46 @@
return startActivityWithOptions(intent, options.toBundle());
}
+ @Override
+ protected boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
+ // Because space is usually constrained in the auto use-case, there should not be a
+ // pinned notification when the shade has been expanded. Ensure this by not pinning any
+ // notification if the shade is already opened.
+ if (mPanelExpanded) {
+ return false;
+ }
+
+ return super.shouldPeek(entry, sbn);
+ }
+
+ @Override
+ public void animateExpandNotificationsPanel() {
+ // Because space is usually constrained in the auto use-case, there should not be a
+ // pinned notification when the shade has been expanded. Ensure this by removing all heads-
+ // up notifications.
+ mHeadsUpManager.removeAllHeadsUpEntries();
+ super.animateExpandNotificationsPanel();
+ }
+
/**
* Ensures that relevant child views are appropriately recreated when the device's density
* changes.
*/
@Override
- protected void onDensityOrFontScaleChanged() {
+ public void onDensityOrFontScaleChanged() {
super.onDensityOrFontScaleChanged();
mController.onDensityOrFontScaleChanged();
+
+ // Need to update the background on density changed in case the change was due to night
+ // mode.
+ mNotificationPanelBackground = getDefaultWallpaper();
+ mScrimController.setScrimBehindDrawable(mNotificationPanelBackground);
+ }
+
+ /**
+ * Returns the {@link Drawable} that represents the wallpaper that the user has currently set.
+ */
+ private Drawable getDefaultWallpaper() {
+ return mContext.getDrawable(com.android.internal.R.drawable.default_wallpaper);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
index f8b6dee..9338887 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -16,8 +16,13 @@
package com.android.systemui.statusbar.car;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.content.res.Resources;
+import android.os.CountDownTimer;
import android.view.View;
import android.view.ViewStub;
+import android.widget.ProgressBar;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -27,30 +32,132 @@
* Manages the fullscreen user switcher.
*/
public class FullscreenUserSwitcher {
+ private final View mContainer;
+ private final View mParent;
+ private final UserGridView mUserGridView;
+ private final UserSwitcherController mUserSwitcherController;
+ private final ProgressBar mProgressBar;
+ private final ProgressBar mSwitchingUsers;
+ private final int mLoginTimeoutMs;
+ private final int mAnimUpdateIntervalMs;
+ private final int mShortAnimDuration;
- private View mContainer;
- private UserGridView mUserGridView;
- private UserSwitcherController mUserSwitcherController;
+ private boolean mShowing;
+
+ private CountDownTimer mTimer;
public FullscreenUserSwitcher(StatusBar statusBar,
UserSwitcherController userSwitcherController,
ViewStub containerStub) {
mUserSwitcherController = userSwitcherController;
- mContainer = containerStub.inflate();
- mUserGridView = (UserGridView) mContainer.findViewById(R.id.user_grid);
+ mParent = containerStub.inflate();
+ mContainer = mParent.findViewById(R.id.container);
+ mUserGridView = mContainer.findViewById(R.id.user_grid);
mUserGridView.init(statusBar, mUserSwitcherController);
+ mUserGridView.setUserSelectionListener(record -> {
+ if (!record.isCurrent) {
+ toggleSwitchInProgress(true);
+ }
+ });
+
+ PageIndicator pageIndicator = mContainer.findViewById(R.id.user_switcher_page_indicator);
+ pageIndicator.setupWithViewPager(mUserGridView);
+
+ mProgressBar = mContainer.findViewById(R.id.countdown_progress);
+ Resources res = mContainer.getResources();
+ mLoginTimeoutMs = res.getInteger(R.integer.car_user_switcher_timeout_ms);
+ mAnimUpdateIntervalMs = res.getInteger(R.integer.car_user_switcher_anim_update_ms);
+ mShortAnimDuration = res.getInteger(android.R.integer.config_shortAnimTime);
+
+ mContainer.findViewById(R.id.start_driving).setOnClickListener(v -> {
+ cancelTimer();
+ automaticallySelectUser();
+ });
+
+ mSwitchingUsers = mParent.findViewById(R.id.switching_users);
}
public void onUserSwitched(int newUserId) {
mUserGridView.onUserSwitched(newUserId);
}
+ private void toggleSwitchInProgress(boolean inProgress) {
+ if (inProgress) {
+ crossFade(mSwitchingUsers, mContainer);
+ } else {
+ crossFade(mContainer, mSwitchingUsers);
+ }
+ }
+
+ private void crossFade(View incoming, View outgoing) {
+ incoming.animate()
+ .alpha(1.0f)
+ .setDuration(mShortAnimDuration)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ incoming.setAlpha(0.0f);
+ incoming.setVisibility(View.VISIBLE);
+ }
+ });
+
+ outgoing.animate()
+ .alpha(0.0f)
+ .setDuration(mShortAnimDuration)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ outgoing.setVisibility(View.GONE);
+ }
+ });
+ }
+
public void show() {
- mContainer.setVisibility(View.VISIBLE);
+ if (mShowing) {
+ return;
+ }
+ mShowing = true;
+ mParent.setVisibility(View.VISIBLE);
+ cancelTimer();
+
+ // This would be the case if we were in the middle of a switch.
+ if (mProgressBar.getVisibility() != View.VISIBLE) {
+ return;
+ }
+
+ mTimer = new CountDownTimer(mLoginTimeoutMs, mAnimUpdateIntervalMs) {
+ @Override
+ public void onTick(long msUntilFinished) {
+ int elapsed = mLoginTimeoutMs - (int) msUntilFinished;
+ mProgressBar.setProgress((int) elapsed, true /* animate */);
+ }
+
+ @Override
+ public void onFinish() {
+ mProgressBar.setProgress(mLoginTimeoutMs, true /* animate */);
+ automaticallySelectUser();
+ }
+ };
+ mTimer.start();
}
public void hide() {
- mContainer.setVisibility(View.GONE);
+ mShowing = false;
+ cancelTimer();
+ toggleSwitchInProgress(false);
+ mParent.setVisibility(View.GONE);
+ }
+
+ private void cancelTimer() {
+ if (mTimer != null) {
+ mTimer.cancel();
+ mTimer = null;
+ }
+ }
+
+ private void automaticallySelectUser() {
+ // TODO: Switch according to some policy. This implementation just tries to drop the
+ // keyguard for the current user.
+ mUserGridView.showOfflineAuthUi();
}
}
-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/PageIndicator.java
new file mode 100644
index 0000000..f7d0906
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/PageIndicator.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.car;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import com.android.systemui.R;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Displays the dots underneath the ViewPager on the lock screen. This is really just a simplified
+ * version of PagerTitleStrip. We don't inherit from there because it's impossible to bypass some
+ * of the overriden logic in that class.
+ */
+public class PageIndicator extends View {
+ private static final String TAG = "PageIndicator";
+ // These can be made a styleable attribute in the future if necessary.
+ private static final int SELECTED_COLOR = 0xFFF5F5F5; // grey 100
+ private static final int UNSELECTED_COLOR = 0xFFBDBDBD; // grey 400
+ private final PageListener mPageListener = new PageListener();
+
+ private ViewPager mPager;
+ private WeakReference<PagerAdapter> mWatchingAdapter;
+
+ private int mPageCount;
+ private int mCurrentPosition;
+ private Paint mPaint;
+ private int mRadius;
+ private int mStep;
+
+ public PageIndicator(Context context) {
+ super(context);
+ init();
+ }
+
+ public PageIndicator(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ private void init() {
+ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mPaint.setStyle(Paint.Style.FILL);
+ mRadius = getResources().getDimensionPixelSize(R.dimen.car_page_indicator_dot_diameter) / 2;
+ mStep = mRadius * 3;
+ }
+
+ public void setupWithViewPager(ViewPager pager) {
+ mPager = pager;
+
+ final PagerAdapter adapter = (PagerAdapter) pager.getAdapter();
+ pager.addOnPageChangeListener(mPageListener);
+ pager.addOnAdapterChangeListener(mPageListener);
+ updateAdapter(mWatchingAdapter != null ? mWatchingAdapter.get() : null, adapter);
+ invalidate();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mPager != null) {
+ updateAdapter(mPager.getAdapter(), null);
+ mPager.removeOnPageChangeListener(mPageListener);
+ mPager.removeOnAdapterChangeListener(mPageListener);
+ mPager = null;
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ int x = canvas.getWidth() / 2 - (mPageCount / 2) * mStep;
+ int y = canvas.getHeight() / 2;
+
+ for (int i = 0; i < mPageCount; i++) {
+ if (i == mCurrentPosition) {
+ mPaint.setColor(SELECTED_COLOR);
+ } else {
+ mPaint.setColor(UNSELECTED_COLOR);
+ }
+
+ canvas.drawCircle(x, y, mRadius, mPaint);
+ x += mStep;
+ }
+ }
+
+ void updateAdapter(PagerAdapter oldAdapter, PagerAdapter newAdapter) {
+ if (oldAdapter != null) {
+ oldAdapter.unregisterDataSetObserver(mPageListener);
+ mWatchingAdapter = null;
+ }
+
+ if (newAdapter != null) {
+ newAdapter.registerDataSetObserver(mPageListener);
+ mWatchingAdapter = new WeakReference<>(newAdapter);
+ }
+
+ updateDots();
+
+ if (mPager != null) {
+ requestLayout();
+ }
+ }
+
+ private <T> T getRef(WeakReference<T> weakRef) {
+ if (weakRef == null) {
+ return null;
+ }
+ return weakRef.get();
+ }
+
+ private void updateDots() {
+ PagerAdapter adapter = getRef(mWatchingAdapter);
+ if (adapter == null) {
+ return;
+ }
+
+ int count = adapter.getCount();
+ if (mPageCount == count) {
+ // Nothing to be done.
+ return;
+ }
+
+ mPageCount = count;
+ mCurrentPosition = 0;
+ invalidate();
+ }
+
+ private class PageListener extends DataSetObserver implements ViewPager.OnPageChangeListener,
+ ViewPager.OnAdapterChangeListener {
+
+ @Override
+ public void onPageScrolled(int unused1, float unused2, int unused3) { }
+
+ @Override
+ public void onPageSelected(int position) {
+ if (mCurrentPosition == position) {
+ return;
+ }
+
+ if (mPageCount <= position) {
+ Log.e(TAG, "Position out of bounds, position=" + position + " size=" + mPageCount);
+ return;
+ }
+
+ mCurrentPosition = position;
+ invalidate();
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) { }
+
+ @Override
+ public void onAdapterChanged(ViewPager viewPager, PagerAdapter oldAdapter,
+ PagerAdapter newAdapter) {
+ updateAdapter(oldAdapter, newAdapter);
+ }
+
+ @Override
+ public void onChanged() {
+ updateDots();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java
index 137b5cf..cfa9864 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java
@@ -17,27 +17,35 @@
package com.android.systemui.statusbar.car;
import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
import android.os.UserHandle;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.GridView;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.internal.util.UserIcons;
import com.android.systemui.R;
import com.android.systemui.statusbar.UserUtil;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.UserSwitcherController;
-public class UserGridView extends GridView {
-
+/**
+ * Displays a ViewPager with icons for the users in the system to allow switching between users.
+ * One of the uses of this is for the lock screen in auto.
+ */
+public class UserGridView extends ViewPager {
private StatusBar mStatusBar;
private UserSwitcherController mUserSwitcherController;
private Adapter mAdapter;
- private int mPendingUserId = UserHandle.USER_NULL;
+ private UserSelectionListener mUserSelectionListener;
public UserGridView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -47,116 +55,184 @@
mStatusBar = statusBar;
mUserSwitcherController = userSwitcherController;
mAdapter = new Adapter(mUserSwitcherController);
+ addOnLayoutChangeListener(mAdapter);
setAdapter(mAdapter);
-
- setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- mPendingUserId = UserHandle.USER_NULL;
- UserSwitcherController.UserRecord record = mAdapter.getItem(position);
- if (record == null) {
- return;
- }
-
- if (record.isGuest || record.isAddUser) {
- mUserSwitcherController.switchTo(record);
- return;
- }
-
- if (record.isCurrent) {
- showOfflineAuthUi();
- } else {
- mPendingUserId = record.info.id;
- mUserSwitcherController.switchTo(record);
- }
- }
- });
-
- setOnItemLongClickListener(new OnItemLongClickListener() {
- @Override
- public boolean onItemLongClick(AdapterView<?> parent,
- View view, int position, long id) {
- UserSwitcherController.UserRecord record = mAdapter.getItem(position);
- if (record == null || record.isAddUser) {
- return false;
- }
- if (record.isGuest) {
- if (record.isCurrent) {
- mUserSwitcherController.switchTo(record);
- }
- return true;
- }
-
- UserUtil.deleteUserWithPrompt(getContext(), record.info.id,
- mUserSwitcherController);
- return true;
- }
- });
}
public void onUserSwitched(int newUserId) {
- if (mPendingUserId == newUserId) {
- // Bring up security view after user switch is completed.
- post(new Runnable() {
- @Override
- public void run() {
- showOfflineAuthUi();
- }
- });
- }
- mPendingUserId = UserHandle.USER_NULL;
+ // Bring up security view after user switch is completed.
+ post(this::showOfflineAuthUi);
}
- private void showOfflineAuthUi() {
+ public void setUserSelectionListener(UserSelectionListener userSelectionListener) {
+ mUserSelectionListener = userSelectionListener;
+ }
+
+ void showOfflineAuthUi() {
// TODO: Show keyguard UI in-place.
mStatusBar.executeRunnableDismissingKeyguard(null, null, true, true, true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- if (widthMode == MeasureSpec.UNSPECIFIED) {
- setNumColumns(AUTO_FIT);
- } else {
- int columnWidth = Math.max(1, getRequestedColumnWidth());
- int itemCount = getAdapter() == null ? 0 : getAdapter().getCount();
- int numColumns = Math.max(1, Math.min(itemCount, widthSize / columnWidth));
- setNumColumns(numColumns);
+ // Wrap content doesn't work in ViewPagers, so simulate the behavior in code.
+ int height = 0;
+ for(int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ child.measure(widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ height = Math.max(child.getMeasuredHeight(), height);
}
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
- private final class Adapter extends UserSwitcherController.BaseUserAdapter {
+ /**
+ * This is a ViewPager.PagerAdapter which deletegates the work to a
+ * UserSwitcherController.BaseUserAdapter. Java doesn't support multiple inheritance so we have
+ * to use composition instead to achieve the same goal since both the base classes are abstract
+ * classes and not interfaces.
+ */
+ private final class Adapter extends PagerAdapter implements View.OnLayoutChangeListener {
+ private final int mPodWidth;
+ private final int mPodMargin;
+
+ private final WrappedBaseUserAdapter mUserAdapter;
+ private int mContainerWidth;
+
public Adapter(UserSwitcherController controller) {
- super(controller);
+ super();
+ mUserAdapter = new WrappedBaseUserAdapter(controller, this);
+ mPodWidth = getResources().getDimensionPixelSize(
+ R.dimen.car_fullscreen_user_pod_image_avatar_width);
+ mPodMargin = getResources().getDimensionPixelSize(
+ R.dimen.car_fullscreen_user_pod_margin_side);
}
@Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- LayoutInflater inflater = (LayoutInflater)getContext().getSystemService
- (Context.LAYOUT_INFLATER_SERVICE);
- convertView = inflater.inflate(R.layout.car_fullscreen_user_pod, null);
- }
- UserSwitcherController.UserRecord record = getItem(position);
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ container.removeView((View) object);
+ }
- TextView nameView = (TextView) convertView.findViewById(R.id.user_name);
+ private int getIconsPerPage() {
+ // We need to know how many pods we need in this page. Each pod has its own width and
+ // margins on both sides. We can then divide the measured width of the parent by the
+ // sum of pod width and margin to get the number of pods that will completely fit.
+ return mContainerWidth / (mPodWidth + mPodMargin * 2);
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ Context context = getContext();
+ LayoutInflater inflater = LayoutInflater.from(context);
+
+ ViewGroup pods = (ViewGroup) inflater.inflate(
+ R.layout.car_fullscreen_user_pod_container, null);
+
+ int iconsPerPage = getIconsPerPage();
+ int limit = Math.min(mUserAdapter.getCount(), (position + 1) * iconsPerPage);
+ for (int i = position * iconsPerPage; i < limit; i++) {
+ pods.addView(makeUserPod(inflater, context, i, pods));
+ }
+ container.addView(pods);
+ return pods;
+ }
+
+ private Drawable getUserIcon(Context context, UserSwitcherController.UserRecord record) {
+ if (record.isAddUser) {
+ Drawable icon = context.getDrawable(R.drawable.ic_add_circle_qs);
+ icon.setTint(Color.WHITE);
+ return icon;
+ }
+ return UserIcons.getDefaultUserIcon(record.resolveId(), /* light= */ true);
+ }
+
+ private View makeUserPod(LayoutInflater inflater, Context context,
+ int position, ViewGroup parent) {
+ final UserSwitcherController.UserRecord record = mUserAdapter.getItem(position);
+ View view = inflater.inflate(R.layout.car_fullscreen_user_pod, parent, false);
+
+ TextView nameView = view.findViewById(R.id.user_name);
if (record != null) {
- nameView.setText(getName(getContext(), record));
- convertView.setActivated(record.isCurrent);
+ nameView.setText(mUserAdapter.getName(context, record));
+ view.setActivated(record.isCurrent);
} else {
- nameView.setText("Unknown");
+ nameView.setText(context.getString(R.string.unknown_user_label));
}
- ImageView iconView = (ImageView) convertView.findViewById(R.id.user_avatar);
+ ImageView iconView = (ImageView) view.findViewById(R.id.user_avatar);
if (record == null || record.picture == null) {
- iconView.setImageDrawable(getDrawable(getContext(), record));
+ iconView.setImageDrawable(getUserIcon(context, record));
} else {
iconView.setImageBitmap(record.picture);
}
- return convertView;
+ iconView.setOnClickListener(v -> {
+ if (record == null) {
+ return;
+ }
+
+ if (mUserSelectionListener != null) {
+ mUserSelectionListener.onUserSelected(record);
+ }
+
+ if (record.isCurrent) {
+ showOfflineAuthUi();
+ } else {
+ mUserSwitcherController.switchTo(record);
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public int getCount() {
+ int iconsPerPage = getIconsPerPage();
+ if (iconsPerPage == 0) {
+ return 0;
+ }
+ return (int) Math.ceil((double) mUserAdapter.getCount() / getIconsPerPage());
+ }
+
+ public void refresh() {
+ mUserAdapter.refresh();
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return view == object;
+ }
+
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ mContainerWidth = Math.max(left - right, right - left);
+ notifyDataSetChanged();
}
}
+
+ private final class WrappedBaseUserAdapter extends UserSwitcherController.BaseUserAdapter {
+ private Adapter mContainer;
+
+ public WrappedBaseUserAdapter(UserSwitcherController controller, Adapter container) {
+ super(controller);
+ mContainer = container;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ throw new UnsupportedOperationException("unused");
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ super.notifyDataSetChanged();
+ mContainer.notifyDataSetChanged();
+ }
+ }
+
+ interface UserSelectionListener {
+ void onUserSelected(UserSwitcherController.UserRecord record);
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AboveShelfChangedListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AboveShelfChangedListener.java
new file mode 100644
index 0000000..07baedc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AboveShelfChangedListener.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+/**
+ * A listener for when the above shelf state of notification changes
+ */
+public interface AboveShelfChangedListener {
+
+ /**
+ * Notifies a listener that the above shelf state changed
+ */
+ void onAboveShelfStateChanged(boolean aboveShelf);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AboveShelfObserver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AboveShelfObserver.java
new file mode 100644
index 0000000..f10d2d7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AboveShelfObserver.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+
+/**
+ * An observer that listens to the above shelf state and can notify listeners
+ */
+public class AboveShelfObserver implements AboveShelfChangedListener {
+
+ private final ViewGroup mHostLayout;
+ private boolean mHasViewsAboveShelf = false;
+ private HasViewAboveShelfChangedListener mListener;
+
+ public AboveShelfObserver(ViewGroup hostLayout) {
+ mHostLayout = hostLayout;
+ }
+
+ public void setListener(HasViewAboveShelfChangedListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void onAboveShelfStateChanged(boolean aboveShelf) {
+ boolean hasViewsAboveShelf = aboveShelf;
+ if (!hasViewsAboveShelf && mHostLayout != null) {
+ int n = mHostLayout.getChildCount();
+ for (int i = 0; i < n; i++) {
+ View child = mHostLayout.getChildAt(i);
+ if (child instanceof ExpandableNotificationRow) {
+ if (((ExpandableNotificationRow) child).isAboveShelf()) {
+ hasViewsAboveShelf = true;
+ break;
+ }
+ }
+ }
+ }
+ if (mHasViewsAboveShelf != hasViewsAboveShelf) {
+ mHasViewsAboveShelf = hasViewsAboveShelf;
+ if (mListener != null) {
+ mListener.onHasViewsAboveShelfChanged(hasViewsAboveShelf);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ boolean hasViewsAboveShelf() {
+ return mHasViewsAboveShelf;
+ }
+
+ public interface HasViewAboveShelfChangedListener {
+ void onHasViewsAboveShelfChanged(boolean hasViewsAboveShelf);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
index e6b3fb8..c891418 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
@@ -48,10 +48,13 @@
@Override
protected boolean sameAs(TransformState otherState) {
+ if (super.sameAs(otherState)) {
+ return true;
+ }
if (otherState instanceof ImageTransformState) {
return mIcon != null && mIcon.sameAs(((ImageTransformState) otherState).getIcon());
}
- return super.sameAs(otherState);
+ return false;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
index 7a34b6e..cf12e94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
@@ -36,8 +36,8 @@
}
@Override
- public void notifyContentUpdated(ExpandableNotificationRow row) {
- super.notifyContentUpdated(row);
+ public void onContentUpdated(ExpandableNotificationRow row) {
+ super.onContentUpdated(row);
updateImageTag(row.getStatusBarNotification());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
index 9476eed..20a3d8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
@@ -41,11 +41,11 @@
}
@Override
- public void notifyContentUpdated(ExpandableNotificationRow row) {
+ public void onContentUpdated(ExpandableNotificationRow row) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
resolveViews(row.getStatusBarNotification());
- super.notifyContentUpdated(row);
+ super.onContentUpdated(row);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
index 0b9244a..fcc982ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
@@ -31,6 +31,7 @@
import com.android.internal.widget.NotificationExpandButton;
import com.android.systemui.Interpolators;
+import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
@@ -61,9 +62,12 @@
private ImageView mWorkProfileImage;
private boolean mIsLowPriority;
private boolean mTransformLowPriorityTitle;
+ private boolean mShowExpandButtonAtEnd;
protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
super(ctx, view, row);
+ mShowExpandButtonAtEnd = ctx.getResources().getBoolean(
+ R.bool.config_showNotificationExpandButtonAtEnd);
mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION);
mTransformationHelper = new ViewTransformationHelper();
@@ -114,6 +118,7 @@
mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
mColor = resolveColor(mExpandButton);
mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header);
+ mNotificationHeader.setShowExpandButtonAtEnd(mShowExpandButtonAtEnd);
getDozer().setColor(mColor);
}
@@ -128,8 +133,8 @@
}
@Override
- public void notifyContentUpdated(ExpandableNotificationRow row) {
- super.notifyContentUpdated(row);
+ public void onContentUpdated(ExpandableNotificationRow row) {
+ super.onContentUpdated(row);
mIsLowPriority = row.isLowPriority();
mTransformLowPriorityTitle = !row.isChildInGroup() && !row.isSummaryWithChildren();
ArraySet<View> previousViews = mTransformationHelper.getAllTransformingViews();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
index 4b08f2b..7eaa290 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
@@ -36,7 +36,6 @@
import com.android.systemui.util.Assert;
import java.util.HashMap;
-import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
@@ -207,7 +206,8 @@
};
applyRemoteView(result, reInflateFlags, flag, row, redactAmbient,
isNewView, remoteViewClickHandler, callback, entry, privateLayout,
- privateLayout.getContractedChild(),
+ privateLayout.getContractedChild(), privateLayout.getVisibleWrapper(
+ NotificationContentView.VISIBLE_TYPE_CONTRACTED),
runningInflations, applyCallback);
}
@@ -229,7 +229,9 @@
};
applyRemoteView(result, reInflateFlags, flag, row,
redactAmbient, isNewView, remoteViewClickHandler, callback, entry,
- privateLayout, privateLayout.getExpandedChild(), runningInflations,
+ privateLayout, privateLayout.getExpandedChild(),
+ privateLayout.getVisibleWrapper(
+ NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations,
applyCallback);
}
}
@@ -252,7 +254,9 @@
};
applyRemoteView(result, reInflateFlags, flag, row,
redactAmbient, isNewView, remoteViewClickHandler, callback, entry,
- privateLayout, privateLayout.getHeadsUpChild(), runningInflations,
+ privateLayout, privateLayout.getHeadsUpChild(),
+ privateLayout.getVisibleWrapper(
+ NotificationContentView.VISIBLE_TYPE_HEADSUP), runningInflations,
applyCallback);
}
}
@@ -274,8 +278,9 @@
};
applyRemoteView(result, reInflateFlags, flag, row,
redactAmbient, isNewView, remoteViewClickHandler, callback, entry,
- publicLayout, publicLayout.getContractedChild(), runningInflations,
- applyCallback);
+ publicLayout, publicLayout.getContractedChild(),
+ publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED),
+ runningInflations, applyCallback);
}
flag = FLAG_REINFLATE_AMBIENT_VIEW;
@@ -296,7 +301,8 @@
};
applyRemoteView(result, reInflateFlags, flag, row,
redactAmbient, isNewView, remoteViewClickHandler, callback, entry,
- newParent, newParent.getAmbientChild(), runningInflations,
+ newParent, newParent.getAmbientChild(), newParent.getVisibleWrapper(
+ NotificationContentView.VISIBLE_TYPE_AMBIENT), runningInflations,
applyCallback);
}
@@ -316,6 +322,7 @@
RemoteViews.OnClickHandler remoteViewClickHandler,
@Nullable final InflationCallback callback, NotificationData.Entry entry,
NotificationContentView parentLayout, View existingView,
+ NotificationViewWrapper existingWrapper,
final HashMap<Integer, CancellationSignal> runningInflations,
ApplyCallback applyCallback) {
RemoteViews.OnViewAppliedListener listener
@@ -326,6 +333,8 @@
if (isNewView) {
v.setIsRootNamespace(true);
applyCallback.setResultView(v);
+ } else if (existingWrapper != null) {
+ existingWrapper.onReinflated();
}
runningInflations.remove(inflationId);
finishIfDone(result, reInflateFlags, runningInflations, callback, row,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
index 8596cb3..eb211a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification;
import android.content.Context;
-import android.service.notification.StatusBarNotification;
import android.view.View;
import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -40,11 +39,11 @@
}
@Override
- public void notifyContentUpdated(ExpandableNotificationRow row) {
+ public void onContentUpdated(ExpandableNotificationRow row) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
resolveViews();
- super.notifyContentUpdated(row);
+ super.onContentUpdated(row);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
index 9631556..f6ee1ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
@@ -70,11 +70,11 @@
}
@Override
- public void notifyContentUpdated(ExpandableNotificationRow row) {
+ public void onContentUpdated(ExpandableNotificationRow row) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
resolveViews();
- super.notifyContentUpdated(row);
+ super.onContentUpdated(row);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index f0b6b2e..bb979eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.graphics.Color;
+import android.graphics.Rect;
import android.service.notification.StatusBarNotification;
import android.view.View;
import android.widget.ImageView;
@@ -41,6 +42,8 @@
private TextView mTitle;
private TextView mText;
private View mActionsContainer;
+ private View mReplyAction;
+ private Rect mTmpRect = new Rect();
private int mContentHeight;
private int mMinHeightHint;
@@ -130,14 +133,37 @@
mProgressBar = null;
}
mActionsContainer = mView.findViewById(com.android.internal.R.id.actions_container);
+ mReplyAction = mView.findViewById(com.android.internal.R.id.reply_icon_action);
}
@Override
- public void notifyContentUpdated(ExpandableNotificationRow row) {
+ public boolean disallowSingleClick(float x, float y) {
+ if (mReplyAction != null && mReplyAction.getVisibility() == View.VISIBLE) {
+ if (isOnView(mReplyAction, x, y) || isOnView(mPicture, x, y)) {
+ return true;
+ }
+ }
+ return super.disallowSingleClick(x, y);
+ }
+
+ private boolean isOnView(View view, float x, float y) {
+ View searchView = (View) view.getParent();
+ while (searchView != null && !(searchView instanceof ExpandableNotificationRow)) {
+ searchView.getHitRect(mTmpRect);
+ x -= mTmpRect.left;
+ y -= mTmpRect.top;
+ searchView = (View) searchView.getParent();
+ }
+ view.getHitRect(mTmpRect);
+ return mTmpRect.contains((int) x,(int) y);
+ }
+
+ @Override
+ public void onContentUpdated(ExpandableNotificationRow row) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
resolveTemplateViews(row.getStatusBarNotification());
- super.notifyContentUpdated(row);
+ super.onContentUpdated(row);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 5cc39cc..5200d69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -66,6 +66,7 @@
mView = view;
mRow = row;
mDozer = createDozer(ctx);
+ onReinflated();
}
protected NotificationDozeHelper createDozer(Context ctx) {
@@ -92,12 +93,15 @@
* Notifies this wrapper that the content of the view might have changed.
* @param row the row this wrapper is attached to
*/
- public void notifyContentUpdated(ExpandableNotificationRow row) {
+ public void onContentUpdated(ExpandableNotificationRow row) {
mDarkInitialized = false;
- Drawable background = mView.getBackground();
+ }
+
+ public void onReinflated() {
if (shouldClearBackgroundOnReapply()) {
mBackgroundColor = 0;
}
+ Drawable background = mView.getBackground();
if (background instanceof ColorDrawable) {
mBackgroundColor = ((ColorDrawable) background).getColor();
mView.setBackground(null);
@@ -182,4 +186,8 @@
public boolean isDimmable() {
return true;
}
+
+ public boolean disallowSingleClick(float x, float y) {
+ return false;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
index 6fe5756..fb4e2ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
@@ -41,6 +41,9 @@
@Override
protected boolean sameAs(TransformState otherState) {
+ if (super.sameAs(otherState)) {
+ return true;
+ }
if (otherState instanceof TextViewTransformState) {
TextViewTransformState otherTvs = (TextViewTransformState) otherState;
if(TextUtils.equals(otherTvs.mText.getText(), mText.getText())) {
@@ -50,7 +53,7 @@
&& getInnerHeight(mText) == getInnerHeight(otherTvs.mText);
}
}
- return super.sameAs(otherState);
+ return false;
}
private int getInnerHeight(TextView text) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index c3f1cb1..a77dd4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -54,6 +54,7 @@
protected View mTransformedView;
private int[] mOwnPosition = new int[2];
+ private boolean mSameAsAny;
private float mTransformationEndY = UNDEFINED;
private float mTransformationEndX = UNDEFINED;
@@ -418,7 +419,7 @@
}
protected boolean sameAs(TransformState otherState) {
- return false;
+ return mSameAsAny;
}
public void appear(float transformationAmount, TransformableView otherView) {
@@ -449,6 +450,9 @@
if (view instanceof ImageView) {
ImageTransformState result = ImageTransformState.obtain();
result.initFrom(view);
+ if (view.getId() == com.android.internal.R.id.reply_icon_action) {
+ ((TransformState) result).setIsSameAsAnyView(true);
+ }
return result;
}
if (view instanceof ProgressBar) {
@@ -461,6 +465,10 @@
return result;
}
+ private void setIsSameAsAnyView(boolean sameAsAny) {
+ mSameAsAny = sameAsAny;
+ }
+
public void recycle() {
reset();
if (getClass() == TransformState.class) {
@@ -514,6 +522,7 @@
protected void reset() {
mTransformedView = null;
+ mSameAsAny = false;
mTransformationEndX = UNDEFINED;
mTransformationEndY = UNDEFINED;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.java
index 020dc25..85d2d73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.java
@@ -15,17 +15,25 @@
package com.android.systemui.statusbar.phone;
import android.content.Context;
+import android.content.om.IOverlayManager;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
import com.android.systemui.ConfigurationChangedReceiver;
import com.android.systemui.statusbar.policy.ConfigurationController;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
public class ConfigurationControllerImpl implements ConfigurationController,
ConfigurationChangedReceiver {
private final ArrayList<ConfigurationListener> mListeners = new ArrayList<>();
+ private final Configuration mLastConfig = new Configuration();
private int mDensity;
private float mFontScale;
@@ -37,14 +45,33 @@
@Override
public void onConfigurationChanged(Configuration newConfig) {
- mListeners.forEach(l -> l.onConfigChanged(newConfig));
+ // Avoid concurrent modification exception
+ ArrayList<ConfigurationListener> listeners = new ArrayList<>(mListeners);
+
+ listeners.forEach(l -> {
+ if (mListeners.contains(l)) {
+ l.onConfigChanged(newConfig);
+ }
+ });
final float fontScale = newConfig.fontScale;
final int density = newConfig.densityDpi;
if (density != mDensity || mFontScale != fontScale) {
- mListeners.forEach(l -> l.onDensityOrFontScaleChanged());
+ listeners.forEach(l -> {
+ if (mListeners.contains(l)) {
+ l.onDensityOrFontScaleChanged();
+ }
+ });
mDensity = density;
mFontScale = fontScale;
}
+
+ if ((mLastConfig.updateFrom(newConfig) & ActivityInfo.CONFIG_ASSETS_PATHS) != 0) {
+ listeners.forEach(l -> {
+ if (mListeners.contains(l)) {
+ l.onOverlayChanged();
+ }
+ });
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index e1ca929..d7e7abe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -77,6 +77,10 @@
return getBoolean("doze.display.supported", R.bool.doze_display_state_supported);
}
+ public boolean getDozeSuspendDisplayStateSupported() {
+ return mContext.getResources().getBoolean(R.bool.doze_suspend_display_state_supported);
+ }
+
public int getPulseDuration(boolean pickup) {
return getPulseInDuration(pickup) + getPulseVisibleDuration() + getPulseOutDuration();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 6fe8827..a4dfcd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -100,6 +100,7 @@
public static final String CAMERA_LAUNCH_SOURCE_AFFORDANCE = "lockscreen_affordance";
public static final String CAMERA_LAUNCH_SOURCE_WIGGLE = "wiggle_gesture";
public static final String CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = "power_double_tap";
+ public static final String CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = "lift_to_launch_ml";
public static final String EXTRA_CAMERA_LAUNCH_SOURCE
= "com.android.systemui.camera_launch_source";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 7370c03..652288d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -16,13 +16,14 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.notification.NotificationUtils.interpolate;
+
import android.content.res.Resources;
import android.graphics.Path;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.PathInterpolator;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.NotificationUtils;
/**
* Utility class to calculate the clock position and top padding of notifications on Keyguard.
@@ -40,6 +41,10 @@
private static final float CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MIN = 1.4f;
private static final float CLOCK_ADJ_TOP_PADDING_MULTIPLIER_MAX = 3.2f;
+ private static final long MILLIS_PER_MINUTES = 1000 * 60;
+ private static final float BURN_IN_PREVENTION_PERIOD_Y = 521;
+ private static final float BURN_IN_PREVENTION_PERIOD_X = 83;
+
private int mClockNotificationsMarginMin;
private int mClockNotificationsMarginMax;
private float mClockYFractionMin;
@@ -52,6 +57,8 @@
private int mKeyguardStatusHeight;
private float mEmptyDragAmount;
private float mDensity;
+ private int mBurnInPreventionOffsetX;
+ private int mBurnInPreventionOffsetY;
/**
* The number (fractional) of notifications the "more" card counts when calculating how many
@@ -86,6 +93,10 @@
(float) res.getDimensionPixelSize(R.dimen.notification_shelf_height) /
res.getDimensionPixelSize(R.dimen.notification_min_height);
mDensity = res.getDisplayMetrics().density;
+ mBurnInPreventionOffsetX = res.getDimensionPixelSize(
+ R.dimen.burn_in_prevention_offset_x);
+ mBurnInPreventionOffsetY = res.getDimensionPixelSize(
+ R.dimen.burn_in_prevention_offset_y);
}
public void setup(int maxKeyguardNotifications, int maxPanelHeight, float expandedHeight,
@@ -122,10 +133,12 @@
y + getClockNotificationsPadding() + mKeyguardStatusHeight);
result.clockAlpha = getClockAlpha(result.clockScale);
- result.stackScrollerPadding = (int) NotificationUtils.interpolate(
+ result.stackScrollerPadding = (int) interpolate(
result.stackScrollerPadding,
mClockBottom + y,
mDarkAmount);
+
+ result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount);
}
private float getClockScale(int notificationPadding, int clockY, int startPadding) {
@@ -154,9 +167,39 @@
private int getClockY() {
// Dark: Align the bottom edge of the clock at one third:
// clockBottomEdge = result - mKeyguardStatusHeight / 2 + mClockBottom
- float clockYDark = (0.33f * mHeight + (float) mKeyguardStatusHeight / 2 - mClockBottom);
+ float clockYDark = (0.33f * mHeight + (float) mKeyguardStatusHeight / 2 - mClockBottom)
+ + burnInPreventionOffsetY();
float clockYRegular = getClockYFraction() * mHeight;
- return (int) NotificationUtils.interpolate(clockYRegular, clockYDark, mDarkAmount);
+ return (int) interpolate(clockYRegular, clockYDark, mDarkAmount);
+ }
+
+ private float burnInPreventionOffsetY() {
+ return zigzag(System.currentTimeMillis() / MILLIS_PER_MINUTES,
+ mBurnInPreventionOffsetY * 2,
+ BURN_IN_PREVENTION_PERIOD_Y)
+ - mBurnInPreventionOffsetY;
+ }
+
+ private float burnInPreventionOffsetX() {
+ return zigzag(System.currentTimeMillis() / MILLIS_PER_MINUTES,
+ mBurnInPreventionOffsetX * 2,
+ BURN_IN_PREVENTION_PERIOD_X)
+ - mBurnInPreventionOffsetX;
+ }
+
+ /**
+ * Implements a continuous, piecewise linear, periodic zig-zag function
+ *
+ * Can be thought of as a linear approximation of abs(sin(x)))
+ *
+ * @param period period of the function, ie. zigzag(x + period) == zigzag(x)
+ * @param amplitude maximum value of the function
+ * @return a value between 0 and amplitude
+ */
+ private float zigzag(float x, float amplitude, float period) {
+ float xprime = (x % period) / (period / 2);
+ float interpolationAmount = (xprime <= 1) ? xprime : (2 - xprime);
+ return interpolate(0, amplitude, interpolationAmount);
}
private float getClockYExpansionAdjustment() {
@@ -230,5 +273,8 @@
* the padding, but not the overall panel size.
*/
public int stackScrollerPaddingAdjustment;
+
+ /** The x translation of the clock. */
+ public int clockX;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 42b09df..f7c2fc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -16,25 +16,37 @@
package com.android.systemui.statusbar.phone;
+import android.annotation.ColorInt;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.settingslib.Utils;
import com.android.systemui.BatteryMeterView;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel;
+import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
+import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
@@ -44,7 +56,7 @@
* The header group on Keyguard.
*/
public class KeyguardStatusBarView extends RelativeLayout
- implements BatteryStateChangeCallback, OnUserInfoChangedListener {
+ implements BatteryStateChangeCallback, OnUserInfoChangedListener, ConfigurationListener {
private boolean mBatteryCharging;
private boolean mKeyguardUserSwitcherShowing;
@@ -63,6 +75,7 @@
private int mSystemIconsSwitcherHiddenExpandedMargin;
private int mSystemIconsBaseMargin;
private View mSystemIconsContainer;
+ private TintedIconManager mIconManager;
public KeyguardStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -203,12 +216,18 @@
mUserSwitcherController = Dependency.get(UserSwitcherController.class);
mMultiUserSwitch.setUserSwitcherController(mUserSwitcherController);
userInfoController.reloadUserInfo();
+ Dependency.get(ConfigurationController.class).addCallback(this);
+ mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
+ Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
+ onOverlayChanged();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Dependency.get(UserInfoController.class).removeCallback(this);
+ Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager);
+ Dependency.get(ConfigurationController.class).removeCallback(this);
}
@Override
@@ -312,4 +331,25 @@
public boolean hasOverlappingRendering() {
return false;
}
+
+ public void onOverlayChanged() {
+ @ColorInt int textColor = Utils.getColorAttr(mContext, R.attr.bgProtectTextColor);
+ mCarrierLabel.setTextColor(textColor);
+ mBatteryView.setFillColor(textColor);
+ mIconManager.setTint(textColor);
+
+ float intensity = textColor == Color.WHITE ? 0 : 1;
+ Rect tintArea = new Rect(0, 0, 0, 0);
+
+ applyDarkness(R.id.signal_cluster, tintArea, intensity, textColor);
+ applyDarkness(R.id.battery, tintArea, intensity, textColor);
+ applyDarkness(R.id.clock, tintArea, intensity, textColor);
+ }
+
+ private void applyDarkness(int id, Rect tintArea, float intensity, int color) {
+ View v = findViewById(id);
+ if (v instanceof DarkReceiver) {
+ ((DarkReceiver) v).onDarkChanged(tintArea, intensity, color);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 99b3aa8..c487901 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -36,6 +36,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.app.WallpaperColors;
import android.util.Log;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -156,6 +157,11 @@
postUpdateWallpaper();
}
+ @Override
+ public void onWallpaperColorsChanged(WallpaperColors colors, int which) {
+
+ }
+
private void postUpdateWallpaper() {
mH.removeCallbacks(this);
mH.post(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index d047fa9..fbad937 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -45,7 +45,6 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
-import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
@@ -94,7 +93,7 @@
*/
public class NavigationBarFragment extends Fragment implements Callbacks {
- private static final String TAG = "NavigationBar";
+ public static final String TAG = "NavigationBar";
private static final boolean DEBUG = false;
private static final String EXTRA_DISABLE_STATE = "disabled_state";
@@ -202,8 +201,7 @@
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
- PowerManager pm = getContext().getSystemService(PowerManager.class);
- notifyNavigationBarScreenOn(pm.isScreenOn());
+ notifyNavigationBarScreenOn();
}
@Override
@@ -378,8 +376,8 @@
((View) mNavigationBarView.getParent()).getLayoutParams());
}
- private void notifyNavigationBarScreenOn(boolean screenOn) {
- mNavigationBarView.notifyScreenOn(screenOn);
+ private void notifyNavigationBarScreenOn() {
+ mNavigationBarView.notifyScreenOn();
}
private void prepareNavigationBarView() {
@@ -604,10 +602,6 @@
return mNavigationBarMode == MODE_SEMI_TRANSPARENT;
}
- public void onKeyguardOccludedChanged(boolean keyguardOccluded) {
- mNavigationBarView.onKeyguardOccludedChanged(keyguardOccluded);
- }
-
public void disableAnimationsDuringHide(long delay) {
mNavigationBarView.setLayoutTransitionsEnabled(false);
mNavigationBarView.postDelayed(() -> mNavigationBarView.setLayoutTransitionsEnabled(true),
@@ -663,10 +657,9 @@
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (Intent.ACTION_SCREEN_OFF.equals(action)) {
- notifyNavigationBarScreenOn(false);
- } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
- notifyNavigationBarScreenOn(true);
+ if (Intent.ACTION_SCREEN_OFF.equals(action)
+ || Intent.ACTION_SCREEN_ON.equals(action)) {
+ notifyNavigationBarScreenOn();
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 8d9d461..cb3222d6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -74,7 +74,6 @@
View[] mRotatedViews = new View[4];
boolean mVertical;
- boolean mScreenOn;
private int mCurrentRotation = -1;
boolean mShowMenu;
@@ -367,8 +366,7 @@
super.setLayoutDirection(layoutDirection);
}
- public void notifyScreenOn(boolean screenOn) {
- mScreenOn = screenOn;
+ public void notifyScreenOn() {
setDisabledFlags(mDisabledFlags, true);
}
@@ -634,9 +632,6 @@
getHomeButton().setVertical(mVertical);
}
- public void onKeyguardOccludedChanged(boolean keyguardOccluded) {
- }
-
private void updateTaskSwitchHelper() {
if (mGestureHelper == null) return;
boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
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 52838b0..41a69b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -4,11 +4,15 @@
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
+import android.graphics.drawable.Icon;
import android.support.annotation.NonNull;
+import android.support.v4.util.ArrayMap;
+import android.support.v4.util.ArraySet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
+import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -180,14 +184,54 @@
}
}
+ // In case we are changing the suppression of a group, the replacement shouldn't flicker
+ // and it should just be replaced instead. We therefore look for notifications that were
+ // just replaced by the child or vice-versa to suppress this.
+ ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons = new ArrayMap<>();
ArrayList<View> toRemove = new ArrayList<>();
for (int i = 0; i < hostLayout.getChildCount(); i++) {
View child = hostLayout.getChildAt(i);
+ if (!(child instanceof StatusBarIconView)) {
+ continue;
+ }
if (!toShow.contains(child)) {
- toRemove.add(child);
+ boolean iconWasReplaced = false;
+ StatusBarIconView removedIcon = (StatusBarIconView) child;
+ String removedGroupKey = removedIcon.getNotification().getGroupKey();
+ for (int j = 0; j < toShow.size(); j++) {
+ StatusBarIconView candidate = toShow.get(j);
+ if (candidate.getSourceIcon().sameAs((removedIcon.getSourceIcon()))
+ && candidate.getNotification().getGroupKey().equals(removedGroupKey)) {
+ if (!iconWasReplaced) {
+ iconWasReplaced = true;
+ } else {
+ iconWasReplaced = false;
+ break;
+ }
+ }
+ }
+ if (iconWasReplaced) {
+ ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(removedGroupKey);
+ if (statusBarIcons == null) {
+ statusBarIcons = new ArrayList<>();
+ replacingIcons.put(removedGroupKey, statusBarIcons);
+ }
+ statusBarIcons.add(removedIcon.getStatusBarIcon());
+ }
+ toRemove.add(removedIcon);
}
}
+ // removing all duplicates
+ ArrayList<String> duplicates = new ArrayList<>();
+ for (String key : replacingIcons.keySet()) {
+ ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(key);
+ if (statusBarIcons.size() != 1) {
+ duplicates.add(key);
+ }
+ }
+ replacingIcons.removeAll(duplicates);
+ hostLayout.setReplacingIcons(replacingIcons);
final int toRemoveCount = toRemove.size();
for (int i = 0; i < toRemoveCount; i++) {
@@ -217,6 +261,7 @@
hostLayout.addView(expected, i);
}
hostLayout.setChangingViewPositions(false);
+ hostLayout.setReplacingIcons(null);
}
/**
@@ -235,4 +280,9 @@
v.setDecorColor(mIconTint);
}
}
+
+ public void setDark(boolean dark) {
+ mNotificationIcons.setDark(dark, false, 0);
+ mShelfIcons.setDark(dark, false, 0);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index f94bb0c..021e11a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -21,9 +21,13 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.drawable.Icon;
+import android.support.v4.util.ArrayMap;
+import android.support.v4.util.ArraySet;
import android.util.AttributeSet;
import android.view.View;
+import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.AlphaOptimizedFrameLayout;
@@ -33,6 +37,7 @@
import com.android.systemui.statusbar.stack.StackStateAnimator;
import com.android.systemui.statusbar.stack.ViewState;
+import java.util.ArrayList;
import java.util.HashMap;
/**
@@ -117,6 +122,8 @@
private float mVisualOverflowAdaption;
private boolean mDisallowNextAnimation;
private boolean mAnimationsEnabled = true;
+ private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons;
+ private int mDarkOffsetX;
public NotificationIconContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -183,11 +190,17 @@
@Override
public void onViewAdded(View child) {
super.onViewAdded(child);
+ boolean isReplacingIcon = isReplacingIcon(child);
if (!mChangingViewPositions) {
- mIconStates.put(child, new IconState());
+ IconState v = new IconState();
+ if (isReplacingIcon) {
+ v.justAdded = false;
+ v.justReplaced = true;
+ }
+ mIconStates.put(child, v);
}
int childIndex = indexOfChild(child);
- if (childIndex < getChildCount() - 1
+ if (childIndex < getChildCount() - 1 && !isReplacingIcon
&& mIconStates.get(getChildAt(childIndex + 1)).iconAppearAmount > 0.0f) {
if (mAddAnimationStartIndex < 0) {
mAddAnimationStartIndex = childIndex;
@@ -200,13 +213,34 @@
}
}
+ private boolean isReplacingIcon(View child) {
+ if (mReplacingIcons == null) {
+ return false;
+ }
+ if (!(child instanceof StatusBarIconView)) {
+ return false;
+ }
+ StatusBarIconView iconView = (StatusBarIconView) child;
+ Icon sourceIcon = iconView.getSourceIcon();
+ String groupKey = iconView.getNotification().getGroupKey();
+ ArrayList<StatusBarIcon> statusBarIcons = mReplacingIcons.get(groupKey);
+ if (statusBarIcons != null) {
+ StatusBarIcon replacedIcon = statusBarIcons.get(0);
+ if (sourceIcon.sameAs(replacedIcon.icon)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
if (child instanceof StatusBarIconView) {
+ boolean isReplacingIcon = isReplacingIcon(child);
final StatusBarIconView icon = (StatusBarIconView) child;
if (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
- && child.getVisibility() == VISIBLE) {
+ && child.getVisibility() == VISIBLE && isReplacingIcon) {
int animationStartIndex = findFirstViewIndexAfter(icon.getTranslationX());
if (mAddAnimationStartIndex < 0) {
mAddAnimationStartIndex = animationStartIndex;
@@ -216,9 +250,11 @@
}
if (!mChangingViewPositions) {
mIconStates.remove(child);
- addTransientView(icon, 0);
- icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, true /* animate */,
- () -> removeTransientView(icon));
+ if (!isReplacingIcon) {
+ addTransientView(icon, 0);
+ icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, true /* animate */,
+ () -> removeTransientView(icon));
+ }
}
}
}
@@ -354,6 +390,14 @@
iconState.xTranslation = getWidth() - iconState.xTranslation - view.getWidth();
}
}
+
+ if (mDark && mDarkOffsetX != 0) {
+ for (int i = 0; i < childCount; i++) {
+ View view = getChildAt(i);
+ IconState iconState = mIconStates.get(view);
+ iconState.xTranslation += mDarkOffsetX;
+ }
+ }
}
private float getLayoutEnd() {
@@ -473,11 +517,20 @@
mAnimationsEnabled = enabled;
}
+ public void setReplacingIcons(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) {
+ mReplacingIcons = replacingIcons;
+ }
+
+ public void setDarkOffsetX(int offsetX) {
+ mDarkOffsetX = offsetX;
+ }
+
public class IconState extends ViewState {
public float iconAppearAmount = 1.0f;
public float clampedAppearAmount = 1.0f;
public int visibleState;
public boolean justAdded = true;
+ private boolean justReplaced;
public boolean needsCannedAnimation;
public boolean useFullTransitionAmount;
public boolean useLinearTransitionAmount;
@@ -496,9 +549,9 @@
&& !mDisallowNextAnimation
&& !noAnimations;
if (animationsAllowed) {
- if (justAdded) {
+ if (justAdded || justReplaced) {
super.applyToView(icon);
- if (iconAppearAmount != 0.0f) {
+ if (justAdded && iconAppearAmount != 0.0f) {
icon.setAlpha(0.0f);
icon.setVisibleState(StatusBarIconView.STATE_HIDDEN,
false /* animate */);
@@ -557,6 +610,7 @@
}
}
justAdded = false;
+ justReplaced = false;
needsCannedAnimation = false;
justUndarkened = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
new file mode 100644
index 0000000..9ff907b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.RemoteException;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.plugins.NotificationListenerController;
+import com.android.systemui.plugins.NotificationListenerController.NotificationProvider;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
+
+import java.util.ArrayList;
+
+/**
+ * A version of NotificationListenerService that passes all info to
+ * any plugins connected. Also allows those plugins the chance to cancel
+ * any incoming callbacks or to trigger new ones.
+ */
+public class NotificationListenerWithPlugins extends NotificationListenerService implements
+ PluginListener<NotificationListenerController> {
+
+ private ArrayList<NotificationListenerController> mPlugins = new ArrayList<>();
+ private boolean mConnected;
+
+ @Override
+ public void registerAsSystemService(Context context, ComponentName componentName,
+ int currentUser) throws RemoteException {
+ super.registerAsSystemService(context, componentName, currentUser);
+ Dependency.get(PluginManager.class).addPluginListener(this,
+ NotificationListenerController.class);
+ }
+
+ @Override
+ public void unregisterAsSystemService() throws RemoteException {
+ super.unregisterAsSystemService();
+ Dependency.get(PluginManager.class).removePluginListener(this);
+ }
+
+ @Override
+ public StatusBarNotification[] getActiveNotifications() {
+ StatusBarNotification[] activeNotifications = super.getActiveNotifications();
+ for (NotificationListenerController plugin : mPlugins) {
+ activeNotifications = plugin.getActiveNotifications(activeNotifications);
+ }
+ return activeNotifications;
+ }
+
+ @Override
+ public RankingMap getCurrentRanking() {
+ RankingMap currentRanking = super.getCurrentRanking();
+ for (NotificationListenerController plugin : mPlugins) {
+ currentRanking = plugin.getCurrentRanking(currentRanking);
+ }
+ return currentRanking;
+ }
+
+ public void onPluginConnected() {
+ mConnected = true;
+ mPlugins.forEach(p -> p.onListenerConnected(getProvider()));
+ }
+
+ /**
+ * Called when listener receives a onNotificationPosted.
+ * Returns true to indicate this callback should be skipped.
+ */
+ public boolean onPluginNotificationPosted(StatusBarNotification sbn,
+ final RankingMap rankingMap) {
+ for (NotificationListenerController plugin : mPlugins) {
+ if (plugin.onNotificationPosted(sbn, rankingMap)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called when listener receives a onNotificationRemoved.
+ * Returns true to indicate this callback should be skipped.
+ */
+ public boolean onPluginNotificationRemoved(StatusBarNotification sbn,
+ final RankingMap rankingMap) {
+ for (NotificationListenerController plugin : mPlugins) {
+ if (plugin.onNotificationRemoved(sbn, rankingMap)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public RankingMap onPluginRankingUpdate(RankingMap rankingMap) {
+ return getCurrentRanking();
+ }
+
+ @Override
+ public void onPluginConnected(NotificationListenerController plugin, Context pluginContext) {
+ mPlugins.add(plugin);
+ if (mConnected) {
+ plugin.onListenerConnected(getProvider());
+ }
+ }
+
+ @Override
+ public void onPluginDisconnected(NotificationListenerController plugin) {
+ mPlugins.remove(plugin);
+ }
+
+ private NotificationProvider getProvider() {
+ return new NotificationProvider() {
+ @Override
+ public StatusBarNotification[] getActiveNotifications() {
+ return NotificationListenerWithPlugins.super.getActiveNotifications();
+ }
+
+ @Override
+ public RankingMap getRankingMap() {
+ return NotificationListenerWithPlugins.super.getCurrentRanking();
+ }
+
+ @Override
+ public void addNotification(StatusBarNotification sbn) {
+ onNotificationPosted(sbn, getRankingMap());
+ }
+
+ @Override
+ public void removeNotification(StatusBarNotification sbn) {
+ onNotificationRemoved(sbn, getRankingMap());
+ }
+
+ @Override
+ public void updateRanking() {
+ onNotificationRankingUpdate(getRankingMap());
+ }
+ };
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 6aab8e1..4701f85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -18,6 +18,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
@@ -26,6 +27,7 @@
import android.content.Context;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@@ -33,6 +35,7 @@
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.MathUtils;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
@@ -41,7 +44,6 @@
import android.view.WindowInsets;
import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
-import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -58,7 +60,9 @@
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyguardAffordanceView;
+import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -111,7 +115,6 @@
private QS mQs;
private FrameLayout mQsFrame;
private KeyguardStatusView mKeyguardStatusView;
- private TextView mClockView;
private View mReserveNotificationSpace;
private View mQsNavbarScrim;
protected NotificationsQuickSettingsContainer mNotificationContainerParent;
@@ -163,8 +166,9 @@
private int mUnlockMoveDistance;
private float mEmptyDragAmount;
- private ObjectAnimator mClockAnimator;
- private int mClockAnimationTarget = -1;
+ private Animator mClockAnimator;
+ private int mClockAnimationTargetX = Integer.MIN_VALUE;
+ private int mClockAnimationTargetY = Integer.MIN_VALUE;
private int mTopPaddingAdjustment;
private KeyguardClockPositionAlgorithm mClockPositionAlgorithm =
new KeyguardClockPositionAlgorithm();
@@ -198,6 +202,7 @@
private int mQsFalsingThreshold;
private float mKeyguardStatusBarAnimateAlpha = 1f;
+ private float mQsClockAlphaOverride = 1f;
private int mOldLayoutDirection;
private HeadsUpTouchHelper mHeadsUpTouchHelper;
private boolean mIsExpansionFromHeadsUp;
@@ -224,11 +229,14 @@
private NotificationGroupManager mGroupManager;
private boolean mShowIconsWhenExpanded;
private int mIndicationBottomPadding;
+ private int mAmbientIndicationBottomPadding;
private boolean mIsFullWidth;
private float mDarkAmount;
private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
private boolean mNoVisibleNotifications = true;
private ValueAnimator mDarkAnimator;
+ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private boolean mUserSetupComplete;
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -240,6 +248,7 @@
public void setStatusBar(StatusBar bar) {
mStatusBar = bar;
+ mKeyguardBottomArea.setStatusBar(mStatusBar);
}
@Override
@@ -247,7 +256,6 @@
super.onFinishInflate();
mKeyguardStatusBar = findViewById(R.id.keyguard_header);
mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
- mClockView = findViewById(R.id.clock_view);
mNotificationContainerParent = (NotificationsQuickSettingsContainer)
findViewById(R.id.notification_container_parent);
@@ -258,10 +266,10 @@
mNotificationStackScroller.setOnEmptySpaceClickListener(this);
mKeyguardBottomArea = findViewById(R.id.keyguard_bottom_area);
mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
- mAffordanceHelper = new KeyguardAffordanceHelper(this, getContext());
- mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper);
mLastOrientation = getResources().getConfiguration().orientation;
+ initBottomArea();
+
mQsFrame = findViewById(R.id.qs_frame);
}
@@ -301,16 +309,18 @@
}
public void updateResources() {
- int panelWidth = getResources().getDimensionPixelSize(R.dimen.notification_panel_width);
+ Resources res = getResources();
+ int qsWidth = res.getDimensionPixelSize(R.dimen.qs_panel_width);
int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
FrameLayout.LayoutParams lp =
(FrameLayout.LayoutParams) mQsFrame.getLayoutParams();
- if (lp.width != panelWidth || lp.gravity != panelGravity) {
- lp.width = panelWidth;
+ if (lp.width != qsWidth || lp.gravity != panelGravity) {
+ lp.width = qsWidth;
lp.gravity = panelGravity;
mQsFrame.setLayoutParams(lp);
}
+ int panelWidth = res.getDimensionPixelSize(R.dimen.notification_panel_width);
lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
if (lp.width != panelWidth || lp.gravity != panelGravity) {
lp.width = panelWidth;
@@ -319,6 +329,38 @@
}
}
+ public void onOverlayChanged() {
+ // Re-inflate the status view group.
+ int index = indexOfChild(mKeyguardStatusView);
+ removeView(mKeyguardStatusView);
+ mKeyguardStatusView = (KeyguardStatusView) LayoutInflater.from(mContext).inflate(
+ R.layout.keyguard_status_view,
+ this,
+ false);
+ addView(mKeyguardStatusView, index);
+
+ // Update keyguard bottom area
+ index = indexOfChild(mKeyguardBottomArea);
+ removeView(mKeyguardBottomArea);
+ mKeyguardBottomArea = (KeyguardBottomAreaView) LayoutInflater.from(mContext).inflate(
+ R.layout.keyguard_bottom_area,
+ this,
+ false);
+ addView(mKeyguardBottomArea, index);
+ initBottomArea();
+ }
+
+ private void initBottomArea() {
+ mAffordanceHelper = new KeyguardAffordanceHelper(this, getContext());
+ mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper);
+ mKeyguardBottomArea.setStatusBar(mStatusBar);
+ mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete);
+ }
+
+ public void setKeyguardIndicationController(KeyguardIndicationController indicationController) {
+ mKeyguardBottomArea.setKeyguardIndicationController(indicationController);
+ }
+
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
@@ -326,7 +368,8 @@
// Update Clock Pivot
mKeyguardStatusView.setPivotX(getWidth() / 2);
- mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize());
+ mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f *
+ mKeyguardStatusView.getClockTextSize());
// Calculate quick setting heights.
int oldMaxHeight = mQsMaxExpansionHeight;
@@ -415,8 +458,9 @@
mDarkAmount);
mClockPositionAlgorithm.run(mClockPositionResult);
if (animate || mClockAnimator != null) {
- startClockAnimation(mClockPositionResult.clockY);
+ startClockAnimation(mClockPositionResult.clockX, mClockPositionResult.clockY);
} else {
+ mKeyguardStatusView.setX(mClockPositionResult.clockX);
mKeyguardStatusView.setY(mClockPositionResult.clockY);
}
updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale);
@@ -424,6 +468,7 @@
mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment;
}
mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
+ mNotificationStackScroller.setDarkShelfOffsetX(mClockPositionResult.clockX);
requestScrollerTopPaddingUpdate(animate);
}
@@ -436,10 +481,11 @@
mKeyguardStatusView.getHeight());
int notificationPadding = Math.max(1, getResources().getDimensionPixelSize(
R.dimen.notification_divider_height));
- float shelfSize = mNotificationStackScroller.getNotificationShelf().getIntrinsicHeight()
- + notificationPadding;
+ NotificationShelf shelf = mNotificationStackScroller.getNotificationShelf();
+ float shelfSize = shelf.getVisibility() == GONE ? 0
+ : shelf.getIntrinsicHeight() + notificationPadding;
float availableSpace = mNotificationStackScroller.getHeight() - minPadding - shelfSize
- - mIndicationBottomPadding;
+ - Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
int count = 0;
for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
@@ -478,11 +524,12 @@
return count;
}
- private void startClockAnimation(int y) {
- if (mClockAnimationTarget == y) {
+ private void startClockAnimation(int x, int y) {
+ if (mClockAnimationTargetX == x && mClockAnimationTargetY == y) {
return;
}
- mClockAnimationTarget = y;
+ mClockAnimationTargetX = x;
+ mClockAnimationTargetY = y;
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
@@ -491,15 +538,20 @@
mClockAnimator.removeAllListeners();
mClockAnimator.cancel();
}
- mClockAnimator = ObjectAnimator
- .ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget);
+ AnimatorSet set = new AnimatorSet();
+ set.play(ObjectAnimator.ofFloat(
+ mKeyguardStatusView, View.Y, mClockAnimationTargetY))
+ .with(ObjectAnimator.ofFloat(
+ mKeyguardStatusView, View.X, mClockAnimationTargetX));
+ mClockAnimator = set;
mClockAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
mClockAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mClockAnimator = null;
- mClockAnimationTarget = -1;
+ mClockAnimationTargetX = Integer.MIN_VALUE;
+ mClockAnimationTargetY = Integer.MIN_VALUE;
}
});
mClockAnimator.start();
@@ -510,7 +562,7 @@
private void updateClock(float alpha, float scale) {
if (!mKeyguardStatusViewAnimating) {
- mKeyguardStatusView.setAlpha(alpha);
+ mKeyguardStatusView.setAlpha(alpha * mQsClockAlphaOverride);
}
mKeyguardStatusView.setScaleX(scale);
mKeyguardStatusView.setScaleY(scale);
@@ -1262,6 +1314,15 @@
mQsNavbarScrim.setAlpha(getQsExpansionFraction());
}
+ // Fade clock when QS is on top of it
+ float newClockAlpha = (height - mKeyguardStatusView.getY()) /
+ mKeyguardStatusView.getHeight();
+ newClockAlpha = 1 - MathUtils.constrain(newClockAlpha, 0, 1);
+ if (newClockAlpha != mQsClockAlphaOverride) {
+ mQsClockAlphaOverride = Interpolators.ALPHA_OUT.getInterpolation(newClockAlpha);
+ updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale);
+ }
+
// Upon initialisation when we are not layouted yet we don't want to announce that we are
// fully expanded, hence the != 0.0f check.
if (height != 0.0f && mQsFullyExpanded && !mLastAnnouncementWasQuickSettings) {
@@ -1657,6 +1718,10 @@
mKeyguardBottomArea.setImportantForAccessibility(alpha == 0f
? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
: IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ View ambientIndicationContainer = mStatusBar.getAmbientIndicationContainer();
+ if (ambientIndicationContainer != null) {
+ ambientIndicationContainer.setAlpha(alpha);
+ }
}
private float getNotificationsTopY() {
@@ -2375,6 +2440,8 @@
mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP;
} else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) {
mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_WIGGLE;
+ } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER) {
+ mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER;
} else {
// Default.
@@ -2529,7 +2596,7 @@
private void setDarkAmount(float amount) {
mDarkAmount = amount;
- mKeyguardStatusView.setDark(amount == 1);
+ mKeyguardStatusView.setDark(mDarkAmount);
positionClockAndNotifications();
}
@@ -2543,4 +2610,41 @@
public void setPulsing(boolean pulsing) {
mKeyguardStatusView.setPulsing(pulsing);
}
+
+ public void setAmbientIndicationBottomPadding(int ambientIndicationBottomPadding) {
+ if (mAmbientIndicationBottomPadding != ambientIndicationBottomPadding) {
+ mAmbientIndicationBottomPadding = ambientIndicationBottomPadding;
+ mStatusBar.updateKeyguardMaxNotifications();
+ }
+ }
+
+ public void refreshTime() {
+ mKeyguardStatusView.refreshTime();
+ if (mDarkAmount > 0) {
+ positionClockAndNotifications();
+ }
+ }
+
+ public void setStatusAccessibilityImportance(int mode) {
+ mKeyguardStatusView.setImportantForAccessibility(mode);
+ }
+
+ /**
+ * TODO: this should be removed.
+ * It's not correct to pass this view forward because other classes will end up adding
+ * children to it. Theme will be out of sync.
+ * @return bottom area view
+ */
+ public KeyguardBottomAreaView getKeyguardBottomAreaView() {
+ return mKeyguardBottomArea;
+ }
+
+ public void setUserSetupComplete(boolean userSetupComplete) {
+ mUserSetupComplete = userSetupComplete;
+ mKeyguardBottomArea.setUserSetupComplete(userSetupComplete);
+ }
+
+ public LockIcon getLockIcon() {
+ return mKeyguardBottomArea.getLockIcon();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index 646f3d8..76cc0ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
+import android.support.annotation.DimenRes;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewStub;
@@ -32,20 +33,20 @@
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.statusbar.NotificationData.Entry;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.notification.AboveShelfObserver;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
/**
* The container with notification stack scroller and quick settings inside.
*/
public class NotificationsQuickSettingsContainer extends FrameLayout
- implements OnInflateListener, FragmentListener, OnHeadsUpChangedListener {
+ implements OnInflateListener, FragmentListener,
+ AboveShelfObserver.HasViewAboveShelfChangedListener {
private FrameLayout mQsFrame;
private View mUserSwitcher;
- private View mStackScroller;
+ private NotificationStackScrollLayout mStackScroller;
private View mKeyguardStatusBar;
private boolean mInflated;
private boolean mQsExpanded;
@@ -53,8 +54,7 @@
private int mBottomPadding;
private int mStackScrollerMargin;
- private boolean mHeadsUp;
- private HeadsUpManager mHeadsUpManager;
+ private boolean mHasViewsAboveShelf;
public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -76,29 +76,27 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
FragmentHostManager.get(this).addTagListener(QS.TAG, this);
- mHeadsUpManager = SysUiServiceProvider.getComponent(getContext(), StatusBar.class)
- .mHeadsUpManager;
- mHeadsUpManager.addListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
FragmentHostManager.get(this).removeTagListener(QS.TAG, this);
- mHeadsUpManager.removeListener(this);
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- reloadWidth(mQsFrame);
- reloadWidth(mStackScroller);
+ reloadWidth(mQsFrame, R.dimen.qs_panel_width);
+ reloadWidth(mStackScroller, R.dimen.notification_panel_width);
}
- private void reloadWidth(View view) {
+ /**
+ * Loads the given width resource and sets it on the given View.
+ */
+ private void reloadWidth(View view, @DimenRes int width) {
LayoutParams params = (LayoutParams) view.getLayoutParams();
- params.width = getContext().getResources().getDimensionPixelSize(
- R.dimen.notification_panel_width);
+ params.width = getResources().getDimensionPixelSize(width);
view.setLayoutParams(params);
}
@@ -114,7 +112,7 @@
boolean userSwitcherVisible = mInflated && mUserSwitcher.getVisibility() == View.VISIBLE;
boolean statusBarVisible = mKeyguardStatusBar.getVisibility() == View.VISIBLE;
- final boolean qsBottom = mHeadsUp;
+ final boolean qsBottom = mHasViewsAboveShelf;
View stackQsTop = qsBottom ? mStackScroller : mQsFrame;
View stackQsBottom = !qsBottom ? mStackScroller : mQsFrame;
// Invert the order of the scroll view and user switcher such that the notifications receive
@@ -181,7 +179,7 @@
setPadding(0, 0, 0, mBottomPadding);
setBottomMargin(mStackScroller, mStackScrollerMargin);
}
-
+ mStackScroller.setQsCustomizerShowing(isShowing);
}
private void setBottomMargin(View v, int bottomMargin) {
@@ -191,12 +189,8 @@
}
@Override
- public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) {
- boolean hasHeadsUp = mHeadsUpManager.getAllEntries().size() != 0;
- if (mHeadsUp == hasHeadsUp) {
- return;
- }
- mHeadsUp = hasHeadsUp;
+ public void onHasViewsAboveShelfChanged(boolean hasViewsAboveShelf) {
+ mHasViewsAboveShelf = hasViewsAboveShelf;
invalidate();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index b1d82b1..b970b1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -28,6 +28,7 @@
import android.util.Log;
import android.view.InputDevice;
import android.view.MotionEvent;
+import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
@@ -1085,21 +1086,25 @@
});
animator.start();
setAnimator(animator);
- mKeyguardBottomArea.getIndicationArea().animate()
- .translationY(-mHintDistance)
- .setDuration(250)
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mKeyguardBottomArea.getIndicationArea().animate()
- .translationY(0)
- .setDuration(450)
- .setInterpolator(mBounceInterpolator)
- .start();
- }
- })
- .start();
+
+ View[] viewsToAnimate = {
+ mKeyguardBottomArea.getIndicationArea(),
+ mStatusBar.getAmbientIndicationContainer()};
+ for (View v : viewsToAnimate) {
+ if (v == null) {
+ continue;
+ }
+ v.animate()
+ .translationY(-mHintDistance)
+ .setDuration(250)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .withEndAction(() -> v.animate()
+ .translationY(0)
+ .setDuration(450)
+ .setInterpolator(mBounceInterpolator)
+ .start())
+ .start();
+ }
}
private void setAnimator(ValueAnimator animator) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 67d5b6a..f502bb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -20,17 +20,21 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
+import android.app.WallpaperManager;
import android.content.Context;
+import android.graphics.Color;
import android.graphics.Rect;
-import android.support.v4.graphics.ColorUtils;
-import android.util.TypedValue;
+import android.graphics.drawable.Drawable;
+import android.util.MathUtils;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
@@ -38,21 +42,29 @@
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.stack.ViewState;
+import com.google.android.colorextraction.ColorExtractor;
+import com.google.android.colorextraction.ColorExtractor.OnColorsChangedListener;
+
/**
* Controls both the scrim behind the notifications and in front of the notifications (when a
* security method gets shown).
*/
public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
- OnHeadsUpChangedListener {
+ OnHeadsUpChangedListener, OnColorsChangedListener {
public static final long ANIMATION_DURATION = 220;
public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR
= new PathInterpolator(0f, 0, 0.7f, 1f);
public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED
= new PathInterpolator(0.3f, 0f, 0.8f, 1f);
- protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.45f;
+ // Default alpha value for most scrims, if unsure use this constant
+ public static final float GRADIENT_SCRIM_ALPHA = 0.60f;
+ // A scrim varies its opacity based on a busyness factor, for example
+ // how many notifications are currently visible.
+ public static final float GRADIENT_SCRIM_ALPHA_BUSY = 0.90f;
+ protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = GRADIENT_SCRIM_ALPHA;
protected static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
- private static final float SCRIM_IN_FRONT_ALPHA = 0.75f;
- private static final float SCRIM_IN_FRONT_ALPHA_LOCKED = 0.85f;
+ private static final float SCRIM_IN_FRONT_ALPHA = GRADIENT_SCRIM_ALPHA;
+ private static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA;
private static final int TAG_KEY_ANIM = R.id.scrim;
private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
@@ -61,11 +73,18 @@
private final LightBarController mLightBarController;
protected final ScrimView mScrimBehind;
- private final ScrimView mScrimInFront;
+ protected final ScrimView mScrimInFront;
private final UnlockMethodCache mUnlockMethodCache;
private final View mHeadsUpScrim;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final ColorExtractor mColorExtractor;
+ private ColorExtractor.GradientColors mLockColors;
+ private ColorExtractor.GradientColors mLockColorsDark;
+ private ColorExtractor.GradientColors mSystemColors;
+ private ColorExtractor.GradientColors mSystemColorsDark;
+ private boolean mNeedsDrawableColorUpdate;
+
protected float mScrimBehindAlpha;
protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
@@ -98,6 +117,7 @@
private boolean mSkipFirstFrame;
private boolean mDontAnimateBouncerChanges;
private boolean mKeyguardFadingOutInProgress;
+ private boolean mAnimatingDozeUnlock;
private ValueAnimator mKeyguardFadeoutAnimation;
public ScrimController(LightBarController lightBarController, ScrimView scrimBehind,
@@ -111,12 +131,26 @@
mLightBarController = lightBarController;
mScrimBehindAlpha = context.getResources().getFloat(R.dimen.scrim_behind_alpha);
+ mColorExtractor = Dependency.get(ColorExtractor.class);
+ mColorExtractor.addOnColorsChangedListener(this);
+ mLockColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK);
+ mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM);
+ // Darker gradient for the top scrim (mScrimInFront)
+ mLockColorsDark = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK,
+ ColorExtractor.TYPE_DARK);
+ mSystemColorsDark = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
+ ColorExtractor.TYPE_DARK);
+ mNeedsDrawableColorUpdate = true;
+
updateHeadsUpScrim(false);
updateScrims();
}
public void setKeyguardShowing(boolean showing) {
mKeyguardShowing = showing;
+
+ // Showing/hiding the keyguard means that scrim colors have to be switched
+ mNeedsDrawableColorUpdate = true;
scheduleUpdate();
}
@@ -157,6 +191,7 @@
public void setWakeAndUnlocking() {
mWakeAndUnlocking = true;
+ mAnimatingDozeUnlock = true;
scheduleUpdate();
}
@@ -192,7 +227,7 @@
public void animateKeyguardUnoccluding(long duration) {
mAnimateChange = false;
- setScrimBehindColor(0f);
+ setScrimBehindAlpha(0f);
mAnimateChange = true;
scheduleUpdate();
mDurationOverride = duration;
@@ -230,12 +265,33 @@
return mDozeInFrontAlpha;
}
+ public void setNotificationCount(int notificationCount) {
+ final float maxNotificationDensity = 3;
+ float notificationDensity = Math.min(notificationCount / maxNotificationDensity, 1f);
+ float newAlpha = MathUtils.map(0, 1,
+ GRADIENT_SCRIM_ALPHA, GRADIENT_SCRIM_ALPHA_BUSY,
+ notificationDensity);
+ if (mScrimBehindAlphaKeyguard != newAlpha) {
+ mScrimBehindAlphaKeyguard = newAlpha;
+ mAnimateChange = true;
+ scheduleUpdate();
+ }
+ }
+
private float getScrimInFrontAlpha() {
return mKeyguardUpdateMonitor.needsSlowUnlockTransition()
? SCRIM_IN_FRONT_ALPHA_LOCKED
: SCRIM_IN_FRONT_ALPHA;
}
+ /**
+ * Sets the given drawable as the background of the scrim that shows up behind the
+ * notifications.
+ */
+ public void setScrimBehindDrawable(Drawable drawable) {
+ mScrimBehind.setDrawable(drawable);
+ }
+
protected void scheduleUpdate() {
if (mUpdatePending) return;
@@ -246,23 +302,39 @@
}
protected void updateScrims() {
- if (mAnimateKeyguardFadingOut || mForceHideScrims) {
- setScrimInFrontColor(0f);
- setScrimBehindColor(0f);
- } else if (mWakeAndUnlocking) {
+ // Make sure we have the right gradients
+ if (mNeedsDrawableColorUpdate) {
+ mNeedsDrawableColorUpdate = false;
+ if (mKeyguardShowing) {
+ // Always animate color changes if we're seeing the keyguard
+ mScrimInFront.setColors(mLockColorsDark);
+ mScrimBehind.setColors(mLockColors);
+ } else {
+ // Only animate scrim color if the scrim view is actually visible
+ boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0;
+ boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0;
+ mScrimInFront.setColors(mSystemColorsDark, animateScrimInFront);
+ mScrimBehind.setColors(mSystemColors, animateScrimBehind);
+ }
+ }
+
+ if (mAnimateKeyguardFadingOut || mForceHideScrims) {
+ setScrimInFrontAlpha(0f);
+ setScrimBehindAlpha(0f);
+ } else if (mWakeAndUnlocking) {
// During wake and unlock, we first hide everything behind a black scrim, which then
// gets faded out from animateKeyguardFadingOut.
if (mDozing) {
- setScrimInFrontColor(0f);
- setScrimBehindColor(1f);
+ setScrimInFrontAlpha(0f);
+ setScrimBehindAlpha(1f);
} else {
- setScrimInFrontColor(1f);
- setScrimBehindColor(0f);
+ setScrimInFrontAlpha(1f);
+ setScrimBehindAlpha(0f);
}
} else if (!mKeyguardShowing && !mBouncerShowing) {
updateScrimNormal();
- setScrimInFrontColor(0);
+ setScrimInFrontAlpha(0);
} else {
updateScrimKeyguard();
}
@@ -275,18 +347,18 @@
float fraction = 1 - behindFraction;
fraction = (float) Math.pow(fraction, 0.8f);
behindFraction = (float) Math.pow(behindFraction, 0.8f);
- setScrimInFrontColor(fraction * getScrimInFrontAlpha());
- setScrimBehindColor(behindFraction * mScrimBehindAlphaKeyguard);
+ setScrimInFrontAlpha(fraction * getScrimInFrontAlpha());
+ setScrimBehindAlpha(behindFraction * mScrimBehindAlphaKeyguard);
} else if (mBouncerShowing && !mBouncerIsKeyguard) {
- setScrimInFrontColor(getScrimInFrontAlpha());
+ setScrimInFrontAlpha(getScrimInFrontAlpha());
updateScrimNormal();
} else if (mBouncerShowing) {
- setScrimInFrontColor(0f);
- setScrimBehindColor(mScrimBehindAlpha);
+ setScrimInFrontAlpha(0f);
+ setScrimBehindAlpha(mScrimBehindAlpha);
} else {
float fraction = Math.max(0, Math.min(mFraction, 1));
- setScrimInFrontColor(0f);
- setScrimBehindColor(fraction
+ setScrimInFrontAlpha(0f);
+ setScrimBehindAlpha(fraction
* (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking)
+ mScrimBehindAlphaUnlocking);
}
@@ -297,30 +369,29 @@
// let's start this 20% of the way down the screen
frac = frac * 1.2f - 0.2f;
if (frac <= 0) {
- setScrimBehindColor(0);
+ setScrimBehindAlpha(0);
} else {
// woo, special effects
final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
- setScrimBehindColor(k * mScrimBehindAlpha);
+ setScrimBehindAlpha(k * mScrimBehindAlpha);
}
}
- private void setScrimBehindColor(float alpha) {
- setScrimColor(mScrimBehind, alpha);
+ private void setScrimBehindAlpha(float alpha) {
+ setScrimAlpha(mScrimBehind, alpha);
}
- private void setScrimInFrontColor(float alpha) {
- setScrimColor(mScrimInFront, alpha);
+ private void setScrimInFrontAlpha(float alpha) {
+ setScrimAlpha(mScrimInFront, alpha);
if (alpha == 0f) {
mScrimInFront.setClickable(false);
} else {
-
// Eat touch events (unless dozing).
mScrimInFront.setClickable(!mDozing);
}
}
- private void setScrimColor(View scrim, float alpha) {
+ private void setScrimAlpha(View scrim, float alpha) {
updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim));
}
@@ -346,15 +417,20 @@
}
}
- protected void updateScrimColor(View scrim) {
+ private void updateScrimColor(View scrim) {
float alpha1 = getCurrentScrimAlpha(scrim);
if (scrim instanceof ScrimView) {
- float alpha2 = getDozeAlpha(scrim);
- float alpha = 1 - (1 - alpha1) * (1 - alpha2);
+ ScrimView scrimView = (ScrimView) scrim;
+ float dozeAlpha = getDozeAlpha(scrim);
+ float alpha = 1 - (1 - alpha1) * (1 - dozeAlpha);
alpha = Math.max(0, Math.min(1.0f, alpha));
- int baseColor = ((ScrimView) scrim).getScrimColor();
- ((ScrimView) scrim).setScrimColor(
- ColorUtils.setAlphaComponent(baseColor, (int) (alpha * 255)));
+ scrimView.setViewAlpha(alpha);
+
+ int dozeTint = Color.TRANSPARENT;
+ if (mAnimatingDozeUnlock || mDozing) {
+ dozeTint = Color.BLACK;
+ }
+ scrimView.setTint(dozeTint);
} else {
scrim.setAlpha(alpha1);
}
@@ -363,13 +439,10 @@
private void startScrimAnimation(final View scrim, float target) {
float current = getCurrentScrimAlpha(scrim);
ValueAnimator anim = ValueAnimator.ofFloat(current, target);
- anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float alpha = (float) animation.getAnimatedValue();
- setCurrentScrimAlpha(scrim, alpha);
- updateScrimColor(scrim);
- }
+ anim.addUpdateListener(animation -> {
+ float alpha = (float) animation.getAnimatedValue();
+ setCurrentScrimAlpha(scrim, alpha);
+ updateScrimColor(scrim);
});
anim.setInterpolator(getInterpolator());
anim.setStartDelay(mAnimationDelay);
@@ -384,6 +457,7 @@
if (mKeyguardFadingOutInProgress) {
mKeyguardFadeoutAnimation = null;
mKeyguardFadingOutInProgress = false;
+ mAnimatingDozeUnlock = false;
}
scrim.setTag(TAG_KEY_ANIM, null);
scrim.setTag(TAG_KEY_ANIM_TARGET, null);
@@ -436,6 +510,8 @@
mOnAnimationFinished = null;
}
mKeyguardFadingOutInProgress = false;
+ if (!mWakeAndUnlocking || force)
+ mAnimatingDozeUnlock = false;
}
}
@@ -559,8 +635,10 @@
mScrimBehind.setExcludedArea(area);
}
- public int getScrimBehindColor() {
- return mScrimBehind.getScrimColorWithAlpha();
+ public int getBackgroundColor() {
+ int color = mLockColors.getMainColor();
+ return Color.argb((int) (mScrimBehind.getAlpha() * Color.alpha(color)),
+ Color.red(color), Color.green(color), Color.blue(color));
}
public void setScrimBehindChangeRunnable(Runnable changeRunnable) {
@@ -577,4 +655,24 @@
public void setCurrentUser(int currentUser) {
// Don't care in the base class.
}
+
+ @Override
+ public void onColorsChanged(ColorExtractor colorExtractor, int which) {
+ if ((which & WallpaperManager.FLAG_LOCK) != 0) {
+ mLockColors = colorExtractor.getColors(WallpaperManager.FLAG_LOCK,
+ ColorExtractor.TYPE_NORMAL);
+ mLockColorsDark = colorExtractor.getColors(WallpaperManager.FLAG_LOCK,
+ ColorExtractor.TYPE_DARK);
+ mNeedsDrawableColorUpdate = true;
+ scheduleUpdate();
+ }
+ if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
+ mSystemColors = colorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
+ ColorExtractor.TYPE_NORMAL);
+ mSystemColorsDark = colorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
+ ColorExtractor.TYPE_DARK);
+ mNeedsDrawableColorUpdate = true;
+ scheduleUpdate();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
index 8cd8791..5ebcde7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
@@ -31,7 +31,6 @@
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.LayoutDirection;
-import android.util.Log;
import com.android.settingslib.R;
import com.android.settingslib.Utils;
@@ -82,6 +81,22 @@
{-1.9f / VIEWPORT, -1.9f / VIEWPORT},
};
+ // Rounded corners are achieved by arcing a circle of radius `mCornerRadius` from its tangent
+ // points along the curve. On the top and left corners of the triangle, the tangents are as
+ // follows:
+ // 1) Along the straight lines (y = 0 and x = width):
+ // Ps = R / tan(45)
+ // 2) Along the diagonal line (y = x):
+ // Pd = √((Ps^2) / 2)
+ //
+ // I.e., the points where we stop the straight lines lie at (starting drawing from the bottom-
+ // right corner and counter-clockwise): (width, height - Radius), (width, Ps), (width - Pd, Pd),
+ // (Pd, height - Pd), (Ps, height), and finally (width - Radius, height).
+ private static final double TAN_THETA = Math.tan(Math.PI / 8.f);
+ private final float mCornerRadius;
+ private final float mCircleOffsetStraight;
+ private final float mCircleOffsetDiag;
+
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint mForegroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final int mDarkModeBackgroundColor;
@@ -91,10 +106,10 @@
private final Path mFullPath = new Path();
private final Path mForegroundPath = new Path();
private final Path mXPath = new Path();
- private final int mIntrinsicSize;
private final Handler mHandler;
private float mOldDarkIntensity = -1;
private float mNumLevels = 1;
+ private int mIntrinsicSize;
private int mLevel;
private int mState;
private boolean mVisible;
@@ -113,6 +128,15 @@
mIntrinsicSize = context.getResources().getDimensionPixelSize(R.dimen.signal_icon_size);
mHandler = new Handler();
setDarkIntensity(0);
+
+ mCornerRadius = context.getResources()
+ .getDimensionPixelSize(R.dimen.stat_sys_mobile_signal_corner_radius);
+ mCircleOffsetStraight = mCornerRadius / (float) TAN_THETA;
+ mCircleOffsetDiag = (float) Math.sqrt(Math.pow(mCircleOffsetStraight, 2.f) / 2.f);
+ }
+
+ public void setIntrinsicSize(int size) {
+ mIntrinsicSize = size;
}
@Override
@@ -204,11 +228,40 @@
mFullPath.setFillType(FillType.WINDING);
float width = getBounds().width();
float height = getBounds().height();
- float padding = (PAD * width);
- mFullPath.moveTo(width - padding, height - padding);
- mFullPath.lineTo(width - padding, padding);
- mFullPath.lineTo(padding, height - padding);
- mFullPath.lineTo(width - padding, height - padding);
+ float padding = (int) (PAD * width); // Stay on pixel boundary
+
+ // 1 - Bottom right, above corner
+ mFullPath.moveTo(width - padding, height - padding - mCornerRadius);
+ // 2 - Line to top right, below corner
+ mFullPath.lineTo(width - padding, padding + mCircleOffsetStraight);
+ // 3 - Arc to top right, on hypotenuse
+ mFullPath.arcTo(
+ width - padding - (2 * mCornerRadius),
+ padding + mCircleOffsetStraight - mCornerRadius,
+ width - padding,
+ padding + mCircleOffsetStraight + mCornerRadius,
+ 0.f, -135.f, false
+ );
+ // 4 - Line to bottom left, on hypotenuse
+ mFullPath.lineTo(padding + mCircleOffsetDiag, height - padding - mCircleOffsetDiag);
+ // 5 - Arc to bottom left, on leg
+ mFullPath.arcTo(
+ padding + mCircleOffsetStraight - mCornerRadius,
+ height - padding - (2 * mCornerRadius),
+ padding + mCircleOffsetStraight + mCornerRadius,
+ height - padding,
+ -135.f, -135.f, false
+ );
+ // 6 - Line to bottom rght, before corner
+ mFullPath.lineTo(width - padding - mCornerRadius, height - padding);
+ // 7 - Arc to beginning (bottom right, above corner)
+ mFullPath.arcTo(
+ width - padding - (2 * mCornerRadius),
+ height - padding - (2 * mCornerRadius),
+ width - padding,
+ height - padding,
+ 90.f, -90.f, false
+ );
if (mState == STATE_CARRIER_CHANGE) {
float cutWidth = (DOT_CUT_WIDTH * width);
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 acc9436..e8b6e07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -21,6 +21,7 @@
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.windowStateToString;
+import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
@@ -35,6 +36,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.StackId;
import android.app.ActivityOptions;
@@ -47,6 +49,7 @@
import android.app.RemoteInput;
import android.app.StatusBarManager;
import android.app.TaskStackBuilder;
+import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
@@ -55,6 +58,8 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
+import android.content.om.IOverlayManager;
+import android.content.om.OverlayInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
@@ -112,7 +117,6 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.IWindowManager;
import android.view.KeyEvent;
@@ -145,7 +149,6 @@
import com.android.internal.util.NotificationMessagingUtil;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardHostView.OnDismissAction;
-import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
@@ -168,8 +171,8 @@
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
+import com.android.systemui.fragments.ExtensionFragmentListener;
import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.fragments.PluginFragmentListener;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QS;
@@ -206,6 +209,7 @@
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.SignalClusterView;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.RowInflaterTask;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -219,6 +223,7 @@
import com.android.systemui.statusbar.policy.DarkIconDispatcher;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
@@ -238,6 +243,8 @@
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.volume.VolumeComponent;
+import com.google.android.colorextraction.ColorExtractor;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -257,7 +264,8 @@
OnHeadsUpChangedListener, VisualStabilityManager.Callback, CommandQueue.Callbacks,
ActivatableNotificationView.OnActivatedListener,
ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
- ExpandableNotificationRow.OnExpandClickListener, InflationCallback {
+ ExpandableNotificationRow.OnExpandClickListener, InflationCallback,
+ ColorExtractor.OnColorsChangedListener, ConfigurationListener {
public static final boolean MULTIUSER_DEBUG = false;
public static final boolean ENABLE_REMOTE_INPUT =
@@ -295,7 +303,7 @@
private static final String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION
= "com.android.systemui.statusbar.work_challenge_unlocked_notification_action";
public static final String TAG = "StatusBar";
- public static final boolean DEBUG = false;
+ public static final boolean DEBUG = true;
public static final boolean SPEW = false;
public static final boolean DUMPTRUCK = true; // extra dumpsys info
public static final boolean DEBUG_GESTURES = false;
@@ -427,13 +435,18 @@
View mExpandedContents;
TextView mNotificationPanelDebugText;
+ /**
+ * {@code true} if notifications not part of a group should by default be rendered in their
+ * expanded state. If {@code false}, then only the first notification will be expanded if
+ * possible.
+ */
+ private boolean mAlwaysExpandNonGroupedNotification;
+
// settings
private QSPanel mQSPanel;
// top bar
protected KeyguardStatusBarView mKeyguardStatusBar;
- KeyguardStatusView mKeyguardStatusView;
- KeyguardBottomAreaView mKeyguardBottomArea;
boolean mLeaveOpenOnKeyguardHide;
KeyguardIndicationController mKeyguardIndicationController;
@@ -501,8 +514,8 @@
mUserSetup = userSetup;
if (!mUserSetup && mStatusBarView != null)
animateCollapseQuickSettings();
- if (mKeyguardBottomArea != null) {
- mKeyguardBottomArea.setUserSetupComplete(mUserSetup);
+ if (mNotificationPanel != null) {
+ mNotificationPanel.setUserSetupComplete(mUserSetup);
}
updateQsExpansionEnabled();
}
@@ -710,13 +723,18 @@
private NetworkController mNetworkController;
private KeyguardMonitorImpl mKeyguardMonitor;
private BatteryController mBatteryController;
- private boolean mPanelExpanded;
+ protected boolean mPanelExpanded;
+ private IOverlayManager mOverlayManager;
+ private boolean mKeyguardRequested;
+ private boolean mIsKeyguard;
private LogMaker mStatusBarStateLog;
private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
private NotificationIconAreaController mNotificationIconAreaController;
- private ConfigurationListener mConfigurationListener;
private boolean mReinflateNotificationsOnUserSwitched;
private HashMap<String, Entry> mPendingNotifications = new HashMap<>();
+ private boolean mClearAllEnabled;
+ @Nullable private View mAmbientIndicationContainer;
+ private ColorExtractor mColorExtractor;
private ForegroundServiceController mForegroundServiceController;
private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
@@ -760,21 +778,31 @@
mAssistManager = Dependency.get(AssistManager.class);
mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
+ mOverlayManager = IOverlayManager.Stub.asInterface(
+ ServiceManager.getService(Context.OVERLAY_SERVICE));
+
+ mColorExtractor = Dependency.get(ColorExtractor.class);
+ mColorExtractor.addOnColorsChangedListener(this);
+
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mForegroundServiceController = Dependency.get(ForegroundServiceController.class);
- mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
mDisplay = mWindowManager.getDefaultDisplay();
updateDisplaySize();
- mScrimSrcModeEnabled = mContext.getResources().getBoolean(
- R.bool.config_status_bar_scrim_behind_use_src);
+
+ Resources res = mContext.getResources();
+ mScrimSrcModeEnabled = res.getBoolean(R.bool.config_status_bar_scrim_behind_use_src);
+ mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
+ mAlwaysExpandNonGroupedNotification =
+ res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER));
putComponent(StatusBar.class, this);
// start old BaseStatusBar.start().
mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
- mDevicePolicyManager = (DevicePolicyManager)mContext.getSystemService(
+ mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
Context.DEVICE_POLICY_SERVICE);
mNotificationData = new NotificationData(this);
@@ -813,7 +841,7 @@
mRecents = getComponent(Recents.class);
- final Configuration currentConfig = mContext.getResources().getConfiguration();
+ final Configuration currentConfig = res.getConfiguration();
mLocale = currentConfig.locale;
mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
@@ -826,7 +854,7 @@
mCommandQueue.addCallbacks(this);
int[] switches = new int[9];
- ArrayList<IBinder> binders = new ArrayList<IBinder>();
+ ArrayList<IBinder> binders = new ArrayList<>();
ArrayList<String> iconSlots = new ArrayList<>();
ArrayList<StatusBarIcon> icons = new ArrayList<>();
Rect fullscreenStackBounds = new Rect();
@@ -850,7 +878,6 @@
// Set up the initial icon state
int N = iconSlots.size();
- int viewIndex = 0;
for (int i=0; i < N; i++) {
mCommandQueue.setIcon(iconSlots.get(i), icons.get(i));
}
@@ -907,8 +934,8 @@
Slog.e(TAG, "Failed to register VR mode state listener: " + e);
}
- mNonBlockablePkgs = new HashSet<String>();
- Collections.addAll(mNonBlockablePkgs, mContext.getResources().getStringArray(
+ mNonBlockablePkgs = new HashSet<>();
+ Collections.addAll(mNonBlockablePkgs, res.getStringArray(
com.android.internal.R.array.config_nonBlockableNotificationPackages));
// end old BaseStatusBar.start().
@@ -945,18 +972,10 @@
Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(this);
- mConfigurationListener = new ConfigurationListener() {
- @Override
- public void onConfigChanged(Configuration newConfig) {
- StatusBar.this.onConfigurationChanged(newConfig);
- }
+ Dependency.get(ConfigurationController.class).addCallback(this);
- @Override
- public void onDensityOrFontScaleChanged() {
- StatusBar.this.onDensityOrFontScaleChanged();
- }
- };
- Dependency.get(ConfigurationController.class).addCallback(mConfigurationListener);
+ // Make sure that we're using the correct theme
+ onOverlayChanged();
}
protected void createIconController() {
@@ -982,6 +1001,9 @@
R.id.notification_stack_scroller);
mNotificationPanel.setStatusBar(this);
mNotificationPanel.setGroupManager(mGroupManager);
+ mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
+ mAboveShelfObserver.setListener(mStatusBarWindow.findViewById(
+ R.id.notification_container_parent));
mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header);
mNotificationIconAreaController = SystemUIFactory.getInstance()
@@ -1005,16 +1027,8 @@
.replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
CollapsedStatusBarFragment.TAG)
.commit();
- Dependency.get(StatusBarIconController.class).addIconGroup(
- new IconManager((ViewGroup) mKeyguardStatusBar.findViewById(R.id.statusIcons)));
mIconController = Dependency.get(StatusBarIconController.class);
- if (!ActivityManager.isHighEndGfx()) {
- mStatusBarWindow.setBackground(null);
- mNotificationPanel.setBackground(new FastColorDrawable(context.getColor(
- R.color.notification_panel_solid_background)));
- }
-
mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow, mGroupManager);
mHeadsUpManager.setBar(this);
mHeadsUpManager.addListener(this);
@@ -1064,15 +1078,15 @@
mLockscreenWallpaper = new LockscreenWallpaper(mContext, this, mHandler);
}
- mKeyguardStatusView =
- (KeyguardStatusView) mStatusBarWindow.findViewById(R.id.keyguard_status_view);
- mKeyguardBottomArea =
- (KeyguardBottomAreaView) mStatusBarWindow.findViewById(R.id.keyguard_bottom_area);
mKeyguardIndicationController =
SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
(ViewGroup) mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
- mKeyguardBottomArea.getLockIcon());
- mKeyguardBottomArea.setKeyguardIndicationController(mKeyguardIndicationController);
+ mNotificationPanel.getLockIcon());
+ mNotificationPanel.setKeyguardIndicationController(mKeyguardIndicationController);
+
+
+ mAmbientIndicationContainer = mStatusBarWindow.findViewById(
+ R.id.ambient_indication_container);
// set the initial view visibility
setAreThereNotifications();
@@ -1122,8 +1136,7 @@
// Other icons
mVolumeComponent = getComponent(VolumeComponent.class);
- mKeyguardBottomArea.setStatusBar(this);
- mKeyguardBottomArea.setUserSetupComplete(mUserSetup);
+ mNotificationPanel.setUserSetupComplete(mUserSetup);
if (UserManager.get(mContext).isUserSwitcherEnabled()) {
createUserSwitcher();
}
@@ -1132,11 +1145,12 @@
View container = mStatusBarWindow.findViewById(R.id.qs_frame);
if (container != null) {
FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
- fragmentHostManager.getFragmentManager().beginTransaction()
- .replace(R.id.qs_frame, new QSFragment(), QS.TAG)
- .commit();
- new PluginFragmentListener(container, QS.TAG, QSFragment.class, QS.class)
- .startListening();
+ ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,
+ Dependency.get(ExtensionController.class).newExtension(QS.class)
+ .withPlugin(QS.class)
+ .withUiMode(UI_MODE_TYPE_CAR, () -> new QSFragment())
+ .withDefault(() -> new QSFragment())
+ .build());
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
mIconController);
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);
@@ -1258,7 +1272,7 @@
mNotificationShelf.setStatusBarState(mState);
}
- protected void onDensityOrFontScaleChanged() {
+ public void onDensityOrFontScaleChanged() {
// start old BaseStatusBar.onDensityOrFontScaleChanged().
if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
updateNotificationsOnDensityOrFontScaleChanged();
@@ -1272,12 +1286,6 @@
if (mBrightnessMirrorController != null) {
mBrightnessMirrorController.onDensityOrFontScaleChanged();
}
- inflateSignalClusters();
- mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
- inflateDismissView();
- updateClearAll();
- inflateEmptyShadeView();
- updateEmptyShadeView();
mStatusBarKeyguardViewManager.onDensityOrFontScaleChanged();
// TODO: Bring these out of StatusBar.
((UserInfoControllerImpl) Dependency.get(UserInfoController.class))
@@ -1286,6 +1294,40 @@
if (mKeyguardUserSwitcher != null) {
mKeyguardUserSwitcher.onDensityOrFontScaleChanged();
}
+ mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
+
+ reevaluateStyles();
+ }
+
+ public void onOverlayChanged() {
+ final boolean usingDarkTheme = isUsingDarkTheme();
+ if (DEBUG) {
+ Log.d(TAG, "Updating theme because overlay changed. Is theme dark? " + usingDarkTheme);
+ }
+ reevaluateStyles();
+
+ // Clock and bottom icons
+ mNotificationPanel.onOverlayChanged();
+
+ // Recreate Indication controller because internal references changed
+ // TODO: unregister callbacks before recreating
+ mKeyguardIndicationController =
+ SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
+ mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
+ mNotificationPanel.getLockIcon());
+ mNotificationPanel.setKeyguardIndicationController(mKeyguardIndicationController);
+ mKeyguardIndicationController
+ .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
+ mKeyguardIndicationController.setVisible(mState == StatusBarState.KEYGUARD);
+ mKeyguardIndicationController.setDozing(mDozing);
+ }
+
+ protected void reevaluateStyles() {
+ inflateSignalClusters();
+ inflateDismissView();
+ updateClearAll();
+ inflateEmptyShadeView();
+ updateEmptyShadeView();
}
private void updateNotificationsOnDensityOrFontScaleChanged() {
@@ -1334,16 +1376,20 @@
}
private void inflateEmptyShadeView() {
+ if (mStackScroller == null) {
+ return;
+ }
mEmptyShadeView = (EmptyShadeView) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_no_notifications, mStackScroller, false);
mStackScroller.setEmptyShadeView(mEmptyShadeView);
}
private void inflateDismissView() {
- // Always inflate with a dark theme, since this sits on the scrim.
- ContextThemeWrapper themedContext = new ContextThemeWrapper(mContext,
- style.Theme_DeviceDefault);
- mDismissView = (DismissView) LayoutInflater.from(themedContext).inflate(
+ if (!mClearAllEnabled || mStackScroller == null) {
+ return;
+ }
+
+ mDismissView = (DismissView) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_notification_dismiss_all, mStackScroller, false);
mDismissView.setOnButtonClickListener(new View.OnClickListener() {
@Override
@@ -1479,10 +1525,8 @@
mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
getBouncerContainer(), mScrimController,
mFingerprintUnlockController);
- mKeyguardIndicationController.setStatusBarKeyguardViewManager(
- mStatusBarKeyguardViewManager);
- mKeyguardIndicationController.setUserInfoController(
- Dependency.get(UserInfoController.class));
+ mKeyguardIndicationController
+ .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mRemoteInputController.addCallback(mStatusBarKeyguardViewManager);
@@ -2079,9 +2123,11 @@
}
private void updateClearAll() {
- boolean showDismissView =
- mState != StatusBarState.KEYGUARD &&
- hasActiveClearableNotifications();
+ if (!mClearAllEnabled) {
+ return;
+ }
+ boolean showDismissView = mState != StatusBarState.KEYGUARD
+ && hasActiveClearableNotifications();
mStackScroller.updateDismissView(showDismissView);
}
@@ -2598,7 +2644,7 @@
public void setQsExpanded(boolean expanded) {
mStatusBarWindowManager.setQsExpanded(expanded);
- mKeyguardStatusView.setImportantForAccessibility(expanded
+ mNotificationPanel.setStatusAccessibilityImportance(expanded
? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
: View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
}
@@ -2792,6 +2838,27 @@
return mNotificationPanel.hideStatusBarIconsWhenExpanded();
}
+ @Override
+ public void onColorsChanged(ColorExtractor extractor, int which) {
+ updateTheme();
+ }
+
+ public boolean isUsingDarkTheme() {
+ OverlayInfo themeInfo = null;
+ try {
+ themeInfo = mOverlayManager.getOverlayInfo("com.android.systemui.theme.dark",
+ mCurrentUserId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ return themeInfo != null && themeInfo.isEnabled();
+ }
+
+ @Nullable
+ public View getAmbientIndicationContainer() {
+ return mAmbientIndicationContainer;
+ }
+
/**
* All changes to the status bar and notifications funnel through here and are batched.
*/
@@ -3832,10 +3899,6 @@
}
}
- public void onKeyguardOccludedChanged(boolean keyguardOccluded) {
- mNavigationBar.onKeyguardOccludedChanged(keyguardOccluded);
- }
-
// State logging
private void logStateToEventlog() {
@@ -4003,7 +4066,7 @@
}
Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(null);
mDeviceProvisionedController.removeCallback(mUserSetupObserver);
- Dependency.get(ConfigurationController.class).removeCallback(mConfigurationListener);
+ Dependency.get(ConfigurationController.class).removeCallback(this);
}
private boolean mDemoModeAllowed;
@@ -4090,6 +4153,36 @@
}
public void showKeyguard() {
+ mKeyguardRequested = true;
+ // Unconditionally show keyguard again. There's some logic that relies on this
+ // being called even when the keyguard is already showing, e.g. for updating
+ // sensitiveness of notifications and making sure the panels are expanded.
+ showKeyguardImpl();
+ }
+
+ public boolean hideKeyguard() {
+ mKeyguardRequested = false;
+ return updateIsKeyguard();
+ }
+
+ private boolean updateIsKeyguard() {
+ // For dozing, keyguard needs to be shown whenever the device is non-interactive. Otherwise
+ // there's no surface we can show to the user.
+ boolean keyguardForDozing = mDozingRequested && !mDeviceInteractive;
+ boolean shouldBeKeyguard = mKeyguardRequested || keyguardForDozing;
+ if (keyguardForDozing) {
+ updatePanelExpansionForKeyguard();
+ }
+ if (shouldBeKeyguard) {
+ showKeyguardImpl();
+ } else if (!shouldBeKeyguard && mIsKeyguard) {
+ return hideKeyguardImpl();
+ }
+ return false;
+ }
+
+ public void showKeyguardImpl() {
+ mIsKeyguard = true;
if (mLaunchTransitionFadingAway) {
mNotificationPanel.animate().cancel();
onLaunchTransitionFadingEnded();
@@ -4101,11 +4194,7 @@
setBarState(StatusBarState.KEYGUARD);
}
updateKeyguardState(false /* goingToFullShade */, false /* fromShadeLocked */);
- if (mState == StatusBarState.KEYGUARD) {
- instantExpandNotificationsPanel();
- } else if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
- instantCollapseNotificationPanel();
- }
+ updatePanelExpansionForKeyguard();
mLeaveOpenOnKeyguardHide = false;
if (mDraggedDownRow != null) {
mDraggedDownRow.setUserLocked(false);
@@ -4116,6 +4205,14 @@
mAssistManager.onLockscreenShown();
}
+ private void updatePanelExpansionForKeyguard() {
+ if (mState == StatusBarState.KEYGUARD) {
+ instantExpandNotificationsPanel();
+ } else if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
+ instantCollapseNotificationPanel();
+ }
+ }
+
private void onLaunchTransitionFadingEnded() {
mNotificationPanel.setAlpha(1.0f);
mNotificationPanel.onAffordanceLaunchEnded();
@@ -4237,7 +4334,8 @@
/**
* @return true if we would like to stay in the shade, false if it should go away entirely
*/
- public boolean hideKeyguard() {
+ public boolean hideKeyguardImpl() {
+ mIsKeyguard = false;
Trace.beginSection("StatusBar#hideKeyguard");
boolean staying = mLeaveOpenOnKeyguardHide;
setBarState(StatusBarState.SHADE);
@@ -4376,6 +4474,9 @@
mKeyguardUserSwitcher.setKeyguard(true, fromShadeLocked);
}
mStatusBarView.removePendingHideExpandedRunnables();
+ if (mAmbientIndicationContainer != null) {
+ mAmbientIndicationContainer.setVisibility(View.VISIBLE);
+ }
} else {
mKeyguardIndicationController.setVisible(false);
if (mKeyguardUserSwitcher != null) {
@@ -4384,6 +4485,9 @@
mState == StatusBarState.SHADE_LOCKED ||
fromShadeLocked);
}
+ if (mAmbientIndicationContainer != null) {
+ mAmbientIndicationContainer.setVisibility(View.INVISIBLE);
+ }
}
if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
mScrimController.setKeyguardShowing(true);
@@ -4391,6 +4495,7 @@
mScrimController.setKeyguardShowing(false);
}
mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade);
+ updateTheme();
updateDozingState();
updatePublicMode();
updateStackScrollerState(goingToFullShade, fromShadeLocked);
@@ -4403,6 +4508,37 @@
Trace.endSection();
}
+ /**
+ * Switches theme from light to dark and vice-versa.
+ */
+ private void updateTheme() {
+ boolean useDarkTheme;
+ // Ignore visibility since we calculate the theme based on the real colors,
+ // not the current state.
+ if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
+ useDarkTheme = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK)
+ .supportsDarkText();
+ } else {
+ useDarkTheme = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM)
+ .supportsDarkText();
+ }
+
+ // Enable/Disable dark overlay
+ if (isUsingDarkTheme() != useDarkTheme) {
+ if (DEBUG) {
+ Log.d(TAG, "Switching theme to: " + (useDarkTheme ? "Dark" : "Light"));
+ }
+ try {
+ mOverlayManager.setEnabled("com.android.systemui.theme.dark",
+ useDarkTheme, mCurrentUserId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Can't change theme", e);
+ return;
+ }
+ mStatusBarWindowManager.setKeyguardDark(useDarkTheme);
+ }
+ }
+
private void updateDozingState() {
Trace.beginSection("StatusBar#updateDozingState");
boolean animate = !mDozing && mDozeServiceHost.shouldAnimateWakeup();
@@ -4504,7 +4640,6 @@
}
private void instantExpandNotificationsPanel() {
-
// Make our window larger and the panel expanded.
makeExpandedVisible(true);
mNotificationPanel.expand(false /* animate */);
@@ -4516,6 +4651,11 @@
@Override
public void onActivated(ActivatableNotificationView view) {
+ onActivated((View)view);
+ mStackScroller.setActivatedChild(view);
+ }
+
+ public void onActivated(View view) {
mLockscreenGestureLogger.write(
MetricsEvent.ACTION_LS_NOTE,
0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
@@ -4524,7 +4664,6 @@
if (previousView != null) {
previousView.makeInactive(true /* animate */);
}
- mStackScroller.setActivatedChild(view);
}
/**
@@ -4551,17 +4690,22 @@
mStackScroller.setStatusBarState(state);
updateReportRejectedTouchVisibility();
updateDozing();
+ updateTheme();
mNotificationShelf.setStatusBarState(state);
}
@Override
public void onActivationReset(ActivatableNotificationView view) {
if (view == mStackScroller.getActivatedChild()) {
- mKeyguardIndicationController.hideTransientIndication();
mStackScroller.setActivatedChild(null);
+ onActivationReset((View)view);
}
}
+ public void onActivationReset(View view) {
+ mKeyguardIndicationController.hideTransientIndication();
+ }
+
public void onTrackingStarted() {
runPostCollapseRunnables();
}
@@ -4622,9 +4766,21 @@
return getMaxKeyguardNotifications(false /* recompute */);
}
- // TODO: Figure out way to remove this.
+ // TODO: Figure out way to remove these.
public NavigationBarView getNavigationBarView() {
- return (NavigationBarView) mNavigationBar.getView();
+ return (mNavigationBar != null ? (NavigationBarView) mNavigationBar.getView() : null);
+ }
+
+ public View getNavigationBarWindow() {
+ return mNavigationBarView;
+ }
+
+ /**
+ * TODO: Remove this method. Views should not be passed forward. Will cause theme issues.
+ * @return bottom area view
+ */
+ public KeyguardBottomAreaView getKeyguardBottomAreaView() {
+ return mNotificationPanel.getKeyguardBottomAreaView();
}
// ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
@@ -4633,7 +4789,8 @@
/* Only ever called as a consequence of a lockscreen expansion gesture. */
@Override
public boolean onDraggedDown(View startingChild, int dragLengthY) {
- if (hasActiveNotifications() && (!isDozing() || isPulsing())) {
+ if (mState == StatusBarState.KEYGUARD
+ && hasActiveNotifications() && (!isDozing() || isPulsing())) {
mLockscreenGestureLogger.write(
MetricsEvent.ACTION_LS_SHADE,
(int) (dragLengthY / mDisplayMetrics.density),
@@ -4920,6 +5077,7 @@
}
});
}
+ updateIsKeyguard();
}
public void onStartedWakingUp() {
@@ -4928,6 +5086,7 @@
mVisualStabilityManager.setScreenOn(true);
mNotificationPanel.setTouchDisabled(false);
updateVisibleToUser();
+ updateIsKeyguard();
}
public void onScreenTurningOn() {
@@ -5041,6 +5200,7 @@
|| mFingerprintUnlockController.getMode()
== FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
mStatusBarWindowManager.setDozing(mDozing);
+ mStatusBarKeyguardViewManager.setDozing(mDozing);
updateDozingState();
Trace.endSection();
}
@@ -5086,6 +5246,7 @@
mDozingRequested = true;
DozeLog.traceDozing(mContext, mDozing);
updateDozing();
+
}
}
@@ -5130,7 +5291,7 @@
@Override
public void dozeTimeTick() {
- mKeyguardStatusView.refreshTime();
+ mNotificationPanel.refreshTime();
}
@Override
@@ -5185,6 +5346,8 @@
// for heads up notifications
protected HeadsUpManager mHeadsUpManager;
+ private AboveShelfObserver mAboveShelfObserver;
+
// handling reordering
protected VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
@@ -5606,11 +5769,12 @@
}
};
- private final NotificationListenerService mNotificationListener =
- new NotificationListenerService() {
+ private final NotificationListenerWithPlugins mNotificationListener =
+ new NotificationListenerWithPlugins() {
@Override
public void onListenerConnected() {
if (DEBUG) Log.d(TAG, "onListenerConnected");
+ onPluginConnected();
final StatusBarNotification[] notifications = getActiveNotifications();
if (notifications == null) {
Log.w(TAG, "onListenerConnected unable to get active notifications.");
@@ -5635,7 +5799,7 @@
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
- if (sbn != null) {
+ if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -5679,14 +5843,9 @@
public void onNotificationRemoved(StatusBarNotification sbn,
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
- if (sbn != null) {
+ if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
final String key = sbn.getKey();
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- removeNotification(key, rankingMap);
- }
- });
+ mHandler.post(() -> removeNotification(key, rankingMap));
}
}
@@ -5694,13 +5853,10 @@
public void onNotificationRankingUpdate(final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onRankingUpdate");
if (rankingMap != null) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- updateNotificationRanking(rankingMap);
- }
- });
- } }
+ RankingMap r = onPluginRankingUpdate(rankingMap);
+ mHandler.post(() -> updateNotificationRanking(r));
+ }
+ }
};
@@ -6121,6 +6277,9 @@
}
public boolean isLockscreenPublicMode(int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ return mLockscreenPublicMode.get(mCurrentUserId, false);
+ }
return mLockscreenPublicMode.get(userId, false);
}
@@ -6219,6 +6378,10 @@
* Called when the notification panel layouts
*/
public void onPanelLaidOut() {
+ updateKeyguardMaxNotifications();
+ }
+
+ public void updateKeyguardMaxNotifications() {
if (mState == StatusBarState.KEYGUARD) {
// Since the number of notifications is determined based on the height of the view, we
// need to update them.
@@ -6253,6 +6416,7 @@
row.setExpansionLogger(this, entry.notification.getKey());
row.setGroupManager(mGroupManager);
row.setHeadsUpManager(mHeadsUpManager);
+ row.setAboveShelfChangedListener(mAboveShelfObserver);
row.setRemoteInputController(mRemoteInputController);
row.setOnExpandClickListener(this);
row.setRemoteViewClickHandler(mOnClickHandler);
@@ -6693,13 +6857,18 @@
while(!stack.isEmpty()) {
ExpandableNotificationRow row = stack.pop();
NotificationData.Entry entry = row.getEntry();
- boolean childNotification = mGroupManager.isChildInGroupWithSummary(entry.notification);
- if (onKeyguard) {
- row.setOnKeyguard(true);
- } else {
- row.setOnKeyguard(false);
- row.setSystemExpanded(visibleNotifications == 0 && !childNotification);
+ boolean isChildNotification =
+ mGroupManager.isChildInGroupWithSummary(entry.notification);
+
+ row.setOnKeyguard(onKeyguard);
+
+ if (!onKeyguard) {
+ // If mAlwaysExpandNonGroupedNotification is false, then only expand the
+ // very first notification and if it's not a child of grouped notifications.
+ row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
+ || (visibleNotifications == 0 && !isChildNotification));
}
+
entry.row.setShowAmbient(isDozing());
int userId = entry.notification.getUserId();
boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
@@ -6714,7 +6883,7 @@
if (wasGone) {
entry.row.setVisibility(View.VISIBLE);
}
- if (!childNotification && !entry.row.isRemoved()) {
+ if (!isChildNotification && !entry.row.isRemoved()) {
if (wasGone) {
// notify the scroller of a child addition
mStackScroller.generateAddAnimation(entry.row,
@@ -6734,9 +6903,26 @@
}
mNotificationPanel.setNoVisibleNotifications(visibleNotifications == 0);
- mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1);
- mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2);
- mStackScroller.changeViewPosition(mNotificationShelf, mStackScroller.getChildCount() - 3);
+ // The following views will be moved to the end of mStackScroller. This counter represents
+ // the offset from the last child. Initialized to 1 for the very last position. It is post-
+ // incremented in the following "changeViewPosition" calls so that its value is correct for
+ // subsequent calls.
+ int offsetFromEnd = 1;
+ if (mDismissView != null) {
+ mStackScroller.changeViewPosition(mDismissView,
+ mStackScroller.getChildCount() - offsetFromEnd++);
+ }
+
+ mStackScroller.changeViewPosition(mEmptyShadeView,
+ mStackScroller.getChildCount() - offsetFromEnd++);
+
+ // No post-increment for this call because it is the last one. Make sure to add one if
+ // another "changeViewPosition" call is ever added.
+ mStackScroller.changeViewPosition(mNotificationShelf,
+ mStackScroller.getChildCount() - offsetFromEnd);
+
+ // Scrim opacity varies based on notification count
+ mScrimController.setNotificationCount(mStackScroller.getNotGoneChildCount());
}
public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
@@ -6799,7 +6985,7 @@
entry.notification = notification;
mGroupManager.onEntryUpdated(entry, oldNotification);
- entry.updateIcons(mContext, n);
+ entry.updateIcons(mContext, notification);
inflateViews(entry, mStackScroller);
mForegroundServiceController.updateNotification(notification,
@@ -6979,6 +7165,10 @@
}
}
+ public NotificationPanelView getPanel() {
+ return mNotificationPanel;
+ }
+
@Override
public void startAssist(Bundle args) {
if (mAssistManager != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index a3ef91d..c240765 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -14,7 +14,10 @@
package com.android.systemui.statusbar.phone;
+import android.annotation.ColorInt;
import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -26,6 +29,7 @@
import android.widget.LinearLayout.LayoutParams;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.StatusBarIconView;
@@ -57,7 +61,6 @@
return ret;
}
-
/**
* Version of ViewGroup that observers state from the DarkIconDispatcher.
*/
@@ -108,6 +111,31 @@
}
}
+ public static class TintedIconManager extends IconManager {
+ private int mColor;
+
+ public TintedIconManager(ViewGroup group) {
+ super(group);
+ }
+
+ @Override
+ protected void onIconAdded(int index, String slot, boolean blocked, StatusBarIcon icon) {
+ StatusBarIconView v = addIcon(index, slot, blocked, icon);
+ v.setStaticDrawableColor(mColor);
+ }
+
+ public void setTint(int color) {
+ mColor = color;
+ for (int i = 0; i < mGroup.getChildCount(); i++) {
+ View child = mGroup.getChildAt(i);
+ if (child instanceof StatusBarIconView) {
+ StatusBarIconView icon = (StatusBarIconView) child;
+ icon.setStaticDrawableColor(mColor);
+ }
+ }
+ }
+ }
+
/**
* Turns info from StatusBarIconController into ImageViews in a ViewGroup.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index bb302bb..7d69a34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -85,6 +85,7 @@
protected boolean mShowing;
protected boolean mOccluded;
protected boolean mRemoteInputActive;
+ private boolean mDozing;
protected boolean mFirstUpdate = true;
protected boolean mLastShowing;
@@ -92,6 +93,7 @@
private boolean mLastBouncerShowing;
private boolean mLastBouncerDismissible;
protected boolean mLastRemoteInputActive;
+ private boolean mLastDozing;
private OnDismissAction mAfterKeyguardGoneAction;
private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>();
@@ -254,6 +256,11 @@
updateStates();
}
+ public void setDozing(boolean dozing) {
+ mDozing = dozing;
+ updateStates();
+ }
+
public void onScreenTurnedOff() {
mScreenTurnedOn = false;
mStatusBar.onScreenTurnedOff();
@@ -272,9 +279,6 @@
}
public void setOccluded(boolean occluded, boolean animate) {
- if (occluded != mOccluded) {
- mStatusBar.onKeyguardOccludedChanged(occluded);
- }
if (occluded && !mOccluded && mShowing) {
if (mStatusBar.isInLaunchTransition()) {
mOccluded = true;
@@ -497,11 +501,11 @@
private long getNavBarShowDelay() {
if (mStatusBar.isKeyguardFadingAway()) {
return mStatusBar.getKeyguardFadingAwayDelay();
- } else {
-
- // Keyguard is not going away, thus we are showing the navigation bar because the
- // bouncer is appearing.
+ } else if (mBouncer.isShowing()) {
return NAV_BAR_SHOW_DELAY_BOUNCER;
+ } else {
+ // No longer dozing, or remote input is active. No delay.
+ return 0;
}
}
@@ -569,6 +573,7 @@
mLastBouncerShowing = bouncerShowing;
mLastBouncerDismissible = bouncerDismissible;
mLastRemoteInputActive = remoteInputActive;
+ mLastDozing = mDozing;
mStatusBar.onKeyguardViewManagerStatesUpdated();
}
@@ -577,14 +582,15 @@
* @return Whether the navigation bar should be made visible based on the current state.
*/
protected boolean isNavBarVisible() {
- return !(mShowing && !mOccluded) || mBouncer.isShowing() || mRemoteInputActive;
+ return !(mShowing && !mOccluded) && !mDozing || mBouncer.isShowing() || mRemoteInputActive;
}
/**
* @return Whether the navigation bar was made visible based on the last known state.
*/
protected boolean getLastNavBarVisible() {
- return !(mLastShowing && !mLastOccluded) || mLastBouncerShowing || mLastRemoteInputActive;
+ return !(mLastShowing && !mLastOccluded) && !mLastDozing || mLastBouncerShowing
+ || mLastRemoteInputActive;
}
public boolean shouldDismissOnMenuPressed() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index 0326e40..93dc6de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -111,6 +111,18 @@
mLpChanged.copyFrom(mLp);
}
+ public void setKeyguardDark(boolean dark) {
+ int vis = mStatusBarView.getSystemUiVisibility();
+ if (dark) {
+ vis = vis | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+ vis = vis | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+ } else {
+ vis = vis & ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+ vis = vis & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+ }
+ mStatusBarView.setSystemUiVisibility(vis);
+ }
+
private void applyKeyguardFlags(State state) {
if (state.keyguardShowing) {
mLpChanged.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index f58a91b..adc33a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -53,6 +53,7 @@
import android.view.WindowManagerGlobal;
import android.widget.FrameLayout;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.view.FloatingActionMode;
import com.android.internal.widget.FloatingToolbar;
import com.android.systemui.R;
@@ -182,7 +183,12 @@
public void setService(StatusBar service) {
mService = service;
- mDragDownHelper = new DragDownHelper(getContext(), this, mStackScrollLayout, mService);
+ setDragDownHelper(new DragDownHelper(getContext(), this, mStackScrollLayout, mService));
+ }
+
+ @VisibleForTesting
+ void setDragDownHelper(DragDownHelper dragDownHelper) {
+ mDragDownHelper = dragDownHelper;
}
@Override
@@ -309,8 +315,8 @@
mDoubleTapHelper.onTouchEvent(ev);
handled = true;
}
- if (mService.getBarState() == StatusBarState.KEYGUARD
- && (!handled || mDragDownHelper.isDraggingDown())) {
+ if ((mService.getBarState() == StatusBarState.KEYGUARD && !handled)
+ || mDragDownHelper.isDraggingDown()) {
// we still want to finish our drag down gesture when locking the screen
handled = mDragDownHelper.onTouchEvent(ev);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index fc86ac3..e8d5af6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -207,14 +207,19 @@
registerReceiver();
updatePowerSave();
} else if (mDemoMode && command.equals(COMMAND_BATTERY)) {
- String level = args.getString("level");
- String plugged = args.getString("plugged");
- if (level != null) {
- mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
- }
- if (plugged != null) {
- mPluggedIn = Boolean.parseBoolean(plugged);
- }
+ String level = args.getString("level");
+ String plugged = args.getString("plugged");
+ String powerSave = args.getString("powersave");
+ if (level != null) {
+ mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
+ }
+ if (plugged != null) {
+ mPluggedIn = Boolean.parseBoolean(plugged);
+ }
+ if (powerSave != null) {
+ mPowerSave = powerSave.equals("true");
+ firePowerSaveChanged();
+ }
fireBatteryLevelChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 21d03fd..ad406c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -109,7 +109,7 @@
FrameLayout.LayoutParams lp =
(FrameLayout.LayoutParams) mBrightnessMirror.getLayoutParams();
lp.width = mBrightnessMirror.getResources().getDimensionPixelSize(
- R.dimen.notification_panel_width);
+ R.dimen.qs_panel_width);
lp.gravity = mBrightnessMirror.getResources().getInteger(
R.integer.notification_panel_layout_gravity);
mBrightnessMirror.setLayoutParams(lp);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
index 788fda8..418c48e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -27,5 +27,6 @@
interface ConfigurationListener {
default void onConfigChanged(Configuration newConfig) {}
default void onDensityOrFontScaleChanged() {}
+ default void onOverlayChanged() {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
index cfaca0d..f2283a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
@@ -102,15 +102,21 @@
}
private void notifyUserChanged() {
- mListeners.forEach(c -> c.onUserSwitched());
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onUserSwitched();
+ }
}
private void notifySetupChanged() {
- mListeners.forEach(c -> c.onUserSetupChanged());
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onUserSetupChanged();
+ }
}
private void notifyProvisionedChanged() {
- mListeners.forEach(c -> c.onDeviceProvisionedChanged());
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onDeviceProvisionedChanged();
+ }
}
protected final ContentObserver mSettingsObserver = new ContentObserver(Dependency.get(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java
index 78e0028..40e3806 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java
@@ -14,6 +14,8 @@
package com.android.systemui.statusbar.policy;
+import android.content.Context;
+
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -28,7 +30,9 @@
interface Extension<T> {
T get();
+ Context getContext();
void destroy();
+ void addCallback(Consumer<T> callback);
/**
* Triggers the extension to cycle through each of the sources again because something
* (like configuration) may have changed.
@@ -44,6 +48,7 @@
PluginConverter<T, P> converter);
ExtensionBuilder<T> withDefault(Supplier<T> def);
ExtensionBuilder<T> withCallback(Consumer<T> callback);
+ ExtensionBuilder<T> withUiMode(int mode, Supplier<T> def);
Extension build();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
index cfc1d56..c2618cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
@@ -14,23 +14,38 @@
package com.android.systemui.statusbar.policy;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.util.ArrayMap;
+
import com.android.systemui.Dependency;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
-import android.content.Context;
-import android.util.ArrayMap;
-
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class ExtensionControllerImpl implements ExtensionController {
+ public static final int SORT_ORDER_PLUGIN = 0;
+ public static final int SORT_ORDER_TUNER = 1;
+ public static final int SORT_ORDER_UI_MODE = 2;
+ public static final int SORT_ORDER_DEFAULT = 3;
+
+ private final Context mDefaultContext;
+
+ public ExtensionControllerImpl(Context context) {
+ mDefaultContext = context;
+ }
+
@Override
public <T> ExtensionBuilder<T> newExtension(Class<T> cls) {
return new ExtensionBuilder<>();
@@ -38,6 +53,7 @@
private interface Producer<T> {
T get();
+
void destroy();
}
@@ -75,6 +91,12 @@
return this;
}
+ public ExtensionController.ExtensionBuilder<T> withUiMode(int uiMode,
+ Supplier<T> supplier) {
+ mExtension.addUiMode(uiMode, supplier);
+ return this;
+ }
+
@Override
public ExtensionController.ExtensionBuilder<T> withCallback(
Consumer<T> callback) {
@@ -85,34 +107,21 @@
@Override
public ExtensionController.Extension build() {
// Manually sort, plugins first, tuners second, defaults last.
- Collections.sort(mExtension.mProducers, (o1, o2) -> {
- if (o1 instanceof ExtensionImpl.PluginItem) {
- if (o2 instanceof ExtensionImpl.PluginItem) {
- return 0;
- } else {
- return -1;
- }
- }
- if (o1 instanceof ExtensionImpl.TunerItem) {
- if (o2 instanceof ExtensionImpl.PluginItem) {
- return 1;
- } else if (o2 instanceof ExtensionImpl.TunerItem) {
- return 0;
- } else {
- return -1;
- }
- }
- return 0;
- });
+ Collections.sort(mExtension.mProducers, Comparator.comparingInt(Item::sortOrder));
mExtension.notifyChanged();
return mExtension;
}
}
private class ExtensionImpl<T> implements ExtensionController.Extension<T> {
- private final ArrayList<Producer<T>> mProducers = new ArrayList<>();
+ private final ArrayList<Item<T>> mProducers = new ArrayList<>();
private final ArrayList<Consumer<T>> mCallbacks = new ArrayList<>();
private T mItem;
+ private Context mPluginContext;
+
+ public void addCallback(Consumer<T> callback) {
+ mCallbacks.add(callback);
+ }
@Override
public T get() {
@@ -120,6 +129,11 @@
}
@Override
+ public Context getContext() {
+ return mPluginContext != null ? mPluginContext : mDefaultContext;
+ }
+
+ @Override
public void destroy() {
for (int i = 0; i < mProducers.size(); i++) {
mProducers.get(i).destroy();
@@ -133,6 +147,7 @@
}
private void notifyChanged() {
+ mItem = null;
for (int i = 0; i < mProducers.size(); i++) {
final T item = mProducers.get(i).get();
if (item != null) {
@@ -157,7 +172,11 @@
mProducers.add(new TunerItem(factory, factory.keys()));
}
- private class PluginItem<P extends Plugin> implements Producer<T>, PluginListener<P> {
+ public void addUiMode(int uiMode, Supplier<T> mode) {
+ mProducers.add(new UiModeItem(uiMode, mode));
+ }
+
+ private class PluginItem<P extends Plugin> implements Item<T>, PluginListener<P> {
private final PluginConverter<T, P> mConverter;
private T mItem;
@@ -168,6 +187,7 @@
@Override
public void onPluginConnected(P plugin, Context pluginContext) {
+ mPluginContext = pluginContext;
if (mConverter != null) {
mItem = mConverter.getInterfaceFromPlugin(plugin);
} else {
@@ -178,6 +198,7 @@
@Override
public void onPluginDisconnected(P plugin) {
+ mPluginContext = null;
mItem = null;
notifyChanged();
}
@@ -191,9 +212,14 @@
public void destroy() {
Dependency.get(PluginManager.class).removePluginListener(this);
}
+
+ @Override
+ public int sortOrder() {
+ return SORT_ORDER_PLUGIN;
+ }
}
- private class TunerItem<T> implements Producer<T>, Tunable {
+ private class TunerItem<T> implements Item<T>, Tunable {
private final TunerFactory<T> mFactory;
private final ArrayMap<String, String> mSettings = new ArrayMap<>();
private T mItem;
@@ -219,9 +245,54 @@
mItem = mFactory.create(mSettings);
notifyChanged();
}
+
+ @Override
+ public int sortOrder() {
+ return SORT_ORDER_TUNER;
+ }
}
- private class Default<T> implements Producer<T> {
+ private class UiModeItem<T> implements Item<T>, ConfigurationListener {
+
+ private final int mDesiredUiMode;
+ private final Supplier<T> mSupplier;
+ private int mUiMode;
+ private Handler mHandler = new Handler();
+
+ public UiModeItem(int uiMode, Supplier<T> supplier) {
+ mDesiredUiMode = uiMode;
+ mSupplier = supplier;
+ mUiMode = mDefaultContext.getResources().getConfiguration().uiMode;
+ Dependency.get(ConfigurationController.class).addCallback(this);
+ }
+
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ int newMode = newConfig.uiMode & Configuration.UI_MODE_TYPE_MASK;
+ if (newMode != mUiMode) {
+ mUiMode = newMode;
+ // Post to make sure we don't have concurrent modifications.
+ mHandler.post(ExtensionImpl.this::notifyChanged);
+ }
+ }
+
+ @Override
+ public T get() {
+ return (mUiMode == mDesiredUiMode) ? mSupplier.get() : null;
+ }
+
+ @Override
+ public void destroy() {
+ Dependency.get(ConfigurationController.class).removeCallback(this);
+ }
+
+ @Override
+ public int sortOrder() {
+ return SORT_ORDER_UI_MODE;
+ }
+ }
+
+ private class Default<T> implements Item<T> {
private final Supplier<T> mSupplier;
public Default(Supplier<T> supplier) {
@@ -237,6 +308,15 @@
public void destroy() {
}
+
+ @Override
+ public int sortOrder() {
+ return SORT_ORDER_DEFAULT;
+ }
}
}
+
+ private interface Item<T> extends Producer<T> {
+ int sortOrder();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 5f7ac5d..53dfb24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -20,6 +20,7 @@
import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.Handler;
+import android.os.Looper;
import android.os.SystemClock;
import android.provider.Settings;
import android.support.v4.util.ArraySet;
@@ -65,7 +66,7 @@
private final ArrayMap<String, Long> mSnoozedPackages;
private final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
private final int mDefaultSnoozeLengthMs;
- private final Handler mHandler = new Handler();
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() {
private Stack<HeadsUpEntry> mPoolObjects = new Stack<>();
@@ -257,6 +258,12 @@
mEntryPool.release(remove);
}
+ public void removeAllHeadsUpEntries() {
+ for (String key : mHeadsUpEntries.keySet()) {
+ removeHeadsUpEntry(mHeadsUpEntries.get(key).entry);
+ }
+ }
+
private void updatePinnedMode() {
boolean hasPinnedNotification = hasPinnedNotificationInternal();
if (hasPinnedNotification == mHasPinnedNotification) {
@@ -618,6 +625,7 @@
@Override
public void onReorderingAllowed() {
+ mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false);
for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
if (isHeadsUp(entry.key)) {
// Maybe the heads-up was removed already
@@ -625,6 +633,7 @@
}
}
mEntriesToRemoveWhenReorderingAllowed.clear();
+ mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true);
}
public void setVisualStabilityManager(VisualStabilityManager visualStabilityManager) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 03a50a2..67b5596 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -253,6 +253,11 @@
}
@Override
+ public int getQsCurrentIconId() {
+ return getCurrentIconId();
+ }
+
+ @Override
public void notifyListeners(SignalCallback callback) {
MobileIconGroup icons = getIcons();
@@ -271,9 +276,9 @@
String description = null;
// Only send data sim callbacks to QS.
if (mCurrentState.dataSim) {
- qsTypeIcon = showDataIcon ? icons.mDataType : 0;
+ qsTypeIcon = showDataIcon ? icons.mQsDataType : 0;
qsIcon = new IconState(mCurrentState.enabled
- && !mCurrentState.isEmergency, getCurrentIconId(), contentDescription);
+ && !mCurrentState.isEmergency, getQsCurrentIconId(), contentDescription);
description = mCurrentState.isEmergency ? null : mCurrentState.networkName;
}
boolean activityIn = mCurrentState.dataConnected
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index efbf76b..b22ce18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -90,6 +90,10 @@
private ArrayMap<Integer, Boolean> mHasCACerts = new ArrayMap<Integer, Boolean>();
public SecurityControllerImpl(Context context) {
+ this(context, null);
+ }
+
+ public SecurityControllerImpl(Context context, SecurityControllerCallback callback) {
super(context);
mContext = context;
mDevicePolicyManager = (DevicePolicyManager)
@@ -102,6 +106,8 @@
mUserManager = (UserManager)
context.getSystemService(Context.USER_SERVICE);
+ addCallback(callback);
+
IntentFilter filter = new IntentFilter();
filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
index d91ae39..aaa0568 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -22,6 +22,16 @@
class TelephonyIcons {
//***** Data connection icons
+ static final int QS_DATA_G = R.drawable.ic_qs_signal_g;
+ static final int QS_DATA_3G = R.drawable.ic_qs_signal_3g;
+ static final int QS_DATA_E = R.drawable.ic_qs_signal_e;
+ static final int QS_DATA_H = R.drawable.ic_qs_signal_h;
+ static final int QS_DATA_1X = R.drawable.ic_qs_signal_1x;
+ static final int QS_DATA_4G = R.drawable.ic_qs_signal_4g;
+ static final int QS_DATA_4G_PLUS = R.drawable.ic_qs_signal_4g_plus;
+ static final int QS_DATA_LTE = R.drawable.ic_qs_signal_lte;
+ static final int QS_DATA_LTE_PLUS = R.drawable.ic_qs_signal_lte_plus;
+
static final int FLIGHT_MODE_ICON = R.drawable.stat_sys_airplane_mode;
static final int ICON_LTE = R.drawable.stat_sys_data_fully_connected_lte;
@@ -36,6 +46,8 @@
static final int ICON_DATA_DISABLED = R.drawable.stat_sys_data_disabled;
+ static final int QS_ICON_DATA_DISABLED = R.drawable.ic_qs_data_disabled;
+
static final MobileIconGroup CARRIER_NETWORK_CHANGE = new MobileIconGroup(
"CARRIER_NETWORK_CHANGE",
null,
@@ -63,7 +75,7 @@
R.string.accessibility_data_connection_3g,
TelephonyIcons.ICON_3G,
true,
- TelephonyIcons.ICON_3G
+ TelephonyIcons.QS_DATA_3G
);
static final MobileIconGroup WFC = new MobileIconGroup(
@@ -102,7 +114,7 @@
R.string.accessibility_data_connection_edge,
TelephonyIcons.ICON_E,
false,
- TelephonyIcons.ICON_E
+ TelephonyIcons.QS_DATA_E
);
static final MobileIconGroup ONE_X = new MobileIconGroup(
@@ -117,7 +129,7 @@
R.string.accessibility_data_connection_cdma,
TelephonyIcons.ICON_1X,
true,
- TelephonyIcons.ICON_1X
+ TelephonyIcons.QS_DATA_1X
);
static final MobileIconGroup G = new MobileIconGroup(
@@ -132,7 +144,7 @@
R.string.accessibility_data_connection_gprs,
TelephonyIcons.ICON_G,
false,
- TelephonyIcons.ICON_G
+ TelephonyIcons.QS_DATA_G
);
static final MobileIconGroup H = new MobileIconGroup(
@@ -147,7 +159,7 @@
R.string.accessibility_data_connection_3_5g,
TelephonyIcons.ICON_H,
false,
- TelephonyIcons.ICON_H
+ TelephonyIcons.QS_DATA_H
);
static final MobileIconGroup FOUR_G = new MobileIconGroup(
@@ -162,7 +174,7 @@
R.string.accessibility_data_connection_4g,
TelephonyIcons.ICON_4G,
true,
- TelephonyIcons.ICON_4G
+ TelephonyIcons.QS_DATA_4G
);
static final MobileIconGroup FOUR_G_PLUS = new MobileIconGroup(
@@ -177,7 +189,7 @@
R.string.accessibility_data_connection_4g_plus,
TelephonyIcons.ICON_4G_PLUS,
true,
- TelephonyIcons.ICON_4G_PLUS
+ TelephonyIcons.QS_DATA_4G_PLUS
);
static final MobileIconGroup LTE = new MobileIconGroup(
@@ -192,7 +204,7 @@
R.string.accessibility_data_connection_lte,
TelephonyIcons.ICON_LTE,
true,
- TelephonyIcons.ICON_LTE
+ TelephonyIcons.QS_DATA_LTE
);
static final MobileIconGroup LTE_PLUS = new MobileIconGroup(
@@ -207,7 +219,7 @@
R.string.accessibility_data_connection_lte_plus,
TelephonyIcons.ICON_LTE_PLUS,
true,
- TelephonyIcons.ICON_LTE_PLUS
+ TelephonyIcons.QS_DATA_LTE_PLUS
);
static final MobileIconGroup DATA_DISABLED = new MobileIconGroup(
@@ -222,7 +234,7 @@
R.string.accessibility_cell_data_off,
TelephonyIcons.ICON_DATA_DISABLED,
false,
- TelephonyIcons.ICON_DATA_DISABLED
+ TelephonyIcons.QS_ICON_DATA_DISABLED
);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 41ef781..b7b991e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -61,6 +61,7 @@
private boolean mPanelFullWidth;
private boolean mHasPulsingNotifications;
private boolean mUnlockHintRunning;
+ private boolean mQsCustomizerShowing;
public AmbientState(Context context) {
reload(context);
@@ -314,4 +315,12 @@
public boolean isUnlockHintRunning() {
return mUnlockHintRunning;
}
+
+ public boolean isQsCustomizerShowing() {
+ return mQsCustomizerShowing;
+ }
+
+ public void setQsCustomizerShowing(boolean qsCustomizerShowing) {
+ mQsCustomizerShowing = qsCustomizerShowing;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index ff06b5b..fe53104 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -19,6 +19,7 @@
import android.app.Notification;
import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.drawable.ColorDrawable;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
@@ -66,8 +67,9 @@
private final HybridGroupManager mHybridGroupManager;
private int mChildPadding;
private int mDividerHeight;
- private int mMaxNotificationHeight;
+ private float mDividerAlpha;
private int mNotificationHeaderMargin;
+
private int mNotificatonTopPadding;
private float mCollapsedBottompadding;
private boolean mChildrenExpanded;
@@ -80,6 +82,11 @@
private boolean mNeverAppliedGroupState;
private int mHeaderHeight;
+ /**
+ * Whether or not individual notifications that are part of this container will have shadows.
+ */
+ private boolean mEnableShadowOnChildNotifications;
+
private NotificationHeaderView mNotificationHeader;
private NotificationViewWrapper mNotificationHeaderWrapper;
private NotificationHeaderView mNotificationHeaderLowPriority;
@@ -93,6 +100,9 @@
private OnClickListener mHeaderClickListener;
private ViewGroup mCurrentHeader;
+ private boolean mShowDividersWhenExpanded;
+ private boolean mHideDividersDuringExpand;
+
public NotificationChildrenContainer(Context context) {
this(context, null);
}
@@ -113,19 +123,25 @@
}
private void initDimens() {
- mChildPadding = getResources().getDimensionPixelSize(
- R.dimen.notification_children_padding);
- mDividerHeight = Math.max(1, getResources().getDimensionPixelSize(
- R.dimen.notification_divider_height));
- mHeaderHeight = getResources().getDimensionPixelSize(R.dimen.notification_header_height);
- mMaxNotificationHeight = getResources().getDimensionPixelSize(
- R.dimen.notification_max_height);
- mNotificationHeaderMargin = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_margin_top);
- mNotificatonTopPadding = getResources().getDimensionPixelSize(
+ Resources res = getResources();
+ mChildPadding = res.getDimensionPixelSize(R.dimen.notification_children_padding);
+ mDividerHeight = res.getDimensionPixelSize(
+ R.dimen.notification_children_container_divider_height);
+ mDividerAlpha = res.getFloat(R.dimen.notification_divider_alpha);
+ mHeaderHeight = res.getDimensionPixelSize(
+ R.dimen.notification_children_container_header_height);
+ mNotificationHeaderMargin = res.getDimensionPixelSize(
+ R.dimen.notification_children_container_margin_top);
+ mNotificatonTopPadding = res.getDimensionPixelSize(
R.dimen.notification_children_container_top_padding);
- mCollapsedBottompadding = getResources().getDimensionPixelSize(
+ mCollapsedBottompadding = res.getDimensionPixelSize(
com.android.internal.R.dimen.notification_content_margin_bottom);
+ mEnableShadowOnChildNotifications =
+ res.getBoolean(R.bool.config_enableShadowOnChildNotifications);
+ mShowDividersWhenExpanded =
+ res.getBoolean(R.bool.config_showDividersWhenGroupNotificationExpanded);
+ mHideDividersDuringExpand =
+ res.getBoolean(R.bool.config_hideDividersDuringExpand);
}
@Override
@@ -297,7 +313,7 @@
} else {
header.reapply(getContext(), mNotificationHeader);
}
- mNotificationHeaderWrapper.notifyContentUpdated(mContainingNotification);
+ mNotificationHeaderWrapper.onContentUpdated(mContainingNotification);
recreateLowPriorityHeader(builder);
recreateAmbientHeader(builder);
updateHeaderVisibility(false /* animate */);
@@ -316,14 +332,14 @@
mNotificationHeaderAmbient = (ViewGroup) header.apply(getContext(), this);
mNotificationHeaderWrapperAmbient = NotificationViewWrapper.wrap(getContext(),
mNotificationHeaderAmbient, mContainingNotification);
- mNotificationHeaderWrapperAmbient.notifyContentUpdated(mContainingNotification);
+ mNotificationHeaderWrapperAmbient.onContentUpdated(mContainingNotification);
addView(mNotificationHeaderAmbient, 0);
invalidate();
} else {
header.reapply(getContext(), mNotificationHeaderAmbient);
}
resetHeaderVisibilityIfNeeded(mNotificationHeaderAmbient, calculateDesiredHeader());
- mNotificationHeaderWrapperAmbient.notifyContentUpdated(mContainingNotification);
+ mNotificationHeaderWrapperAmbient.onContentUpdated(mContainingNotification);
}
/**
@@ -354,7 +370,7 @@
} else {
header.reapply(getContext(), mNotificationHeaderLowPriority);
}
- mNotificationHeaderWrapperLowPriority.notifyContentUpdated(mContainingNotification);
+ mNotificationHeaderWrapperLowPriority.onContentUpdated(mContainingNotification);
resetHeaderVisibilityIfNeeded(mNotificationHeaderLowPriority, calculateDesiredHeader());
} else {
removeView(mNotificationHeaderLowPriority);
@@ -566,7 +582,8 @@
childState.hidden = false;
// When the group is expanded, the children cast the shadows rather than the parent
// so use the parent's elevation here.
- childState.zTranslation = childrenExpandedAndNotAnimating
+ childState.zTranslation =
+ (childrenExpandedAndNotAnimating && mEnableShadowOnChildNotifications)
? mContainingNotification.getTranslationZ()
: 0;
childState.dimmed = parentState.dimmed;
@@ -677,7 +694,9 @@
expandFraction = getGroupExpandFraction();
}
final boolean dividersVisible = mUserLocked && !showingAsLowPriority()
- || mContainingNotification.isGroupExpansionChanging();
+ || (mChildrenExpanded && mShowDividersWhenExpanded)
+ || (mContainingNotification.isGroupExpansionChanging()
+ && !mHideDividersDuringExpand);
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
ExpandableViewState viewState = state.getViewStateForView(child);
@@ -687,7 +706,7 @@
View divider = mDividers.get(i);
tmpState.initFrom(divider);
tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
- float alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
+ float alpha = mChildrenExpanded && viewState.alpha != 0 ? mDividerAlpha : 0;
if (mUserLocked && !showingAsLowPriority() && viewState.alpha != 0) {
alpha = NotificationUtils.interpolate(0, 0.5f,
Math.min(viewState.alpha, expandFraction));
@@ -751,7 +770,9 @@
ViewState tmpState = new ViewState();
float expandFraction = getGroupExpandFraction();
final boolean dividersVisible = mUserLocked && !showingAsLowPriority()
- || mContainingNotification.isGroupExpansionChanging();
+ || (mChildrenExpanded && mShowDividersWhenExpanded)
+ || (mContainingNotification.isGroupExpansionChanging()
+ && !mHideDividersDuringExpand);
for (int i = childCount - 1; i >= 0; i--) {
ExpandableNotificationRow child = mChildren.get(i);
ExpandableViewState viewState = state.getViewStateForView(child);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 3cc3604..cbd315b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -38,6 +38,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.service.notification.StatusBarNotification;
+import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
@@ -57,6 +58,7 @@
import android.view.animation.Interpolator;
import android.widget.OverScroller;
import android.widget.ScrollView;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.ExpandHelper;
@@ -87,6 +89,8 @@
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.ScrollAdapter;
+import android.support.v4.graphics.ColorUtils;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -147,6 +151,7 @@
private int mPaddingBetweenElements;
private int mIncreasedPaddingBetweenElements;
private int mTopPadding;
+ private int mBottomMargin;
private int mBottomInset = 0;
/**
@@ -205,6 +210,7 @@
protected DismissView mDismissView;
protected EmptyShadeView mEmptyShadeView;
private boolean mDismissAllInProgress;
+ private boolean mFadeNotificationsOnDismiss;
/**
* Was the scroller scrolled to the top when the down motion was observed?
@@ -369,6 +375,7 @@
private boolean mHeadsUpAnimatingAway;
private int mStatusBarState;
private int mCachedBackgroundColor;
+ private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
private Runnable mAnimateScroll = this::animateScroll;
public NotificationStackScrollLayout(Context context) {
@@ -403,6 +410,8 @@
mFalsingManager = FalsingManager.getInstance(context);
mShouldDrawNotificationBackground =
res.getBoolean(R.bool.config_drawNotificationBackground);
+ mFadeNotificationsOnDismiss =
+ res.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
updateWillNotDraw();
if (DEBUG) {
@@ -475,16 +484,8 @@
float alpha = BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
alpha *= mBackgroundFadeAmount;
// We need to manually blend in the background color
- int scrimColor = mScrimController.getScrimBehindColor();
- // SRC_OVER blending Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc
- float alphaInv = 1 - alpha;
- int color = Color.argb((int) (alpha * 255 + alphaInv * Color.alpha(scrimColor)),
- (int) (mBackgroundFadeAmount * Color.red(mBgColor)
- + alphaInv * Color.red(scrimColor)),
- (int) (mBackgroundFadeAmount * Color.green(mBgColor)
- + alphaInv * Color.green(scrimColor)),
- (int) (mBackgroundFadeAmount * Color.blue(mBgColor)
- + alphaInv * Color.blue(scrimColor)));
+ int scrimColor = mScrimController.getBackgroundColor();
+ int color = ColorUtils.blendARGB(scrimColor, mBgColor, alpha);
if (mCachedBackgroundColor != color) {
mCachedBackgroundColor = color;
mBackgroundPaint.setColor(color);
@@ -501,17 +502,19 @@
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mOverflingDistance = configuration.getScaledOverflingDistance();
- mCollapsedSize = context.getResources()
- .getDimensionPixelSize(R.dimen.notification_min_height);
+
+ Resources res = context.getResources();
+ mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
mStackScrollAlgorithm.initView(context);
mAmbientState.reload(context);
- mPaddingBetweenElements = Math.max(1, context.getResources()
- .getDimensionPixelSize(R.dimen.notification_divider_height));
- mIncreasedPaddingBetweenElements = context.getResources()
- .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
- mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
+ mPaddingBetweenElements = Math.max(1,
+ res.getDimensionPixelSize(R.dimen.notification_divider_height));
+ mIncreasedPaddingBetweenElements =
+ res.getDimensionPixelSize(R.dimen.notification_divider_height_increased);
+ mMinTopOverScrollToEscape = res.getDimensionPixelSize(
R.dimen.min_top_overscroll_to_qs);
- mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height);
+ mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
+ mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
}
public void setDrawBackgroundAsSrc(boolean asSrc) {
@@ -824,7 +827,8 @@
} else {
appearPosition = 0;
}
- if (notGoneChildCount >= minNotificationsForShelf) {
+ if (notGoneChildCount >= minNotificationsForShelf
+ && mShelf.getVisibility() != GONE) {
appearPosition += mShelf.getIntrinsicHeight();
}
} else {
@@ -966,7 +970,8 @@
mScrimController.setTopHeadsUpDragAmount(animView,
Math.min(Math.abs(swipeProgress / 2f - 1.0f), 1.0f));
}
- return true; // Don't fade out the notification
+ // Returning true prevents alpha fading.
+ return !mFadeNotificationsOnDismiss;
}
@Override
@@ -1995,7 +2000,7 @@
}
}
}
- mContentHeight = height + mTopPadding;
+ mContentHeight = height + mTopPadding + mBottomMargin;
updateScrollability();
mAmbientState.setLayoutMaxHeight(mContentHeight);
}
@@ -2232,10 +2237,11 @@
top = (int) Math.ceil(firstView.getTranslationY());
}
}
- ActivatableNotificationView lastView = mShelf.hasItemsInStableShelf()
- ? mShelf
- : mLastVisibleBackgroundChild;
- int bottom = 0;
+ ActivatableNotificationView lastView =
+ mShelf.hasItemsInStableShelf() && mShelf.getVisibility() != GONE
+ ? mShelf
+ : mLastVisibleBackgroundChild;
+ int bottom;
if (lastView != null) {
int finalTranslationY;
if (lastView == mShelf) {
@@ -2385,7 +2391,7 @@
}
public int getLayoutMinHeight() {
- return mShelf.getIntrinsicHeight();
+ return mShelf.getVisibility() == GONE ? 0 : mShelf.getIntrinsicHeight();
}
public int getFirstChildIntrinsicHeight() {
@@ -2410,7 +2416,7 @@
final int firstChildMinHeight = firstChild != null ? firstChild.getCollapsedHeight()
: mCollapsedSize;
int shelfHeight = 0;
- if (mLastVisibleBackgroundChild != null) {
+ if (mLastVisibleBackgroundChild != null && mShelf.getVisibility() != GONE) {
shelfHeight = mShelf.getIntrinsicHeight();
}
return mIntrinsicPadding + firstChildMinHeight + shelfHeight;
@@ -3454,7 +3460,7 @@
endPosition += row.getNotificationParent().getTranslationY();
}
int layoutEnd = mMaxLayoutHeight + (int) mStackTranslation;
- if (row != mLastVisibleBackgroundChild) {
+ if (row != mLastVisibleBackgroundChild && mShelf.getVisibility() != GONE) {
layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
}
if (endPosition > layoutEnd) {
@@ -3628,7 +3634,9 @@
}
public void goToFullShade(long delay) {
- mDismissView.setInvisible();
+ if (mDismissView != null) {
+ mDismissView.setInvisible();
+ }
mEmptyShadeView.setInvisible();
mGoToFullShadeNeedsAnimation = true;
mGoToFullShadeDelay = delay;
@@ -3741,7 +3749,7 @@
return -1;
}
- public void setDismissView(DismissView dismissView) {
+ public void setDismissView(@NonNull DismissView dismissView) {
int index = -1;
if (mDismissView != null) {
index = indexOfChild(mDismissView);
@@ -3797,6 +3805,10 @@
}
public void updateDismissView(boolean visible) {
+ if (mDismissView == null) {
+ return;
+ }
+
int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
int newVisibility = visible ? VISIBLE : GONE;
if (oldVisibility != newVisibility) {
@@ -3854,15 +3866,17 @@
}
public boolean isDismissViewNotGone() {
- return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
+ return mDismissView != null
+ && mDismissView.getVisibility() != View.GONE
+ && !mDismissView.willBeGone();
}
public boolean isDismissViewVisible() {
- return mDismissView.isVisible();
+ return mDismissView != null && mDismissView.isVisible();
}
public int getDismissViewHeight() {
- return mDismissView.getHeight() + mPaddingBetweenElements;
+ return mDismissView == null ? 0 : mDismissView.getHeight() + mPaddingBetweenElements;
}
public int getEmptyShadeViewHeight() {
@@ -4047,7 +4061,7 @@
}
public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
- if (mAnimationsEnabled) {
+ if (mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed)) {
mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
mNeedsAnimation = true;
if (!mIsExpanded && !isHeadsUp) {
@@ -4081,12 +4095,7 @@
public void setScrimController(ScrimController scrimController) {
mScrimController = scrimController;
- mScrimController.setScrimBehindChangeRunnable(new Runnable() {
- @Override
- public void run() {
- updateBackgroundDimming();
- }
- });
+ mScrimController.setScrimBehindChangeRunnable(this::updateBackgroundDimming);
}
public void forceNoOverlappingRendering(boolean force) {
@@ -4234,6 +4243,19 @@
mAmbientState.setUnlockHintRunning(running);
}
+ public void setQsCustomizerShowing(boolean isShowing) {
+ mAmbientState.setQsCustomizerShowing(isShowing);
+ requestChildrenUpdate();
+ }
+
+ public void setHeadsUpGoingAwayAnimationsAllowed(boolean headsUpGoingAwayAnimationsAllowed) {
+ mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed;
+ }
+
+ public void setDarkShelfOffsetX(int shelfOffsetX) {
+ mShelf.setDarkOffsetX(shelfOffsetX);
+ }
+
/**
* A listener that is notified when some child locations might have changed.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 5c10e7e..8235bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -17,10 +17,10 @@
package com.android.systemui.statusbar.stack;
import android.content.Context;
+import android.content.res.Resources;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
-
import com.android.systemui.R;
import com.android.systemui.statusbar.DismissView;
import com.android.systemui.statusbar.EmptyShadeView;
@@ -48,6 +48,7 @@
private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
private boolean mIsExpanded;
+ private boolean mClipNotificationScrollToTop;
private int mStatusBarHeight;
public StackScrollAlgorithm(Context context) {
@@ -59,13 +60,14 @@
}
private void initConstants(Context context) {
- mPaddingBetweenElements = context.getResources().getDimensionPixelSize(
+ Resources res = context.getResources();
+ mPaddingBetweenElements = res.getDimensionPixelSize(
R.dimen.notification_divider_height);
- mIncreasedPaddingBetweenElements = context.getResources()
- .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
- mCollapsedSize = context.getResources()
- .getDimensionPixelSize(R.dimen.notification_min_height);
- mStatusBarHeight = context.getResources().getDimensionPixelSize(R.dimen.status_bar_height);
+ mIncreasedPaddingBetweenElements =
+ res.getDimensionPixelSize(R.dimen.notification_divider_height_increased);
+ mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
+ mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height);
+ mClipNotificationScrollToTop = res.getBoolean(R.bool.config_clipNotificationScrollToTop);
}
public void getStackScrollState(AmbientState ambientState, StackScrollState resultState) {
@@ -142,7 +144,8 @@
float newNotificationEnd = newYTranslation + newHeight;
boolean isHeadsUp = (child instanceof ExpandableNotificationRow)
&& ((ExpandableNotificationRow) child).isPinned();
- if (!state.inShelf && newYTranslation < previousNotificationEnd
+ if (mClipNotificationScrollToTop
+ && !state.inShelf && newYTranslation < previousNotificationEnd
&& (!isHeadsUp || ambientState.isShadeExpanded())) {
// The previous view is overlapping on top, clip!
float overlapAmount = previousNotificationEnd - newYTranslation;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunablePadding.java b/packages/SystemUI/src/com/android/systemui/tuner/TunablePadding.java
new file mode 100644
index 0000000..af99236
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunablePadding.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.tuner;
+
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+/**
+ * Version of Space that can be resized by a tunable setting.
+ */
+public class TunablePadding implements Tunable {
+
+ public static final int FLAG_START = 1;
+ public static final int FLAG_END = 2;
+ public static final int FLAG_TOP = 4;
+ public static final int FLAG_BOTTOM = 8;
+
+ private final int mFlags;
+ private final View mView;
+ private final int mDefaultSize;
+ private final float mDensity;
+
+ private TunablePadding(String key, int def, int flags, View view) {
+ mDefaultSize = def;
+ mFlags = flags;
+ mView = view;
+ DisplayMetrics metrics = new DisplayMetrics();
+ view.getContext().getSystemService(WindowManager.class)
+ .getDefaultDisplay().getMetrics(metrics);
+ mDensity = metrics.density;
+ Dependency.get(TunerService.class).addTunable(this, key);
+ }
+
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ int dimen = mDefaultSize;
+ if (newValue != null) {
+ dimen = (int) (Integer.parseInt(newValue) * mDensity);
+ }
+ int left = mView.isLayoutRtl() ? FLAG_END : FLAG_START;
+ int right = mView.isLayoutRtl() ? FLAG_START : FLAG_END;
+ mView.setPadding(getPadding(dimen, left), getPadding(dimen, FLAG_TOP),
+ getPadding(dimen, right), getPadding(dimen, FLAG_BOTTOM));
+ }
+
+ private int getPadding(int dimen, int flag) {
+ return ((mFlags & flag) != 0) ? dimen : 0;
+ }
+
+ public void destroy() {
+ Dependency.get(TunerService.class).removeTunable(this);
+ }
+
+ // Exists for easy injecting in tests.
+ public static class TunablePaddingService {
+ public TunablePadding add(View view, String key, int defaultSize, int flags) {
+ if (view == null) {
+ throw new IllegalArgumentException();
+ }
+ return new TunablePadding(key, defaultSize, flags, view);
+ }
+ }
+
+ public static TunablePadding addTunablePadding(View view, String key, int defaultSize,
+ int flags) {
+ return Dependency.get(TunablePaddingService.class).add(view, key, defaultSize, flags);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
new file mode 100644
index 0000000..b835909
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.wakelock;
+
+import android.os.Handler;
+
+/**
+ * A wake lock that has a built in delay when releasing to give the framebuffer time to update.
+ */
+public class DelayedWakeLock implements WakeLock {
+
+ private static final long RELEASE_DELAY_MS = 100;
+
+ private final Handler mHandler;
+ private final WakeLock mInner;
+ private final Runnable mRelease;
+
+ public DelayedWakeLock(Handler h, WakeLock inner) {
+ mHandler = h;
+ mInner = inner;
+ mRelease = mInner::release;
+ }
+
+ @Override
+ public void acquire() {
+ mInner.acquire();
+ }
+
+ @Override
+ public void release() {
+ mHandler.postDelayed(mRelease, RELEASE_DELAY_MS);
+ }
+
+ @Override
+ public Runnable wrap(Runnable r) {
+ return WakeLock.wrapImpl(this, r);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
index 215604b..edf294e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
@@ -42,6 +42,17 @@
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag);
}
+ static Runnable wrapImpl(WakeLock w, Runnable r) {
+ w.acquire();
+ return () -> {
+ try {
+ r.run();
+ } finally {
+ w.release();
+ }
+ };
+ }
+
static WakeLock wrap(final PowerManager.WakeLock inner) {
return new WakeLock() {
/** @see PowerManager.WakeLock#acquire() */
@@ -56,7 +67,7 @@
/** @see PowerManager.WakeLock#wrap(Runnable) */
public Runnable wrap(Runnable runnable) {
- return inner.wrap(runnable);
+ return wrapImpl(this, runnable);
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 8d8931f..1d3b533 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -18,6 +18,7 @@
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.animation.ObjectAnimator;
@@ -25,6 +26,7 @@
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.app.KeyguardManager;
+import android.app.WallpaperManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
@@ -32,6 +34,7 @@
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.ColorDrawable;
@@ -59,6 +62,7 @@
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.Window;
import android.view.WindowManager;
@@ -73,16 +77,21 @@
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
+import com.android.systemui.HardwareUiLayout;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
import com.android.systemui.plugins.VolumeDialogController.StreamState;
import com.android.systemui.plugins.VolumeDialog;
+import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerZenModePanel;
+import com.google.android.colorextraction.ColorExtractor;
+import com.google.android.colorextraction.drawable.GradientDrawable;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -94,7 +103,8 @@
*
* Methods ending in "H" must be called on the (ui) handler.
*/
-public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
+public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable,
+ ColorExtractor.OnColorsChangedListener {
private static final String TAG = Util.logTag(VolumeDialogImpl.class);
public static final String SHOW_FULL_ZEN = "sysui_show_full_zen";
@@ -104,6 +114,8 @@
private final Context mContext;
private final H mHandler = new H();
+ private final GradientDrawable mGradientDrawable;
+ private final ColorExtractor mColorExtractor;
private VolumeDialogController mController;
private Window mWindow;
@@ -158,6 +170,9 @@
(AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext));
mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive);
+ mGradientDrawable = new GradientDrawable(mContext);
+ mGradientDrawable.setAlpha((int) (ScrimController.GRADIENT_SCRIM_ALPHA * 255));
+ mColorExtractor = Dependency.get(ColorExtractor.class);
}
public void init(int windowType, Callback callback) {
@@ -179,6 +194,7 @@
@Override
public void destroy() {
mController.removeCallback(mControllerCallbackH);
+ mColorExtractor.removeOnColorsChangedListener(this);
}
private void initDialog() {
@@ -189,7 +205,7 @@
mShowing = false;
mWindow = mDialog.getWindow();
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
- mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ mWindow.setBackgroundDrawable(mGradientDrawable);
mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
@@ -197,55 +213,49 @@
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ Point displaySize = new Point();
+ mContext.getDisplay().getRealSize(displaySize);
mDialog.setCanceledOnTouchOutside(true);
final Resources res = mContext.getResources();
- final WindowManager.LayoutParams lp = mWindow.getAttributes();
- lp.type = mWindowType;
- lp.format = PixelFormat.TRANSLUCENT;
- lp.setTitle(VolumeDialogImpl.class.getSimpleName());
- lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
- lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top);
- lp.gravity = Gravity.TOP;
- lp.windowAnimations = -1;
- mWindow.setAttributes(lp);
mWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
- mDialog.setContentView(R.layout.volume_dialog);
- mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog);
- mDialogView.setOnHoverListener(new View.OnHoverListener() {
- @Override
- public boolean onHover(View v, MotionEvent event) {
- int action = event.getActionMasked();
- mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
- || (action == MotionEvent.ACTION_HOVER_MOVE);
- rescheduleTimeoutH();
- return true;
- }
+ mDialog.setContentView(R.layout.volume_dialog_wrapped);
+ mDialogView = mDialog.findViewById(R.id.volume_dialog);
+ mDialogView.setOnHoverListener((v, event) -> {
+ int action = event.getActionMasked();
+ mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
+ || (action == MotionEvent.ACTION_HOVER_MOVE);
+ rescheduleTimeoutH();
+ return true;
});
- mDialogContentView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog_content);
- mDialogRowsView = (ViewGroup) mDialogContentView.findViewById(R.id.volume_dialog_rows);
+
+ mColorExtractor.addOnColorsChangedListener(this);
+ mGradientDrawable.setScreenSize(displaySize.x, displaySize.y);
+ ColorExtractor.GradientColors colors = mColorExtractor.getColors(
+ mKeyguard.isKeyguardLocked() ? WallpaperManager.FLAG_LOCK
+ : WallpaperManager.FLAG_SYSTEM);
+ mGradientDrawable.setColors(colors, false);
+
+ mDialogContentView = mDialog.findViewById(R.id.volume_dialog_content);
+ mDialogRowsView = mDialogContentView.findViewById(R.id.volume_dialog_rows);
mExpanded = false;
- mExpandButton = (ImageButton) mDialogView.findViewById(R.id.volume_expand_button);
+ mExpandButton = mDialogView.findViewById(R.id.volume_expand_button);
mExpandButton.setOnClickListener(mClickExpand);
mExpandButton.setVisibility(
AudioSystem.isSingleVolume(mContext) ? View.GONE : View.VISIBLE);
- updateWindowWidthH();
updateExpandButtonH();
- mMotion = new VolumeDialogMotion(mDialog, mDialogView, mDialogContentView, mExpandButton,
- new VolumeDialogMotion.Callback() {
- @Override
- public void onAnimatingChanged(boolean animating) {
- if (animating) return;
- if (mPendingStateChanged) {
- mHandler.sendEmptyMessage(H.STATE_CHANGED);
- mPendingStateChanged = false;
- }
- if (mPendingRecheckAll) {
- mHandler.sendEmptyMessage(H.RECHECK_ALL);
- mPendingRecheckAll = false;
- }
+ mMotion = new VolumeDialogMotion(mDialog, (View) mDialogView.getParent(),
+ mDialogContentView, mExpandButton, mGradientDrawable, animating -> {
+ if (animating) return;
+ if (mPendingStateChanged) {
+ mHandler.sendEmptyMessage(H.STATE_CHANGED);
+ mPendingStateChanged = false;
+ }
+ if (mPendingRecheckAll) {
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ mPendingRecheckAll = false;
}
});
@@ -270,11 +280,20 @@
addExistingRows();
}
mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration);
- mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer);
+ mZenFooter = mDialog.findViewById(R.id.volume_zen_footer);
mZenFooter.init(mZenModeController);
- mZenPanel = (TunerZenModePanel) mDialog.findViewById(R.id.tuner_zen_mode_panel);
+ mZenPanel = mDialog.findViewById(R.id.tuner_zen_mode_panel);
mZenPanel.init(mZenModeController);
mZenPanel.setCallback(mZenPanelCallback);
+
+ final WindowManager.LayoutParams lp = mWindow.getAttributes();
+ lp.width = MATCH_PARENT;
+ lp.height = MATCH_PARENT;
+ lp.type = mWindowType;
+ lp.format = PixelFormat.TRANSLUCENT;
+ lp.setTitle(VolumeDialogImpl.class.getSimpleName());
+ lp.windowAnimations = -1;
+ mWindow.setAttributes(lp);
}
@Override
@@ -288,20 +307,6 @@
return ColorStateList.valueOf(mContext.getColor(colorResId));
}
- private void updateWindowWidthH() {
- final ViewGroup.LayoutParams lp = mDialogView.getLayoutParams();
- final DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
- if (D.BUG) Log.d(TAG, "updateWindowWidth dm.w=" + dm.widthPixels);
- int w = dm.widthPixels;
- final int max = mContext.getResources()
- .getDimensionPixelSize(R.dimen.volume_dialog_panel_width);
- if (w > max) {
- w = max;
- }
- lp.width = w;
- mDialogView.setLayoutParams(lp);
- }
-
public void setStreamImportant(int stream, boolean important) {
mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
}
@@ -542,10 +547,8 @@
}
private void updateDialogBottomMarginH() {
- final long diff = System.currentTimeMillis() - mCollapseTime;
- final boolean collapsing = mCollapseTime != 0 && diff < getConservativeCollapseDuration();
final ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) mDialogView.getLayoutParams();
- final int bottomMargin = collapsing ? mDialogContentView.getHeight() :
+ final int bottomMargin =
mContext.getResources().getDimensionPixelSize(R.dimen.volume_dialog_margin_bottom);
if (bottomMargin != mlp.bottomMargin) {
if (D.BUG) Log.d(TAG, "bottomMargin " + mlp.bottomMargin + " -> " + bottomMargin);
@@ -575,7 +578,7 @@
TransitionManager.endTransitions(mDialogView);
final VolumeRow activeRow = getActiveRow();
if (!dismissing) {
- mWindow.setLayout(mWindow.getAttributes().width, ViewGroup.LayoutParams.MATCH_PARENT);
+ mWindow.setLayout(mWindow.getAttributes().width, MATCH_PARENT);
TransitionManager.beginDelayedTransition(mDialogView, getTransistion());
}
updateRowsH(activeRow);
@@ -637,7 +640,7 @@
final boolean isActive = row == activeRow;
final boolean shouldBeVisible = shouldBeVisibleH(row, isActive);
Util.setVisOrGone(row.view, shouldBeVisible);
- Util.setVisOrGone(row.header, shouldBeVisible);
+ Util.setVisOrGone(row.header, shouldBeVisible && mExpanded);
if (row.view.isShown()) {
updateVolumeRowSliderTintH(row, isActive);
}
@@ -697,6 +700,13 @@
if (wasVisible != visible) {
mZenFooter.update();
+ if (visible) {
+ HardwareUiLayout.get(mZenFooter).setDivisionView(mZenFooter);
+ } else {
+ mHandler.postDelayed(() ->
+ HardwareUiLayout.get(mZenFooter).setDivisionView(mZenFooter),
+ mExpandButtonAnimationDuration);
+ }
Util.setVisOrGone(mZenFooter, visible);
}
@@ -958,8 +968,7 @@
@Override
public void onTransitionEnd(Transition transition) {
- mWindow.setLayout(
- mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT);
+ mWindow.setLayout(MATCH_PARENT, MATCH_PARENT);
}
@Override
@@ -968,8 +977,7 @@
@Override
public void onTransitionPause(Transition transition) {
- mWindow.setLayout(
- mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT);
+ mWindow.setLayout(MATCH_PARENT, MATCH_PARENT);
}
@Override
@@ -1021,7 +1029,6 @@
initDialog();
mDensity = density;
}
- updateWindowWidthH();
mConfigurableTexts.update();
mZenFooter.onConfigurationChanged();
}
@@ -1077,10 +1084,26 @@
if (mExpandButtonAnimationRunning) return;
final boolean newExpand = !mExpanded;
Events.writeEvent(mContext, Events.EVENT_EXPAND, newExpand);
+ if (!newExpand) {
+ HardwareUiLayout.get(mDialogContentView).setCollapse();
+ }
updateExpandedH(newExpand, false /* dismissing */);
}
};
+ @Override
+ public void onColorsChanged(ColorExtractor extractor, int which) {
+ if (mKeyguard.isKeyguardLocked()) {
+ if ((WallpaperManager.FLAG_LOCK & which) != 0) {
+ mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_LOCK));
+ }
+ } else {
+ if ((WallpaperManager.FLAG_SYSTEM & which) != 0) {
+ mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM));
+ }
+ }
+ }
+
private final class H extends Handler {
private static final int SHOW = 1;
private static final int DISMISS = 2;
@@ -1152,8 +1175,8 @@
event.setPackageName(mContext.getPackageName());
ViewGroup.LayoutParams params = getWindow().getAttributes();
- boolean isFullScreen = (params.width == ViewGroup.LayoutParams.MATCH_PARENT) &&
- (params.height == ViewGroup.LayoutParams.MATCH_PARENT);
+ boolean isFullScreen = (params.width == MATCH_PARENT) &&
+ (params.height == MATCH_PARENT);
event.setFullScreen(isFullScreen);
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
index d6d0f75..2df2227 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.systemui.volume;
import android.animation.Animator;
@@ -25,6 +24,7 @@
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.content.DialogInterface.OnShowListener;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.Log;
import android.view.View;
@@ -41,8 +41,10 @@
private final View mDialogView;
private final ViewGroup mContents; // volume rows + zen footer
private final View mChevron;
+ private final Drawable mBackground;
private final Handler mHandler = new Handler();
private final Callback mCallback;
+ private final int mBackgroundTargetAlpha;
private boolean mAnimating; // show or dismiss animation is running
private boolean mShowing; // show animation is running
@@ -51,12 +53,14 @@
private ValueAnimator mContentsPositionAnimator;
public VolumeDialogMotion(Dialog dialog, View dialogView, ViewGroup contents, View chevron,
- Callback callback) {
+ Drawable background, Callback callback) {
mDialog = dialog;
mDialogView = dialogView;
mContents = contents;
mChevron = chevron;
mCallback = callback;
+ mBackground = background;
+ mBackgroundTargetAlpha = mBackground.getAlpha();
mDialog.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
@@ -67,8 +71,9 @@
@Override
public void onShow(DialogInterface dialog) {
if (D.BUG) Log.d(TAG, "mDialog.onShow");
- final int h = mDialogView.getHeight();
- mDialogView.setTranslationY(-h);
+ final int w = mDialogView.getWidth() / 4;
+ mDialogView.setTranslationX(w);
+ mBackground.setAlpha(0);
startShowAnimation();
}
});
@@ -117,7 +122,7 @@
}
private int chevronDistance() {
- return mChevron.getHeight() / 6;
+ return 0;
}
private int chevronPosY() {
@@ -128,27 +133,29 @@
private void startShowAnimation() {
if (D.BUG) Log.d(TAG, "startShowAnimation");
mDialogView.animate()
+ .translationX(0)
.translationY(0)
+ .alpha(1)
.setDuration(scaledDuration(300))
.setInterpolator(new LogDecelerateInterpolator())
.setListener(null)
- .setUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
+ .setUpdateListener(animation -> {
+ mBackground.setAlpha(
+ (int) (animation.getAnimatedFraction() * mBackgroundTargetAlpha));
+ if (mChevronPositionAnimator != null) {
+ final float v = (Float) mChevronPositionAnimator.getAnimatedValue();
if (mChevronPositionAnimator == null) return;
// reposition chevron
- final float v = (Float) mChevronPositionAnimator.getAnimatedValue();
final int posY = chevronPosY();
- mChevron.setTranslationY(posY + v + -mDialogView.getTranslationY());
}
})
.withEndAction(new Runnable() {
@Override
public void run() {
+ mBackground.setAlpha(mBackgroundTargetAlpha);
if (mChevronPositionAnimator == null) return;
// reposition chevron
final int posY = chevronPosY();
- mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
}
})
.start();
@@ -164,19 +171,13 @@
if (D.BUG) Log.d(TAG, "show.onAnimationEnd");
setShowing(false);
}
+
@Override
public void onAnimationCancel(Animator animation) {
if (D.BUG) Log.d(TAG, "show.onAnimationCancel");
mCancelled = true;
}
});
- mContentsPositionAnimator.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float v = (Float) animation.getAnimatedValue();
- mContents.setTranslationY(v + -mDialogView.getTranslationY());
- }
- });
mContentsPositionAnimator.setInterpolator(new LogDecelerateInterpolator());
mContentsPositionAnimator.start();
@@ -218,34 +219,30 @@
setShowing(false);
}
mDialogView.animate()
- .translationY(-mDialogView.getHeight())
+ .translationX(mDialogView.getWidth() / 4)
+ .alpha(0)
.setDuration(scaledDuration(250))
.setInterpolator(new LogAccelerateInterpolator())
- .setUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mContents.setTranslationY(-mDialogView.getTranslationY());
- final int posY = chevronPosY();
- mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
- }
+ .setUpdateListener(animation -> {
+ final float v = 1 - mChevronPositionAnimator.getAnimatedFraction();
+ mBackground.setAlpha((int) (v * mBackgroundTargetAlpha));
})
.setListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
+
@Override
public void onAnimationEnd(Animator animation) {
if (mCancelled) return;
if (D.BUG) Log.d(TAG, "dismiss.onAnimationEnd");
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
- mDialog.dismiss();
- onComplete.run();
- setDismissing(false);
- }
+ mHandler.postDelayed(() -> {
+ if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
+ mDialog.dismiss();
+ onComplete.run();
+ setDismissing(false);
}, PRE_DISMISS_DELAY);
}
+
@Override
public void onAnimationCancel(Animator animation) {
if (D.BUG) Log.d(TAG, "dismiss.onAnimationCancel");
@@ -258,13 +255,13 @@
return (int) (base * ANIMATION_SCALE);
}
- private static final class LogDecelerateInterpolator implements TimeInterpolator {
+ public static final class LogDecelerateInterpolator implements TimeInterpolator {
private final float mBase;
private final float mDrift;
private final float mTimeScale;
private final float mOutputScale;
- private LogDecelerateInterpolator() {
+ public LogDecelerateInterpolator() {
this(400f, 1.4f, 0);
}
@@ -286,12 +283,12 @@
}
}
- private static final class LogAccelerateInterpolator implements TimeInterpolator {
+ public static final class LogAccelerateInterpolator implements TimeInterpolator {
private final int mBase;
private final int mDrift;
private final float mLogScale;
- private LogAccelerateInterpolator() {
+ public LogAccelerateInterpolator() {
this(100, 0);
}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 5e71dd4..136aaad 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -45,7 +45,8 @@
android-support-v7-mediarouter \
android-support-v7-palette \
android-support-v14-preference \
- android-support-v17-leanback
+ android-support-v17-leanback \
+ colorextraction
LOCAL_STATIC_JAVA_LIBRARIES := \
metrics-helper-lib \
diff --git a/packages/SystemUI/tests/AndroidTest.xml b/packages/SystemUI/tests/AndroidTest.xml
index ee78bcd..6ca42e8 100644
--- a/packages/SystemUI/tests/AndroidTest.xml
+++ b/packages/SystemUI/tests/AndroidTest.xml
@@ -19,8 +19,9 @@
</target_preparer>
<option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
<option name="test-tag" value="SystemUITests" />
- <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.systemui.tests" />
<option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
</test>
diff --git a/packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java b/packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java
new file mode 100644
index 0000000..945af34
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui;
+
+import static com.android.systemui.tuner.TunablePadding.FLAG_END;
+import static com.android.systemui.tuner.TunablePadding.FLAG_START;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Fragment;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.systemui.R.dimen;
+import com.android.systemui.RoundedCorners.TunablePaddingTagListener;
+import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarWindowView;
+import com.android.systemui.tuner.TunablePadding;
+import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
+import com.android.systemui.tuner.TunerService;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@FlakyTest
+@SmallTest
+public class RoundedCornersTest extends SysuiTestCase {
+
+ private RoundedCorners mRoundedCorners;
+ private StatusBar mStatusBar;
+ private WindowManager mWindowManager;
+ private FragmentService mFragmentService;
+ private FragmentHostManager mFragmentHostManager;
+ private TunerService mTunerService;
+ private StatusBarWindowView mView;
+ private TunablePaddingService mTunablePaddingService;
+
+ @Before
+ public void setup() {
+ mStatusBar = mock(StatusBar.class);
+ mWindowManager = mock(WindowManager.class);
+ mView = spy(new StatusBarWindowView(mContext, null));
+ when(mStatusBar.getStatusBarWindow()).thenReturn(mView);
+ mContext.putComponent(StatusBar.class, mStatusBar);
+
+ Display display = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
+ when(mWindowManager.getDefaultDisplay()).thenReturn(display);
+ mContext.addMockSystemService(WindowManager.class, mWindowManager);
+
+ mFragmentService = mDependency.injectMockDependency(FragmentService.class);
+ mFragmentHostManager = mock(FragmentHostManager.class);
+ when(mFragmentService.getFragmentHostManager(any())).thenReturn(mFragmentHostManager);
+
+ mTunerService = mDependency.injectMockDependency(TunerService.class);
+
+ mRoundedCorners = new RoundedCorners();
+ mRoundedCorners.mContext = mContext;
+ mRoundedCorners.mComponents = mContext.getComponents();
+
+ mTunablePaddingService = mDependency.injectMockDependency(TunablePaddingService.class);
+ }
+
+ @Test
+ public void testNoRounding() {
+ mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius, 0);
+
+ mRoundedCorners.start();
+ // No views added.
+ verify(mWindowManager, never()).addView(any(), any());
+ // No Fragments watched.
+ verify(mFragmentHostManager, never()).addTagListener(any(), any());
+ // No Tuners tuned.
+ verify(mTunerService, never()).addTunable(any(), any());
+ }
+
+ @Test
+ public void testRounding() {
+ mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius, 20);
+
+ mRoundedCorners.start();
+ // Add 2 windows for rounded corners (top and bottom).
+ verify(mWindowManager, times(2)).addView(any(), any());
+
+ // Add 3 tag listeners for each of the fragments that are needed.
+ verify(mFragmentHostManager, times(3)).addTagListener(any(), any());
+ // One tunable.
+ verify(mTunerService, times(1)).addTunable(any(), any());
+ // One TunablePadding.
+ verify(mTunablePaddingService, times(1)).add(any(), anyString(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void testPaddingTagListener() {
+ TunablePaddingTagListener tagListener = new TunablePaddingTagListener(14, 5);
+ View v = mock(View.class);
+ View child = mock(View.class);
+ Fragment f = mock(Fragment.class);
+ TunablePadding padding = mock(TunablePadding.class);
+
+ when(mTunablePaddingService.add(any(), anyString(), anyInt(), anyInt()))
+ .thenReturn(padding);
+ when(f.getView()).thenReturn(v);
+ when(v.findViewById(5)).thenReturn(child);
+
+ // Trigger callback and verify we get a TunablePadding created.
+ tagListener.onFragmentViewCreated(null, f);
+ verify(mTunablePaddingService).add(eq(child), eq(RoundedCorners.PADDING), eq(14),
+ eq(FLAG_START | FLAG_END));
+
+ // Call again and verify destroy is called.
+ tagListener.onFragmentViewCreated(null, f);
+ verify(padding).destroy();
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java
index b94a2fb..9d3124e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java
@@ -31,6 +31,11 @@
super(base, check);
}
+ public ArrayMap<Class<?>, Object> getComponents() {
+ if (mComponents == null) mComponents = new ArrayMap<>();
+ return mComponents;
+ }
+
@SuppressWarnings("unchecked")
public <T> T getComponent(Class<T> interfaceType) {
return (T) (mComponents != null ? mComponents.get(interfaceType) : null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java
index bbe324d..64507be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java
@@ -16,7 +16,7 @@
package com.android.systemui.doze;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import android.os.UserHandle;
import android.provider.Settings;
@@ -42,7 +42,7 @@
}
@Test
- public void alwaysOn_offByDefault() throws Exception {
+ public void alwaysOn_onByDefault() throws Exception {
if (!mDozeConfig.alwaysOnAvailable()) {
return;
}
@@ -50,6 +50,6 @@
Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.DOZE_ALWAYS_ON,
null);
- assertFalse(mDozeConfig.alwaysOnEnabled(UserHandle.USER_CURRENT));
+ assertTrue(mDozeConfig.alwaysOnEnabled(UserHandle.USER_CURRENT));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
index ee0fe7b..56c07f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
@@ -27,6 +27,7 @@
boolean pulseAborted;
boolean pulseExtended;
boolean animateWakeup;
+ boolean dozing;
@Override
public void addCallback(@NonNull Callback callback) {
@@ -40,7 +41,7 @@
@Override
public void startDozing() {
- throw new RuntimeException("not implemented");
+ dozing = true;
}
@Override
@@ -50,7 +51,7 @@
@Override
public void stopDozing() {
- throw new RuntimeException("not implemented");
+ dozing = false;
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index 7519b2d..3a09ce0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -229,7 +229,7 @@
mMachine.requestState(DOZE_AOD);
- assertEquals(Display.STATE_DOZE, mServiceFake.screenState);
+ assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
}
@Test
@@ -239,7 +239,7 @@
mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
mMachine.requestState(DOZE_PULSING);
- assertEquals(Display.STATE_DOZE, mServiceFake.screenState);
+ assertEquals(Display.STATE_ON, mServiceFake.screenState);
}
@Test
@@ -259,7 +259,7 @@
mMachine.requestState(DOZE_AOD);
mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
- assertEquals(Display.STATE_DOZE, mServiceFake.screenState);
+ assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
new file mode 100644
index 0000000..d78e739
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.doze;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.support.test.filters.SmallTest;
+import android.view.Display;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.phone.DozeParameters;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@SmallTest
+public class DozeSuspendScreenStatePreventingAdapterTest extends SysuiTestCase {
+
+ private DozeMachine.Service mInner;
+ private DozeSuspendScreenStatePreventingAdapter mWrapper;
+
+ @Before
+ public void setup() throws Exception {
+ mInner = mock(DozeMachine.Service.class);
+ mWrapper = new DozeSuspendScreenStatePreventingAdapter(mInner);
+ }
+
+ @Test
+ public void forwards_finish() throws Exception {
+ mWrapper.finish();
+ verify(mInner).finish();
+ }
+
+ @Test
+ public void forwards_setDozeScreenState_on() throws Exception {
+ mWrapper.setDozeScreenState(Display.STATE_ON);
+ verify(mInner).setDozeScreenState(Display.STATE_ON);
+ }
+
+ @Test
+ public void forwards_setDozeScreenState_off() throws Exception {
+ mWrapper.setDozeScreenState(Display.STATE_OFF);
+ verify(mInner).setDozeScreenState(Display.STATE_OFF);
+ }
+
+ @Test
+ public void forwards_setDozeScreenState_doze() throws Exception {
+ mWrapper.setDozeScreenState(Display.STATE_DOZE);
+ verify(mInner).setDozeScreenState(Display.STATE_DOZE);
+ }
+
+ @Test
+ public void forwards_setDozeScreenState_doze_suspend() throws Exception {
+ mWrapper.setDozeScreenState(Display.STATE_DOZE_SUSPEND);
+ verify(mInner).setDozeScreenState(Display.STATE_DOZE);
+ }
+
+ @Test
+ public void forwards_requestWakeUp() throws Exception {
+ mWrapper.requestWakeUp();
+ verify(mInner).requestWakeUp();
+ }
+
+ @Test
+ public void wrapIfNeeded_needed() throws Exception {
+ DozeParameters params = mock(DozeParameters.class);
+ when(params.getDozeSuspendDisplayStateSupported()).thenReturn(false);
+
+ assertEquals(DozeSuspendScreenStatePreventingAdapter.class,
+ DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params).getClass());
+ }
+
+ @Test
+ public void wrapIfNeeded_not_needed() throws Exception {
+ DozeParameters params = mock(DozeParameters.class);
+ when(params.getDozeSuspendDisplayStateSupported()).thenReturn(true);
+
+ assertSame(mInner, DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params));
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
new file mode 100644
index 0000000..19a30cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.doze;
+
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
+import static com.android.systemui.doze.DozeMachine.State.INITIALIZED;
+import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.wakelock.WakeLockFake;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DozeUiTest extends SysuiTestCase {
+
+ private AlarmManager mAlarmManager;
+ private DozeMachine mMachine;
+ private WakeLockFake mWakeLock;
+ private DozeHostFake mHost;
+ private Handler mHandler;
+ private HandlerThread mHandlerThread;
+ private DozeUi mDozeUi;
+
+ @Before
+ public void setUp() throws Exception {
+ mHandlerThread = new HandlerThread("DozeUiTest");
+ mHandlerThread.start();
+ mAlarmManager = mock(AlarmManager.class);
+ mMachine = mock(DozeMachine.class);
+ mWakeLock = new WakeLockFake();
+ mHost = new DozeHostFake();
+ mHandler = mHandlerThread.getThreadHandler();
+
+ mDozeUi = new DozeUi(mContext, mAlarmManager, mMachine, mWakeLock, mHost, mHandler);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mHandlerThread.quit();
+ mHandler = null;
+ mHandlerThread = null;
+ }
+
+ @Test
+ public void pausingAndUnpausingAod_registersTimeTickAfterUnpausing() throws Exception {
+ mDozeUi.transitionTo(UNINITIALIZED, INITIALIZED);
+ mDozeUi.transitionTo(INITIALIZED, DOZE_AOD);
+ mDozeUi.transitionTo(DOZE_AOD, DOZE_AOD_PAUSED);
+
+ clearInvocations(mAlarmManager);
+
+ mDozeUi.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
+
+ verify(mAlarmManager).setExact(anyInt(), anyLong(), eq("doze_time_tick"), any(), any());
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
index 7cbe985..4d5416b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.when;
import android.support.test.filters.SmallTest;
+import android.support.test.filters.FlakyTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -46,6 +47,7 @@
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
+@FlakyTest
public class QSDetailTest extends SysuiTestCase {
private MetricsLogger mMetricsLogger;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
index 402b12d..e3ecf77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
@@ -21,6 +21,7 @@
import static org.mockito.Mockito.when;
import android.support.test.filters.SmallTest;
+import android.support.test.filters.FlakyTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -41,6 +42,7 @@
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
+@FlakyTest
public class QSFooterTest extends LeakCheckedTest {
private QSFooter mFooter;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 0b7da1f..f0bac28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -52,6 +52,7 @@
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
+@FlakyTest
public class QSFragmentTest extends SysuiBaseFragmentTest {
private MetricsLogger mMockMetricsLogger;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
index e5ff866..637b244 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -20,6 +20,7 @@
import static org.mockito.Mockito.when;
import android.support.test.filters.SmallTest;
+import android.support.test.filters.FlakyTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -39,6 +40,7 @@
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
+@FlakyTest
public class QSPanelTest extends SysuiTestCase {
private MetricsLogger mMetricsLogger;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index ddd6615..27d781d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -52,7 +52,6 @@
@Before
public void setUp() throws Exception {
- TestableLooper.get(this).setAsMainLooper();
mManagers = new ArrayList<>();
QSTileHost host = new QSTileHost(mContext, null,
mock(StatusBarIconController.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
index d44f845..664ea71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
@@ -29,6 +29,7 @@
import android.support.test.runner.AndroidJUnit4;
import android.view.View;
+import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
import com.android.systemui.SysuiTestCase;
@@ -40,6 +41,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@FlakyTest
public class ExpandableNotificationRowTest extends SysuiTestCase {
private ExpandableNotificationRow mGroup;
@@ -95,4 +97,47 @@
row.setHideSensitive(true, false, 0, 0);
verify(row).updateShelfIconColor();
}
+
+ @Test
+ public void testIconColorShouldBeUpdatedWhenSettingDark() throws Exception {
+ ExpandableNotificationRow row = spy(mNotificationTestHelper.createRow());
+ row.setDark(true, false, 0);
+ verify(row).updateShelfIconColor();
+ }
+
+ @Test
+ public void testAboveShelfChangedListenerCalled() throws Exception {
+ ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
+ row.setAboveShelfChangedListener(listener);
+ row.setHeadsUp(true);
+ verify(listener).onAboveShelfStateChanged(true);
+ }
+
+ @Test
+ public void testAboveShelfChangedListenerCalledPinned() throws Exception {
+ ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
+ row.setAboveShelfChangedListener(listener);
+ row.setPinned(true);
+ verify(listener).onAboveShelfStateChanged(true);
+ }
+
+ @Test
+ public void testAboveShelfChangedListenerCalledHeadsUpGoingAway() throws Exception {
+ ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
+ row.setAboveShelfChangedListener(listener);
+ row.setHeadsUpAnimatingAway(true);
+ verify(listener).onAboveShelfStateChanged(true);
+ }
+ @Test
+ public void testAboveShelfChangedListenerCalledWhenGoingBelow() throws Exception {
+ ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ row.setHeadsUp(true);
+ AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
+ row.setAboveShelfChangedListener(listener);
+ row.setAboveShelf(false);
+ verify(listener).onAboveShelfStateChanged(false);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java
index 8dcc408..4dce2f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java
@@ -39,6 +39,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@FlakyTest
public class NotificationContentViewTest extends SysuiTestCase {
NotificationContentView mView;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
index fc9b608..d18e63b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
@@ -38,6 +38,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@FlakyTest
public class NotificationCustomViewWrapperTest extends SysuiTestCase {
private ExpandableNotificationRow mRow;
@@ -53,11 +54,14 @@
RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.custom_view_dark);
View v = views.apply(mContext, null);
NotificationViewWrapper wrap = NotificationCustomViewWrapper.wrap(mContext, v, mRow);
- wrap.notifyContentUpdated(mRow);
- Assert.assertTrue(wrap.getCustomBackgroundColor() != 0);
+ wrap.onContentUpdated(mRow);
+ Assert.assertTrue("No background set, when applying custom background view",
+ wrap.getCustomBackgroundColor() != 0);
views.reapply(mContext, v);
- wrap.notifyContentUpdated(mRow);
- Assert.assertTrue(wrap.getCustomBackgroundColor() != 0);
+ wrap.onReinflated();
+ wrap.onContentUpdated(mRow);
+ Assert.assertTrue("Reapplying a custom remote view lost it's background!",
+ wrap.getCustomBackgroundColor() != 0);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
index 6380847..b6827ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
@@ -232,6 +232,17 @@
}
@Test
+ public void testBindNotification_UnblockablePackageDoesNotUseChannelName() throws Exception {
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, Collections.singleton(TEST_PACKAGE_NAME));
+ final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.channel_name);
+ assertEquals(mContext.getString(R.string.notification_header_default_channel),
+ textView.getText());
+ }
+
+ @Test
public void testBindNotification_DefaultChannelUsesNameWhenMoreThanOneChannelExists()
throws Exception {
when(mMockINotificationManager.getNumNotificationChannelsForPackage(
@@ -332,6 +343,21 @@
}
@Test
+ public void testBindNotification_SettingsTextWithMultipleChannelsForUnblockableApp()
+ throws Exception {
+ when(mMockINotificationManager.getNumNotificationChannelsForPackage(
+ eq(TEST_PACKAGE_NAME), eq(TEST_UID), anyBoolean())).thenReturn(2);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn,
+ (View v, NotificationChannel c, int appUid) -> {
+ }, null, null, null, Collections.singleton(TEST_PACKAGE_NAME));
+ final TextView settingsButton =
+ (TextView) mNotificationInfo.findViewById(R.id.more_settings);
+ assertEquals(getStringById(R.string.notification_more_settings), settingsButton.getText());
+ }
+
+ @Test
public void testBindNotification_SetsOnClickListenerForDone() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
@@ -502,6 +528,19 @@
}
@Test
+ public void testbindNotification_UnblockableTextVisibleWhenAppUnblockable() throws Exception {
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ mNotificationChannel.getImportance(), mSbn, null, null, null,
+ null, Collections.singleton(TEST_PACKAGE_NAME));
+ final TextView numChannelsView =
+ (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
+ assertEquals(View.VISIBLE, numChannelsView.getVisibility());
+ assertEquals(mContext.getString(R.string.notification_unblockable_desc),
+ numChannelsView.getText());
+ }
+
+ @Test
@UiThreadTest
public void testBindNotification_ChannelDisabledTextShowsForDefaultChannel()
throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index cb238dd..6e7477f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -24,12 +24,16 @@
import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
import android.view.LayoutInflater;
+import android.widget.FrameLayout;
import android.widget.RemoteViews;
import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
+import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.NotificationInflaterTest;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
/**
* A helper class to create {@link ExpandableNotificationRow}
@@ -42,10 +46,12 @@
private final NotificationGroupManager mGroupManager = new NotificationGroupManager();
private ExpandableNotificationRow mRow;
private InflationException mException;
+ private HeadsUpManager mHeadsUpManager;
public NotificationTestHelper(Context context) {
mContext = context;
mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mHeadsUpManager = new HeadsUpManager(mContext, null, mGroupManager);
}
public ExpandableNotificationRow createRow() throws Exception {
@@ -73,6 +79,8 @@
});
ExpandableNotificationRow row = mRow;
row.setGroupManager(mGroupManager);
+ row.setHeadsUpManager(mHeadsUpManager);
+ row.setAboveShelfChangedListener(aboveShelf -> {});
UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
StatusBarNotification sbn = new StatusBarNotification("com.android.systemui",
"com.android.systemui", mId++, null, 1000,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
new file mode 100644
index 0000000..0886780
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.testing.AndroidTestingRunner;
+import android.support.test.filters.SmallTest;
+import android.view.View;
+
+import com.android.systemui.SysuiTestCase;
+
+import com.google.android.colorextraction.ColorExtractor;
+import com.google.android.colorextraction.drawable.GradientDrawable;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class ScrimViewTest extends SysuiTestCase {
+
+ ScrimView mView;
+
+ @Before
+ public void setUp() {
+ mView = new ScrimView(getContext());
+ mView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ mView.layout(0, 0, 1920, 1080);
+ }
+
+ @Test
+ public void testSetDrawable_UpdateDrawable() {
+ Drawable drawable = new ColorDrawable(Color.GREEN);
+ mView.setDrawable(drawable);
+ assertEquals(drawable, mView.getDrawable());
+ }
+
+ @Test
+ public void testCreation_initialColor() {
+ GradientDrawable drawable = (GradientDrawable) mView.getDrawable();
+ ColorExtractor.GradientColors colors = mView.getColors();
+ assertEquals("Main color should be set upon creation",
+ drawable.getMainColor(), colors.getMainColor());
+ assertEquals("Secondary color should be set upon creation",
+ drawable.getSecondaryColor(), colors.getSecondaryColor());
+ }
+
+ @Test
+ public void testSetViewAlpha_propagatesToDrawable() {
+ float alpha = 0.5f;
+ mView.setViewAlpha(alpha);
+ assertEquals(mView.getViewAlpha(), alpha);
+ }
+
+ @Test
+ public void testOnDraw_ExcludeRectDrawable() {
+ mView.setExcludedArea(new Rect(10, 10, 20, 20));
+ Canvas canvas = mock(Canvas.class);
+ mView.onDraw(canvas);
+ // One time for each rect side
+ verify(canvas, times(4)).clipRect(anyInt(), anyInt(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void setTint_set() {
+ int tint = Color.BLUE;
+ mView.setTint(tint);
+ assertEquals(mView.getTint(), tint);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SignalClusterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SignalClusterViewTest.java
deleted file mode 100644
index 28a5aa3..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SignalClusterViewTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.support.test.filters.SmallTest;
-import android.telephony.SubscriptionInfo;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.LayoutInflater;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Arrays;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class SignalClusterViewTest extends SysuiTestCase {
-
- private SignalClusterView mSignalCluster;
-
- @Before
- public void setup() {
- mSignalCluster = (SignalClusterView) LayoutInflater.from(mContext)
- .inflate(R.layout.signal_cluster_view, null);
- }
-
- @Test
- public void testNonDefaultSim() {
- SubscriptionInfo first = mock(SubscriptionInfo.class);
- SubscriptionInfo second = mock(SubscriptionInfo.class);
- when(first.getSubscriptionId()).thenReturn(0);
- when(second.getSubscriptionId()).thenReturn(1);
- mSignalCluster.setSubs(Arrays.asList(first, second));
- mSignalCluster.setQsSignalCluster();
- mSignalCluster.setMobileDataIndicators(new IconState(true, 0, 0, ""), null, 0, 0,
- false, false, "", "", false, 1, false);
- }
-
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
new file mode 100644
index 0000000..1ee9b32
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.FrameLayout;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationTestHelper;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AboveShelfObserverTest extends SysuiTestCase {
+
+ private AboveShelfObserver mObserver;
+ private FrameLayout mHostLayout;
+ private NotificationTestHelper mNotificationTestHelper;
+ private AboveShelfObserver.HasViewAboveShelfChangedListener mListener;
+
+ @Before
+ public void setUp() throws Exception {
+ mNotificationTestHelper = new NotificationTestHelper(getContext());
+ mHostLayout = new FrameLayout(getContext());
+ mObserver = new AboveShelfObserver(mHostLayout);
+ ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ row.setAboveShelfChangedListener(mObserver);
+ mHostLayout.addView(row);
+ row = mNotificationTestHelper.createRow();
+ row.setAboveShelfChangedListener(mObserver);
+ mHostLayout.addView(row);
+ mListener = mock(AboveShelfObserver.HasViewAboveShelfChangedListener.class);
+ }
+
+ @Test
+ public void testObserverChangesWhenGoingAbove() {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) mHostLayout.getChildAt(0);
+ mObserver.setListener(mListener);
+ row.setHeadsUp(true);
+ verify(mListener).onHasViewsAboveShelfChanged(true);
+ }
+
+ @Test
+ public void testObserverChangesWhenGoingBelow() {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) mHostLayout.getChildAt(0);
+ row.setHeadsUp(true);
+ mObserver.setListener(mListener);
+ row.setHeadsUp(false);
+ verify(mListener).onHasViewsAboveShelfChanged(false);
+ }
+
+ @Test
+ public void testStaysAboveWhenOneGoesAway() {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) mHostLayout.getChildAt(0);
+ row.setHeadsUp(true);
+ row = (ExpandableNotificationRow) mHostLayout.getChildAt(1);
+ row.setHeadsUp(true);
+ row.setHeadsUp(false);
+ Assert.assertTrue("There are still views above the shelf but removing one cleared it",
+ mObserver.hasViewsAboveShelf());
+ }
+}
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java
index ee8db88..c7af0d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java
@@ -49,6 +49,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@FlakyTest
public class NotificationInflaterTest extends SysuiTestCase {
private NotificationInflater mNotificationInflater;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationViewWrapperTest.java
index ebeb24c8..9da28a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationViewWrapperTest.java
@@ -24,6 +24,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationTestHelper;
import org.junit.Before;
import org.junit.Test;
@@ -35,7 +36,9 @@
@Test
public void constructor_doesntUseViewContext() throws Exception {
- new TestableNotificationViewWrapper(mContext, null /* view */, null /* row */);
+ new TestableNotificationViewWrapper(mContext,
+ new View(mContext),
+ new NotificationTestHelper(getContext()).createRow());
}
static class TestableNotificationViewWrapper extends NotificationViewWrapper {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 55ec307..db6647c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -72,7 +72,7 @@
NotificationData mNotificationData;
PowerManager mPowerManager;
SystemServicesProxy mSystemServicesProxy;
-
+ NotificationPanelView mNotificationPanelView;
private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
@Before
@@ -85,6 +85,7 @@
mHeadsUpManager = mock(HeadsUpManager.class);
mNotificationData = mock(NotificationData.class);
mSystemServicesProxy = mock(SystemServicesProxy.class);
+ mNotificationPanelView = mock(NotificationPanelView.class);
IPowerManager powerManagerService = mock(IPowerManager.class);
HandlerThread handlerThread = new HandlerThread("TestThread");
handlerThread.start();
@@ -95,7 +96,7 @@
mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
mKeyguardIndicationController, mStackScroller, mHeadsUpManager,
- mNotificationData, mPowerManager, mSystemServicesProxy);
+ mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView);
doAnswer(invocation -> {
OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0];
@@ -287,7 +288,7 @@
public TestableStatusBar(StatusBarKeyguardViewManager man,
UnlockMethodCache unlock, KeyguardIndicationController key,
NotificationStackScrollLayout stack, HeadsUpManager hum, NotificationData nd,
- PowerManager pm, SystemServicesProxy ssp) {
+ PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView) {
mStatusBarKeyguardViewManager = man;
mUnlockMethodCache = unlock;
mKeyguardIndicationController = key;
@@ -297,6 +298,7 @@
mUseHeadsUp = true;
mPowerManager = pm;
mSystemServicesProxy = ssp;
+ mNotificationPanel = panelView;
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
new file mode 100644
index 0000000..a068a5e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.DragDownHelper;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.StatusBarState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StatusBarWindowViewTest extends SysuiTestCase {
+
+ private StatusBarWindowView mView;
+ private StatusBar mStatusBar;
+ private DragDownHelper mDragDownHelper;
+
+ @Before
+ public void setUp() {
+ mView = new StatusBarWindowView(getContext(), null);
+ mStatusBar = mock(StatusBar.class);
+ mView.setService(mStatusBar);
+ mDragDownHelper = mock(DragDownHelper.class);
+ mView.setDragDownHelper(mDragDownHelper);
+ }
+
+ @Test
+ public void testDragDownHelperCalledWhenDraggingDown() throws Exception {
+ when(mStatusBar.getBarState()).thenReturn(StatusBarState.SHADE);
+ when(mDragDownHelper.isDraggingDown()).thenReturn(true);
+ long now = SystemClock.elapsedRealtime();
+ MotionEvent ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0 /* x */, 0 /* y */,
+ 0 /* meta */);
+ mView.onTouchEvent(ev);
+ verify(mDragDownHelper).onTouchEvent(ev);
+ ev.recycle();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
index 6157d44..cb20639 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
@@ -111,7 +111,7 @@
String typeDescription = "Test 1";
String description = "Test 2";
int type = R.drawable.stat_sys_data_fully_connected_1x;
- int qsType = type;
+ int qsType = R.drawable.ic_qs_signal_1x;
boolean wide = true;
int subId = 5;
boolean roaming = true;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
new file mode 100644
index 0000000..a915cb2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+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.content.res.Configuration;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.util.Log;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.OverlayPlugin;
+import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.statusbar.policy.ExtensionController.Extension;
+import com.android.systemui.statusbar.policy.ExtensionController.TunerFactory;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Map;
+import java.util.function.Consumer;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class ExtensionControllerImplTest extends SysuiTestCase {
+
+ private PluginManager mPluginManager;
+ private TunerService mTunerService;
+ private ExtensionController mExtensionController;
+ private ConfigurationController mConfigurationController;
+
+ @Before
+ public void setup() {
+ mPluginManager = mDependency.injectMockDependency(PluginManager.class);
+ mTunerService = mDependency.injectMockDependency(TunerService.class);
+ mConfigurationController = mDependency.injectMockDependency(ConfigurationController.class);
+ mExtensionController = Dependency.get(ExtensionController.class);
+ }
+
+ @Test
+ public void testPlugin() {
+ OverlayPlugin plugin = mock(OverlayPlugin.class);
+
+ Extension ext = mExtensionController.newExtension(OverlayPlugin.class)
+ .withPlugin(OverlayPlugin.class)
+ .build();
+ ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class);
+ verify(mPluginManager).addPluginListener(eq(OverlayPlugin.ACTION), listener.capture(),
+ eq(OverlayPlugin.class));
+
+ listener.getValue().onPluginConnected(plugin, null);
+ assertEquals(plugin, ext.get());
+
+ ext.destroy();
+ verify(mPluginManager).removePluginListener(any());
+ }
+
+ @Test
+ public void testTuner() {
+ String[] keys = new String[] { "key1", "key2" };
+ TunerFactory<Object> factory = new ExtensionController.TunerFactory() {
+ @Override
+ public String[] keys() {
+ return keys;
+ }
+
+ @Override
+ public Object create(Map settings) {
+ return null;
+ }
+ };
+ Extension ext = mExtensionController.newExtension(Object.class)
+ .withTunerFactory(factory)
+ .build();
+ verify(mTunerService).addTunable(any(), eq(keys[0]), eq(keys[1]));
+
+ ext.destroy();
+ verify(mTunerService).removeTunable(any());
+ }
+
+ @Test
+ @RunWithLooper
+ public void testUiMode() {
+ Object o = new Object();
+ Extension ext = mExtensionController.newExtension(Object.class)
+ .withUiMode(Configuration.UI_MODE_TYPE_CAR, () -> o)
+ .build();
+ ArgumentCaptor<ConfigurationListener> captor = ArgumentCaptor.forClass(
+ ConfigurationListener.class);
+ verify(mConfigurationController).addCallback(captor.capture());
+
+ Configuration c = new Configuration(mContext.getResources().getConfiguration());
+ c.uiMode = 0;
+ captor.getValue().onConfigChanged(c);
+ TestableLooper.get(this).processAllMessages();
+ assertNull(ext.get());
+
+ c.uiMode = Configuration.UI_MODE_TYPE_CAR;
+ captor.getValue().onConfigChanged(c);
+ TestableLooper.get(this).processAllMessages();
+ assertEquals(o, ext.get());
+
+ ext.destroy();
+ verify(mConfigurationController).removeCallback(eq(captor.getValue()));
+ }
+
+ @Test
+ public void testDefault() {
+ Object o = new Object();
+ Extension ext = mExtensionController.newExtension(Object.class)
+ .withDefault(() -> o)
+ .build();
+ assertEquals(o, ext.get());
+ }
+
+ @Test
+ @RunWithLooper
+ public void testSortOrder() {
+ Log.d("TestTest", "Config " + mContext.getResources().getConfiguration().uiMode);
+ Object def = new Object();
+ Object uiMode = new Object();
+ Object tuner = new Object();
+ Plugin plugin = mock(Plugin.class);
+ TunerFactory<Object> factory = mock(TunerFactory.class);
+ Extension ext = mExtensionController.newExtension(Object.class)
+ .withDefault(() -> def)
+ .withUiMode(Configuration.UI_MODE_TYPE_CAR, () -> uiMode)
+ .withTunerFactory(factory)
+ .withPlugin(Object.class, "some_action")
+ .build();
+
+ // Test default first.
+ assertEquals(def, ext.get());
+
+ // Enable a UI mode and check that.
+ ArgumentCaptor<ConfigurationListener> captor = ArgumentCaptor.forClass(
+ ConfigurationListener.class);
+ verify(mConfigurationController).addCallback(captor.capture());
+ Configuration c = new Configuration(mContext.getResources().getConfiguration());
+ c.uiMode |= Configuration.UI_MODE_TYPE_CAR;
+ captor.getValue().onConfigChanged(c);
+ TestableLooper.get(this).processAllMessages();
+ assertEquals(uiMode, ext.get());
+
+ // Turn on tuner item and check that.
+ when(factory.create(any())).thenReturn(tuner);
+ ArgumentCaptor<Tunable> tunable = ArgumentCaptor.forClass(Tunable.class);
+ verify(mTunerService).addTunable(tunable.capture(), any());
+ tunable.getValue().onTuningChanged(null, null);
+ assertEquals(tuner, ext.get());
+
+ // Lastly, check a plugin.
+ ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class);
+ verify(mPluginManager).addPluginListener(any(), listener.capture(), any());
+ listener.getValue().onPluginConnected(plugin, null);
+ assertEquals(plugin, ext.get());
+ }
+
+ @Test
+ public void testCallback() {
+ Consumer<Object> callback = mock(Consumer.class);
+ final Object o = new Object();
+ mExtensionController.newExtension(Object.class)
+ .withDefault(() -> o)
+ .withCallback(callback)
+ .build();
+ verify(callback).accept(eq(o));
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java
deleted file mode 100644
index 3d79d5b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import static org.junit.Assert.assertEquals;
-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 android.support.test.filters.SmallTest;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.OverlayPlugin;
-import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.statusbar.policy.ExtensionController.Extension;
-import com.android.systemui.statusbar.policy.ExtensionController.TunerFactory;
-import com.android.systemui.tuner.TunerService;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Map;
-import java.util.function.Consumer;
-
-@SmallTest
-public class ExtensionControllerTest extends SysuiTestCase {
-
- private PluginManager mPluginManager;
- private TunerService mTunerService;
- private ExtensionController mExtensionController;
-
- @Before
- public void setup() {
- mPluginManager = mDependency.injectMockDependency(PluginManager.class);
- mTunerService = mDependency.injectMockDependency(TunerService.class);
- mExtensionController = Dependency.get(ExtensionController.class);
- }
-
- @Test
- public void testPlugin() {
- Extension ext = mExtensionController.newExtension(OverlayPlugin.class)
- .withPlugin(OverlayPlugin.class)
- .build();
- verify(mPluginManager).addPluginListener(eq(OverlayPlugin.ACTION), any(),
- eq(OverlayPlugin.class));
-
- ext.destroy();
- verify(mPluginManager).removePluginListener(any());
- }
-
- @Test
- public void testTuner() {
- String[] keys = new String[] { "key1", "key2" };
- TunerFactory<Object> factory = new ExtensionController.TunerFactory() {
- @Override
- public String[] keys() {
- return keys;
- }
-
- @Override
- public Object create(Map settings) {
- return null;
- }
- };
- Extension ext = mExtensionController.newExtension(Object.class)
- .withTunerFactory(factory)
- .build();
- verify(mTunerService).addTunable(any(), eq(keys[0]), eq(keys[1]));
-
- ext.destroy();
- verify(mTunerService).removeTunable(any());
- }
-
- @Test
- public void testDefault() {
- Object o = new Object();
- Extension ext = mExtensionController.newExtension(Object.class)
- .withDefault(() -> o)
- .build();
- assertEquals(o, ext.get());
- }
-
- @Test
- public void testCallback() {
- Consumer<Object> callback = mock(Consumer.class);
- final Object o = new Object();
- mExtensionController.newExtension(Object.class)
- .withDefault(() -> o)
- .withCallback(callback)
- .build();
- verify(callback).accept(eq(o));
- }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index c233fea..505e1d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -70,7 +70,7 @@
protected static final int DEFAULT_SIGNAL_STRENGTH = DEFAULT_LEVEL;
protected static final int DEFAULT_QS_SIGNAL_STRENGTH = DEFAULT_LEVEL;
protected static final int DEFAULT_ICON = TelephonyIcons.ICON_3G;
- protected static final int DEFAULT_QS_ICON = DEFAULT_ICON;
+ protected static final int DEFAULT_QS_ICON = TelephonyIcons.QS_DATA_3G;
protected NetworkControllerImpl mNetworkController;
protected MobileSignalController mMobileSignalController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index ac64263..dfe00f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -25,7 +25,7 @@
setupDefaultSignal();
verifyDataIndicators(TelephonyIcons.ICON_3G,
- TelephonyIcons.ICON_3G);
+ TelephonyIcons.QS_DATA_3G);
}
@Test
@@ -35,7 +35,7 @@
TelephonyManager.NETWORK_TYPE_GSM);
verifyDataIndicators(TelephonyIcons.ICON_G,
- TelephonyIcons.ICON_G);
+ TelephonyIcons.QS_DATA_G);
}
@Test
@@ -45,7 +45,7 @@
TelephonyManager.NETWORK_TYPE_CDMA);
verifyDataIndicators(TelephonyIcons.ICON_1X,
- TelephonyIcons.ICON_1X);
+ TelephonyIcons.QS_DATA_1X);
}
@Test
@@ -55,7 +55,7 @@
TelephonyManager.NETWORK_TYPE_EDGE);
verifyDataIndicators(TelephonyIcons.ICON_E,
- TelephonyIcons.ICON_E);
+ TelephonyIcons.QS_DATA_E);
}
@Test
@@ -65,7 +65,7 @@
TelephonyManager.NETWORK_TYPE_LTE);
verifyDataIndicators(TelephonyIcons.ICON_LTE,
- TelephonyIcons.ICON_LTE);
+ TelephonyIcons.QS_DATA_LTE);
}
@Test
@@ -75,7 +75,7 @@
TelephonyManager.NETWORK_TYPE_HSPA);
verifyDataIndicators(TelephonyIcons.ICON_H,
- TelephonyIcons.ICON_H);
+ TelephonyIcons.QS_DATA_H);
}
@Test
@@ -104,7 +104,7 @@
TelephonyManager.NETWORK_TYPE_LTE);
verifyDataIndicators(TelephonyIcons.ICON_4G,
- TelephonyIcons.ICON_4G);
+ TelephonyIcons.QS_DATA_4G);
}
@Ignore("Flaky")
@@ -117,7 +117,7 @@
setConnectivity(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
verifyDataIndicators(TelephonyIcons.ICON_DATA_DISABLED,
- TelephonyIcons.ICON_DATA_DISABLED);
+ TelephonyIcons.QS_ICON_DATA_DISABLED);
}
@Test
@@ -148,7 +148,7 @@
mNetworkController.handleConfigurationChanged();
verifyDataIndicators(TelephonyIcons.ICON_4G,
- TelephonyIcons.ICON_4G);
+ TelephonyIcons.QS_DATA_4G);
}
@Test
@@ -158,13 +158,13 @@
TelephonyManager.NETWORK_TYPE_LTE);
verifyDataIndicators(TelephonyIcons.ICON_LTE,
- TelephonyIcons.ICON_LTE);
+ TelephonyIcons.QS_DATA_LTE);
when(mServiceState.getDataNetworkType())
.thenReturn(TelephonyManager.NETWORK_TYPE_HSPA);
updateServiceState();
verifyDataIndicators(TelephonyIcons.ICON_H,
- TelephonyIcons.ICON_H);
+ TelephonyIcons.QS_DATA_H);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index aa62075..1627925 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -214,7 +214,7 @@
verifyLastQsMobileDataIndicators(true,
testStrength,
- TelephonyIcons.ICON_1X, false, false);
+ TelephonyIcons.QS_DATA_1X, false, false);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index ae0509a..7ca9d73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.doThrow;
@@ -29,7 +30,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.StringParceledListSlice;
+import android.content.pm.UserInfo;
import android.net.ConnectivityManager;
+import android.os.UserManager;
import android.security.IKeyChainService;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
@@ -55,6 +58,7 @@
public class SecurityControllerTest extends SysuiTestCase implements SecurityControllerCallback {
private final DevicePolicyManager mDevicePolicyManager = mock(DevicePolicyManager.class);
private final IKeyChainService.Stub mKeyChainService = mock(IKeyChainService.Stub.class);
+ private final UserManager mUserManager = mock(UserManager.class);
private SecurityControllerImpl mSecurityController;
private CountDownLatch mStateChangedLatch;
@@ -67,12 +71,15 @@
@Before
public void setUp() throws Exception {
mContext.addMockSystemService(Context.DEVICE_POLICY_SERVICE, mDevicePolicyManager);
+ mContext.addMockSystemService(Context.USER_SERVICE, mUserManager);
mContext.addMockSystemService(Context.CONNECTIVITY_SERVICE, mock(ConnectivityManager.class));
Intent intent = new Intent(IKeyChainService.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
mContext.addMockService(comp, mKeyChainService);
+ when(mUserManager.getUserInfo(anyInt())).thenReturn(new UserInfo());
+
when(mKeyChainService.getUserCaAliases())
.thenReturn(new StringParceledListSlice(new ArrayList<String>()));
// Without this line, mKeyChainService gets wrapped in a proxy when Stub.asInterface() is
@@ -80,12 +87,10 @@
when(mKeyChainService.queryLocalInterface("android.security.IKeyChainService"))
.thenReturn(mKeyChainService);
- mSecurityController = new SecurityControllerImpl(mContext);
-
- // Wait for one or two state changes from the CACertLoader(s) in the constructor of
- // mSecurityController
- mStateChangedLatch = new CountDownLatch(mSecurityController.hasWorkProfile() ? 2 : 1);
- mSecurityController.addCallback(this);
+ // Wait for callbacks from 1) the CACertLoader and 2) the onUserSwitched() function in the
+ // constructor of mSecurityController
+ mStateChangedLatch = new CountDownLatch(2);
+ mSecurityController = new SecurityControllerImpl(mContext, this);
}
@After
@@ -109,13 +114,40 @@
}
@Test
- @Ignore("Flaky")
- public void testCaCertLoader() throws Exception {
+ public void testWorkAccount() throws Exception {
+ // Wait for the callbacks from setUp()
+ assertTrue(mStateChangedLatch.await(1, TimeUnit.SECONDS));
+ assertFalse(mSecurityController.hasCACertInCurrentUser());
+
+ final int PRIMARY_USER_ID = 0;
+ final int MANAGED_USER_ID = 1;
+ List<UserInfo> profiles = Arrays.asList(new UserInfo(PRIMARY_USER_ID, "Primary",
+ UserInfo.FLAG_PRIMARY),
+ new UserInfo(MANAGED_USER_ID, "Working",
+ UserInfo.FLAG_MANAGED_PROFILE));
+ when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+ assertTrue(mSecurityController.hasWorkProfile());
+ assertFalse(mSecurityController.hasCACertInWorkProfile());
+
+ mStateChangedLatch = new CountDownLatch(1);
+
+ when(mKeyChainService.getUserCaAliases())
+ .thenReturn(new StringParceledListSlice(Arrays.asList("One CA Alias")));
+
+ mSecurityController.new CACertLoader()
+ .execute(MANAGED_USER_ID);
+
assertTrue(mStateChangedLatch.await(3, TimeUnit.SECONDS));
+ assertTrue(mSecurityController.hasCACertInWorkProfile());
+ }
+
+ @Test
+ public void testCaCertLoader() throws Exception {
+ // Wait for the callbacks from setUp()
+ assertTrue(mStateChangedLatch.await(1, TimeUnit.SECONDS));
assertFalse(mSecurityController.hasCACertInCurrentUser());
// With a CA cert
-
mStateChangedLatch = new CountDownLatch(1);
when(mKeyChainService.getUserCaAliases())
@@ -138,7 +170,7 @@
mSecurityController.new CACertLoader()
.execute(0);
- assertFalse(mStateChangedLatch.await(3, TimeUnit.SECONDS));
+ assertFalse(mStateChangedLatch.await(1, TimeUnit.SECONDS));
assertTrue(mSecurityController.hasCACertInCurrentUser());
// The retry takes 30s
//assertTrue(mStateChangedLatch.await(31, TimeUnit.SECONDS));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java
index 2dd96b6..bc6833d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java
@@ -37,6 +37,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@FlakyTest
public class NotificationChildrenContainerTest extends SysuiTestCase {
private ExpandableNotificationRow mGroup;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/tuner/TunablePaddingTest.java b/packages/SystemUI/tests/src/com/android/systemui/tuner/TunablePaddingTest.java
new file mode 100644
index 0000000..9fd8cc3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/tuner/TunablePaddingTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.tuner;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
+
+import android.support.test.filters.SmallTest;
+import android.testing.LeakCheck.Tracker;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.systemui.utils.leaks.LeakCheckedTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@SmallTest
+public class TunablePaddingTest extends LeakCheckedTest {
+
+ private static final String KEY = "KEY";
+ private static final int DEFAULT = 42;
+ private View mView;
+ private TunablePadding mTunablePadding;
+ private TunerService mTunerService;
+
+ @Before
+ public void setup() {
+ injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
+ mView = mock(View.class, withSettings().spiedInstance(new View(mContext)));
+
+ mTunerService = mDependency.injectMockDependency(TunerService.class);
+ Tracker tracker = mLeakCheck.getTracker("tuner");
+ doAnswer(invocation -> {
+ tracker.getLeakInfo(invocation.getArguments()[0]).addAllocation(new Throwable());
+ return null;
+ }).when(mTunerService).addTunable(any(), any());
+ doAnswer(invocation -> {
+ tracker.getLeakInfo(invocation.getArguments()[0]).clearAllocations();
+ return null;
+ }).when(mTunerService).removeTunable(any());
+ }
+
+ @Test
+ public void testFlags() {
+ mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
+ TunablePadding.FLAG_START);
+ mTunablePadding.onTuningChanged(null, null);
+ verify(mView).setPadding(eq(DEFAULT), eq(0), eq(0), eq(0));
+ mTunablePadding.destroy();
+
+ mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
+ TunablePadding.FLAG_TOP);
+ mTunablePadding.onTuningChanged(null, null);
+ verify(mView).setPadding(eq(0), eq(DEFAULT), eq(0), eq(0));
+ mTunablePadding.destroy();
+
+ mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
+ TunablePadding.FLAG_END);
+ mTunablePadding.onTuningChanged(null, null);
+ verify(mView).setPadding(eq(0), eq(0), eq(DEFAULT), eq(0));
+ mTunablePadding.destroy();
+
+ mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
+ TunablePadding.FLAG_BOTTOM);
+ mTunablePadding.onTuningChanged(null, null);
+ verify(mView).setPadding(eq(0), eq(0), eq(0), eq(DEFAULT));
+ mTunablePadding.destroy();
+ }
+
+ @Test
+ public void testRtl() {
+ when(mView.isLayoutRtl()).thenReturn(true);
+
+ mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
+ TunablePadding.FLAG_END);
+ mTunablePadding.onTuningChanged(null, null);
+ verify(mView).setPadding(eq(DEFAULT), eq(0), eq(0), eq(0));
+ mTunablePadding.destroy();
+
+ mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
+ TunablePadding.FLAG_START);
+ mTunablePadding.onTuningChanged(null, null);
+ verify(mView).setPadding(eq(0), eq(0), eq(DEFAULT), eq(0));
+ mTunablePadding.destroy();
+ }
+
+ @Test
+ public void testTuning() {
+ int value = 3;
+ mTunablePadding = TunablePadding.addTunablePadding(mView, KEY, DEFAULT,
+ TunablePadding.FLAG_START);
+ mTunablePadding.onTuningChanged(KEY, String.valueOf(value));
+
+ DisplayMetrics metrics = new DisplayMetrics();
+ mContext.getSystemService(WindowManager.class).getDefaultDisplay().getMetrics(metrics);
+ int output = (int) (metrics.density * value);
+ verify(mView).setPadding(eq(output), eq(0), eq(0), eq(0));
+
+ mTunablePadding.destroy();
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java
index 00846dc..daf7547 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java
@@ -14,6 +14,7 @@
package com.android.systemui.utils.leaks;
+import android.content.Context;
import android.testing.LeakCheck;
import android.testing.LeakCheck.Tracker;
@@ -75,6 +76,11 @@
}
@Override
+ public ExtensionBuilder<T> withUiMode(int mode, Supplier<T> def) {
+ return null;
+ }
+
+ @Override
public Extension build() {
return new FakeExtension(mAllocation);
}
@@ -94,11 +100,19 @@
}
@Override
+ public Context getContext() {
+ return null;
+ }
+
+ @Override
public void destroy() {
mTracker.getLeakInfo(mAllocation).clearAllocations();
}
@Override
+ public void addCallback(Consumer<T> callback) {
+ }
+
public T reload() {
return null;
}
diff --git a/packages/SysuiDarkThemeOverlay/Android.mk b/packages/SysuiDarkThemeOverlay/Android.mk
new file mode 100644
index 0000000..4b83058
--- /dev/null
+++ b/packages/SysuiDarkThemeOverlay/Android.mk
@@ -0,0 +1,13 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_RRO_THEME := SysuiDarkTheme
+LOCAL_CERTIFICATE := platform
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := SysuiDarkThemeOverlay
+
+include $(BUILD_RRO_PACKAGE)
diff --git a/packages/SysuiDarkThemeOverlay/AndroidManifest.xml b/packages/SysuiDarkThemeOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..ba1c91c
--- /dev/null
+++ b/packages/SysuiDarkThemeOverlay/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.theme.dark"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <overlay android:targetPackage="android" android:priority="1"/>
+
+ <application android:label="@string/sysui_overlay_dark" android:hasCode="false"/>
+</manifest>
diff --git a/packages/SysuiDarkThemeOverlay/res/values/strings.xml b/packages/SysuiDarkThemeOverlay/res/values/strings.xml
new file mode 100644
index 0000000..71f48d6
--- /dev/null
+++ b/packages/SysuiDarkThemeOverlay/res/values/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <string name="sysui_overlay_dark">Dark</string>
+
+</resources>
+
diff --git a/packages/SysuiDarkThemeOverlay/res/values/themes_device_defaults.xml b/packages/SysuiDarkThemeOverlay/res/values/themes_device_defaults.xml
new file mode 100644
index 0000000..28ecfa0
--- /dev/null
+++ b/packages/SysuiDarkThemeOverlay/res/values/themes_device_defaults.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Dark theme for a window that should look like the Settings app. -->
+ <style name="Theme.DeviceDefault.QuickSettings" parent="android:Theme.DeviceDefault">
+ <!-- Color palette -->
+ <item name="android:colorPrimary">@*android:color/primary_device_default_settings</item>
+ <item name="android:colorPrimaryDark">@*android:color/primary_dark_device_default_settings</item>
+ <item name="android:textColorPrimaryInverse">@*android:color/primary_text_material_light</item>
+ <item name="android:textColorSecondaryInverse">@*android:color/secondary_text_material_light</item>
+ <item name="android:colorSecondary">@*android:color/secondary_device_default_settings</item>
+ <item name="android:colorAccent">@*android:color/accent_device_default_dark</item>
+ <item name="android:colorControlNormal">?android:attr/textColorPrimary</item>
+ <item name="android:colorBackgroundFloating">#000</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/packages/VpnDialogs/res/values-fr-rCA/strings.xml b/packages/VpnDialogs/res/values-fr-rCA/strings.xml
index 83aef21..0c434a0 100644
--- a/packages/VpnDialogs/res/values-fr-rCA/strings.xml
+++ b/packages/VpnDialogs/res/values-fr-rCA/strings.xml
@@ -18,7 +18,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Demande de connexion"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> veut configurer une connexion RPV qui permet de surveiller le trafic réseau. N\'acceptez que si vous faites confiance à la source. <br /><br /><img src=vpn_icon/> s\'affiche dans le haut de votre écran lorsqu\'une connexion RPV est active."</string>
- <string name="legacy_title" msgid="192936250066580964">"VPN connecté"</string>
+ <string name="legacy_title" msgid="192936250066580964">"RPV connecté"</string>
<string name="configure" msgid="4905518375574791375">"Configurer"</string>
<string name="disconnect" msgid="971412338304200056">"Déconnecter"</string>
<string name="session" msgid="6470628549473641030">"Session :"</string>
diff --git a/proto/src/gnss.proto b/proto/src/gnss.proto
new file mode 100644
index 0000000..33a5584
--- /dev/null
+++ b/proto/src/gnss.proto
@@ -0,0 +1,36 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+// Author: siddharthr@google.com (Siddharth Ray)
+// Protos for uploading GNSS metrics.
+
+syntax = "proto2";
+
+package clearcut.connectivity;
+
+option java_package = "com.android.internal.location";
+option java_outer_classname = "GnssLogsProto";
+
+message GnssLog {
+ // Number of location reports processed
+ optional int32 num_location_report_processed = 1;
+
+ // Location failure (in percent)
+ optional int32 percentage_location_failure = 2;
+
+ // Number of time to first fix processed
+ optional int32 num_time_to_first_fix_processed = 3;
+
+ // Mean time to first fix (in seconds)
+ optional int32 mean_time_to_first_fix_secs = 4;
+
+ // Standard deviation of time to first fix (in seconds)
+ optional int32 standard_deviation_time_to_first_fix_secs = 5;
+
+ // Number of position accuracy processed
+ optional int32 num_position_accuracy_processed = 6;
+
+ // Mean position accuracy (in meters)
+ optional int32 mean_position_accuracy_meters = 7;
+
+ // Standard deviation of position accuracy (in meters)
+ optional int32 standard_deviation_position_accuracy_meters = 8;
+}
\ No newline at end of file
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 3dd75bb..11ea841 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3974,6 +3974,122 @@
// ---- End O Constants, all O constants go above this line ----
+ // OPEN: Settings > System > Languages & input > Advanced > Lift to open camera
+ SETTINGS_GESTURE_CAMERA_LIFT_TRIGGER = 986;
+
+ // OPEN: Settings > Battery > High Usage > Abnormal app page
+ // CATEGORY: SETTINGS
+ FUELGAUGE_ANOMALY_DETAIL = 987;
+
+ // OPEN: Settings > Battery > High Usage
+ DIALOG_HANDLE_ANOMALY = 988;
+
+ // ACTION: Camera lift gesture
+ // CATEGORY: GLOBAL_SYSTEM_UI
+ // OS: O
+ ACTION_CAMERA_LIFT_TRIGGER = 989;
+
+ // OPEN: Choose screen lock dialog in Settings
+ // CATEGORY: SETTINGS
+ // OS: O DR
+ SETTINGS_CHOOSE_LOCK_DIALOG = 990;
+
+ // OPEN: Assist Gesture training intro in Settings
+ // CATEGORY: SETTINGS
+ // OS: O DR
+ SETTINGS_ASSIST_GESTURE_TRAINING_INTRO = 991;
+
+ // OPEN: Assist Gesture training enrolling in Settings
+ // CATEGORY: SETTINGS
+ // OS: O DR
+ SETTINGS_ASSIST_GESTURE_TRAINING_ENROLLING = 992;
+
+ // OPEN: Assist Gesture training finished in Settings
+ // CATEGORY: SETTINGS
+ // OS: O DR
+ SETTINGS_ASSIST_GESTURE_TRAINING_FINISHED = 993;
+
+ // FIELD: The numeric preference value (of type long) when it is changed in Settings
+ FIELD_SETTINGS_PREFERENCE_CHANGE_LONG_VALUE = 994;
+
+ // FIELD: The numeric preference value (of type float) when it is changed in Settings
+ FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE = 995;
+
+ // OPEN: Settings > System > Languages & input > Assist gesture
+ // CATEGORY: SETTINGS
+ // OS: O DR
+ SETTINGS_ASSIST_GESTURE = 996;
+
+ // ACTION: Assist gesture released without triggering
+ // CATEGORY: GLOBAL_SYSTEM_UI
+ // OS: O DR
+ ASSIST_GESTURE_RELEASED = 997;
+
+ // ACTION: Assist gesture primed
+ // CATEGORY: GLOBAL_SYSTEM_UI
+ // OS: O DR
+ ASSIST_GESTURE_PRIMED = 998;
+
+ // ACTION: Assist gesture triggered
+ // CATEGORY: GLOBAL_SYSTEM_UI
+ // OS: O DR
+ ASSIST_GESTURE_TRIGGERED = 999;
+
+ // ACTION: Update default app from Settings
+ ACTION_SETTINGS_UPDATE_DEFAULT_APP = 1000;
+
+ // FIELD - Query length when Settings search result is clicked
+ FIELD_SETTINGS_SERACH_QUERY_LENGTH = 1001;
+
+ // FIELD - Number of results when Settings search result is clicked
+ FIELD_SETTINGS_SERACH_RESULT_COUNT = 1002;
+
+ // OPEN: Settings > Display > Ambient Display
+ // CATEGORY: SETTINGS
+ // OS: O DR
+ AMBIENT_DISPLAY_SETTINGS = 1003;
+
+ // ACTION: CaptivePortalLoginActivity starts
+ // CATEGORY: GLOBAL_SYSTEM_UI
+ // OS: O DR
+ ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY = 1004;
+
+ // ACTION: CaptivePortalLoginActivity auto-closes
+ // CATEGORY: GLOBAL_SYSTEM_UI
+ // OS: O DR
+ ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_DISMISSED = 1005;
+
+ // ACTION: CaptivePortalLoginActivity > Menu > Do not use this network
+ // CATEGORY: GLOBAL_SYSTEM_UI
+ // OS: O DR
+ ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_UNWANTED = 1006;
+
+ // ACTION: CaptivePortalLoginActivity > Menu > Use this network
+ // CATEGORY: GLOBAL_SYSTEM_UI
+ // OS: O DR
+ ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_WANTED_AS_IS = 1007;
+
+ // ACTION: Settings > Wi-Fi > [Long press network] > Sign in to network
+ // CATEGORY: SETTINGS
+ // OS: O DR
+ ACTION_WIFI_SIGNIN = 1008;
+
+ // OPEN: Settings->Connected Devices->Bluetooth->(click on details link for a paired device)
+ // CATEGORY: SETTINGS
+ // OS: O DR
+ BLUETOOTH_DEVICE_DETAILS = 1009;
+
+ // OPEN: Settings > credential pages - prompt for key guard configuration confirmation
+ CONFIGURE_KEYGUARD_DIALOG = 1010;
+
+ // Open: Settings > Search > No Result View
+ SETTINGS_SEARCH_NO_RESULT = 1011;
+
+ // OPEN: Assist Gesture before training
+ // CATEGORY: SETTINGS
+ // OS: O DR
+ SETTINGS_ASSIST_GESTURE_FIRST_TIME = 1012;
+
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
}
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 2f6b7e6..8cecb4b 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -180,6 +180,9 @@
// Package: android
NOTE_FOREGROUND_SERVICES = 40;
+ // Inform the user that the connected audio accessory is not supported
+ NOTE_USB_AUDIO_ACCESSORY_NOT_SUPPORTED = 41;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index 564d135..fbb1a57 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -255,6 +255,9 @@
// Indicates the number of times an error was encountered in
// Wificond when wifi was turned on.
optional int32 num_wifi_on_failure_due_to_wificond = 56;
+
+ // Wi-Fi Aware metrics
+ optional WifiAwareLog wifi_aware_log = 57;
}
// Information that gets logged for every WiFi connection.
@@ -676,3 +679,189 @@
// Authentication failure reason, as reported by WifiManager (calculated from state & deauth code)
optional AuthFailureReason auth_failure_reason = 13 [default = AUTH_FAILURE_UNKNOWN];
}
+
+// Wi-Fi Aware metrics
+message WifiAwareLog {
+ // total number of unique apps that used Aware (measured on attach)
+ optional int32 num_apps = 1;
+
+ // total number of unique apps that used an identity callback when attaching
+ optional int32 num_apps_using_identity_callback = 2;
+
+ // maximum number of attaches for an app
+ optional int32 max_concurrent_attach_sessions_in_app = 3;
+
+ // histogram of attach request results
+ repeated NanStatusHistogramBucket histogram_attach_session_status = 4;
+
+ // maximum number of concurrent publish sessions in a single app
+ optional int32 max_concurrent_publish_in_app = 5;
+
+ // maximum number of concurrent subscribe sessions in a single app
+ optional int32 max_concurrent_subscribe_in_app = 6;
+
+ // maximum number of concurrent discovery (publish+subscribe) sessions in a single app
+ optional int32 max_concurrent_discovery_sessions_in_app = 7;
+
+ // maximum number of concurrent publish sessions in the system
+ optional int32 max_concurrent_publish_in_system = 8;
+
+ // maximum number of concurrent subscribe sessions in the system
+ optional int32 max_concurrent_subscribe_in_system = 9;
+
+ // maximum number of concurrent discovery (publish+subscribe) sessions in the system
+ optional int32 max_concurrent_discovery_sessions_in_system = 10;
+
+ // histogram of publish request results
+ repeated NanStatusHistogramBucket histogram_publish_status = 11;
+
+ // histogram of subscribe request results
+ repeated NanStatusHistogramBucket histogram_subscribe_status = 12;
+
+ // number of unique apps which experienced a discovery session creation failure due to lack of
+ // resources
+ optional int32 num_apps_with_discovery_session_failure_out_of_resources = 13;
+
+ // histogram of create ndp request results
+ repeated NanStatusHistogramBucket histogram_request_ndp_status = 14;
+
+ // histogram of create ndp out-of-band (OOB) request results
+ repeated NanStatusHistogramBucket histogram_request_ndp_oob_status = 15;
+
+ // maximum number of concurrent active data-interfaces (NDI) in a single app
+ optional int32 max_concurrent_ndi_in_app = 19;
+
+ // maximum number of concurrent active data-interfaces (NDI) in the system
+ optional int32 max_concurrent_ndi_in_system = 20;
+
+ // maximum number of concurrent data-paths (NDP) in a single app
+ optional int32 max_concurrent_ndp_in_app = 21;
+
+ // maximum number of concurrent data-paths (NDP) in the system
+ optional int32 max_concurrent_ndp_in_system = 22;
+
+ // maximum number of concurrent secure data-paths (NDP) in a single app
+ optional int32 max_concurrent_secure_ndp_in_app = 23;
+
+ // maximum number of concurrent secure data-paths (NDP) in the system
+ optional int32 max_concurrent_secure_ndp_in_system = 24;
+
+ // maximum number of concurrent data-paths (NDP) per data-interface (NDI)
+ optional int32 max_concurrent_ndp_per_ndi = 25;
+
+ // histogram of durations of Aware being available
+ repeated HistogramBucket histogram_aware_available_duration_ms = 26;
+
+ // histogram of durations of Aware being enabled
+ repeated HistogramBucket histogram_aware_enabled_duration_ms = 27;
+
+ // histogram of duration (in ms) of attach sessions
+ repeated HistogramBucket histogram_attach_duration_ms = 28;
+
+ // histogram of duration (in ms) of publish sessions
+ repeated HistogramBucket histogram_publish_session_duration_ms = 29;
+
+ // histogram of duration (in ms) of subscribe sessions
+ repeated HistogramBucket histogram_subscribe_session_duration_ms = 30;
+
+ // histogram of duration (in ms) of data-paths (NDP)
+ repeated HistogramBucket histogram_ndp_session_duration_ms = 31;
+
+ // histogram of usage (in MB) of data-paths (NDP)
+ repeated HistogramBucket histogram_ndp_session_data_usage_mb = 32;
+
+ // histogram of usage (in MB) of data-path creation time (in ms) measured as request -> confirm
+ repeated HistogramBucket histogram_ndp_creation_time_ms = 33;
+
+ // statistics for data-path (NDP) creation time (in ms) measured as request -> confirm: minimum
+ optional int64 ndp_creation_time_ms_min = 34;
+
+ // statistics for data-path (NDP) creation time (in ms) measured as request -> confirm: maximum
+ optional int64 ndp_creation_time_ms_max = 35;
+
+ // statistics for data-path (NDP) creation time (in ms) measured as request -> confirm: sum
+ optional int64 ndp_creation_time_ms_sum = 36;
+
+ // statistics for data-path (NDP) creation time (in ms) measured as request -> confirm: sum of sq
+ optional int64 ndp_creation_time_ms_sum_of_sq = 37;
+
+ // statistics for data-path (NDP) creation time (in ms) measured as request -> confirm: number of
+ // samples
+ optional int64 ndp_creation_time_ms_num_samples = 38;
+
+ // total time within the logging window that aware was available
+ optional int64 available_time_ms = 39;
+
+ // total time within the logging window that aware was enabled
+ optional int64 enabled_time_ms = 40;
+
+ // Histogram bucket for Wi-Fi Aware logs. Range is [start, end)
+ message HistogramBucket {
+ // lower range of the bucket (inclusive)
+ optional int64 start = 1;
+
+ // upper range of the bucket (exclusive)
+ optional int64 end = 2;
+
+ // number of samples in the bucket
+ optional int32 count = 3;
+ }
+
+ // Status of various NAN operations
+ enum NanStatusTypeEnum {
+ // constant to be used by proto
+ UNKNOWN = 0;
+
+ // NAN operation succeeded
+ SUCCESS = 1;
+
+ // NAN Discovery Engine/Host driver failures
+ INTERNAL_FAILURE = 2;
+
+ // NAN OTA failures
+ PROTOCOL_FAILURE = 3;
+
+ // The publish/subscribe discovery session id is invalid
+ INVALID_SESSION_ID = 4;
+
+ // Out of resources to fufill request
+ NO_RESOURCES_AVAILABLE = 5;
+
+ // Invalid arguments passed
+ INVALID_ARGS = 6;
+
+ // Invalid peer id
+ INVALID_PEER_ID = 7;
+
+ // Invalid NAN data-path (ndp) id
+ INVALID_NDP_ID = 8;
+
+ // Attempting to enable NAN when not available, e.g. wifi is disabled
+ NAN_NOT_ALLOWED = 9;
+
+ // Over the air ACK not received
+ NO_OTA_ACK = 10;
+
+ // Attempting to enable NAN when already enabled
+ ALREADY_ENABLED = 11;
+
+ // Can't queue tx followup message foor transmission
+ FOLLOWUP_TX_QUEUE_FULL = 12;
+
+ // Unsupported concurrency of NAN and another feature - NAN disabled
+ UNSUPPORTED_CONCURRENCY_NAN_DISABLED = 13;
+
+ // Unknown NanStatusType
+ UNKNOWN_HAL_STATUS = 14;
+ }
+
+ // Histogram bucket for Wi-Fi Aware (NAN) status.
+ message NanStatusHistogramBucket {
+ // status type defining the bucket
+ optional NanStatusTypeEnum nan_status_type = 1;
+
+ // number of samples in the bucket
+ optional int32 count = 2;
+ }
+}
+
diff --git a/rs/jni/Android.mk b/rs/jni/Android.mk
index 4040db3..21438e0 100644
--- a/rs/jni/Android.mk
+++ b/rs/jni/Android.mk
@@ -18,7 +18,8 @@
libgui \
libjnigraphics
-LOCAL_STATIC_LIBRARIES :=
+LOCAL_HEADER_LIBRARIES := \
+ libbase_headers
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE) \
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 0acbb02..ee48289 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -27,6 +27,7 @@
#include <androidfw/Asset.h>
#include <androidfw/AssetManager.h>
#include <androidfw/ResourceTypes.h>
+#include <android-base/macros.h>
#include "jni.h"
#include "JNIHelp.h"
@@ -1324,12 +1325,10 @@
SkBitmap bitmap;
GraphicsJNI::getSkBitmap(_env, jbitmap, &bitmap);
- bitmap.lockPixels();
const void* ptr = bitmap.getPixels();
jlong id = (jlong)(uintptr_t)rsAllocationCreateFromBitmap((RsContext)con,
(RsType)type, (RsAllocationMipmapControl)mip,
ptr, bitmap.getSize(), usage);
- bitmap.unlockPixels();
return id;
}
@@ -1340,12 +1339,10 @@
SkBitmap bitmap;
GraphicsJNI::getSkBitmap(_env, jbitmap, &bitmap);
- bitmap.lockPixels();
const void* ptr = bitmap.getPixels();
jlong id = (jlong)(uintptr_t)rsAllocationCreateTyped((RsContext)con,
(RsType)type, (RsAllocationMipmapControl)mip,
(uint32_t)usage, (uintptr_t)ptr);
- bitmap.unlockPixels();
return id;
}
@@ -1356,12 +1353,10 @@
SkBitmap bitmap;
GraphicsJNI::getSkBitmap(_env, jbitmap, &bitmap);
- bitmap.lockPixels();
const void* ptr = bitmap.getPixels();
jlong id = (jlong)(uintptr_t)rsAllocationCubeCreateFromBitmap((RsContext)con,
(RsType)type, (RsAllocationMipmapControl)mip,
ptr, bitmap.getSize(), usage);
- bitmap.unlockPixels();
return id;
}
@@ -1373,12 +1368,10 @@
int w = bitmap.width();
int h = bitmap.height();
- bitmap.lockPixels();
const void* ptr = bitmap.getPixels();
rsAllocation2DData((RsContext)con, (RsAllocation)alloc, 0, 0,
0, RS_ALLOCATION_CUBEMAP_FACE_POSITIVE_X,
w, h, ptr, bitmap.getSize(), 0);
- bitmap.unlockPixels();
}
static void
@@ -1387,10 +1380,8 @@
SkBitmap bitmap;
GraphicsJNI::getSkBitmap(_env, jbitmap, &bitmap);
- bitmap.lockPixels();
void* ptr = bitmap.getPixels();
rsAllocationCopyToBitmap((RsContext)con, (RsAllocation)alloc, ptr, bitmap.getSize());
- bitmap.unlockPixels();
bitmap.notifyPixelsChanged();
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index fa78f10..9e2f52a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -487,15 +487,6 @@
if (pip != null) {
int pipId = pip.getId();
event.setWindowId(pipId);
- event.setSealed(true);
- AccessibilityNodeInfo info = event.getSource();
- info.setSealed(false);
- event.setSealed(false);
- if (info != null) {
- info.setSourceNodeId(info.getSourceNodeId(), pipId);
- event.setSource(info);
- info.recycle();
- }
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 03f25bf..e85f96b 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -19,10 +19,10 @@
import static android.Manifest.permission.MANAGE_AUTO_FILL;
import static android.content.Context.AUTOFILL_MANAGER_SERVICE;
+import static com.android.server.autofill.Helper.bundleToString;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sPartitionMaxCount;
import static com.android.server.autofill.Helper.sVerbose;
-import static com.android.server.autofill.Helper.bundleToString;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -58,6 +58,7 @@
import android.util.SparseBooleanArray;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManagerInternal;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManager;
import android.view.autofill.IAutoFillManagerClient;
@@ -251,6 +252,7 @@
@Override
public void onStart() {
publishBinderService(AUTOFILL_MANAGER_SERVICE, new AutoFillManagerServiceStub());
+ publishLocalService(AutofillManagerInternal.class, new LocalService());
}
@Override
@@ -463,6 +465,15 @@
}
}
+ private final class LocalService extends AutofillManagerInternal {
+
+ @Override
+ public void onBackKeyPressed() {
+ if (sDebug) Slog.d(TAG, "onBackKeyPressed()");
+ mUi.hideAll(null);
+ }
+ }
+
final class AutoFillManagerServiceStub extends IAutoFillManager.Stub {
@Override
public int addClient(IAutoFillManagerClient client, int userId) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 980a7d4..073d7b2 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -774,20 +774,29 @@
break;
}
- final AutofillValue currentValue = viewState.getCurrentValue();
- if (currentValue == null || currentValue.isEmpty()) {
- if (sDebug) {
- Slog.d(TAG, "showSaveLocked(): empty value for required " + id );
+ AutofillValue value = viewState.getCurrentValue();
+ if (value == null || value.isEmpty()) {
+ final AutofillValue initialValue = getValueFromContexts(id);
+ if (initialValue != null) {
+ if (sDebug) {
+ Slog.d(TAG, "Value of required field " + id + " didn't change; "
+ + "using initial value (" + initialValue + ") instead");
+ }
+ value = initialValue;
+ } else {
+ if (sDebug) {
+ Slog.d(TAG, "showSaveLocked(): empty value for required " + id );
+ }
+ allRequiredAreNotEmpty = false;
+ break;
}
- allRequiredAreNotEmpty = false;
- break;
}
final AutofillValue filledValue = viewState.getAutofilledValue();
- if (!currentValue.equals(filledValue)) {
+ if (!value.equals(filledValue)) {
if (sDebug) {
Slog.d(TAG, "showSaveLocked(): found a change on required " + id + ": "
- + filledValue + " => " + currentValue);
+ + filledValue + " => " + value);
}
atLeastOneChanged = true;
}
@@ -845,6 +854,31 @@
}
/**
+ * Gets the latest non-empty value for the given id in the autofill contexts.
+ */
+ @Nullable
+ private AutofillValue getValueFromContexts(AutofillId id) {
+ AutofillValue value = null;
+ final int numContexts = mContexts.size();
+ for (int i = 0; i < numContexts; i++) {
+ final FillContext context = mContexts.get(i);
+ // TODO: create a function that gets just one node so it doesn't create an array
+ // unnecessarily
+ final ViewNode[] nodes = context.findViewNodesByAutofillIds(id);
+ if (nodes != null) {
+ AutofillValue candidate = nodes[0].getAutofillValue();
+ if (sDebug) {
+ Slog.d(TAG, "getValueFromContexts(" + id + ") at " + i + ": " + candidate);
+ }
+ if (candidate != null && !candidate.isEmpty()) {
+ value = candidate;
+ }
+ }
+ }
+ return value;
+ }
+
+ /**
* Calls service when user requested save.
*/
void callSaveLocked() {
@@ -1009,7 +1043,7 @@
|| action == ACTION_VIEW_ENTERED) {
if (sVerbose) Slog.v(TAG, "Creating viewState for " + id + " on " + action);
boolean isIgnored = isIgnoredLocked(id);
- viewState = new ViewState(this, id, value, this,
+ viewState = new ViewState(this, id, this,
isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL);
mViewStates.put(id, viewState);
if (isIgnored) {
@@ -1307,7 +1341,7 @@
if (viewState != null) {
viewState.setState(state);
} else {
- viewState = new ViewState(this, id, null, this, state);
+ viewState = new ViewState(this, id, this, state);
if (sVerbose) {
Slog.v(TAG, "Adding autofillable view with id " + id + " and state " + state);
}
@@ -1370,10 +1404,16 @@
}
}
+ @Override
+ public String toString() {
+ return "Session: [id=" + id + ", pkg=" + mPackageName + "]";
+ }
+
void dumpLocked(String prefix, PrintWriter pw) {
final String prefix2 = prefix + " ";
pw.print(prefix); pw.print("id: "); pw.println(id);
pw.print(prefix); pw.print("uid: "); pw.println(uid);
+ pw.print(prefix); pw.print("mPackagename: "); pw.println(mPackageName);
pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
pw.print(prefix); pw.print("mResponses: ");
if (mResponses == null) {
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index cd8f4a5..51659bb 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -74,16 +74,14 @@
private final Session mSession;
private FillResponse mResponse;
- private AutofillValue mInitialValue;
private AutofillValue mCurrentValue;
private AutofillValue mAutofilledValue;
private Rect mVirtualBounds;
private int mState;
- ViewState(Session session, AutofillId id, AutofillValue value, Listener listener, int state) {
+ ViewState(Session session, AutofillId id, Listener listener, int state) {
mSession = session;
this.id = id;
- mInitialValue = value;
mListener = listener;
mState = state;
}
@@ -118,11 +116,6 @@
}
@Nullable
- AutofillValue getInitialValue() {
- return mInitialValue;
- }
-
- @Nullable
FillResponse getResponse() {
return mResponse;
}
@@ -189,8 +182,8 @@
@Override
public String toString() {
- return "ViewState: [id=" + id + ", initialValue=" + mInitialValue
- + ", currentValue=" + mCurrentValue + ", autofilledValue=" + mAutofilledValue
+ return "ViewState: [id=" + id + ", currentValue=" + mCurrentValue
+ + ", autofilledValue=" + mAutofilledValue
+ ", bounds=" + mVirtualBounds + ", state=" + getStateAsString() + "]";
}
@@ -207,7 +200,6 @@
pw.println(mResponse.getRequestId());
}
}
- pw.print(prefix); pw.print("initialValue:" ); pw.println(mInitialValue);
pw.print(prefix); pw.print("currentValue:" ); pw.println(mCurrentValue);
pw.print(prefix); pw.print("autofilledValue:" ); pw.println(mAutofilledValue);
pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds);
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index c3d55054..8ffe8f5 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -332,6 +332,10 @@
@android.annotation.UiThread
private void hideSaveUiUiThread(@Nullable AutoFillUiCallback callback) {
+ if (sVerbose) {
+ Slog.v(TAG, "hideSaveUiUiThread(): mSaveUi=" + mSaveUi + ", callback=" + callback
+ + ", mCallback=" + mCallback);
+ }
if (mSaveUi != null && (callback == null || callback == mCallback)) {
mSaveUi.destroy();
mSaveUi = null;
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index d566d3d..e9c98e9 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -398,6 +398,12 @@
} catch (WindowManager.BadTokenException e) {
if (sDebug) Slog.d(TAG, "Filed with with token " + params.token + " gone.");
mCallback.onDestroy();
+ } catch (IllegalStateException e) {
+ // WM throws an ISE if mContentView was added twice; this should never happen -
+ // since show() and hide() are always called in the UIThread - but when it does,
+ // it should not crash the system.
+ Slog.e(TAG, "Exception showing window " + params, e);
+ mCallback.onDestroy();
}
}
@@ -405,10 +411,18 @@
* Hides the window.
*/
void hide() {
- if (mShowing) {
- mContentView.setOnTouchListener(null);
- mWm.removeView(mContentView);
- mShowing = false;
+ try {
+ if (mShowing) {
+ mContentView.setOnTouchListener(null);
+ mWm.removeView(mContentView);
+ mShowing = false;
+ }
+ } catch (IllegalStateException e) {
+ // WM might thrown an ISE when removing the mContentView; this should never
+ // happen - since show() and hide() are always called in the UIThread - but if it
+ // does, it should not crash the system.
+ Slog.e(TAG, "Exception hiding window ", e);
+ mCallback.onDestroy();
}
}
@@ -489,7 +503,7 @@
final String value = item.getValue();
// No value, i.e. null, matches any filter
if (value == null
- || value.toLowerCase().contains(constraintLowerCase)) {
+ || value.toLowerCase().startsWith(constraintLowerCase)) {
filteredItems.add(item);
}
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index d25ffce..491af91 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -17,6 +17,7 @@
package com.android.server.autofill.ui;
import static com.android.server.autofill.Helper.sDebug;
+import static com.android.server.autofill.Helper.sVerbose;
import android.annotation.NonNull;
import android.app.Dialog;
@@ -63,7 +64,7 @@
@Override
public void onSave() {
- if (sDebug) Slog.d(TAG, "onSave(): " + mDone);
+ if (sDebug) Slog.d(TAG, "OneTimeListener.onSave(): " + mDone);
if (mDone) {
return;
}
@@ -73,7 +74,7 @@
@Override
public void onCancel(IntentSender listener) {
- if (sDebug) Slog.d(TAG, "onCancel(): " + mDone);
+ if (sDebug) Slog.d(TAG, "OneTimeListener.onCancel(): " + mDone);
if (mDone) {
return;
}
@@ -83,7 +84,7 @@
@Override
public void onDestroy() {
- if (sDebug) Slog.d(TAG, "onDestroy(): " + mDone);
+ if (sDebug) Slog.d(TAG, "OneTimeListener.onDestroy(): " + mDone);
if (mDone) {
return;
}
@@ -158,9 +159,8 @@
subTitleView.setVisibility(View.VISIBLE);
}
- Slog.i(TAG, "Showing save dialog: " + mTitle);
if (sDebug) {
- Slog.d(TAG, "SubTitle: " + mSubTitle);
+ Slog.d(TAG, "on constructor: title=" + mTitle + ", subTitle=" + mSubTitle);
}
final TextView noButton = view.findViewById(R.id.autofill_save_no);
@@ -169,15 +169,15 @@
} else {
noButton.setText(R.string.autofill_save_no);
}
- noButton.setOnClickListener((v) -> mListener.onCancel(
- info.getNegativeActionListener()));
+ View.OnClickListener cancelListener =
+ (v) -> mListener.onCancel(info.getNegativeActionListener());
+ noButton.setOnClickListener(cancelListener);
final View yesButton = view.findViewById(R.id.autofill_save_yes);
yesButton.setOnClickListener((v) -> mListener.onSave());
final View closeButton = view.findViewById(R.id.autofill_save_close);
- closeButton.setOnClickListener((v) -> mListener.onCancel(
- info.getNegativeActionListener()));
+ closeButton.setOnClickListener(cancelListener);
mDialog = new Dialog(context, R.style.Theme_DeviceDefault_Light_Panel);
mDialog.setContentView(view);
@@ -195,13 +195,16 @@
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.accessibilityTitle = context.getString(R.string.autofill_save_accessibility_title);
+ Slog.i(TAG, "Showing save dialog: " + mTitle);
mDialog.show();
}
void destroy() {
+ if (sDebug) Slog.d(TAG, "destroy()");
throwIfDestroyed();
mListener.onDestroy();
mHandler.removeCallbacksAndMessages(mListener);
+ if (sVerbose) Slog.v(TAG, "destroy(): dismissing dialog");
mDialog.dismiss();
mDestroyed = true;
}
@@ -212,6 +215,11 @@
}
}
+ @Override
+ public String toString() {
+ return mTitle == null ? "NO TITLE" : mTitle.toString();
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("title: "); pw.println(mTitle);
pw.print(prefix); pw.print("subtitle: "); pw.println(mSubTitle);
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 30de4ba..f1f8757 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -189,7 +189,7 @@
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
-public class BackupManagerService {
+public class BackupManagerService implements BackupManagerServiceInterface {
private static final String TAG = "BackupManagerService";
static final boolean DEBUG = true;
@@ -306,6 +306,7 @@
private PowerManager mPowerManager;
private AlarmManager mAlarmManager;
private IStorageManager mStorageManager;
+
IBackupManager mBackupManagerBinder;
private final TransportManager mTransportManager;
@@ -763,7 +764,8 @@
ArrayList<FullBackupEntry> mFullBackupQueue;
// Utility: build a new random integer token
- int generateToken() {
+ @Override
+ public int generateRandomIntegerToken() {
int token;
do {
synchronized (mTokenGenerator) {
@@ -837,6 +839,29 @@
return !appGetsFullBackup(pkg);
}
+ /*
+ * Construct a backup agent instance for the metadata pseudopackage. This is a
+ * process-local non-lifecycle agent instance, so we manually set up the context
+ * topology for it.
+ */
+ PackageManagerBackupAgent makeMetadataAgent() {
+ PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager);
+ pmAgent.attach(mContext);
+ pmAgent.onCreate();
+ return pmAgent;
+ }
+
+ /*
+ * Same as above but with the explicit package-set configuration.
+ */
+ PackageManagerBackupAgent makeMetadataAgent(List<PackageInfo> packages) {
+ PackageManagerBackupAgent pmAgent =
+ new PackageManagerBackupAgent(mPackageManager, packages);
+ pmAgent.attach(mContext);
+ pmAgent.onCreate();
+ return pmAgent;
+ }
+
// ----- Asynchronous backup/restore handler thread -----
private class BackupHandler extends Handler {
@@ -1752,6 +1777,7 @@
return false;
}
+ @Override
public boolean setBackupPassword(String currentPw, String newPw) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"setBackupPassword");
@@ -1832,6 +1858,7 @@
return false;
}
+ @Override
public boolean hasBackupPassword() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"hasBackupPassword");
@@ -2297,7 +2324,8 @@
}
// fire off a backup agent, blocking until it attaches or times out
- IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode) {
+ @Override
+ public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode) {
IBackupAgent agent = null;
synchronized(mAgentConnectLock) {
mConnecting = true;
@@ -2393,6 +2421,7 @@
// Get the restore-set token for the best-available restore set for this package:
// the active set if possible, else the ancestral one. Returns zero if none available.
+ @Override
public long getAvailableRestoreToken(String packageName) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getAvailableRestoreToken");
@@ -2410,10 +2439,12 @@
return token;
}
+ @Override
public int requestBackup(String[] packages, IBackupObserver observer, int flags) {
return requestBackup(packages, observer, null, flags);
}
+ @Override
public int requestBackup(String[] packages, IBackupObserver observer,
IBackupManagerMonitor monitor, int flags) {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "requestBackup");
@@ -2485,6 +2516,7 @@
}
// Cancel all running backups.
+ @Override
public void cancelBackups(){
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "cancelBackups");
if (MORE_DEBUG) {
@@ -2514,22 +2546,9 @@
}
}
- // -----
- // Interface and methods used by the asynchronous-with-timeout backup/restore operations
-
- interface BackupRestoreTask {
- // Execute one tick of whatever state machine the task implements
- void execute();
-
- // An operation that wanted a callback has completed
- void operationComplete(long result);
-
- // An operation that wanted a callback has timed out
- void handleCancel(boolean cancelAll);
- }
-
- void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback,
- int operationType) {
+ @Override
+ public void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback,
+ int operationType) {
if (operationType != OP_TYPE_BACKUP_WAIT && operationType != OP_TYPE_RESTORE_WAIT) {
Slog.wtf(TAG, "prepareOperationTimeout() doesn't support operation " +
Integer.toHexString(token) + " of type " + operationType);
@@ -2573,7 +2592,8 @@
}
// synchronous waiter case
- boolean waitUntilOperationComplete(int token) {
+ @Override
+ public boolean waitUntilOperationComplete(int token) {
if (MORE_DEBUG) Slog.i(TAG, "Blocking until operation complete for "
+ Integer.toHexString(token));
int finalState = OP_PENDING;
@@ -2739,7 +2759,7 @@
mNonIncremental = nonIncremental;
mStateDir = new File(mBaseStateDir, dirName);
- mCurrentOpToken = generateToken();
+ mCurrentOpToken = generateRandomIntegerToken();
mFinished = false;
@@ -2896,8 +2916,7 @@
// because it's cheap and this way we guarantee that we don't get out of
// step even if we're selecting among various transports at run time.
if (mStatus == BackupTransport.TRANSPORT_OK) {
- PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
- mPackageManager);
+ PackageManagerBackupAgent pmAgent = makeMetadataAgent();
mStatus = invokeAgentForBackup(PACKAGE_MANAGER_SENTINEL,
IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
addBackupTrace("PMBA invoke: " + mStatus);
@@ -3190,7 +3209,7 @@
mNewState = null;
boolean callingAgent = false;
- mEphemeralOpToken = generateToken();
+ mEphemeralOpToken = generateRandomIntegerToken();
try {
// Look up the package info & signatures. This is first so that if it
// throws an exception, there's no file setup yet that would need to
@@ -3687,7 +3706,7 @@
ParcelFileDescriptor[] pipes = null;
try {
pipes = ParcelFileDescriptor.createPipe();
- int token = generateToken();
+ int token = generateRandomIntegerToken();
prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL,
null, OP_TYPE_BACKUP_WAIT);
mService.backupObbs(pkg.packageName, pipes[1], token, mBackupManagerBinder);
@@ -3773,7 +3792,8 @@
}
}
- void tearDownAgentAndKill(ApplicationInfo app) {
+ @Override
+ public void tearDownAgentAndKill(ApplicationInfo app) {
if (app == null) {
// Null means the system package, so just quietly move on. :)
return;
@@ -4225,7 +4245,7 @@
String curPassword, String encryptPassword, boolean doAllApps, boolean doSystem,
boolean doCompress, boolean doKeyValue, String[] packages, AtomicBoolean latch) {
super(observer);
- mCurrentOpToken = generateToken();
+ mCurrentOpToken = generateRandomIntegerToken();
mLatch = latch;
mOutputFile = fd;
@@ -4675,8 +4695,8 @@
mBackupObserver = backupObserver;
mMonitor = monitor;
mUserInitiated = userInitiated;
- mCurrentOpToken = generateToken();
- mBackupRunnerOpToken = generateToken();
+ mCurrentOpToken = generateRandomIntegerToken();
+ mBackupRunnerOpToken = generateRandomIntegerToken();
if (isBackupOperationInProgress()) {
if (DEBUG) {
@@ -5269,7 +5289,7 @@
mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor());
mTarget = target;
mCurrentOpToken = currentOpToken;
- mEphemeralToken = generateToken();
+ mEphemeralToken = generateRandomIntegerToken();
mPreflight = new SinglePackageBackupPreflight(transport, quota, mEphemeralToken);
mPreflightLatch = new CountDownLatch(1);
mBackupLatch = new CountDownLatch(1);
@@ -5499,7 +5519,8 @@
* @return Whether ongoing work will continue. The return value here will be passed
* along as the return value to the scheduled job's onStartJob() callback.
*/
- boolean beginFullBackup(FullBackupJob scheduledJob) {
+ @Override
+ public boolean beginFullBackup(FullBackupJob scheduledJob) {
long now = System.currentTimeMillis();
FullBackupEntry entry = null;
long latency = MIN_FULL_BACKUP_INTERVAL;
@@ -5657,7 +5678,8 @@
// The job scheduler says our constraints don't hold any more,
// so tear down any ongoing backup task right away.
- void endFullBackup() {
+ @Override
+ public void endFullBackup() {
// offload the mRunningFullBackupTask.handleCancel() call to another thread,
// as we might have to wait for mCancelLock
Runnable endFullBackupRunnable = new Runnable() {
@@ -5725,30 +5747,6 @@
// ----- Full restore from a file/socket -----
- // Description of a file in the restore datastream
- static class FileMetadata {
- String packageName; // name of the owning app
- String installerPackageName; // name of the market-type app that installed the owner
- int type; // e.g. BackupAgent.TYPE_DIRECTORY
- String domain; // e.g. FullBackup.DATABASE_TREE_TOKEN
- String path; // subpath within the semantic domain
- long mode; // e.g. 0666 (actually int)
- long mtime; // last mod time, UTC time_t (actually int)
- long size; // bytes of content
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("FileMetadata{");
- sb.append(packageName); sb.append(',');
- sb.append(type); sb.append(',');
- sb.append(domain); sb.append(':'); sb.append(path); sb.append(',');
- sb.append(size);
- sb.append('}');
- return sb.toString();
- }
- }
-
enum RestorePolicy {
IGNORE,
ACCEPT,
@@ -7179,7 +7177,7 @@
mObserver = observer;
mLatchObject = latch;
mAgent = null;
- mPackageManagerBackupAgent = new PackageManagerBackupAgent(mPackageManager);
+ mPackageManagerBackupAgent = makeMetadataAgent();
mAgentPackage = null;
mTargetApp = null;
mObbConnection = new FullBackupObbConnection();
@@ -7598,7 +7596,7 @@
final boolean isSharedStorage = pkg.equals(SHARED_BACKUP_AGENT_PACKAGE);
final long timeout = isSharedStorage ?
TIMEOUT_SHARED_BACKUP_INTERVAL : TIMEOUT_RESTORE_INTERVAL;
- final int token = generateToken();
+ final int token = generateRandomIntegerToken();
try {
prepareOperationTimeout(token, timeout, null,
OP_TYPE_RESTORE_WAIT);
@@ -7742,7 +7740,7 @@
try {
// In the adb restore case, we do restore-finished here
if (doRestoreFinished) {
- final int token = generateToken();
+ final int token = generateRandomIntegerToken();
final AdbRestoreFinishedLatch latch = new AdbRestoreFinishedLatch(token);
prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, latch,
OP_TYPE_RESTORE_WAIT);
@@ -8621,7 +8619,7 @@
PerformUnifiedRestoreTask(IBackupTransport transport, IRestoreObserver observer,
IBackupManagerMonitor monitor, long restoreSetToken, PackageInfo targetPackage,
int pmToken, boolean isFullSystemRestore, String[] filterSet) {
- mEphemeralOpToken = generateToken();
+ mEphemeralOpToken = generateRandomIntegerToken();
mState = UnifiedRestoreState.INITIAL;
mStartRealtime = SystemClock.elapsedRealtime();
@@ -8841,7 +8839,7 @@
// Pull the Package Manager metadata from the restore set first
mCurrentPackage = new PackageInfo();
mCurrentPackage.packageName = PACKAGE_MANAGER_SENTINEL;
- mPmAgent = new PackageManagerBackupAgent(mPackageManager, null);
+ mPmAgent = makeMetadataAgent(null);
mAgent = IBackupAgent.Stub.asInterface(mPmAgent.onBind());
if (MORE_DEBUG) {
Slog.v(TAG, "initiating restore for PMBA");
@@ -9254,7 +9252,7 @@
private final int mEphemeralOpToken;
public StreamFeederThread() throws IOException {
- mEphemeralOpToken = generateToken();
+ mEphemeralOpToken = generateRandomIntegerToken();
mTransportPipes = ParcelFileDescriptor.createPipe();
mEnginePipes = ParcelFileDescriptor.createPipe();
setRunning(true);
@@ -9909,6 +9907,7 @@
// ----- IBackupManager binder interface -----
+ @Override
public void dataChanged(final String packageName) {
final int callingUserHandle = UserHandle.getCallingUserId();
if (callingUserHandle != UserHandle.USER_SYSTEM) {
@@ -9939,6 +9938,7 @@
}
// Clear the given package's backup data from the current transport
+ @Override
public void clearBackupData(String transportName, String packageName) {
if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName + " on " + transportName);
PackageInfo info;
@@ -9996,6 +9996,7 @@
// Run a backup pass immediately for any applications that have declared
// that they have pending updates.
+ @Override
public void backupNow() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "backupNow");
@@ -10032,6 +10033,7 @@
//
// This is the variant used by 'adb backup'; it requires on-screen confirmation
// by the user because it can be used to offload data over untrusted USB.
+ @Override
public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
boolean includeShared, boolean doWidgets, boolean doAllApps, boolean includeSystem,
boolean compress, boolean doKeyValue, String[] pkgList) {
@@ -10072,7 +10074,7 @@
AdbBackupParams params = new AdbBackupParams(fd, includeApks, includeObbs,
includeShared, doWidgets, doAllApps, includeSystem, compress, doKeyValue,
pkgList);
- final int token = generateToken();
+ final int token = generateRandomIntegerToken();
synchronized (mAdbBackupRestoreConfirmations) {
mAdbBackupRestoreConfirmations.put(token, params);
}
@@ -10107,6 +10109,7 @@
}
}
+ @Override
public void fullTransportBackup(String[] pkgNames) {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP,
"fullTransportBackup");
@@ -10156,6 +10159,7 @@
}
}
+ @Override
public void adbRestore(ParcelFileDescriptor fd) {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbRestore");
@@ -10178,7 +10182,7 @@
Slog.i(TAG, "Beginning restore...");
AdbRestoreParams params = new AdbRestoreParams(fd);
- final int token = generateToken();
+ final int token = generateRandomIntegerToken();
synchronized (mAdbBackupRestoreConfirmations) {
mAdbBackupRestoreConfirmations.put(token, params);
}
@@ -10254,6 +10258,7 @@
// Confirm that the previously-requested full backup/restore operation can proceed. This
// is used to require a user-facing disclosure about the operation.
+ @Override
public void acknowledgeAdbBackupOrRestore(int token, boolean allow,
String curPassword, String encPpassword, IFullBackupRestoreObserver observer) {
if (DEBUG) Slog.d(TAG, "acknowledgeAdbBackupOrRestore : token=" + token
@@ -10354,6 +10359,7 @@
}
// Enable/disable backups
+ @Override
public void setBackupEnabled(boolean enable) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"setBackupEnabled");
@@ -10401,6 +10407,7 @@
}
// Enable/disable automatic restore of app data at install time
+ @Override
public void setAutoRestore(boolean doAutoRestore) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"setAutoRestore");
@@ -10420,6 +10427,7 @@
}
// Mark the backup service as having been provisioned
+ @Override
public void setBackupProvisioned(boolean available) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"setBackupProvisioned");
@@ -10429,12 +10437,14 @@
}
// Report whether the backup mechanism is currently enabled
+ @Override
public boolean isBackupEnabled() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "isBackupEnabled");
return mEnabled; // no need to synchronize just to read it
}
// Report the name of the currently active transport
+ @Override
public String getCurrentTransport() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getCurrentTransport");
@@ -10444,18 +10454,21 @@
}
// Report all known, available backup transports
+ @Override
public String[] listAllTransports() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "listAllTransports");
return mTransportManager.getBoundTransportNames();
}
+ @Override
public ComponentName[] listAllTransportComponents() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"listAllTransportComponents");
return mTransportManager.getAllTransportCompenents();
}
+ @Override
public String[] getTransportWhitelist() {
// No permission check, intentionally.
Set<ComponentName> whitelistedComponents = mTransportManager.getTransportWhitelist();
@@ -10469,6 +10482,7 @@
}
// Select which transport to use for the next backup operation.
+ @Override
public String selectBackupTransport(String transport) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"selectBackupTransport");
@@ -10485,6 +10499,7 @@
}
}
+ @Override
public void selectBackupTransportAsync(final ComponentName transport,
final ISelectBackupTransportCallback listener) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
@@ -10547,6 +10562,7 @@
// Supply the configuration Intent for the given transport. If the name is not one
// of the available transports, or if the transport does not supply any configuration
// UI, the method returns null.
+ @Override
public Intent getConfigurationIntent(String transportName) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getConfigurationIntent");
@@ -10572,6 +10588,7 @@
// summary / destination string, the method can return null.
//
// This string is used VERBATIM as the summary text of the relevant Settings item!
+ @Override
public String getDestinationString(String transportName) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getDestinationString");
@@ -10592,6 +10609,7 @@
}
// Supply the manage-data intent for the given transport.
+ @Override
public Intent getDataManagementIntent(String transportName) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getDataManagementIntent");
@@ -10614,6 +10632,7 @@
// Supply the menu label for affordances that fire the manage-data intent
// for the given transport.
+ @Override
public String getDataManagementLabel(String transportName) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getDataManagementLabel");
@@ -10635,6 +10654,7 @@
// Callback: a requested backup agent has been instantiated. This should only
// be called from the Activity Manager.
+ @Override
public void agentConnected(String packageName, IBinder agentBinder) {
synchronized(mAgentConnectLock) {
if (Binder.getCallingUid() == Process.SYSTEM_UID) {
@@ -10653,6 +10673,7 @@
// Callback: a backup agent has failed to come up, or has unexpectedly quit.
// If the agent failed to come up in the first place, the agentBinder argument
// will be null. This should only be called from the Activity Manager.
+ @Override
public void agentDisconnected(String packageName) {
// TODO: handle backup being interrupted
synchronized(mAgentConnectLock) {
@@ -10669,6 +10690,7 @@
// An application being installed will need a restore pass, then the Package Manager
// will need to be told when the restore is finished.
+ @Override
public void restoreAtInstall(String packageName, int token) {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
Slog.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
@@ -10736,6 +10758,7 @@
}
// Hand off a restore session
+ @Override
public IRestoreSession beginRestoreSession(String packageName, String transport) {
if (DEBUG) Slog.v(TAG, "beginRestoreSession: pkg=" + packageName
+ " transport=" + transport);
@@ -10799,6 +10822,7 @@
// Note that a currently-active backup agent has notified us that it has
// completed the given outstanding asynchronous backup/restore operation.
+ @Override
public void opComplete(int token, long result) {
if (MORE_DEBUG) {
Slog.v(TAG, "opComplete: " + Integer.toHexString(token) + " result=" + result);
@@ -10836,6 +10860,7 @@
}
}
+ @Override
public boolean isAppEligibleForBackup(String packageName) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"isAppEligibleForBackup");
@@ -11200,6 +11225,7 @@
}
}
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
@@ -11425,4 +11451,10 @@
}
return null;
}
+
+ @Override
+ public IBackupManager getBackupManagerBinder() {
+ return mBackupManagerBinder;
+ }
+
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java b/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java
new file mode 100644
index 0000000..e415494
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup;
+
+import android.app.IBackupAgent;
+import android.app.backup.IBackupManager;
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IBackupObserver;
+import android.app.backup.IFullBackupRestoreObserver;
+import android.app.backup.IRestoreSession;
+import android.app.backup.ISelectBackupTransportCallback;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Interface for BackupManagerService.
+ *
+ * Current and future implementations of BackupManagerService should use this interface, so that
+ * Trampoline is able to switch between them.
+ */
+public interface BackupManagerServiceInterface {
+
+ // Utility: build a new random integer token
+ int generateRandomIntegerToken();
+
+ boolean setBackupPassword(String currentPw, String newPw);
+
+ boolean hasBackupPassword();
+
+ // fire off a backup agent, blocking until it attaches or times out
+ IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode);
+
+ // Get the restore-set token for the best-available restore set for this package:
+ // the active set if possible, else the ancestral one. Returns zero if none available.
+ long getAvailableRestoreToken(String packageName);
+
+ int requestBackup(String[] packages, IBackupObserver observer, int flags);
+
+ int requestBackup(String[] packages, IBackupObserver observer,
+ IBackupManagerMonitor monitor, int flags);
+
+ // Cancel all running backups.
+ void cancelBackups();
+
+ void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback,
+ int operationType);
+
+ // synchronous waiter case
+ boolean waitUntilOperationComplete(int token);
+
+ void tearDownAgentAndKill(ApplicationInfo app);
+
+ boolean beginFullBackup(FullBackupJob scheduledJob);
+
+ // The job scheduler says our constraints don't hold any more,
+ // so tear down any ongoing backup task right away.
+ void endFullBackup();
+
+ void dataChanged(String packageName);
+
+ // Clear the given package's backup data from the current transport
+ void clearBackupData(String transportName, String packageName);
+
+ // Run a backup pass immediately for any applications that have declared
+ // that they have pending updates.
+ void backupNow();
+
+ // Run a backup pass for the given packages, writing the resulting data stream
+ // to the supplied file descriptor. This method is synchronous and does not return
+ // to the caller until the backup has been completed.
+ //
+ // This is the variant used by 'adb backup'; it requires on-screen confirmation
+ // by the user because it can be used to offload data over untrusted USB.
+ void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
+ boolean includeShared, boolean doWidgets, boolean doAllApps, boolean includeSystem,
+ boolean compress, boolean doKeyValue, String[] pkgList);
+
+ void fullTransportBackup(String[] pkgNames);
+
+ void adbRestore(ParcelFileDescriptor fd);
+
+ // Confirm that the previously-requested full backup/restore operation can proceed. This
+ // is used to require a user-facing disclosure about the operation.
+ void acknowledgeAdbBackupOrRestore(int token, boolean allow,
+ String curPassword, String encPpassword, IFullBackupRestoreObserver observer);
+
+ // Enable/disable backups
+ void setBackupEnabled(boolean enable);
+
+ // Enable/disable automatic restore of app data at install time
+ void setAutoRestore(boolean doAutoRestore);
+
+ // Mark the backup service as having been provisioned
+ void setBackupProvisioned(boolean available);
+
+ // Report whether the backup mechanism is currently enabled
+ boolean isBackupEnabled();
+
+ // Report the name of the currently active transport
+ String getCurrentTransport();
+
+ // Report all known, available backup transports
+ String[] listAllTransports();
+
+ ComponentName[] listAllTransportComponents();
+
+ String[] getTransportWhitelist();
+
+ // Select which transport to use for the next backup operation.
+ String selectBackupTransport(String transport);
+
+ void selectBackupTransportAsync(ComponentName transport,
+ ISelectBackupTransportCallback listener);
+
+ // Supply the configuration Intent for the given transport. If the name is not one
+ // of the available transports, or if the transport does not supply any configuration
+ // UI, the method returns null.
+ Intent getConfigurationIntent(String transportName);
+
+ // Supply the configuration summary string for the given transport. If the name is
+ // not one of the available transports, or if the transport does not supply any
+ // summary / destination string, the method can return null.
+ //
+ // This string is used VERBATIM as the summary text of the relevant Settings item!
+ String getDestinationString(String transportName);
+
+ // Supply the manage-data intent for the given transport.
+ Intent getDataManagementIntent(String transportName);
+
+ // Supply the menu label for affordances that fire the manage-data intent
+ // for the given transport.
+ String getDataManagementLabel(String transportName);
+
+ // Callback: a requested backup agent has been instantiated. This should only
+ // be called from the Activity Manager.
+ void agentConnected(String packageName, IBinder agentBinder);
+
+ // Callback: a backup agent has failed to come up, or has unexpectedly quit.
+ // If the agent failed to come up in the first place, the agentBinder argument
+ // will be null. This should only be called from the Activity Manager.
+ void agentDisconnected(String packageName);
+
+ // An application being installed will need a restore pass, then the Package Manager
+ // will need to be told when the restore is finished.
+ void restoreAtInstall(String packageName, int token);
+
+ // Hand off a restore session
+ IRestoreSession beginRestoreSession(String packageName, String transport);
+
+ // Note that a currently-active backup agent has notified us that it has
+ // completed the given outstanding asynchronous backup/restore operation.
+ void opComplete(int token, long result);
+
+ boolean isAppEligibleForBackup(String packageName);
+
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+
+ IBackupManager getBackupManagerBinder();
+}
diff --git a/services/backup/java/com/android/server/backup/BackupRestoreTask.java b/services/backup/java/com/android/server/backup/BackupRestoreTask.java
new file mode 100644
index 0000000..acaab0c
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/BackupRestoreTask.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup;
+
+/**
+ * Interface and methods used by the asynchronous-with-timeout backup/restore operations.
+ */
+public interface BackupRestoreTask {
+
+ // Execute one tick of whatever state machine the task implements
+ void execute();
+
+ // An operation that wanted a callback has completed
+ void operationComplete(long result);
+
+ // An operation that wanted a callback has timed out
+ void handleCancel(boolean cancelAll);
+}
diff --git a/services/backup/java/com/android/server/backup/FileMetadata.java b/services/backup/java/com/android/server/backup/FileMetadata.java
new file mode 100644
index 0000000..5465609
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/FileMetadata.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup;
+
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+
+import android.app.backup.BackupAgent;
+import android.util.Slog;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Description of a file in the restore datastream.
+ */
+public class FileMetadata {
+ public String packageName; // name of the owning app
+ public String installerPackageName; // name of the market-type app that installed the owner
+ public int type; // e.g. BackupAgent.TYPE_DIRECTORY
+ public String domain; // e.g. FullBackup.DATABASE_TREE_TOKEN
+ public String path; // subpath within the semantic domain
+ public long mode; // e.g. 0666 (actually int)
+ public long mtime; // last mod time, UTC time_t (actually int)
+ public long size; // bytes of content
+ public int version; // App version.
+ public boolean hasApk; // Whether backup file contains apk.
+
+ @Override
+ public String toString() {
+ // TODO: Clean this up.
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("FileMetadata{");
+ sb.append(packageName);
+ sb.append(',');
+ sb.append(type);
+ sb.append(',');
+ sb.append(domain);
+ sb.append(':');
+ sb.append(path);
+ sb.append(',');
+ sb.append(size);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public void dump() {
+ StringBuilder b = new StringBuilder(128);
+
+ // mode string
+ b.append((type == BackupAgent.TYPE_DIRECTORY) ? 'd' : '-');
+ b.append(((mode & 0400) != 0) ? 'r' : '-');
+ b.append(((mode & 0200) != 0) ? 'w' : '-');
+ b.append(((mode & 0100) != 0) ? 'x' : '-');
+ b.append(((mode & 0040) != 0) ? 'r' : '-');
+ b.append(((mode & 0020) != 0) ? 'w' : '-');
+ b.append(((mode & 0010) != 0) ? 'x' : '-');
+ b.append(((mode & 0004) != 0) ? 'r' : '-');
+ b.append(((mode & 0002) != 0) ? 'w' : '-');
+ b.append(((mode & 0001) != 0) ? 'x' : '-');
+ b.append(String.format(" %9d ", size));
+
+ Date stamp = new Date(mtime);
+ b.append(new SimpleDateFormat("MMM dd HH:mm:ss ").format(stamp));
+
+ b.append(packageName);
+ b.append(" :: ");
+ b.append(domain);
+ b.append(" :: ");
+ b.append(path);
+
+ Slog.i(TAG, b.toString());
+ }
+
+}
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
index cd13760..279c828 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -35,7 +35,7 @@
* TODO: We should create unified backup/restore engines that can be used for both transport and
* adb backup/restore, and for fullbackup and key-value backup.
*/
-class KeyValueAdbBackupEngine {
+public class KeyValueAdbBackupEngine {
private static final String TAG = "KeyValueAdbBackupEngine";
private static final boolean DEBUG = false;
@@ -44,7 +44,7 @@
private static final String BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX = ".data";
private static final String BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX = ".new";
- private BackupManagerService mBackupManagerService;
+ private BackupManagerServiceInterface mBackupManagerService;
private final PackageManager mPackageManager;
private final OutputStream mOutput;
private final PackageInfo mCurrentPackage;
@@ -58,8 +58,8 @@
private ParcelFileDescriptor mBackupData;
private ParcelFileDescriptor mNewState;
- KeyValueAdbBackupEngine(OutputStream output, PackageInfo packageInfo,
- BackupManagerService backupManagerService, PackageManager packageManager,
+ public KeyValueAdbBackupEngine(OutputStream output, PackageInfo packageInfo,
+ BackupManagerServiceInterface backupManagerService, PackageManager packageManager,
File baseStateDir, File dataDir) {
mOutput = output;
mCurrentPackage = packageInfo;
@@ -81,7 +81,7 @@
mManifestFile = new File(mDataDir, BackupManagerService.BACKUP_MANIFEST_FILENAME);
}
- void backupOnePackage() throws IOException {
+ public void backupOnePackage() throws IOException {
ApplicationInfo targetApp = mCurrentPackage.applicationInfo;
try {
@@ -145,14 +145,14 @@
// Return true on backup success, false otherwise
private boolean invokeAgentForAdbBackup(String packageName, IBackupAgent agent) {
- int token = mBackupManagerService.generateToken();
+ int token = mBackupManagerService.generateRandomIntegerToken();
try {
mBackupManagerService.prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, null,
OP_TYPE_BACKUP_WAIT);
// Start backup and wait for BackupManagerService to get callback for success or timeout
agent.doBackup(mSavedState, mBackupData, mNewState, Long.MAX_VALUE, token,
- mBackupManagerService.mBackupManagerBinder);
+ mBackupManagerService.getBackupManagerBinder());
if (!mBackupManagerService.waitUntilOperationComplete(token)) {
Slog.e(TAG, "Key-value backup failed on package " + packageName);
return false;
@@ -214,7 +214,7 @@
}
try {
- mBackupManagerService.mBackupManagerBinder.opComplete(mToken, 0);
+ mBackupManagerService.getBackupManagerBinder().opComplete(mToken, 0);
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
@@ -229,7 +229,7 @@
private void writeBackupData() throws IOException {
- int token = mBackupManagerService.generateToken();
+ int token = mBackupManagerService.generateRandomIntegerToken();
ParcelFileDescriptor[] pipes = null;
try {
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
index 6fb9355..b62bb5c 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
@@ -13,7 +13,6 @@
import android.os.RemoteException;
import android.util.Slog;
-import com.android.server.backup.BackupManagerService.FileMetadata;
import libcore.io.IoUtils;
import java.io.File;
@@ -34,11 +33,11 @@
* TODO: We should create unified backup/restore engines that can be used for both transport and
* adb backup/restore, and for fullbackup and key-value backup.
*/
-class KeyValueAdbRestoreEngine implements Runnable {
+public class KeyValueAdbRestoreEngine implements Runnable {
private static final String TAG = "KeyValueAdbRestoreEngine";
private static final boolean DEBUG = false;
- private final BackupManagerService mBackupManagerService;
+ private final BackupManagerServiceInterface mBackupManagerService;
private final File mDataDir;
FileMetadata mInfo;
@@ -47,8 +46,9 @@
IBackupAgent mAgent;
int mToken;
- KeyValueAdbRestoreEngine(BackupManagerService backupManagerService, File dataDir,
- FileMetadata info, ParcelFileDescriptor inFD, IBackupAgent agent, int token) {
+ public KeyValueAdbRestoreEngine(BackupManagerServiceInterface backupManagerService,
+ File dataDir, FileMetadata info, ParcelFileDescriptor inFD, IBackupAgent agent,
+ int token) {
mBackupManagerService = backupManagerService;
mDataDir = dataDir;
mInfo = info;
@@ -96,7 +96,7 @@
+ versionCode);
}
agent.doRestore(backupData, versionCode, newState, mToken,
- mBackupManagerService.mBackupManagerBinder);
+ mBackupManagerService.getBackupManagerBinder());
} catch (IOException e) {
Slog.e(TAG, "Exception opening file. " + e);
} catch (RemoteException e) {
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index 642b8bf..8d91e0d 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -108,11 +108,11 @@
// We're constructed with the set of applications that are participating
// in backup. This set changes as apps are installed & removed.
- PackageManagerBackupAgent(PackageManager packageMgr, List<PackageInfo> packages) {
+ public PackageManagerBackupAgent(PackageManager packageMgr, List<PackageInfo> packages) {
init(packageMgr, packages);
}
- PackageManagerBackupAgent(PackageManager packageMgr) {
+ public PackageManagerBackupAgent(PackageManager packageMgr) {
init(packageMgr, null);
evaluateStorablePackages();
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
new file mode 100644
index 0000000..b57c0bc
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -0,0 +1,3652 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup;
+
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_BACKUP_IN_FOREGROUND;
+
+import static com.android.server.backup.internal.BackupHandler.MSG_BACKUP_OPERATION_TIMEOUT;
+import static com.android.server.backup.internal.BackupHandler.MSG_FULL_CONFIRMATION_TIMEOUT;
+import static com.android.server.backup.internal.BackupHandler.MSG_OP_COMPLETE;
+import static com.android.server.backup.internal.BackupHandler.MSG_REQUEST_BACKUP;
+import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT;
+import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_SESSION_TIMEOUT;
+import static com.android.server.backup.internal.BackupHandler.MSG_RETRY_CLEAR;
+import static com.android.server.backup.internal.BackupHandler.MSG_RETRY_INIT;
+import static com.android.server.backup.internal.BackupHandler.MSG_RUN_ADB_BACKUP;
+import static com.android.server.backup.internal.BackupHandler.MSG_RUN_ADB_RESTORE;
+import static com.android.server.backup.internal.BackupHandler.MSG_RUN_BACKUP;
+import static com.android.server.backup.internal.BackupHandler.MSG_RUN_CLEAR;
+import static com.android.server.backup.internal.BackupHandler.MSG_RUN_INITIALIZE;
+import static com.android.server.backup.internal.BackupHandler.MSG_RUN_RESTORE;
+import static com.android.server.backup.internal.BackupHandler.MSG_SCHEDULE_BACKUP_PACKAGE;
+
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.app.AppGlobals;
+import android.app.IActivityManager;
+import android.app.IBackupAgent;
+import android.app.PendingIntent;
+import android.app.backup.BackupManager;
+import android.app.backup.BackupManagerMonitor;
+import android.app.backup.FullBackup;
+import android.app.backup.IBackupManager;
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IBackupObserver;
+import android.app.backup.IFullBackupRestoreObserver;
+import android.app.backup.IRestoreSession;
+import android.app.backup.ISelectBackupTransportCallback;
+import android.app.backup.SelectBackupTransportCallback;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.PowerSaveState;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SELinux;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.storage.IStorageManager;
+import android.os.storage.StorageManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.AtomicFile;
+import android.util.EventLog;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.backup.IBackupTransport;
+import com.android.internal.util.DumpUtils;
+import com.android.server.AppWidgetBackupBridge;
+import com.android.server.EventLogTags;
+import com.android.server.SystemConfig;
+import com.android.server.SystemService;
+import com.android.server.backup.fullbackup.FullBackupEntry;
+import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
+import com.android.server.backup.internal.BackupHandler;
+import com.android.server.backup.internal.BackupRequest;
+import com.android.server.backup.internal.ClearDataObserver;
+import com.android.server.backup.internal.Operation;
+import com.android.server.backup.internal.ProvisionedObserver;
+import com.android.server.backup.internal.RunBackupReceiver;
+import com.android.server.backup.internal.RunInitializeReceiver;
+import com.android.server.backup.params.AdbBackupParams;
+import com.android.server.backup.params.AdbParams;
+import com.android.server.backup.params.AdbRestoreParams;
+import com.android.server.backup.params.BackupParams;
+import com.android.server.backup.params.ClearParams;
+import com.android.server.backup.params.ClearRetryParams;
+import com.android.server.backup.params.RestoreParams;
+import com.android.server.backup.restore.ActiveRestoreSession;
+import com.android.server.backup.restore.PerformUnifiedRestoreTask;
+import com.android.server.backup.utils.AppBackupUtils;
+import com.android.server.backup.utils.BackupManagerMonitorUtils;
+import com.android.server.backup.utils.BackupObserverUtils;
+import com.android.server.backup.utils.PasswordUtils;
+import com.android.server.power.BatterySaverPolicy.ServiceType;
+
+import libcore.io.IoUtils;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.RandomAccessFile;
+import java.security.SecureRandom;
+import java.text.SimpleDateFormat;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Queue;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+public class RefactoredBackupManagerService implements BackupManagerServiceInterface {
+
+ public static final String TAG = "BackupManagerService";
+ public static final boolean DEBUG = true;
+ public static final boolean MORE_DEBUG = false;
+ public static final boolean DEBUG_SCHEDULING = MORE_DEBUG || true;
+
+ // File containing backup-enabled state. Contains a single byte;
+ // nonzero == enabled. File missing or contains a zero byte == disabled.
+ private static final String BACKUP_ENABLE_FILE = "backup_enabled";
+
+ // System-private key used for backing up an app's widget state. Must
+ // begin with U+FFxx by convention (we reserve all keys starting
+ // with U+FF00 or higher for system use).
+ public static final String KEY_WIDGET_STATE = "\uffed\uffedwidget";
+
+ // Historical and current algorithm names
+ public static final String PBKDF_CURRENT = "PBKDF2WithHmacSHA1";
+ public static final String PBKDF_FALLBACK = "PBKDF2WithHmacSHA1And8bit";
+
+ // Name and current contents version of the full-backup manifest file
+ //
+ // Manifest version history:
+ //
+ // 1 : initial release
+ public static final String BACKUP_MANIFEST_FILENAME = "_manifest";
+ public static final int BACKUP_MANIFEST_VERSION = 1;
+
+ // External archive format version history:
+ //
+ // 1 : initial release
+ // 2 : no format change per se; version bump to facilitate PBKDF2 version skew detection
+ // 3 : introduced "_meta" metadata file; no other format change per se
+ // 4 : added support for new device-encrypted storage locations
+ // 5 : added support for key-value packages
+ public static final int BACKUP_FILE_VERSION = 5;
+ public static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n";
+ private static final int BACKUP_PW_FILE_VERSION = 2;
+ public static final String BACKUP_METADATA_FILENAME = "_meta";
+ public static final int BACKUP_METADATA_VERSION = 1;
+ public static final int BACKUP_WIDGET_METADATA_TOKEN = 0x01FFED01;
+
+ private static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production
+
+ public static final String SETTINGS_PACKAGE = "com.android.providers.settings";
+ public static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
+ private static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
+
+ // Retry interval for clear/init when the transport is unavailable
+ private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR;
+
+ public static final String RUN_BACKUP_ACTION = "android.app.backup.intent.RUN";
+ public static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT";
+
+ // Timeout interval for deciding that a bind or clear-data has taken too long
+ private static final long TIMEOUT_INTERVAL = 10 * 1000;
+
+ // Timeout intervals for agent backup & restore operations
+ public static final long TIMEOUT_BACKUP_INTERVAL = 30 * 1000;
+ public static final long TIMEOUT_FULL_BACKUP_INTERVAL = 5 * 60 * 1000;
+ public static final long TIMEOUT_SHARED_BACKUP_INTERVAL = 30 * 60 * 1000;
+ public static final long TIMEOUT_RESTORE_INTERVAL = 60 * 1000;
+ public static final long TIMEOUT_RESTORE_FINISHED_INTERVAL = 30 * 1000;
+
+ // User confirmation timeout for a full backup/restore operation. It's this long in
+ // order to give them time to enter the backup password.
+ private static final long TIMEOUT_FULL_CONFIRMATION = 60 * 1000;
+
+ // How long between attempts to perform a full-data backup of any given app
+ private static final long MIN_FULL_BACKUP_INTERVAL = 1000 * 60 * 60 * 24; // one day
+
+ // If an app is busy when we want to do a full-data backup, how long to defer the retry.
+ // This is fuzzed, so there are two parameters; backoff_min + Rand[0, backoff_fuzz)
+ private static final long BUSY_BACKOFF_MIN_MILLIS = 1000 * 60 * 60; // one hour
+ private static final int BUSY_BACKOFF_FUZZ = 1000 * 60 * 60 * 2; // two hours
+
+ private Context mContext;
+ private PackageManager mPackageManager;
+ private IPackageManager mPackageManagerBinder;
+ private IActivityManager mActivityManager;
+ private PowerManager mPowerManager;
+ private AlarmManager mAlarmManager;
+ private IStorageManager mStorageManager;
+
+ private IBackupManager mBackupManagerBinder;
+
+ private final TransportManager mTransportManager;
+
+ private boolean mEnabled; // access to this is synchronized on 'this'
+ private boolean mProvisioned;
+ private boolean mAutoRestore;
+ private PowerManager.WakeLock mWakelock;
+ private HandlerThread mHandlerThread;
+ private BackupHandler mBackupHandler;
+ private PendingIntent mRunBackupIntent;
+ private PendingIntent mRunInitIntent;
+ private BroadcastReceiver mRunBackupReceiver;
+ private BroadcastReceiver mRunInitReceiver;
+ // map UIDs to the set of participating packages under that UID
+ private final SparseArray<HashSet<String>> mBackupParticipants
+ = new SparseArray<>();
+
+ // Backups that we haven't started yet. Keys are package names.
+ private HashMap<String, BackupRequest> mPendingBackups
+ = new HashMap<>();
+
+ // Pseudoname that we use for the Package Manager metadata "package"
+ public static final String PACKAGE_MANAGER_SENTINEL = "@pm@";
+
+ // locking around the pending-backup management
+ private final Object mQueueLock = new Object();
+
+ // The thread performing the sequence of queued backups binds to each app's agent
+ // in succession. Bind notifications are asynchronously delivered through the
+ // Activity Manager; use this lock object to signal when a requested binding has
+ // completed.
+ private final Object mAgentConnectLock = new Object();
+ private IBackupAgent mConnectedAgent;
+ private volatile boolean mBackupRunning;
+ private volatile boolean mConnecting;
+ private volatile long mLastBackupPass;
+
+ // For debugging, we maintain a progress trace of operations during backup
+ public static final boolean DEBUG_BACKUP_TRACE = true;
+ private final List<String> mBackupTrace = new ArrayList<>();
+
+ // A similar synchronization mechanism around clearing apps' data for restore
+ private final Object mClearDataLock = new Object();
+ private volatile boolean mClearingData;
+
+ @GuardedBy("mPendingRestores")
+ private boolean mIsRestoreInProgress;
+ @GuardedBy("mPendingRestores")
+ private final Queue<PerformUnifiedRestoreTask> mPendingRestores = new ArrayDeque<>();
+
+ private ActiveRestoreSession mActiveRestoreSession;
+
+ // Watch the device provisioning operation during setup
+ private ContentObserver mProvisionedObserver;
+
+ // The published binder is actually to a singleton trampoline object that calls
+ // through to the proper code. This indirection lets us turn down the heavy
+ // implementation object on the fly without disturbing binders that have been
+ // cached elsewhere in the system.
+ static Trampoline sInstance;
+
+ static Trampoline getInstance() {
+ // Always constructed during system bringup, so no need to lazy-init
+ return sInstance;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public void setContext(Context context) {
+ mContext = context;
+ }
+
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ public void setPackageManager(PackageManager packageManager) {
+ mPackageManager = packageManager;
+ }
+
+ public IPackageManager getPackageManagerBinder() {
+ return mPackageManagerBinder;
+ }
+
+ public void setPackageManagerBinder(IPackageManager packageManagerBinder) {
+ mPackageManagerBinder = packageManagerBinder;
+ }
+
+ public IActivityManager getActivityManager() {
+ return mActivityManager;
+ }
+
+ public void setActivityManager(IActivityManager activityManager) {
+ mActivityManager = activityManager;
+ }
+
+ public AlarmManager getAlarmManager() {
+ return mAlarmManager;
+ }
+
+ public void setAlarmManager(AlarmManager alarmManager) {
+ mAlarmManager = alarmManager;
+ }
+
+ public void setBackupManagerBinder(IBackupManager backupManagerBinder) {
+ mBackupManagerBinder = backupManagerBinder;
+ }
+
+ public TransportManager getTransportManager() {
+ return mTransportManager;
+ }
+
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ }
+
+ public boolean isProvisioned() {
+ return mProvisioned;
+ }
+
+ public void setProvisioned(boolean provisioned) {
+ mProvisioned = provisioned;
+ }
+
+ public PowerManager.WakeLock getWakelock() {
+ return mWakelock;
+ }
+
+ public void setWakelock(PowerManager.WakeLock wakelock) {
+ mWakelock = wakelock;
+ }
+
+ public BackupHandler getBackupHandler() {
+ return mBackupHandler;
+ }
+
+ public void setBackupHandler(BackupHandler backupHandler) {
+ mBackupHandler = backupHandler;
+ }
+
+ public PendingIntent getRunInitIntent() {
+ return mRunInitIntent;
+ }
+
+ public void setRunInitIntent(PendingIntent runInitIntent) {
+ mRunInitIntent = runInitIntent;
+ }
+
+ public HashMap<String, BackupRequest> getPendingBackups() {
+ return mPendingBackups;
+ }
+
+ public void setPendingBackups(
+ HashMap<String, BackupRequest> pendingBackups) {
+ mPendingBackups = pendingBackups;
+ }
+
+ public Object getQueueLock() {
+ return mQueueLock;
+ }
+
+ public boolean isBackupRunning() {
+ return mBackupRunning;
+ }
+
+ public void setBackupRunning(boolean backupRunning) {
+ mBackupRunning = backupRunning;
+ }
+
+ public long getLastBackupPass() {
+ return mLastBackupPass;
+ }
+
+ public void setLastBackupPass(long lastBackupPass) {
+ mLastBackupPass = lastBackupPass;
+ }
+
+ public Object getClearDataLock() {
+ return mClearDataLock;
+ }
+
+ public boolean isClearingData() {
+ return mClearingData;
+ }
+
+ public void setClearingData(boolean clearingData) {
+ mClearingData = clearingData;
+ }
+
+ public boolean isRestoreInProgress() {
+ return mIsRestoreInProgress;
+ }
+
+ public void setRestoreInProgress(boolean restoreInProgress) {
+ mIsRestoreInProgress = restoreInProgress;
+ }
+
+ public Queue<PerformUnifiedRestoreTask> getPendingRestores() {
+ return mPendingRestores;
+ }
+
+ public ActiveRestoreSession getActiveRestoreSession() {
+ return mActiveRestoreSession;
+ }
+
+ public void setActiveRestoreSession(
+ ActiveRestoreSession activeRestoreSession) {
+ mActiveRestoreSession = activeRestoreSession;
+ }
+
+ public SparseArray<Operation> getCurrentOperations() {
+ return mCurrentOperations;
+ }
+
+ public Object getCurrentOpLock() {
+ return mCurrentOpLock;
+ }
+
+ public SparseArray<AdbParams> getAdbBackupRestoreConfirmations() {
+ return mAdbBackupRestoreConfirmations;
+ }
+
+ public File getBaseStateDir() {
+ return mBaseStateDir;
+ }
+
+ public void setBaseStateDir(File baseStateDir) {
+ mBaseStateDir = baseStateDir;
+ }
+
+ public File getDataDir() {
+ return mDataDir;
+ }
+
+ public void setDataDir(File dataDir) {
+ mDataDir = dataDir;
+ }
+
+ public File getJournal() {
+ return mJournal;
+ }
+
+ public void setJournal(File journal) {
+ mJournal = journal;
+ }
+
+ public SecureRandom getRng() {
+ return mRng;
+ }
+
+ public Set<String> getAncestralPackages() {
+ return mAncestralPackages;
+ }
+
+ public void setAncestralPackages(Set<String> ancestralPackages) {
+ mAncestralPackages = ancestralPackages;
+ }
+
+ public long getAncestralToken() {
+ return mAncestralToken;
+ }
+
+ public void setAncestralToken(long ancestralToken) {
+ mAncestralToken = ancestralToken;
+ }
+
+ public long getCurrentToken() {
+ return mCurrentToken;
+ }
+
+ public void setCurrentToken(long currentToken) {
+ mCurrentToken = currentToken;
+ }
+
+ public HashSet<String> getPendingInits() {
+ return mPendingInits;
+ }
+
+ public void setPendingInits(HashSet<String> pendingInits) {
+ mPendingInits = pendingInits;
+ }
+
+ public PerformFullTransportBackupTask getRunningFullBackupTask() {
+ return mRunningFullBackupTask;
+ }
+
+ public void setRunningFullBackupTask(
+ PerformFullTransportBackupTask runningFullBackupTask) {
+ mRunningFullBackupTask = runningFullBackupTask;
+ }
+
+ public static final class Lifecycle extends SystemService {
+
+ public Lifecycle(Context context) {
+ super(context);
+ sInstance = new Trampoline(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.BACKUP_SERVICE, sInstance);
+ }
+
+ @Override
+ public void onUnlockUser(int userId) {
+ if (userId == UserHandle.USER_SYSTEM) {
+ sInstance.initialize(userId);
+
+ // Migrate legacy setting
+ if (!backupSettingMigrated(userId)) {
+ if (DEBUG) {
+ Slog.i(TAG, "Backup enable apparently not migrated");
+ }
+ final ContentResolver r = sInstance.mContext.getContentResolver();
+ final int enableState = Settings.Secure.getIntForUser(r,
+ Settings.Secure.BACKUP_ENABLED, -1, userId);
+ if (enableState >= 0) {
+ if (DEBUG) {
+ Slog.i(TAG, "Migrating enable state " + (enableState != 0));
+ }
+ writeBackupEnableState(enableState != 0, userId);
+ Settings.Secure.putStringForUser(r,
+ Settings.Secure.BACKUP_ENABLED, null, userId);
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "Backup not yet configured; retaining null enable state");
+ }
+ }
+ }
+
+ try {
+ sInstance.setBackupEnabled(readBackupEnableState(userId));
+ } catch (RemoteException e) {
+ // can't happen; it's a local object
+ }
+ }
+ }
+ }
+
+ // Bookkeeping of in-flight operations for timeout etc. purposes. The operation
+ // token is the index of the entry in the pending-operations list.
+ public static final int OP_PENDING = 0;
+ private static final int OP_ACKNOWLEDGED = 1;
+ private static final int OP_TIMEOUT = -1;
+
+ // Waiting for backup agent to respond during backup operation.
+ public static final int OP_TYPE_BACKUP_WAIT = 0;
+
+ // Waiting for backup agent to respond during restore operation.
+ public static final int OP_TYPE_RESTORE_WAIT = 1;
+
+ // An entire backup operation spanning multiple packages.
+ public static final int OP_TYPE_BACKUP = 2;
+
+ /**
+ * mCurrentOperations contains the list of currently active operations.
+ *
+ * If type of operation is OP_TYPE_WAIT, it are waiting for an ack or timeout.
+ * An operation wraps a BackupRestoreTask within it.
+ * It's the responsibility of this task to remove the operation from this array.
+ *
+ * A BackupRestore task gets notified of ack/timeout for the operation via
+ * BackupRestoreTask#handleCancel, BackupRestoreTask#operationComplete and notifyAll called
+ * on the mCurrentOpLock.
+ * {@link RefactoredBackupManagerService#waitUntilOperationComplete(int)} is
+ * used in various places to 'wait' for notifyAll and detect change of pending state of an
+ * operation. So typically, an operation will be removed from this array by:
+ * - BackupRestoreTask#handleCancel and
+ * - BackupRestoreTask#operationComplete OR waitUntilOperationComplete. Do not remove at both
+ * these places because waitUntilOperationComplete relies on the operation being present to
+ * determine its completion status.
+ *
+ * If type of operation is OP_BACKUP, it is a task running backups. It provides a handle to
+ * cancel backup tasks.
+ */
+ @GuardedBy("mCurrentOpLock")
+ private final SparseArray<Operation> mCurrentOperations = new SparseArray<>();
+ private final Object mCurrentOpLock = new Object();
+ private final Random mTokenGenerator = new Random();
+
+ private final SparseArray<AdbParams> mAdbBackupRestoreConfirmations = new SparseArray<>();
+
+ // Where we keep our journal files and other bookkeeping
+ private File mBaseStateDir;
+ private File mDataDir;
+ private File mJournalDir;
+ private File mJournal;
+
+ // Backup password, if any, and the file where it's saved. What is stored is not the
+ // password text itself; it's the result of a PBKDF2 hash with a randomly chosen (but
+ // persisted) salt. Validation is performed by running the challenge text through the
+ // same PBKDF2 cycle with the persisted salt; if the resulting derived key string matches
+ // the saved hash string, then the challenge text matches the originally supplied
+ // password text.
+ private final SecureRandom mRng = new SecureRandom();
+ private String mPasswordHash;
+ private File mPasswordHashFile;
+ private int mPasswordVersion;
+ private File mPasswordVersionFile;
+ private byte[] mPasswordSalt;
+
+ // Keep a log of all the apps we've ever backed up, and what the
+ // dataset tokens are for both the current backup dataset and
+ // the ancestral dataset.
+ private File mEverStored;
+ private HashSet<String> mEverStoredApps = new HashSet<>();
+
+ private static final int CURRENT_ANCESTRAL_RECORD_VERSION = 1;
+ // increment when the schema changes
+ private File mTokenFile;
+ private Set<String> mAncestralPackages = null;
+ private long mAncestralToken = 0;
+ private long mCurrentToken = 0;
+
+ // Persistently track the need to do a full init
+ private static final String INIT_SENTINEL_FILE_NAME = "_need_init_";
+ private HashSet<String> mPendingInits = new HashSet<>(); // transport names
+
+ // Round-robin queue for scheduling full backup passes
+ private static final int SCHEDULE_FILE_VERSION = 1; // current version of the schedule file
+
+ private File mFullBackupScheduleFile;
+ // If we're running a schedule-driven full backup, this is the task instance doing it
+
+ @GuardedBy("mQueueLock")
+ private PerformFullTransportBackupTask mRunningFullBackupTask;
+
+ @GuardedBy("mQueueLock")
+ private ArrayList<FullBackupEntry> mFullBackupQueue;
+
+ // Utility: build a new random integer token
+ @Override
+ public int generateRandomIntegerToken() {
+ int token;
+ do {
+ synchronized (mTokenGenerator) {
+ token = mTokenGenerator.nextInt();
+ }
+ } while (token < 0);
+ return token;
+ }
+
+ // ----- Debug-only backup operation trace -----
+ public void addBackupTrace(String s) {
+ if (DEBUG_BACKUP_TRACE) {
+ synchronized (mBackupTrace) {
+ mBackupTrace.add(s);
+ }
+ }
+ }
+
+ public void clearBackupTrace() {
+ if (DEBUG_BACKUP_TRACE) {
+ synchronized (mBackupTrace) {
+ mBackupTrace.clear();
+ }
+ }
+ }
+
+ // ----- Main service implementation -----
+
+ public RefactoredBackupManagerService(Context context, Trampoline parent) {
+ mContext = context;
+ mPackageManager = context.getPackageManager();
+ mPackageManagerBinder = AppGlobals.getPackageManager();
+ mActivityManager = ActivityManager.getService();
+
+ mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mStorageManager = IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
+
+ mBackupManagerBinder = Trampoline.asInterface(parent.asBinder());
+
+ // spin up the backup/restore handler thread
+ mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND);
+ mHandlerThread.start();
+ mBackupHandler = new BackupHandler(this, mHandlerThread.getLooper());
+
+ // Set up our bookkeeping
+ final ContentResolver resolver = context.getContentResolver();
+ mProvisioned = Settings.Global.getInt(resolver,
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ mAutoRestore = Settings.Secure.getInt(resolver,
+ Settings.Secure.BACKUP_AUTO_RESTORE, 1) != 0;
+
+ mProvisionedObserver = new ProvisionedObserver(this, mBackupHandler);
+ resolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+ false, mProvisionedObserver);
+
+ // If Encrypted file systems is enabled or disabled, this call will return the
+ // correct directory.
+ mBaseStateDir = new File(Environment.getDataDirectory(), "backup");
+ mBaseStateDir.mkdirs();
+ if (!SELinux.restorecon(mBaseStateDir)) {
+ Slog.e(TAG, "SELinux restorecon failed on " + mBaseStateDir);
+ }
+
+ // This dir on /cache is managed directly in init.rc
+ mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup_stage");
+
+ mPasswordVersion = 1; // unless we hear otherwise
+ mPasswordVersionFile = new File(mBaseStateDir, "pwversion");
+ if (mPasswordVersionFile.exists()) {
+ FileInputStream fin = null;
+ DataInputStream in = null;
+ try {
+ fin = new FileInputStream(mPasswordVersionFile);
+ in = new DataInputStream(fin);
+ mPasswordVersion = in.readInt();
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to read backup pw version");
+ } finally {
+ try {
+ if (in != null) in.close();
+ if (fin != null) fin.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Error closing pw version files");
+ }
+ }
+ }
+
+ mPasswordHashFile = new File(mBaseStateDir, "pwhash");
+ if (mPasswordHashFile.exists()) {
+ FileInputStream fin = null;
+ DataInputStream in = null;
+ try {
+ fin = new FileInputStream(mPasswordHashFile);
+ in = new DataInputStream(new BufferedInputStream(fin));
+ // integer length of the salt array, followed by the salt,
+ // then the hex pw hash string
+ int saltLen = in.readInt();
+ byte[] salt = new byte[saltLen];
+ in.readFully(salt);
+ mPasswordHash = in.readUTF();
+ mPasswordSalt = salt;
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to read saved backup pw hash");
+ } finally {
+ try {
+ if (in != null) in.close();
+ if (fin != null) fin.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Unable to close streams");
+ }
+ }
+ }
+
+ // Alarm receivers for scheduled backups & initialization operations
+ mRunBackupReceiver = new RunBackupReceiver(this);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(RUN_BACKUP_ACTION);
+ context.registerReceiver(mRunBackupReceiver, filter,
+ android.Manifest.permission.BACKUP, null);
+
+ mRunInitReceiver = new RunInitializeReceiver(this);
+ filter = new IntentFilter();
+ filter.addAction(RUN_INITIALIZE_ACTION);
+ context.registerReceiver(mRunInitReceiver, filter,
+ android.Manifest.permission.BACKUP, null);
+
+ Intent backupIntent = new Intent(RUN_BACKUP_ACTION);
+ backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mRunBackupIntent = PendingIntent.getBroadcast(context, MSG_RUN_BACKUP, backupIntent, 0);
+
+ Intent initIntent = new Intent(RUN_INITIALIZE_ACTION);
+ backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mRunInitIntent = PendingIntent.getBroadcast(context, MSG_RUN_INITIALIZE, initIntent, 0);
+
+ // Set up the backup-request journaling
+ mJournalDir = new File(mBaseStateDir, "pending");
+ mJournalDir.mkdirs(); // creates mBaseStateDir along the way
+ mJournal = null; // will be created on first use
+
+ // Set up the various sorts of package tracking we do
+ mFullBackupScheduleFile = new File(mBaseStateDir, "fb-schedule");
+ initPackageTracking();
+
+ // Build our mapping of uid to backup client services. This implicitly
+ // schedules a backup pass on the Package Manager metadata the first
+ // time anything needs to be backed up.
+ synchronized (mBackupParticipants) {
+ addPackageParticipantsLocked(null);
+ }
+
+ // Set up our transport options and initialize the default transport
+ // TODO: Don't create transports that we don't need to?
+ SystemConfig systemConfig = SystemConfig.getInstance();
+ Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist();
+
+ String transport = Settings.Secure.getString(context.getContentResolver(),
+ Settings.Secure.BACKUP_TRANSPORT);
+ if (TextUtils.isEmpty(transport)) {
+ transport = null;
+ }
+ String currentTransport = transport;
+ if (DEBUG) Slog.v(TAG, "Starting with transport " + currentTransport);
+
+ mTransportManager = new TransportManager(context, transportWhitelist, currentTransport,
+ mTransportBoundListener, mHandlerThread.getLooper());
+ mTransportManager.registerAllTransports();
+
+ // Now that we know about valid backup participants, parse any
+ // leftover journal files into the pending backup set
+ mBackupHandler.post(() -> parseLeftoverJournals());
+
+ // Power management
+ mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
+ }
+
+ private void initPackageTracking() {
+ if (MORE_DEBUG) Slog.v(TAG, "` tracking");
+
+ // Remember our ancestral dataset
+ mTokenFile = new File(mBaseStateDir, "ancestral");
+ try {
+ RandomAccessFile tf = new RandomAccessFile(mTokenFile, "r");
+ int version = tf.readInt();
+ if (version == CURRENT_ANCESTRAL_RECORD_VERSION) {
+ mAncestralToken = tf.readLong();
+ mCurrentToken = tf.readLong();
+
+ int numPackages = tf.readInt();
+ if (numPackages >= 0) {
+ mAncestralPackages = new HashSet<>();
+ for (int i = 0; i < numPackages; i++) {
+ String pkgName = tf.readUTF();
+ mAncestralPackages.add(pkgName);
+ }
+ }
+ }
+ tf.close();
+ } catch (FileNotFoundException fnf) {
+ // Probably innocuous
+ Slog.v(TAG, "No ancestral data");
+ } catch (IOException e) {
+ Slog.w(TAG, "Unable to read token file", e);
+ }
+
+ // Keep a log of what apps we've ever backed up. Because we might have
+ // rebooted in the middle of an operation that was removing something from
+ // this log, we sanity-check its contents here and reconstruct it.
+ mEverStored = new File(mBaseStateDir, "processed");
+ File tempProcessedFile = new File(mBaseStateDir, "processed.new");
+
+ // If we were in the middle of removing something from the ever-backed-up
+ // file, there might be a transient "processed.new" file still present.
+ // Ignore it -- we'll validate "processed" against the current package set.
+ if (tempProcessedFile.exists()) {
+ tempProcessedFile.delete();
+ }
+
+ // If there are previous contents, parse them out then start a new
+ // file to continue the recordkeeping.
+ if (mEverStored.exists()) {
+ RandomAccessFile temp = null;
+ RandomAccessFile in = null;
+
+ try {
+ temp = new RandomAccessFile(tempProcessedFile, "rws");
+ in = new RandomAccessFile(mEverStored, "r");
+
+ // Loop until we hit EOF
+ while (true) {
+ String pkg = in.readUTF();
+ try {
+ // is this package still present?
+ mPackageManager.getPackageInfo(pkg, 0);
+ // if we get here then yes it is; remember it
+ mEverStoredApps.add(pkg);
+ temp.writeUTF(pkg);
+ if (MORE_DEBUG) Slog.v(TAG, " + " + pkg);
+ } catch (NameNotFoundException e) {
+ // nope, this package was uninstalled; don't include it
+ if (MORE_DEBUG) Slog.v(TAG, " - " + pkg);
+ }
+ }
+ } catch (EOFException e) {
+ // Once we've rewritten the backup history log, atomically replace the
+ // old one with the new one then reopen the file for continuing use.
+ if (!tempProcessedFile.renameTo(mEverStored)) {
+ Slog.e(TAG, "Error renaming " + tempProcessedFile + " to " + mEverStored);
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Error in processed file", e);
+ } finally {
+ try {
+ if (temp != null) temp.close();
+ } catch (IOException e) {
+ }
+ try {
+ if (in != null) in.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ synchronized (mQueueLock) {
+ // Resume the full-data backup queue
+ mFullBackupQueue = readFullBackupSchedule();
+ }
+
+ // Register for broadcasts about package install, etc., so we can
+ // update the provider list.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addDataScheme("package");
+ mContext.registerReceiver(mBroadcastReceiver, filter);
+ // Register for events related to sdcard installation.
+ IntentFilter sdFilter = new IntentFilter();
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ mContext.registerReceiver(mBroadcastReceiver, sdFilter);
+ }
+
+ private ArrayList<FullBackupEntry> readFullBackupSchedule() {
+ boolean changed = false;
+ ArrayList<FullBackupEntry> schedule = null;
+ List<PackageInfo> apps =
+ PackageManagerBackupAgent.getStorableApplications(mPackageManager);
+
+ if (mFullBackupScheduleFile.exists()) {
+ FileInputStream fstream = null;
+ BufferedInputStream bufStream = null;
+ DataInputStream in = null;
+ try {
+ fstream = new FileInputStream(mFullBackupScheduleFile);
+ bufStream = new BufferedInputStream(fstream);
+ in = new DataInputStream(bufStream);
+
+ int version = in.readInt();
+ if (version != SCHEDULE_FILE_VERSION) {
+ Slog.e(TAG, "Unknown backup schedule version " + version);
+ return null;
+ }
+
+ final int N = in.readInt();
+ schedule = new ArrayList<>(N);
+
+ // HashSet instead of ArraySet specifically because we want the eventual
+ // lookups against O(hundreds) of entries to be as fast as possible, and
+ // we discard the set immediately after the scan so the extra memory
+ // overhead is transient.
+ HashSet<String> foundApps = new HashSet<>(N);
+
+ for (int i = 0; i < N; i++) {
+ String pkgName = in.readUTF();
+ long lastBackup = in.readLong();
+ foundApps.add(pkgName); // all apps that we've addressed already
+ try {
+ PackageInfo pkg = mPackageManager.getPackageInfo(pkgName, 0);
+ if (AppBackupUtils.appGetsFullBackup(pkg)
+ && AppBackupUtils.appIsEligibleForBackup(
+ pkg.applicationInfo)) {
+ schedule.add(new FullBackupEntry(pkgName, lastBackup));
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "Package " + pkgName
+ + " no longer eligible for full backup");
+ }
+ }
+ } catch (NameNotFoundException e) {
+ if (DEBUG) {
+ Slog.i(TAG, "Package " + pkgName
+ + " not installed; dropping from full backup");
+ }
+ }
+ }
+
+ // New apps can arrive "out of band" via OTA and similar, so we also need to
+ // scan to make sure that we're tracking all full-backup candidates properly
+ for (PackageInfo app : apps) {
+ if (AppBackupUtils.appGetsFullBackup(app)
+ && AppBackupUtils.appIsEligibleForBackup(
+ app.applicationInfo)) {
+ if (!foundApps.contains(app.packageName)) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "New full backup app " + app.packageName + " found");
+ }
+ schedule.add(new FullBackupEntry(app.packageName, 0));
+ changed = true;
+ }
+ }
+ }
+
+ Collections.sort(schedule);
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to read backup schedule", e);
+ mFullBackupScheduleFile.delete();
+ schedule = null;
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(bufStream);
+ IoUtils.closeQuietly(fstream);
+ }
+ }
+
+ if (schedule == null) {
+ // no prior queue record, or unable to read it. Set up the queue
+ // from scratch.
+ changed = true;
+ schedule = new ArrayList<>(apps.size());
+ for (PackageInfo info : apps) {
+ if (AppBackupUtils.appGetsFullBackup(info) && AppBackupUtils.appIsEligibleForBackup(
+ info.applicationInfo)) {
+ schedule.add(new FullBackupEntry(info.packageName, 0));
+ }
+ }
+ }
+
+ if (changed) {
+ writeFullBackupScheduleAsync();
+ }
+ return schedule;
+ }
+
+ private Runnable mFullBackupScheduleWriter = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mQueueLock) {
+ try {
+ ByteArrayOutputStream bufStream = new ByteArrayOutputStream(4096);
+ DataOutputStream bufOut = new DataOutputStream(bufStream);
+ bufOut.writeInt(SCHEDULE_FILE_VERSION);
+
+ // version 1:
+ //
+ // [int] # of packages in the queue = N
+ // N * {
+ // [utf8] package name
+ // [long] last backup time for this package
+ // }
+ int N = mFullBackupQueue.size();
+ bufOut.writeInt(N);
+
+ for (int i = 0; i < N; i++) {
+ FullBackupEntry entry = mFullBackupQueue.get(i);
+ bufOut.writeUTF(entry.packageName);
+ bufOut.writeLong(entry.lastBackup);
+ }
+ bufOut.flush();
+
+ AtomicFile af = new AtomicFile(mFullBackupScheduleFile);
+ FileOutputStream out = af.startWrite();
+ out.write(bufStream.toByteArray());
+ af.finishWrite(out);
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to write backup schedule!", e);
+ }
+ }
+ }
+ };
+
+ private void writeFullBackupScheduleAsync() {
+ mBackupHandler.removeCallbacks(mFullBackupScheduleWriter);
+ mBackupHandler.post(mFullBackupScheduleWriter);
+ }
+
+ private void parseLeftoverJournals() {
+ for (File f : mJournalDir.listFiles()) {
+ if (mJournal == null || f.compareTo(mJournal) != 0) {
+ // This isn't the current journal, so it must be a leftover. Read
+ // out the package names mentioned there and schedule them for
+ // backup.
+ DataInputStream in = null;
+ try {
+ Slog.i(TAG, "Found stale backup journal, scheduling");
+ // Journals will tend to be on the order of a few kilobytes(around 4k), hence,
+ // setting the buffer size to 8192.
+ InputStream bufferedInputStream = new BufferedInputStream(
+ new FileInputStream(f), 8192);
+ in = new DataInputStream(bufferedInputStream);
+ while (true) {
+ String packageName = in.readUTF();
+ if (MORE_DEBUG) Slog.i(TAG, " " + packageName);
+ dataChangedImpl(packageName);
+ }
+ } catch (EOFException e) {
+ // no more data; we're done
+ } catch (Exception e) {
+ Slog.e(TAG, "Can't read " + f, e);
+ } finally {
+ // close/delete the file
+ try {
+ if (in != null) in.close();
+ } catch (IOException e) {
+ }
+ f.delete();
+ }
+ }
+ }
+ }
+
+ // Used for generating random salts or passwords
+ public byte[] randomBytes(int bits) {
+ byte[] array = new byte[bits / 8];
+ mRng.nextBytes(array);
+ return array;
+ }
+
+ private boolean passwordMatchesSaved(String algorithm, String candidatePw, int rounds) {
+ if (mPasswordHash == null) {
+ // no current password case -- require that 'currentPw' be null or empty
+ if (candidatePw == null || "".equals(candidatePw)) {
+ return true;
+ } // else the non-empty candidate does not match the empty stored pw
+ } else {
+ // hash the stated current pw and compare to the stored one
+ if (candidatePw != null && candidatePw.length() > 0) {
+ String currentPwHash = PasswordUtils.buildPasswordHash(algorithm, candidatePw,
+ mPasswordSalt,
+ rounds);
+ if (mPasswordHash.equalsIgnoreCase(currentPwHash)) {
+ // candidate hash matches the stored hash -- the password matches
+ return true;
+ }
+ } // else the stored pw is nonempty but the candidate is empty; no match
+ }
+ return false;
+ }
+
+ @Override
+ public boolean setBackupPassword(String currentPw, String newPw) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "setBackupPassword");
+
+ // When processing v1 passwords we may need to try two different PBKDF2 checksum regimes
+ final boolean pbkdf2Fallback = (mPasswordVersion < BACKUP_PW_FILE_VERSION);
+
+ // If the supplied pw doesn't hash to the the saved one, fail. The password
+ // might be caught in the legacy crypto mismatch; verify that too.
+ if (!passwordMatchesSaved(PBKDF_CURRENT, currentPw, PasswordUtils.PBKDF2_HASH_ROUNDS)
+ && !(pbkdf2Fallback && passwordMatchesSaved(PBKDF_FALLBACK,
+ currentPw, PasswordUtils.PBKDF2_HASH_ROUNDS))) {
+ return false;
+ }
+
+ // Snap up to current on the pw file version
+ mPasswordVersion = BACKUP_PW_FILE_VERSION;
+ FileOutputStream pwFout = null;
+ DataOutputStream pwOut = null;
+ try {
+ pwFout = new FileOutputStream(mPasswordVersionFile);
+ pwOut = new DataOutputStream(pwFout);
+ pwOut.writeInt(mPasswordVersion);
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to write backup pw version; password not changed");
+ return false;
+ } finally {
+ try {
+ if (pwOut != null) pwOut.close();
+ if (pwFout != null) pwFout.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Unable to close pw version record");
+ }
+ }
+
+ // Clearing the password is okay
+ if (newPw == null || newPw.isEmpty()) {
+ if (mPasswordHashFile.exists()) {
+ if (!mPasswordHashFile.delete()) {
+ // Unable to delete the old pw file, so fail
+ Slog.e(TAG, "Unable to clear backup password");
+ return false;
+ }
+ }
+ mPasswordHash = null;
+ mPasswordSalt = null;
+ return true;
+ }
+
+ try {
+ // Okay, build the hash of the new backup password
+ byte[] salt = randomBytes(PasswordUtils.PBKDF2_SALT_SIZE);
+ String newPwHash = PasswordUtils.buildPasswordHash(PBKDF_CURRENT, newPw, salt,
+ PasswordUtils.PBKDF2_HASH_ROUNDS);
+
+ OutputStream pwf = null, buffer = null;
+ DataOutputStream out = null;
+ try {
+ pwf = new FileOutputStream(mPasswordHashFile);
+ buffer = new BufferedOutputStream(pwf);
+ out = new DataOutputStream(buffer);
+ // integer length of the salt array, followed by the salt,
+ // then the hex pw hash string
+ out.writeInt(salt.length);
+ out.write(salt);
+ out.writeUTF(newPwHash);
+ out.flush();
+ mPasswordHash = newPwHash;
+ mPasswordSalt = salt;
+ return true;
+ } finally {
+ if (out != null) out.close();
+ if (buffer != null) buffer.close();
+ if (pwf != null) pwf.close();
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to set backup password");
+ }
+ return false;
+ }
+
+ @Override
+ public boolean hasBackupPassword() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "hasBackupPassword");
+
+ return mPasswordHash != null && mPasswordHash.length() > 0;
+ }
+
+ public boolean backupPasswordMatches(String currentPw) {
+ if (hasBackupPassword()) {
+ final boolean pbkdf2Fallback = (mPasswordVersion < BACKUP_PW_FILE_VERSION);
+ if (!passwordMatchesSaved(PBKDF_CURRENT, currentPw, PasswordUtils.PBKDF2_HASH_ROUNDS)
+ && !(pbkdf2Fallback && passwordMatchesSaved(PBKDF_FALLBACK,
+ currentPw, PasswordUtils.PBKDF2_HASH_ROUNDS))) {
+ if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Maintain persistent state around whether need to do an initialize operation.
+ // Must be called with the queue lock held.
+ public void recordInitPendingLocked(boolean isPending, String transportName) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "recordInitPendingLocked: " + isPending
+ + " on transport " + transportName);
+ }
+ mBackupHandler.removeMessages(MSG_RETRY_INIT);
+
+ try {
+ IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
+ if (transport != null) {
+ String transportDirName = transport.transportDirName();
+ File stateDir = new File(mBaseStateDir, transportDirName);
+ File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME);
+
+ if (isPending) {
+ // We need an init before we can proceed with sending backup data.
+ // Record that with an entry in our set of pending inits, as well as
+ // journaling it via creation of a sentinel file.
+ mPendingInits.add(transportName);
+ try {
+ (new FileOutputStream(initPendingFile)).close();
+ } catch (IOException ioe) {
+ // Something is badly wrong with our permissions; just try to move on
+ }
+ } else {
+ // No more initialization needed; wipe the journal and reset our state.
+ initPendingFile.delete();
+ mPendingInits.remove(transportName);
+ }
+ return; // done; don't fall through to the error case
+ }
+ } catch (Exception e) {
+ // transport threw when asked its name; fall through to the lookup-failed case
+ Slog.e(TAG, "Transport " + transportName + " failed to report name: "
+ + e.getMessage());
+ }
+
+ // The named transport doesn't exist or threw. This operation is
+ // important, so we record the need for a an init and post a message
+ // to retry the init later.
+ if (isPending) {
+ mPendingInits.add(transportName);
+ mBackupHandler.sendMessageDelayed(
+ mBackupHandler.obtainMessage(MSG_RETRY_INIT,
+ (isPending ? 1 : 0),
+ 0,
+ transportName),
+ TRANSPORT_RETRY_INTERVAL);
+ }
+ }
+
+ // Reset all of our bookkeeping, in response to having been told that
+ // the backend data has been wiped [due to idle expiry, for example],
+ // so we must re-upload all saved settings.
+ public void resetBackupState(File stateFileDir) {
+ synchronized (mQueueLock) {
+ // Wipe the "what we've ever backed up" tracking
+ mEverStoredApps.clear();
+ mEverStored.delete();
+
+ mCurrentToken = 0;
+ writeRestoreTokens();
+
+ // Remove all the state files
+ for (File sf : stateFileDir.listFiles()) {
+ // ... but don't touch the needs-init sentinel
+ if (!sf.getName().equals(INIT_SENTINEL_FILE_NAME)) {
+ sf.delete();
+ }
+ }
+ }
+
+ // Enqueue a new backup of every participant
+ synchronized (mBackupParticipants) {
+ final int N = mBackupParticipants.size();
+ for (int i = 0; i < N; i++) {
+ HashSet<String> participants = mBackupParticipants.valueAt(i);
+ if (participants != null) {
+ for (String packageName : participants) {
+ dataChangedImpl(packageName);
+ }
+ }
+ }
+ }
+ }
+
+ private TransportManager.TransportBoundListener mTransportBoundListener =
+ new TransportManager.TransportBoundListener() {
+ @Override
+ public boolean onTransportBound(IBackupTransport transport) {
+ // If the init sentinel file exists, we need to be sure to perform the init
+ // as soon as practical. We also create the state directory at registration
+ // time to ensure it's present from the outset.
+ String name = null;
+ try {
+ name = transport.name();
+ String transportDirName = transport.transportDirName();
+ File stateDir = new File(mBaseStateDir, transportDirName);
+ stateDir.mkdirs();
+
+ File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME);
+ if (initSentinel.exists()) {
+ synchronized (mQueueLock) {
+ mPendingInits.add(name);
+
+ // TODO: pick a better starting time than now + 1 minute
+ long delay = 1000 * 60; // one minute, in milliseconds
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+ System.currentTimeMillis() + delay, mRunInitIntent);
+ }
+ }
+ return true;
+ } catch (Exception e) {
+ // the transport threw when asked its file naming prefs; declare it invalid
+ Slog.w(TAG, "Failed to regiser transport: " + name);
+ return false;
+ }
+ }
+ };
+
+ // ----- Track installation/removal of packages -----
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ if (MORE_DEBUG) Slog.d(TAG, "Received broadcast " + intent);
+
+ String action = intent.getAction();
+ boolean replacing = false;
+ boolean added = false;
+ boolean changed = false;
+ Bundle extras = intent.getExtras();
+ String pkgList[] = null;
+ if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
+ Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
+ Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+ Uri uri = intent.getData();
+ if (uri == null) {
+ return;
+ }
+ String pkgName = uri.getSchemeSpecificPart();
+ if (pkgName != null) {
+ pkgList = new String[]{pkgName};
+ }
+ changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
+
+ // At package-changed we only care about looking at new transport states
+ if (changed) {
+ String[] components =
+ intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Package " + pkgName + " changed; rechecking");
+ for (int i = 0; i < components.length; i++) {
+ Slog.i(TAG, " * " + components[i]);
+ }
+ }
+
+ mTransportManager.onPackageChanged(pkgName, components);
+ return; // nothing more to do in the PACKAGE_CHANGED case
+ }
+
+ added = Intent.ACTION_PACKAGE_ADDED.equals(action);
+ replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false);
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
+ added = true;
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+ added = false;
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ }
+
+ if (pkgList == null || pkgList.length == 0) {
+ return;
+ }
+
+ final int uid = extras.getInt(Intent.EXTRA_UID);
+ if (added) {
+ synchronized (mBackupParticipants) {
+ if (replacing) {
+ // This is the package-replaced case; we just remove the entry
+ // under the old uid and fall through to re-add. If an app
+ // just added key/value backup participation, this picks it up
+ // as a known participant.
+ removePackageParticipantsLocked(pkgList, uid);
+ }
+ addPackageParticipantsLocked(pkgList);
+ }
+ // If they're full-backup candidates, add them there instead
+ final long now = System.currentTimeMillis();
+ for (String packageName : pkgList) {
+ try {
+ PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
+ if (AppBackupUtils.appGetsFullBackup(app)
+ && AppBackupUtils.appIsEligibleForBackup(
+ app.applicationInfo)) {
+ enqueueFullBackup(packageName, now);
+ scheduleNextFullBackupJob(0);
+ } else {
+ // The app might have just transitioned out of full-data into
+ // doing key/value backups, or might have just disabled backups
+ // entirely. Make sure it is no longer in the full-data queue.
+ synchronized (mQueueLock) {
+ dequeueFullBackupLocked(packageName);
+ }
+ writeFullBackupScheduleAsync();
+ }
+
+ mTransportManager.onPackageAdded(packageName);
+
+ } catch (NameNotFoundException e) {
+ // doesn't really exist; ignore it
+ if (DEBUG) {
+ Slog.w(TAG, "Can't resolve new app " + packageName);
+ }
+ }
+ }
+
+ // Whenever a package is added or updated we need to update
+ // the package metadata bookkeeping.
+ dataChangedImpl(PACKAGE_MANAGER_SENTINEL);
+ } else {
+ if (replacing) {
+ // The package is being updated. We'll receive a PACKAGE_ADDED shortly.
+ } else {
+ // Outright removal. In the full-data case, the app will be dropped
+ // from the queue when its (now obsolete) name comes up again for
+ // backup.
+ synchronized (mBackupParticipants) {
+ removePackageParticipantsLocked(pkgList, uid);
+ }
+ }
+ for (String pkgName : pkgList) {
+ mTransportManager.onPackageRemoved(pkgName);
+ }
+ }
+ }
+ };
+
+ // Add the backup agents in the given packages to our set of known backup participants.
+ // If 'packageNames' is null, adds all backup agents in the whole system.
+ private void addPackageParticipantsLocked(String[] packageNames) {
+ // Look for apps that define the android:backupAgent attribute
+ List<PackageInfo> targetApps = allAgentPackages();
+ if (packageNames != null) {
+ if (MORE_DEBUG) Slog.v(TAG, "addPackageParticipantsLocked: #" + packageNames.length);
+ for (String packageName : packageNames) {
+ addPackageParticipantsLockedInner(packageName, targetApps);
+ }
+ } else {
+ if (MORE_DEBUG) Slog.v(TAG, "addPackageParticipantsLocked: all");
+ addPackageParticipantsLockedInner(null, targetApps);
+ }
+ }
+
+ private void addPackageParticipantsLockedInner(String packageName,
+ List<PackageInfo> targetPkgs) {
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "Examining " + packageName + " for backup agent");
+ }
+
+ for (PackageInfo pkg : targetPkgs) {
+ if (packageName == null || pkg.packageName.equals(packageName)) {
+ int uid = pkg.applicationInfo.uid;
+ HashSet<String> set = mBackupParticipants.get(uid);
+ if (set == null) {
+ set = new HashSet<>();
+ mBackupParticipants.put(uid, set);
+ }
+ set.add(pkg.packageName);
+ if (MORE_DEBUG) Slog.v(TAG, "Agent found; added");
+
+ // Schedule a backup for it on general principles
+ if (MORE_DEBUG) Slog.i(TAG, "Scheduling backup for new app " + pkg.packageName);
+ Message msg = mBackupHandler
+ .obtainMessage(MSG_SCHEDULE_BACKUP_PACKAGE, pkg.packageName);
+ mBackupHandler.sendMessage(msg);
+ }
+ }
+ }
+
+ // Remove the given packages' entries from our known active set.
+ private void removePackageParticipantsLocked(String[] packageNames, int oldUid) {
+ if (packageNames == null) {
+ Slog.w(TAG, "removePackageParticipants with null list");
+ return;
+ }
+
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "removePackageParticipantsLocked: uid=" + oldUid
+ + " #" + packageNames.length);
+ }
+ for (String pkg : packageNames) {
+ // Known previous UID, so we know which package set to check
+ HashSet<String> set = mBackupParticipants.get(oldUid);
+ if (set != null && set.contains(pkg)) {
+ removePackageFromSetLocked(set, pkg);
+ if (set.isEmpty()) {
+ if (MORE_DEBUG) Slog.v(TAG, " last one of this uid; purging set");
+ mBackupParticipants.remove(oldUid);
+ }
+ }
+ }
+ }
+
+ private void removePackageFromSetLocked(final HashSet<String> set,
+ final String packageName) {
+ if (set.contains(packageName)) {
+ // Found it. Remove this one package from the bookkeeping, and
+ // if it's the last participating app under this uid we drop the
+ // (now-empty) set as well.
+ // Note that we deliberately leave it 'known' in the "ever backed up"
+ // bookkeeping so that its current-dataset data will be retrieved
+ // if the app is subsequently reinstalled
+ if (MORE_DEBUG) Slog.v(TAG, " removing participant " + packageName);
+ set.remove(packageName);
+ mPendingBackups.remove(packageName);
+ }
+ }
+
+ // Returns the set of all applications that define an android:backupAgent attribute
+ private List<PackageInfo> allAgentPackages() {
+ // !!! TODO: cache this and regenerate only when necessary
+ int flags = PackageManager.GET_SIGNATURES;
+ List<PackageInfo> packages = mPackageManager.getInstalledPackages(flags);
+ int N = packages.size();
+ for (int a = N - 1; a >= 0; a--) {
+ PackageInfo pkg = packages.get(a);
+ try {
+ ApplicationInfo app = pkg.applicationInfo;
+ if (((app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0)
+ || app.backupAgentName == null
+ || (app.flags & ApplicationInfo.FLAG_FULL_BACKUP_ONLY) != 0) {
+ packages.remove(a);
+ } else {
+ // we will need the shared library path, so look that up and store it here.
+ // This is used implicitly when we pass the PackageInfo object off to
+ // the Activity Manager to launch the app for backup/restore purposes.
+ app = mPackageManager.getApplicationInfo(pkg.packageName,
+ PackageManager.GET_SHARED_LIBRARY_FILES);
+ pkg.applicationInfo.sharedLibraryFiles = app.sharedLibraryFiles;
+ }
+ } catch (NameNotFoundException e) {
+ packages.remove(a);
+ }
+ }
+ return packages;
+ }
+
+ // Called from the backup tasks: record that the given app has been successfully
+ // backed up at least once. This includes both key/value and full-data backups
+ // through the transport.
+ public void logBackupComplete(String packageName) {
+ if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) return;
+
+ synchronized (mEverStoredApps) {
+ if (!mEverStoredApps.add(packageName)) return;
+
+ RandomAccessFile out = null;
+ try {
+ out = new RandomAccessFile(mEverStored, "rws");
+ out.seek(out.length());
+ out.writeUTF(packageName);
+ } catch (IOException e) {
+ Slog.e(TAG, "Can't log backup of " + packageName + " to " + mEverStored);
+ } finally {
+ try {
+ if (out != null) out.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ // Remove our awareness of having ever backed up the given package
+ void removeEverBackedUp(String packageName) {
+ if (DEBUG) Slog.v(TAG, "Removing backed-up knowledge of " + packageName);
+ if (MORE_DEBUG) Slog.v(TAG, "New set:");
+
+ synchronized (mEverStoredApps) {
+ // Rewrite the file and rename to overwrite. If we reboot in the middle,
+ // we'll recognize on initialization time that the package no longer
+ // exists and fix it up then.
+ File tempKnownFile = new File(mBaseStateDir, "processed.new");
+ RandomAccessFile known = null;
+ try {
+ known = new RandomAccessFile(tempKnownFile, "rws");
+ mEverStoredApps.remove(packageName);
+ for (String s : mEverStoredApps) {
+ known.writeUTF(s);
+ if (MORE_DEBUG) Slog.v(TAG, " " + s);
+ }
+ known.close();
+ known = null;
+ if (!tempKnownFile.renameTo(mEverStored)) {
+ throw new IOException("Can't rename " + tempKnownFile + " to " + mEverStored);
+ }
+ } catch (IOException e) {
+ // Bad: we couldn't create the new copy. For safety's sake we
+ // abandon the whole process and remove all what's-backed-up
+ // state entirely, meaning we'll force a backup pass for every
+ // participant on the next boot or [re]install.
+ Slog.w(TAG, "Error rewriting " + mEverStored, e);
+ mEverStoredApps.clear();
+ tempKnownFile.delete();
+ mEverStored.delete();
+ } finally {
+ try {
+ if (known != null) known.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ // Persistently record the current and ancestral backup tokens as well
+ // as the set of packages with data [supposedly] available in the
+ // ancestral dataset.
+ public void writeRestoreTokens() {
+ try {
+ RandomAccessFile af = new RandomAccessFile(mTokenFile, "rwd");
+
+ // First, the version number of this record, for futureproofing
+ af.writeInt(CURRENT_ANCESTRAL_RECORD_VERSION);
+
+ // Write the ancestral and current tokens
+ af.writeLong(mAncestralToken);
+ af.writeLong(mCurrentToken);
+
+ // Now write the set of ancestral packages
+ if (mAncestralPackages == null) {
+ af.writeInt(-1);
+ } else {
+ af.writeInt(mAncestralPackages.size());
+ if (DEBUG) Slog.v(TAG, "Ancestral packages: " + mAncestralPackages.size());
+ for (String pkgName : mAncestralPackages) {
+ af.writeUTF(pkgName);
+ if (MORE_DEBUG) Slog.v(TAG, " " + pkgName);
+ }
+ }
+ af.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Unable to write token file:", e);
+ }
+ }
+
+ // What name is this transport registered under...?
+ private String getTransportName(IBackupTransport transport) {
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "Searching for transport name of " + transport);
+ }
+ return mTransportManager.getTransportName(transport);
+ }
+
+ // fire off a backup agent, blocking until it attaches or times out
+ @Override
+ public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode) {
+ IBackupAgent agent = null;
+ synchronized (mAgentConnectLock) {
+ mConnecting = true;
+ mConnectedAgent = null;
+ try {
+ if (mActivityManager.bindBackupAgent(app.packageName, mode,
+ UserHandle.USER_OWNER)) {
+ Slog.d(TAG, "awaiting agent for " + app);
+
+ // success; wait for the agent to arrive
+ // only wait 10 seconds for the bind to happen
+ long timeoutMark = System.currentTimeMillis() + TIMEOUT_INTERVAL;
+ while (mConnecting && mConnectedAgent == null
+ && (System.currentTimeMillis() < timeoutMark)) {
+ try {
+ mAgentConnectLock.wait(5000);
+ } catch (InterruptedException e) {
+ // just bail
+ Slog.w(TAG, "Interrupted: " + e);
+ mConnecting = false;
+ mConnectedAgent = null;
+ }
+ }
+
+ // if we timed out with no connect, abort and move on
+ if (mConnecting == true) {
+ Slog.w(TAG, "Timeout waiting for agent " + app);
+ mConnectedAgent = null;
+ }
+ if (DEBUG) Slog.i(TAG, "got agent " + mConnectedAgent);
+ agent = mConnectedAgent;
+ }
+ } catch (RemoteException e) {
+ // can't happen - ActivityManager is local
+ }
+ }
+ if (agent == null) {
+ try {
+ mActivityManager.clearPendingBackup();
+ } catch (RemoteException e) {
+ // can't happen - ActivityManager is local
+ }
+ }
+ return agent;
+ }
+
+ // clear an application's data, blocking until the operation completes or times out
+ public void clearApplicationDataSynchronous(String packageName) {
+ // Don't wipe packages marked allowClearUserData=false
+ try {
+ PackageInfo info = mPackageManager.getPackageInfo(packageName, 0);
+ if ((info.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA) == 0) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "allowClearUserData=false so not wiping "
+ + packageName);
+ }
+ return;
+ }
+ } catch (NameNotFoundException e) {
+ Slog.w(TAG, "Tried to clear data for " + packageName + " but not found");
+ return;
+ }
+
+ ClearDataObserver observer = new ClearDataObserver(this);
+
+ synchronized (mClearDataLock) {
+ mClearingData = true;
+ try {
+ mActivityManager.clearApplicationUserData(packageName, observer, 0);
+ } catch (RemoteException e) {
+ // can't happen because the activity manager is in this process
+ }
+
+ // only wait 10 seconds for the clear data to happen
+ long timeoutMark = System.currentTimeMillis() + TIMEOUT_INTERVAL;
+ while (mClearingData && (System.currentTimeMillis() < timeoutMark)) {
+ try {
+ mClearDataLock.wait(5000);
+ } catch (InterruptedException e) {
+ // won't happen, but still.
+ mClearingData = false;
+ }
+ }
+ }
+ }
+
+ // Get the restore-set token for the best-available restore set for this package:
+ // the active set if possible, else the ancestral one. Returns zero if none available.
+ @Override
+ public long getAvailableRestoreToken(String packageName) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "getAvailableRestoreToken");
+
+ long token = mAncestralToken;
+ synchronized (mQueueLock) {
+ if (mEverStoredApps.contains(packageName)) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "App in ever-stored, so using current token");
+ }
+ token = mCurrentToken;
+ }
+ }
+ if (MORE_DEBUG) Slog.i(TAG, "getAvailableRestoreToken() == " + token);
+ return token;
+ }
+
+ @Override
+ public int requestBackup(String[] packages, IBackupObserver observer, int flags) {
+ return requestBackup(packages, observer, null, flags);
+ }
+
+ @Override
+ public int requestBackup(String[] packages, IBackupObserver observer,
+ IBackupManagerMonitor monitor, int flags) {
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "requestBackup");
+
+ if (packages == null || packages.length < 1) {
+ Slog.e(TAG, "No packages named for backup request");
+ BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
+ monitor = BackupManagerMonitorUtils.monitorEvent(monitor,
+ BackupManagerMonitor.LOG_EVENT_ID_NO_PACKAGES,
+ null, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null);
+ throw new IllegalArgumentException("No packages are provided for backup");
+ }
+
+ IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
+ if (transport == null) {
+ BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
+ monitor = BackupManagerMonitorUtils.monitorEvent(monitor,
+ BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL,
+ null, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null);
+ return BackupManager.ERROR_TRANSPORT_ABORTED;
+ }
+
+ ArrayList<String> fullBackupList = new ArrayList<>();
+ ArrayList<String> kvBackupList = new ArrayList<>();
+ for (String packageName : packages) {
+ if (PACKAGE_MANAGER_SENTINEL.equals(packageName)) {
+ kvBackupList.add(packageName);
+ continue;
+ }
+ try {
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_SIGNATURES);
+ if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo)) {
+ BackupObserverUtils.sendBackupOnPackageResult(observer, packageName,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
+ continue;
+ }
+ if (AppBackupUtils.appGetsFullBackup(packageInfo)) {
+ fullBackupList.add(packageInfo.packageName);
+ } else {
+ kvBackupList.add(packageInfo.packageName);
+ }
+ } catch (NameNotFoundException e) {
+ BackupObserverUtils.sendBackupOnPackageResult(observer, packageName,
+ BackupManager.ERROR_PACKAGE_NOT_FOUND);
+ }
+ }
+ EventLog.writeEvent(EventLogTags.BACKUP_REQUESTED, packages.length, kvBackupList.size(),
+ fullBackupList.size());
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Backup requested for " + packages.length + " packages, of them: " +
+ fullBackupList.size() + " full backups, " + kvBackupList.size()
+ + " k/v backups");
+ }
+
+ String dirName;
+ try {
+ dirName = transport.transportDirName();
+ } catch (Exception e) {
+ Slog.e(TAG, "Transport unavailable while attempting backup: " + e.getMessage());
+ BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
+ return BackupManager.ERROR_TRANSPORT_ABORTED;
+ }
+
+ boolean nonIncrementalBackup = (flags & BackupManager.FLAG_NON_INCREMENTAL_BACKUP) != 0;
+
+ Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP);
+ msg.obj = new BackupParams(transport, dirName, kvBackupList, fullBackupList, observer,
+ monitor, true, nonIncrementalBackup);
+ mBackupHandler.sendMessage(msg);
+ return BackupManager.SUCCESS;
+ }
+
+ // Cancel all running backups.
+ @Override
+ public void cancelBackups() {
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "cancelBackups");
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "cancelBackups() called.");
+ }
+ final long oldToken = Binder.clearCallingIdentity();
+ try {
+ List<Integer> operationsToCancel = new ArrayList<>();
+ synchronized (mCurrentOpLock) {
+ for (int i = 0; i < mCurrentOperations.size(); i++) {
+ Operation op = mCurrentOperations.valueAt(i);
+ int token = mCurrentOperations.keyAt(i);
+ if (op.type == OP_TYPE_BACKUP) {
+ operationsToCancel.add(token);
+ }
+ }
+ }
+ for (Integer token : operationsToCancel) {
+ handleCancel(token, true /* cancelAll */);
+ }
+ // We don't want the backup jobs to kick in any time soon.
+ // Reschedules them to run in the distant future.
+ KeyValueBackupJob.schedule(mContext, BUSY_BACKOFF_MIN_MILLIS);
+ FullBackupJob.schedule(mContext, 2 * BUSY_BACKOFF_MIN_MILLIS);
+ } finally {
+ Binder.restoreCallingIdentity(oldToken);
+ }
+ }
+
+ @Override
+ public void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback,
+ int operationType) {
+ if (operationType != OP_TYPE_BACKUP_WAIT && operationType != OP_TYPE_RESTORE_WAIT) {
+ Slog.wtf(TAG, "prepareOperationTimeout() doesn't support operation " +
+ Integer.toHexString(token) + " of type " + operationType);
+ return;
+ }
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "starting timeout: token=" + Integer.toHexString(token)
+ + " interval=" + interval + " callback=" + callback);
+ }
+
+ synchronized (mCurrentOpLock) {
+ mCurrentOperations.put(token, new Operation(OP_PENDING, callback, operationType));
+ Message msg = mBackupHandler.obtainMessage(getMessageIdForOperationType(operationType),
+ token, 0, callback);
+ mBackupHandler.sendMessageDelayed(msg, interval);
+ }
+ }
+
+ private int getMessageIdForOperationType(int operationType) {
+ switch (operationType) {
+ case OP_TYPE_BACKUP_WAIT:
+ return MSG_BACKUP_OPERATION_TIMEOUT;
+ case OP_TYPE_RESTORE_WAIT:
+ return MSG_RESTORE_OPERATION_TIMEOUT;
+ default:
+ Slog.wtf(TAG, "getMessageIdForOperationType called on invalid operation type: " +
+ operationType);
+ return -1;
+ }
+ }
+
+ public void removeOperation(int token) {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Removing operation token=" + Integer.toHexString(token));
+ }
+ synchronized (mCurrentOpLock) {
+ if (mCurrentOperations.get(token) == null) {
+ Slog.w(TAG, "Duplicate remove for operation. token=" +
+ Integer.toHexString(token));
+ }
+ mCurrentOperations.remove(token);
+ }
+ }
+
+ // synchronous waiter case
+ @Override
+ public boolean waitUntilOperationComplete(int token) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Blocking until operation complete for "
+ + Integer.toHexString(token));
+ }
+ int finalState = OP_PENDING;
+ Operation op = null;
+ synchronized (mCurrentOpLock) {
+ while (true) {
+ op = mCurrentOperations.get(token);
+ if (op == null) {
+ // mysterious disappearance: treat as success with no callback
+ break;
+ } else {
+ if (op.state == OP_PENDING) {
+ try {
+ mCurrentOpLock.wait();
+ } catch (InterruptedException e) {
+ }
+ // When the wait is notified we loop around and recheck the current state
+ } else {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Unblocked waiting for operation token=" +
+ Integer.toHexString(token));
+ }
+ // No longer pending; we're done
+ finalState = op.state;
+ break;
+ }
+ }
+ }
+ }
+
+ removeOperation(token);
+ if (op != null) {
+ mBackupHandler.removeMessages(getMessageIdForOperationType(op.type));
+ }
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "operation " + Integer.toHexString(token)
+ + " complete: finalState=" + finalState);
+ }
+ return finalState == OP_ACKNOWLEDGED;
+ }
+
+ public void handleCancel(int token, boolean cancelAll) {
+ // Notify any synchronous waiters
+ Operation op = null;
+ synchronized (mCurrentOpLock) {
+ op = mCurrentOperations.get(token);
+ if (MORE_DEBUG) {
+ if (op == null) {
+ Slog.w(TAG, "Cancel of token " + Integer.toHexString(token)
+ + " but no op found");
+ }
+ }
+ int state = (op != null) ? op.state : OP_TIMEOUT;
+ if (state == OP_ACKNOWLEDGED) {
+ // The operation finished cleanly, so we have nothing more to do.
+ if (DEBUG) {
+ Slog.w(TAG, "Operation already got an ack." +
+ "Should have been removed from mCurrentOperations.");
+ }
+ op = null;
+ mCurrentOperations.delete(token);
+ } else if (state == OP_PENDING) {
+ if (DEBUG) Slog.v(TAG, "Cancel: token=" + Integer.toHexString(token));
+ op.state = OP_TIMEOUT;
+ // Can't delete op from mCurrentOperations here. waitUntilOperationComplete may be
+ // called after we receive cancel here. We need this op's state there.
+
+ // Remove all pending timeout messages for this operation type.
+ mBackupHandler.removeMessages(getMessageIdForOperationType(op.type));
+ }
+ mCurrentOpLock.notifyAll();
+ }
+
+ // If there's a TimeoutHandler for this event, call it
+ if (op != null && op.callback != null) {
+ if (MORE_DEBUG) {
+ Slog.v(TAG, " Invoking cancel on " + op.callback);
+ }
+ op.callback.handleCancel(cancelAll);
+ }
+ }
+
+ // ----- Back up a set of applications via a worker thread -----
+
+ public boolean isBackupOperationInProgress() {
+ synchronized (mCurrentOpLock) {
+ for (int i = 0; i < mCurrentOperations.size(); i++) {
+ Operation op = mCurrentOperations.valueAt(i);
+ if (op.type == OP_TYPE_BACKUP && op.state == OP_PENDING) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+
+ @Override
+ public void tearDownAgentAndKill(ApplicationInfo app) {
+ if (app == null) {
+ // Null means the system package, so just quietly move on. :)
+ return;
+ }
+
+ try {
+ // unbind and tidy up even on timeout or failure, just in case
+ mActivityManager.unbindBackupAgent(app);
+
+ // The agent was running with a stub Application object, so shut it down.
+ // !!! We hardcode the confirmation UI's package name here rather than use a
+ // manifest flag! TODO something less direct.
+ if (app.uid >= Process.FIRST_APPLICATION_UID
+ && !app.packageName.equals("com.android.backupconfirm")) {
+ if (MORE_DEBUG) Slog.d(TAG, "Killing agent host process");
+ mActivityManager.killApplicationProcess(app.processName, app.uid);
+ } else {
+ if (MORE_DEBUG) Slog.d(TAG, "Not killing after operation: " + app.processName);
+ }
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Lost app trying to shut down");
+ }
+ }
+
+ public boolean deviceIsEncrypted() {
+ try {
+ return mStorageManager.getEncryptionState()
+ != StorageManager.ENCRYPTION_STATE_NONE
+ && mStorageManager.getPasswordType()
+ != StorageManager.CRYPT_TYPE_DEFAULT;
+ } catch (Exception e) {
+ // If we can't talk to the storagemanager service we have a serious problem; fail
+ // "secure" i.e. assuming that the device is encrypted.
+ Slog.e(TAG, "Unable to communicate with storagemanager service: " + e.getMessage());
+ return true;
+ }
+ }
+
+ // ----- Full-data backup scheduling -----
+
+ /**
+ * Schedule a job to tell us when it's a good time to run a full backup
+ */
+ public void scheduleNextFullBackupJob(long transportMinLatency) {
+ synchronized (mQueueLock) {
+ if (mFullBackupQueue.size() > 0) {
+ // schedule the next job at the point in the future when the least-recently
+ // backed up app comes due for backup again; or immediately if it's already
+ // due.
+ final long upcomingLastBackup = mFullBackupQueue.get(0).lastBackup;
+ final long timeSinceLast = System.currentTimeMillis() - upcomingLastBackup;
+ final long appLatency = (timeSinceLast < MIN_FULL_BACKUP_INTERVAL)
+ ? (MIN_FULL_BACKUP_INTERVAL - timeSinceLast) : 0;
+ final long latency = Math.max(transportMinLatency, appLatency);
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ FullBackupJob.schedule(mContext, latency);
+ }
+ };
+ mBackupHandler.postDelayed(r, 2500);
+ } else {
+ if (DEBUG_SCHEDULING) {
+ Slog.i(TAG, "Full backup queue empty; not scheduling");
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove a package from the full-data queue.
+ */
+ private void dequeueFullBackupLocked(String packageName) {
+ final int N = mFullBackupQueue.size();
+ for (int i = N - 1; i >= 0; i--) {
+ final FullBackupEntry e = mFullBackupQueue.get(i);
+ if (packageName.equals(e.packageName)) {
+ mFullBackupQueue.remove(i);
+ }
+ }
+ }
+
+ /**
+ * Enqueue full backup for the given app, with a note about when it last ran.
+ */
+ public void enqueueFullBackup(String packageName, long lastBackedUp) {
+ FullBackupEntry newEntry = new FullBackupEntry(packageName, lastBackedUp);
+ synchronized (mQueueLock) {
+ // First, sanity check that we aren't adding a duplicate. Slow but
+ // straightforward; we'll have at most on the order of a few hundred
+ // items in this list.
+ dequeueFullBackupLocked(packageName);
+
+ // This is also slow but easy for modest numbers of apps: work backwards
+ // from the end of the queue until we find an item whose last backup
+ // time was before this one, then insert this new entry after it. If we're
+ // adding something new we don't bother scanning, and just prepend.
+ int which = -1;
+ if (lastBackedUp > 0) {
+ for (which = mFullBackupQueue.size() - 1; which >= 0; which--) {
+ final FullBackupEntry entry = mFullBackupQueue.get(which);
+ if (entry.lastBackup <= lastBackedUp) {
+ mFullBackupQueue.add(which + 1, newEntry);
+ break;
+ }
+ }
+ }
+ if (which < 0) {
+ // this one is earlier than any existing one, so prepend
+ mFullBackupQueue.add(0, newEntry);
+ }
+ }
+ writeFullBackupScheduleAsync();
+ }
+
+ private boolean fullBackupAllowable(IBackupTransport transport) {
+ if (transport == null) {
+ Slog.w(TAG, "Transport not present; full data backup not performed");
+ return false;
+ }
+
+ // Don't proceed unless we have already established package metadata
+ // for the current dataset via a key/value backup pass.
+ try {
+ File stateDir = new File(mBaseStateDir, transport.transportDirName());
+ File pmState = new File(stateDir, PACKAGE_MANAGER_SENTINEL);
+ if (pmState.length() <= 0) {
+ if (DEBUG) {
+ Slog.i(TAG, "Full backup requested but dataset not yet initialized");
+ }
+ return false;
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to get transport name: " + e.getMessage());
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Conditions are right for a full backup operation, so run one. The model we use is
+ * to perform one app backup per scheduled job execution, and to reschedule the job
+ * with zero latency as long as conditions remain right and we still have work to do.
+ *
+ * <p>This is the "start a full backup operation" entry point called by the scheduled job.
+ *
+ * @return Whether ongoing work will continue. The return value here will be passed
+ * along as the return value to the scheduled job's onStartJob() callback.
+ */
+ @Override
+ public boolean beginFullBackup(FullBackupJob scheduledJob) {
+ long now = System.currentTimeMillis();
+ FullBackupEntry entry = null;
+ long latency = MIN_FULL_BACKUP_INTERVAL;
+
+ if (!mEnabled || !mProvisioned) {
+ // Backups are globally disabled, so don't proceed. We also don't reschedule
+ // the job driving automatic backups; that job will be scheduled again when
+ // the user enables backup.
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "beginFullBackup but e=" + mEnabled
+ + " p=" + mProvisioned + "; ignoring");
+ }
+ return false;
+ }
+
+ // Don't run the backup if we're in battery saver mode, but reschedule
+ // to try again in the not-so-distant future.
+ final PowerSaveState result =
+ mPowerManager.getPowerSaveState(ServiceType.FULL_BACKUP);
+ if (result.batterySaverEnabled) {
+ if (DEBUG) Slog.i(TAG, "Deferring scheduled full backups in battery saver mode");
+ FullBackupJob.schedule(mContext, KeyValueBackupJob.BATCH_INTERVAL);
+ return false;
+ }
+
+ if (DEBUG_SCHEDULING) {
+ Slog.i(TAG, "Beginning scheduled full backup operation");
+ }
+
+ // Great; we're able to run full backup jobs now. See if we have any work to do.
+ synchronized (mQueueLock) {
+ if (mRunningFullBackupTask != null) {
+ Slog.e(TAG, "Backup triggered but one already/still running!");
+ return false;
+ }
+
+ // At this point we think that we have work to do, but possibly not right now.
+ // Any exit without actually running backups will also require that we
+ // reschedule the job.
+ boolean runBackup = true;
+ boolean headBusy;
+
+ do {
+ // Recheck each time, because culling due to ineligibility may
+ // have emptied the queue.
+ if (mFullBackupQueue.size() == 0) {
+ // no work to do so just bow out
+ if (DEBUG) {
+ Slog.i(TAG, "Backup queue empty; doing nothing");
+ }
+ runBackup = false;
+ break;
+ }
+
+ headBusy = false;
+
+ if (!fullBackupAllowable(mTransportManager.getCurrentTransportBinder())) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Preconditions not met; not running full backup");
+ }
+ runBackup = false;
+ // Typically this means we haven't run a key/value backup yet. Back off
+ // full-backup operations by the key/value job's run interval so that
+ // next time we run, we are likely to be able to make progress.
+ latency = KeyValueBackupJob.BATCH_INTERVAL;
+ }
+
+ if (runBackup) {
+ entry = mFullBackupQueue.get(0);
+ long timeSinceRun = now - entry.lastBackup;
+ runBackup = (timeSinceRun >= MIN_FULL_BACKUP_INTERVAL);
+ if (!runBackup) {
+ // It's too early to back up the next thing in the queue, so bow out
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Device ready but too early to back up next app");
+ }
+ // Wait until the next app in the queue falls due for a full data backup
+ latency = MIN_FULL_BACKUP_INTERVAL - timeSinceRun;
+ break; // we know we aren't doing work yet, so bail.
+ }
+
+ try {
+ PackageInfo appInfo = mPackageManager.getPackageInfo(entry.packageName, 0);
+ if (!AppBackupUtils.appGetsFullBackup(appInfo)) {
+ // The head app isn't supposed to get full-data backups [any more];
+ // so we cull it and force a loop around to consider the new head
+ // app.
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Culling package " + entry.packageName
+ + " in full-backup queue but not eligible");
+ }
+ mFullBackupQueue.remove(0);
+ headBusy = true; // force the while() condition
+ continue;
+ }
+
+ final int privFlags = appInfo.applicationInfo.privateFlags;
+ headBusy = (privFlags & PRIVATE_FLAG_BACKUP_IN_FOREGROUND) == 0
+ && mActivityManager.isAppForeground(appInfo.applicationInfo.uid);
+
+ if (headBusy) {
+ final long nextEligible = System.currentTimeMillis()
+ + BUSY_BACKOFF_MIN_MILLIS
+ + mTokenGenerator.nextInt(BUSY_BACKOFF_FUZZ);
+ if (DEBUG_SCHEDULING) {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ Slog.i(TAG, "Full backup time but " + entry.packageName
+ + " is busy; deferring to "
+ + sdf.format(new Date(nextEligible)));
+ }
+ // This relocates the app's entry from the head of the queue to
+ // its order-appropriate position further down, so upon looping
+ // a new candidate will be considered at the head.
+ enqueueFullBackup(entry.packageName,
+ nextEligible - MIN_FULL_BACKUP_INTERVAL);
+ }
+ } catch (NameNotFoundException nnf) {
+ // So, we think we want to back this up, but it turns out the package
+ // in question is no longer installed. We want to drop it from the
+ // queue entirely and move on, but if there's nothing else in the queue
+ // we should bail entirely. headBusy cannot have been set to true yet.
+ runBackup = (mFullBackupQueue.size() > 1);
+ } catch (RemoteException e) {
+ // Cannot happen; the Activity Manager is in the same process
+ }
+ }
+ } while (headBusy);
+
+ if (!runBackup) {
+ if (DEBUG_SCHEDULING) {
+ Slog.i(TAG, "Nothing pending full backup; rescheduling +" + latency);
+ }
+ final long deferTime = latency; // pin for the closure
+ mBackupHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ FullBackupJob.schedule(mContext, deferTime);
+ }
+ });
+ return false;
+ }
+
+ // Okay, the top thing is ready for backup now. Do it.
+ mFullBackupQueue.remove(0);
+ CountDownLatch latch = new CountDownLatch(1);
+ String[] pkg = new String[]{entry.packageName};
+ mRunningFullBackupTask = new PerformFullTransportBackupTask(this, null, pkg, true,
+ scheduledJob, latch, null, null, false /* userInitiated */);
+ // Acquiring wakelock for PerformFullTransportBackupTask before its start.
+ mWakelock.acquire();
+ (new Thread(mRunningFullBackupTask)).start();
+ }
+
+ return true;
+ }
+
+ // The job scheduler says our constraints don't hold any more,
+ // so tear down any ongoing backup task right away.
+ @Override
+ public void endFullBackup() {
+ synchronized (mQueueLock) {
+ if (mRunningFullBackupTask != null) {
+ if (DEBUG_SCHEDULING) {
+ Slog.i(TAG, "Telling running backup to stop");
+ }
+ mRunningFullBackupTask.handleCancel(true);
+ }
+ }
+ }
+
+ // Used by both incremental and full restore
+ public void restoreWidgetData(String packageName, byte[] widgetData) {
+ // Apply the restored widget state and generate the ID update for the app
+ // TODO: http://b/22388012
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Incorporating restored widget data");
+ }
+ AppWidgetBackupBridge.restoreWidgetState(packageName, widgetData, UserHandle.USER_SYSTEM);
+ }
+
+ // *****************************
+ // NEW UNIFIED RESTORE IMPLEMENTATION
+ // *****************************
+
+ public void dataChangedImpl(String packageName) {
+ HashSet<String> targets = dataChangedTargets(packageName);
+ dataChangedImpl(packageName, targets);
+ }
+
+ private void dataChangedImpl(String packageName, HashSet<String> targets) {
+ // Record that we need a backup pass for the caller. Since multiple callers
+ // may share a uid, we need to note all candidates within that uid and schedule
+ // a backup pass for each of them.
+ if (targets == null) {
+ Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'"
+ + " uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ synchronized (mQueueLock) {
+ // Note that this client has made data changes that need to be backed up
+ if (targets.contains(packageName)) {
+ // Add the caller to the set of pending backups. If there is
+ // one already there, then overwrite it, but no harm done.
+ BackupRequest req = new BackupRequest(packageName);
+ if (mPendingBackups.put(packageName, req) == null) {
+ if (MORE_DEBUG) Slog.d(TAG, "Now staging backup of " + packageName);
+
+ // Journal this request in case of crash. The put()
+ // operation returned null when this package was not already
+ // in the set; we want to avoid touching the disk redundantly.
+ writeToJournalLocked(packageName);
+ }
+ }
+ }
+
+ // ...and schedule a backup pass if necessary
+ KeyValueBackupJob.schedule(mContext);
+ }
+
+ // Note: packageName is currently unused, but may be in the future
+ private HashSet<String> dataChangedTargets(String packageName) {
+ // If the caller does not hold the BACKUP permission, it can only request a
+ // backup of its own data.
+ if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(),
+ Binder.getCallingUid())) == PackageManager.PERMISSION_DENIED) {
+ synchronized (mBackupParticipants) {
+ return mBackupParticipants.get(Binder.getCallingUid());
+ }
+ }
+
+ // a caller with full permission can ask to back up any participating app
+ HashSet<String> targets = new HashSet<>();
+ if (PACKAGE_MANAGER_SENTINEL.equals(packageName)) {
+ targets.add(PACKAGE_MANAGER_SENTINEL);
+ } else {
+ synchronized (mBackupParticipants) {
+ int N = mBackupParticipants.size();
+ for (int i = 0; i < N; i++) {
+ HashSet<String> s = mBackupParticipants.valueAt(i);
+ if (s != null) {
+ targets.addAll(s);
+ }
+ }
+ }
+ }
+ return targets;
+ }
+
+ private void writeToJournalLocked(String str) {
+ RandomAccessFile out = null;
+ try {
+ if (mJournal == null) mJournal = File.createTempFile("journal", null, mJournalDir);
+ out = new RandomAccessFile(mJournal, "rws");
+ out.seek(out.length());
+ out.writeUTF(str);
+ } catch (IOException e) {
+ Slog.e(TAG, "Can't write " + str + " to backup journal", e);
+ mJournal = null;
+ } finally {
+ try {
+ if (out != null) out.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ // ----- IBackupManager binder interface -----
+
+ @Override
+ public void dataChanged(final String packageName) {
+ final int callingUserHandle = UserHandle.getCallingUserId();
+ if (callingUserHandle != UserHandle.USER_SYSTEM) {
+ // TODO: http://b/22388012
+ // App is running under a non-owner user profile. For now, we do not back
+ // up data from secondary user profiles.
+ // TODO: backups for all user profiles although don't add backup for profiles
+ // without adding admin control in DevicePolicyManager.
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "dataChanged(" + packageName + ") ignored because it's user "
+ + callingUserHandle);
+ }
+ return;
+ }
+
+ final HashSet<String> targets = dataChangedTargets(packageName);
+ if (targets == null) {
+ Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'"
+ + " uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ mBackupHandler.post(new Runnable() {
+ public void run() {
+ dataChangedImpl(packageName, targets);
+ }
+ });
+ }
+
+ // Clear the given package's backup data from the current transport
+ @Override
+ public void clearBackupData(String transportName, String packageName) {
+ if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName + " on " + transportName);
+ PackageInfo info;
+ try {
+ info = mPackageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+ } catch (NameNotFoundException e) {
+ Slog.d(TAG, "No such package '" + packageName + "' - not clearing backup data");
+ return;
+ }
+
+ // If the caller does not hold the BACKUP permission, it can only request a
+ // wipe of its own backed-up data.
+ HashSet<String> apps;
+ if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(),
+ Binder.getCallingUid())) == PackageManager.PERMISSION_DENIED) {
+ apps = mBackupParticipants.get(Binder.getCallingUid());
+ } else {
+ // a caller with full permission can ask to back up any participating app
+ // !!! TODO: allow data-clear of ANY app?
+ if (MORE_DEBUG) Slog.v(TAG, "Privileged caller, allowing clear of other apps");
+ apps = new HashSet<>();
+ int N = mBackupParticipants.size();
+ for (int i = 0; i < N; i++) {
+ HashSet<String> s = mBackupParticipants.valueAt(i);
+ if (s != null) {
+ apps.addAll(s);
+ }
+ }
+ }
+
+ // Is the given app an available participant?
+ if (apps.contains(packageName)) {
+ // found it; fire off the clear request
+ if (MORE_DEBUG) Slog.v(TAG, "Found the app - running clear process");
+ mBackupHandler.removeMessages(MSG_RETRY_CLEAR);
+ synchronized (mQueueLock) {
+ final IBackupTransport transport =
+ mTransportManager.getTransportBinder(transportName);
+ if (transport == null) {
+ // transport is currently unavailable -- make sure to retry
+ Message msg = mBackupHandler.obtainMessage(MSG_RETRY_CLEAR,
+ new ClearRetryParams(transportName, packageName));
+ mBackupHandler.sendMessageDelayed(msg, TRANSPORT_RETRY_INTERVAL);
+ return;
+ }
+ long oldId = Binder.clearCallingIdentity();
+ mWakelock.acquire();
+ Message msg = mBackupHandler.obtainMessage(MSG_RUN_CLEAR,
+ new ClearParams(transport, info));
+ mBackupHandler.sendMessage(msg);
+ Binder.restoreCallingIdentity(oldId);
+ }
+ }
+ }
+
+ // Run a backup pass immediately for any applications that have declared
+ // that they have pending updates.
+ @Override
+ public void backupNow() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "backupNow");
+
+ final PowerSaveState result =
+ mPowerManager.getPowerSaveState(ServiceType.KEYVALUE_BACKUP);
+ if (result.batterySaverEnabled) {
+ if (DEBUG) Slog.v(TAG, "Not running backup while in battery save mode");
+ KeyValueBackupJob.schedule(mContext); // try again in several hours
+ } else {
+ if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
+ synchronized (mQueueLock) {
+ // Fire the intent that kicks off the whole shebang...
+ try {
+ mRunBackupIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ // should never happen
+ Slog.e(TAG, "run-backup intent cancelled!");
+ }
+
+ // ...and cancel any pending scheduled job, because we've just superseded it
+ KeyValueBackupJob.cancel(mContext);
+ }
+ }
+ }
+
+ public boolean deviceIsProvisioned() {
+ final ContentResolver resolver = mContext.getContentResolver();
+ return (Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0);
+ }
+
+ // Run a backup pass for the given packages, writing the resulting data stream
+ // to the supplied file descriptor. This method is synchronous and does not return
+ // to the caller until the backup has been completed.
+ //
+ // This is the variant used by 'adb backup'; it requires on-screen confirmation
+ // by the user because it can be used to offload data over untrusted USB.
+ @Override
+ public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
+ boolean includeShared, boolean doWidgets, boolean doAllApps, boolean includeSystem,
+ boolean compress, boolean doKeyValue, String[] pkgList) {
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbBackup");
+
+ final int callingUserHandle = UserHandle.getCallingUserId();
+ // TODO: http://b/22388012
+ if (callingUserHandle != UserHandle.USER_SYSTEM) {
+ throw new IllegalStateException("Backup supported only for the device owner");
+ }
+
+ // Validate
+ if (!doAllApps) {
+ if (!includeShared) {
+ // If we're backing up shared data (sdcard or equivalent), then we can run
+ // without any supplied app names. Otherwise, we'd be doing no work, so
+ // report the error.
+ if (pkgList == null || pkgList.length == 0) {
+ throw new IllegalArgumentException(
+ "Backup requested but neither shared nor any apps named");
+ }
+ }
+ }
+
+ long oldId = Binder.clearCallingIdentity();
+ try {
+ // Doesn't make sense to do a full backup prior to setup
+ if (!deviceIsProvisioned()) {
+ Slog.i(TAG, "Backup not supported before setup");
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.v(TAG, "Requesting backup: apks=" + includeApks + " obb=" + includeObbs
+ + " shared=" + includeShared + " all=" + doAllApps + " system="
+ + includeSystem + " includekeyvalue=" + doKeyValue + " pkgs=" + pkgList);
+ }
+ Slog.i(TAG, "Beginning adb backup...");
+
+ AdbBackupParams params = new AdbBackupParams(fd, includeApks, includeObbs,
+ includeShared, doWidgets, doAllApps, includeSystem, compress, doKeyValue,
+ pkgList);
+ final int token = generateRandomIntegerToken();
+ synchronized (mAdbBackupRestoreConfirmations) {
+ mAdbBackupRestoreConfirmations.put(token, params);
+ }
+
+ // start up the confirmation UI
+ if (DEBUG) Slog.d(TAG, "Starting backup confirmation UI, token=" + token);
+ if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) {
+ Slog.e(TAG, "Unable to launch backup confirmation UI");
+ mAdbBackupRestoreConfirmations.delete(token);
+ return;
+ }
+
+ // make sure the screen is lit for the user interaction
+ mPowerManager.userActivity(SystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_OTHER,
+ 0);
+
+ // start the confirmation countdown
+ startConfirmationTimeout(token, params);
+
+ // wait for the backup to be performed
+ if (DEBUG) Slog.d(TAG, "Waiting for backup completion...");
+ waitForCompletion(params);
+ } finally {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ // just eat it
+ }
+ Binder.restoreCallingIdentity(oldId);
+ Slog.d(TAG, "Adb backup processing complete.");
+ }
+ }
+
+ @Override
+ public void fullTransportBackup(String[] pkgNames) {
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP,
+ "fullTransportBackup");
+
+ final int callingUserHandle = UserHandle.getCallingUserId();
+ // TODO: http://b/22388012
+ if (callingUserHandle != UserHandle.USER_SYSTEM) {
+ throw new IllegalStateException("Restore supported only for the device owner");
+ }
+
+ if (!fullBackupAllowable(mTransportManager.getCurrentTransportBinder())) {
+ Slog.i(TAG, "Full backup not currently possible -- key/value backup not yet run?");
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "fullTransportBackup()");
+ }
+
+ final long oldId = Binder.clearCallingIdentity();
+ try {
+ CountDownLatch latch = new CountDownLatch(1);
+ PerformFullTransportBackupTask task = new PerformFullTransportBackupTask(this, null,
+ pkgNames, false, null, latch, null, null, false /* userInitiated */);
+ // Acquiring wakelock for PerformFullTransportBackupTask before its start.
+ mWakelock.acquire();
+ (new Thread(task, "full-transport-master")).start();
+ do {
+ try {
+ latch.await();
+ break;
+ } catch (InterruptedException e) {
+ // Just go back to waiting for the latch to indicate completion
+ }
+ } while (true);
+
+ // We just ran a backup on these packages, so kick them to the end of the queue
+ final long now = System.currentTimeMillis();
+ for (String pkg : pkgNames) {
+ enqueueFullBackup(pkg, now);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Done with full transport backup.");
+ }
+ }
+
+ @Override
+ public void adbRestore(ParcelFileDescriptor fd) {
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbRestore");
+
+ final int callingUserHandle = UserHandle.getCallingUserId();
+ // TODO: http://b/22388012
+ if (callingUserHandle != UserHandle.USER_SYSTEM) {
+ throw new IllegalStateException("Restore supported only for the device owner");
+ }
+
+ long oldId = Binder.clearCallingIdentity();
+
+ try {
+ // Check whether the device has been provisioned -- we don't handle
+ // full restores prior to completing the setup process.
+ if (!deviceIsProvisioned()) {
+ Slog.i(TAG, "Full restore not permitted before setup");
+ return;
+ }
+
+ Slog.i(TAG, "Beginning restore...");
+
+ AdbRestoreParams params = new AdbRestoreParams(fd);
+ final int token = generateRandomIntegerToken();
+ synchronized (mAdbBackupRestoreConfirmations) {
+ mAdbBackupRestoreConfirmations.put(token, params);
+ }
+
+ // start up the confirmation UI
+ if (DEBUG) Slog.d(TAG, "Starting restore confirmation UI, token=" + token);
+ if (!startConfirmationUi(token, FullBackup.FULL_RESTORE_INTENT_ACTION)) {
+ Slog.e(TAG, "Unable to launch restore confirmation");
+ mAdbBackupRestoreConfirmations.delete(token);
+ return;
+ }
+
+ // make sure the screen is lit for the user interaction
+ mPowerManager.userActivity(SystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_OTHER,
+ 0);
+
+ // start the confirmation countdown
+ startConfirmationTimeout(token, params);
+
+ // wait for the restore to be performed
+ if (DEBUG) Slog.d(TAG, "Waiting for restore completion...");
+ waitForCompletion(params);
+ } finally {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Error trying to close fd after adb restore: " + e);
+ }
+ Binder.restoreCallingIdentity(oldId);
+ Slog.i(TAG, "adb restore processing complete.");
+ }
+ }
+
+ private boolean startConfirmationUi(int token, String action) {
+ try {
+ Intent confIntent = new Intent(action);
+ confIntent.setClassName("com.android.backupconfirm",
+ "com.android.backupconfirm.BackupRestoreConfirmation");
+ confIntent.putExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, token);
+ confIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivityAsUser(confIntent, UserHandle.SYSTEM);
+ } catch (ActivityNotFoundException e) {
+ return false;
+ }
+ return true;
+ }
+
+ private void startConfirmationTimeout(int token, AdbParams params) {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Posting conf timeout msg after "
+ + TIMEOUT_FULL_CONFIRMATION + " millis");
+ }
+ Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT,
+ token, 0, params);
+ mBackupHandler.sendMessageDelayed(msg, TIMEOUT_FULL_CONFIRMATION);
+ }
+
+ private void waitForCompletion(AdbParams params) {
+ synchronized (params.latch) {
+ while (params.latch.get() == false) {
+ try {
+ params.latch.wait();
+ } catch (InterruptedException e) { /* never interrupted */ }
+ }
+ }
+ }
+
+ public void signalAdbBackupRestoreCompletion(AdbParams params) {
+ synchronized (params.latch) {
+ params.latch.set(true);
+ params.latch.notifyAll();
+ }
+ }
+
+ // Confirm that the previously-requested full backup/restore operation can proceed. This
+ // is used to require a user-facing disclosure about the operation.
+ @Override
+ public void acknowledgeAdbBackupOrRestore(int token, boolean allow,
+ String curPassword, String encPpassword, IFullBackupRestoreObserver observer) {
+ if (DEBUG) {
+ Slog.d(TAG, "acknowledgeAdbBackupOrRestore : token=" + token
+ + " allow=" + allow);
+ }
+
+ // TODO: possibly require not just this signature-only permission, but even
+ // require that the specific designated confirmation-UI app uid is the caller?
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP,
+ "acknowledgeAdbBackupOrRestore");
+
+ long oldId = Binder.clearCallingIdentity();
+ try {
+
+ AdbParams params;
+ synchronized (mAdbBackupRestoreConfirmations) {
+ params = mAdbBackupRestoreConfirmations.get(token);
+ if (params != null) {
+ mBackupHandler.removeMessages(MSG_FULL_CONFIRMATION_TIMEOUT, params);
+ mAdbBackupRestoreConfirmations.delete(token);
+
+ if (allow) {
+ final int verb = params instanceof AdbBackupParams
+ ? MSG_RUN_ADB_BACKUP
+ : MSG_RUN_ADB_RESTORE;
+
+ params.observer = observer;
+ params.curPassword = curPassword;
+
+ params.encryptPassword = encPpassword;
+
+ if (MORE_DEBUG) Slog.d(TAG, "Sending conf message with verb " + verb);
+ mWakelock.acquire();
+ Message msg = mBackupHandler.obtainMessage(verb, params);
+ mBackupHandler.sendMessage(msg);
+ } else {
+ Slog.w(TAG, "User rejected full backup/restore operation");
+ // indicate completion without having actually transferred any data
+ signalAdbBackupRestoreCompletion(params);
+ }
+ } else {
+ Slog.w(TAG, "Attempted to ack full backup/restore with invalid token");
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
+ }
+
+ private static boolean backupSettingMigrated(int userId) {
+ File base = new File(Environment.getDataDirectory(), "backup");
+ File enableFile = new File(base, BACKUP_ENABLE_FILE);
+ return enableFile.exists();
+ }
+
+ private static boolean readBackupEnableState(int userId) {
+ File base = new File(Environment.getDataDirectory(), "backup");
+ File enableFile = new File(base, BACKUP_ENABLE_FILE);
+ if (enableFile.exists()) {
+ try (FileInputStream fin = new FileInputStream(enableFile)) {
+ int state = fin.read();
+ return state != 0;
+ } catch (IOException e) {
+ // can't read the file; fall through to assume disabled
+ Slog.e(TAG, "Cannot read enable state; assuming disabled");
+ }
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "isBackupEnabled() => false due to absent settings file");
+ }
+ }
+ return false;
+ }
+
+ private static void writeBackupEnableState(boolean enable, int userId) {
+ File base = new File(Environment.getDataDirectory(), "backup");
+ File enableFile = new File(base, BACKUP_ENABLE_FILE);
+ File stage = new File(base, BACKUP_ENABLE_FILE + "-stage");
+ FileOutputStream fout = null;
+ try {
+ fout = new FileOutputStream(stage);
+ fout.write(enable ? 1 : 0);
+ fout.close();
+ stage.renameTo(enableFile);
+ // will be synced immediately by the try-with-resources call to close()
+ } catch (IOException | RuntimeException e) {
+ // Whoops; looks like we're doomed. Roll everything out, disabled,
+ // including the legacy state.
+ Slog.e(TAG, "Unable to record backup enable state; reverting to disabled: "
+ + e.getMessage());
+
+ final ContentResolver r = sInstance.mContext.getContentResolver();
+ Settings.Secure.putStringForUser(r,
+ Settings.Secure.BACKUP_ENABLED, null, userId);
+ enableFile.delete();
+ stage.delete();
+ } finally {
+ IoUtils.closeQuietly(fout);
+ }
+ }
+
+ // Enable/disable backups
+ @Override
+ public void setBackupEnabled(boolean enable) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "setBackupEnabled");
+
+ Slog.i(TAG, "Backup enabled => " + enable);
+
+ long oldId = Binder.clearCallingIdentity();
+ try {
+ boolean wasEnabled = mEnabled;
+ synchronized (this) {
+ writeBackupEnableState(enable, UserHandle.USER_SYSTEM);
+ mEnabled = enable;
+ }
+
+ synchronized (mQueueLock) {
+ if (enable && !wasEnabled && mProvisioned) {
+ // if we've just been enabled, start scheduling backup passes
+ KeyValueBackupJob.schedule(mContext);
+ scheduleNextFullBackupJob(0);
+ } else if (!enable) {
+ // No longer enabled, so stop running backups
+ if (MORE_DEBUG) Slog.i(TAG, "Opting out of backup");
+
+ KeyValueBackupJob.cancel(mContext);
+
+ // This also constitutes an opt-out, so we wipe any data for
+ // this device from the backend. We start that process with
+ // an alarm in order to guarantee wakelock states.
+ if (wasEnabled && mProvisioned) {
+ // NOTE: we currently flush every registered transport, not just
+ // the currently-active one.
+ String[] allTransports = mTransportManager.getBoundTransportNames();
+ // build the set of transports for which we are posting an init
+ for (String transport : allTransports) {
+ recordInitPendingLocked(true, transport);
+ }
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
+ mRunInitIntent);
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
+ }
+
+ // Enable/disable automatic restore of app data at install time
+ @Override
+ public void setAutoRestore(boolean doAutoRestore) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "setAutoRestore");
+
+ Slog.i(TAG, "Auto restore => " + doAutoRestore);
+
+ final long oldId = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.BACKUP_AUTO_RESTORE, doAutoRestore ? 1 : 0);
+ mAutoRestore = doAutoRestore;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
+ }
+
+ // Mark the backup service as having been provisioned
+ @Override
+ public void setBackupProvisioned(boolean available) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "setBackupProvisioned");
+ /*
+ * This is now a no-op; provisioning is simply the device's own setup state.
+ */
+ }
+
+ // Report whether the backup mechanism is currently enabled
+ @Override
+ public boolean isBackupEnabled() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "isBackupEnabled");
+ return mEnabled; // no need to synchronize just to read it
+ }
+
+ // Report the name of the currently active transport
+ @Override
+ public String getCurrentTransport() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "getCurrentTransport");
+ String currentTransport = mTransportManager.getCurrentTransportName();
+ if (MORE_DEBUG) Slog.v(TAG, "... getCurrentTransport() returning " + currentTransport);
+ return currentTransport;
+ }
+
+ // Report all known, available backup transports
+ @Override
+ public String[] listAllTransports() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "listAllTransports");
+
+ return mTransportManager.getBoundTransportNames();
+ }
+
+ @Override
+ public ComponentName[] listAllTransportComponents() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "listAllTransportComponents");
+ return mTransportManager.getAllTransportCompenents();
+ }
+
+ @Override
+ public String[] getTransportWhitelist() {
+ // No permission check, intentionally.
+ Set<ComponentName> whitelistedComponents = mTransportManager.getTransportWhitelist();
+ String[] whitelistedTransports = new String[whitelistedComponents.size()];
+ int i = 0;
+ for (ComponentName component : whitelistedComponents) {
+ whitelistedTransports[i] = component.flattenToShortString();
+ i++;
+ }
+ return whitelistedTransports;
+ }
+
+ // Select which transport to use for the next backup operation.
+ @Override
+ public String selectBackupTransport(String transport) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "selectBackupTransport");
+
+ final long oldId = Binder.clearCallingIdentity();
+ try {
+ String prevTransport = mTransportManager.selectTransport(transport);
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.BACKUP_TRANSPORT, transport);
+ Slog.v(TAG, "selectBackupTransport() set " + mTransportManager.getCurrentTransportName()
+ + " returning " + prevTransport);
+ return prevTransport;
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
+ }
+
+ @Override
+ public void selectBackupTransportAsync(final ComponentName transport,
+ final ISelectBackupTransportCallback listener) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "selectBackupTransportAsync");
+
+ final long oldId = Binder.clearCallingIdentity();
+
+ Slog.v(TAG, "selectBackupTransportAsync() called with transport " +
+ transport.flattenToShortString());
+
+ mTransportManager.ensureTransportReady(transport, new SelectBackupTransportCallback() {
+ @Override
+ public void onSuccess(String transportName) {
+ mTransportManager.selectTransport(transportName);
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.BACKUP_TRANSPORT,
+ mTransportManager.getCurrentTransportName());
+ Slog.v(TAG, "Transport successfully selected: " + transport.flattenToShortString());
+ try {
+ listener.onSuccess(transportName);
+ } catch (RemoteException e) {
+ // Nothing to do here.
+ }
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ Slog.v(TAG, "Failed to select transport: " + transport.flattenToShortString());
+ try {
+ listener.onFailure(reason);
+ } catch (RemoteException e) {
+ // Nothing to do here.
+ }
+ }
+ });
+
+ Binder.restoreCallingIdentity(oldId);
+ }
+
+ // Supply the configuration Intent for the given transport. If the name is not one
+ // of the available transports, or if the transport does not supply any configuration
+ // UI, the method returns null.
+ @Override
+ public Intent getConfigurationIntent(String transportName) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "getConfigurationIntent");
+
+ final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
+ if (transport != null) {
+ try {
+ final Intent intent = transport.configurationIntent();
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "getConfigurationIntent() returning config intent "
+ + intent);
+ }
+ return intent;
+ } catch (Exception e) {
+ /* fall through to return null */
+ Slog.e(TAG, "Unable to get configuration intent from transport: " + e.getMessage());
+ }
+ }
+
+ return null;
+ }
+
+ // Supply the configuration summary string for the given transport. If the name is
+ // not one of the available transports, or if the transport does not supply any
+ // summary / destination string, the method can return null.
+ //
+ // This string is used VERBATIM as the summary text of the relevant Settings item!
+ @Override
+ public String getDestinationString(String transportName) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "getDestinationString");
+
+ final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
+ if (transport != null) {
+ try {
+ final String text = transport.currentDestinationString();
+ if (MORE_DEBUG) Slog.d(TAG, "getDestinationString() returning " + text);
+ return text;
+ } catch (Exception e) {
+ /* fall through to return null */
+ Slog.e(TAG, "Unable to get string from transport: " + e.getMessage());
+ }
+ }
+
+ return null;
+ }
+
+ // Supply the manage-data intent for the given transport.
+ @Override
+ public Intent getDataManagementIntent(String transportName) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "getDataManagementIntent");
+
+ final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
+ if (transport != null) {
+ try {
+ final Intent intent = transport.dataManagementIntent();
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "getDataManagementIntent() returning intent "
+ + intent);
+ }
+ return intent;
+ } catch (Exception e) {
+ /* fall through to return null */
+ Slog.e(TAG, "Unable to get management intent from transport: " + e.getMessage());
+ }
+ }
+
+ return null;
+ }
+
+ // Supply the menu label for affordances that fire the manage-data intent
+ // for the given transport.
+ @Override
+ public String getDataManagementLabel(String transportName) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "getDataManagementLabel");
+
+ final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
+ if (transport != null) {
+ try {
+ final String text = transport.dataManagementLabel();
+ if (MORE_DEBUG) Slog.d(TAG, "getDataManagementLabel() returning " + text);
+ return text;
+ } catch (Exception e) {
+ /* fall through to return null */
+ Slog.e(TAG, "Unable to get management label from transport: " + e.getMessage());
+ }
+ }
+
+ return null;
+ }
+
+ // Callback: a requested backup agent has been instantiated. This should only
+ // be called from the Activity Manager.
+ @Override
+ public void agentConnected(String packageName, IBinder agentBinder) {
+ synchronized (mAgentConnectLock) {
+ if (Binder.getCallingUid() == Process.SYSTEM_UID) {
+ Slog.d(TAG, "agentConnected pkg=" + packageName + " agent=" + agentBinder);
+ IBackupAgent agent = IBackupAgent.Stub.asInterface(agentBinder);
+ mConnectedAgent = agent;
+ mConnecting = false;
+ } else {
+ Slog.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
+ + " claiming agent connected");
+ }
+ mAgentConnectLock.notifyAll();
+ }
+ }
+
+ // Callback: a backup agent has failed to come up, or has unexpectedly quit.
+ // If the agent failed to come up in the first place, the agentBinder argument
+ // will be null. This should only be called from the Activity Manager.
+ @Override
+ public void agentDisconnected(String packageName) {
+ // TODO: handle backup being interrupted
+ synchronized (mAgentConnectLock) {
+ if (Binder.getCallingUid() == Process.SYSTEM_UID) {
+ mConnectedAgent = null;
+ mConnecting = false;
+ } else {
+ Slog.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
+ + " claiming agent disconnected");
+ }
+ mAgentConnectLock.notifyAll();
+ }
+ }
+
+ // An application being installed will need a restore pass, then the Package Manager
+ // will need to be told when the restore is finished.
+ @Override
+ public void restoreAtInstall(String packageName, int token) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ Slog.w(TAG, "Non-system process uid=" + Binder.getCallingUid()
+ + " attemping install-time restore");
+ return;
+ }
+
+ boolean skip = false;
+
+ long restoreSet = getAvailableRestoreToken(packageName);
+ if (DEBUG) {
+ Slog.v(TAG, "restoreAtInstall pkg=" + packageName
+ + " token=" + Integer.toHexString(token)
+ + " restoreSet=" + Long.toHexString(restoreSet));
+ }
+ if (restoreSet == 0) {
+ if (MORE_DEBUG) Slog.i(TAG, "No restore set");
+ skip = true;
+ }
+
+ // Do we have a transport to fetch data for us?
+ IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
+ if (transport == null) {
+ if (DEBUG) Slog.w(TAG, "No transport");
+ skip = true;
+ }
+
+ if (!mAutoRestore) {
+ if (DEBUG) {
+ Slog.w(TAG, "Non-restorable state: auto=" + mAutoRestore);
+ }
+ skip = true;
+ }
+
+ if (!skip) {
+ try {
+ // okay, we're going to attempt a restore of this package from this restore set.
+ // The eventual message back into the Package Manager to run the post-install
+ // steps for 'token' will be issued from the restore handling code.
+
+ // This can throw and so *must* happen before the wakelock is acquired
+ String dirName = transport.transportDirName();
+
+ mWakelock.acquire();
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Restore at install of " + packageName);
+ }
+ Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
+ msg.obj = new RestoreParams(transport, dirName, null, null,
+ restoreSet, packageName, token);
+ mBackupHandler.sendMessage(msg);
+ } catch (Exception e) {
+ // Calling into the transport broke; back off and proceed with the installation.
+ Slog.e(TAG, "Unable to contact transport: " + e.getMessage());
+ skip = true;
+ }
+ }
+
+ if (skip) {
+ // Auto-restore disabled or no way to attempt a restore; just tell the Package
+ // Manager to proceed with the post-install handling for this package.
+ if (DEBUG) Slog.v(TAG, "Finishing install immediately");
+ try {
+ mPackageManagerBinder.finishPackageInstall(token, false);
+ } catch (RemoteException e) { /* can't happen */ }
+ }
+ }
+
+ // Hand off a restore session
+ @Override
+ public IRestoreSession beginRestoreSession(String packageName, String transport) {
+ if (DEBUG) {
+ Slog.v(TAG, "beginRestoreSession: pkg=" + packageName
+ + " transport=" + transport);
+ }
+
+ boolean needPermission = true;
+ if (transport == null) {
+ transport = mTransportManager.getCurrentTransportName();
+
+ if (packageName != null) {
+ PackageInfo app = null;
+ try {
+ app = mPackageManager.getPackageInfo(packageName, 0);
+ } catch (NameNotFoundException nnf) {
+ Slog.w(TAG, "Asked to restore nonexistent pkg " + packageName);
+ throw new IllegalArgumentException("Package " + packageName + " not found");
+ }
+
+ if (app.applicationInfo.uid == Binder.getCallingUid()) {
+ // So: using the current active transport, and the caller has asked
+ // that its own package will be restored. In this narrow use case
+ // we do not require the caller to hold the permission.
+ needPermission = false;
+ }
+ }
+ }
+
+ if (needPermission) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "beginRestoreSession");
+ } else {
+ if (DEBUG) Slog.d(TAG, "restoring self on current transport; no permission needed");
+ }
+
+ synchronized (this) {
+ if (mActiveRestoreSession != null) {
+ Slog.i(TAG, "Restore session requested but one already active");
+ return null;
+ }
+ if (mBackupRunning) {
+ Slog.i(TAG, "Restore session requested but currently running backups");
+ return null;
+ }
+ mActiveRestoreSession = new ActiveRestoreSession(this, packageName, transport);
+ mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_SESSION_TIMEOUT,
+ TIMEOUT_RESTORE_INTERVAL);
+ }
+ return mActiveRestoreSession;
+ }
+
+ public void clearRestoreSession(ActiveRestoreSession currentSession) {
+ synchronized (this) {
+ if (currentSession != mActiveRestoreSession) {
+ Slog.e(TAG, "ending non-current restore session");
+ } else {
+ if (DEBUG) Slog.v(TAG, "Clearing restore session and halting timeout");
+ mActiveRestoreSession = null;
+ mBackupHandler.removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
+ }
+ }
+ }
+
+ // Note that a currently-active backup agent has notified us that it has
+ // completed the given outstanding asynchronous backup/restore operation.
+ @Override
+ public void opComplete(int token, long result) {
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "opComplete: " + Integer.toHexString(token) + " result=" + result);
+ }
+ Operation op = null;
+ synchronized (mCurrentOpLock) {
+ op = mCurrentOperations.get(token);
+ if (op != null) {
+ if (op.state == OP_TIMEOUT) {
+ // The operation already timed out, and this is a late response. Tidy up
+ // and ignore it; we've already dealt with the timeout.
+ op = null;
+ mCurrentOperations.delete(token);
+ } else if (op.state == OP_ACKNOWLEDGED) {
+ if (DEBUG) {
+ Slog.w(TAG, "Received duplicate ack for token=" +
+ Integer.toHexString(token));
+ }
+ op = null;
+ mCurrentOperations.remove(token);
+ } else if (op.state == OP_PENDING) {
+ // Can't delete op from mCurrentOperations. waitUntilOperationComplete can be
+ // called after we we receive this call.
+ op.state = OP_ACKNOWLEDGED;
+ }
+ }
+ mCurrentOpLock.notifyAll();
+ }
+
+ // The completion callback, if any, is invoked on the handler
+ if (op != null && op.callback != null) {
+ Pair<BackupRestoreTask, Long> callbackAndResult = Pair.create(op.callback, result);
+ Message msg = mBackupHandler.obtainMessage(MSG_OP_COMPLETE, callbackAndResult);
+ mBackupHandler.sendMessage(msg);
+ }
+ }
+
+ // We also avoid backups of 'disabled' apps
+ private static boolean appIsDisabled(ApplicationInfo app, PackageManager pm) {
+ switch (pm.getApplicationEnabledSetting(app.packageName)) {
+ case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
+ case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
+ case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isAppEligibleForBackup(String packageName) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "isAppEligibleForBackup");
+ try {
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_SIGNATURES);
+ if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo) ||
+ AppBackupUtils.appIsStopped(packageInfo.applicationInfo) ||
+ appIsDisabled(packageInfo.applicationInfo, mPackageManager)) {
+ return false;
+ }
+ IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
+ if (transport != null) {
+ try {
+ return transport.isAppEligibleForBackup(packageInfo,
+ AppBackupUtils.appGetsFullBackup(packageInfo));
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to ask about eligibility: " + e.getMessage());
+ }
+ }
+ // If transport is not present we couldn't tell that the package is not eligible.
+ return true;
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
+
+ long identityToken = Binder.clearCallingIdentity();
+ try {
+ if (args != null) {
+ for (String arg : args) {
+ if ("-h".equals(arg)) {
+ pw.println("'dumpsys backup' optional arguments:");
+ pw.println(" -h : this help text");
+ pw.println(" a[gents] : dump information about defined backup agents");
+ return;
+ } else if ("agents".startsWith(arg)) {
+ dumpAgents(pw);
+ return;
+ }
+ }
+ }
+ dumpInternal(pw);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ }
+
+ private void dumpAgents(PrintWriter pw) {
+ List<PackageInfo> agentPackages = allAgentPackages();
+ pw.println("Defined backup agents:");
+ for (PackageInfo pkg : agentPackages) {
+ pw.print(" ");
+ pw.print(pkg.packageName);
+ pw.println(':');
+ pw.print(" ");
+ pw.println(pkg.applicationInfo.backupAgentName);
+ }
+ }
+
+ private void dumpInternal(PrintWriter pw) {
+ synchronized (mQueueLock) {
+ pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled")
+ + " / " + (!mProvisioned ? "not " : "") + "provisioned / "
+ + (this.mPendingInits.size() == 0 ? "not " : "") + "pending init");
+ pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled"));
+ if (mBackupRunning) pw.println("Backup currently running");
+ pw.println("Last backup pass started: " + mLastBackupPass
+ + " (now = " + System.currentTimeMillis() + ')');
+ pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled());
+
+ pw.println("Transport whitelist:");
+ for (ComponentName transport : mTransportManager.getTransportWhitelist()) {
+ pw.print(" ");
+ pw.println(transport.flattenToShortString());
+ }
+
+ pw.println("Available transports:");
+ final String[] transports = listAllTransports();
+ if (transports != null) {
+ for (String t : listAllTransports()) {
+ pw.println((t.equals(mTransportManager.getCurrentTransportName()) ? " * "
+ : " ") + t);
+ try {
+ IBackupTransport transport = mTransportManager.getTransportBinder(t);
+ File dir = new File(mBaseStateDir, transport.transportDirName());
+ pw.println(" destination: " + transport.currentDestinationString());
+ pw.println(" intent: " + transport.configurationIntent());
+ for (File f : dir.listFiles()) {
+ pw.println(
+ " " + f.getName() + " - " + f.length() + " state bytes");
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Error in transport", e);
+ pw.println(" Error: " + e);
+ }
+ }
+ }
+
+ pw.println("Pending init: " + mPendingInits.size());
+ for (String s : mPendingInits) {
+ pw.println(" " + s);
+ }
+
+ if (DEBUG_BACKUP_TRACE) {
+ synchronized (mBackupTrace) {
+ if (!mBackupTrace.isEmpty()) {
+ pw.println("Most recent backup trace:");
+ for (String s : mBackupTrace) {
+ pw.println(" " + s);
+ }
+ }
+ }
+ }
+
+ pw.print("Ancestral: ");
+ pw.println(Long.toHexString(mAncestralToken));
+ pw.print("Current: ");
+ pw.println(Long.toHexString(mCurrentToken));
+
+ int N = mBackupParticipants.size();
+ pw.println("Participants:");
+ for (int i = 0; i < N; i++) {
+ int uid = mBackupParticipants.keyAt(i);
+ pw.print(" uid: ");
+ pw.println(uid);
+ HashSet<String> participants = mBackupParticipants.valueAt(i);
+ for (String app : participants) {
+ pw.println(" " + app);
+ }
+ }
+
+ pw.println("Ancestral packages: "
+ + (mAncestralPackages == null ? "none" : mAncestralPackages.size()));
+ if (mAncestralPackages != null) {
+ for (String pkg : mAncestralPackages) {
+ pw.println(" " + pkg);
+ }
+ }
+
+ pw.println("Ever backed up: " + mEverStoredApps.size());
+ for (String pkg : mEverStoredApps) {
+ pw.println(" " + pkg);
+ }
+
+ pw.println("Pending key/value backup: " + mPendingBackups.size());
+ for (BackupRequest req : mPendingBackups.values()) {
+ pw.println(" " + req);
+ }
+
+ pw.println("Full backup queue:" + mFullBackupQueue.size());
+ for (FullBackupEntry entry : mFullBackupQueue) {
+ pw.print(" ");
+ pw.print(entry.lastBackup);
+ pw.print(" : ");
+ pw.println(entry.packageName);
+ }
+ }
+ }
+
+
+ @Override
+ public IBackupManager getBackupManagerBinder() {
+ return mBackupManagerBinder;
+ }
+
+}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index aafa88a..9e7a29e 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -34,6 +34,7 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Slog;
import com.android.internal.util.DumpUtils;
@@ -43,6 +44,22 @@
import java.io.IOException;
import java.io.PrintWriter;
+
+/**
+ * A proxy to BackupManagerService implementation.
+ *
+ * This is an external interface to the BackupManagerService which is being accessed via published
+ * binder (see BackupManagerService$Lifecycle). This lets us turn down the heavy implementation
+ * object on the fly without disturbing binders that have been cached somewhere in the system.
+ *
+ * This is where it is decided whether backup subsystem is available. It can be disabled with the
+ * following two methods:
+ *
+ * <ul>
+ * <li> Temporarily - create a file named Trampoline.BACKUP_SUPPRESS_FILENAME, or
+ * <li> Product level - set Trampoline.BACKUP_DISABLE_PROPERTY system property to true.
+ * </ul>
+ */
public class Trampoline extends IBackupManager.Stub {
static final String TAG = "BackupManagerService";
static final boolean DEBUG_TRAMPOLINE = false;
@@ -56,14 +73,49 @@
final Context mContext;
final File mSuppressFile; // existence testing & creating synchronized on 'this'
final boolean mGlobalDisable;
- volatile BackupManagerService mService;
+ volatile BackupManagerServiceInterface mService;
public Trampoline(Context context) {
mContext = context;
- File dir = new File(Environment.getDataDirectory(), "backup");
- dir.mkdirs();
- mSuppressFile = new File(dir, BACKUP_SUPPRESS_FILENAME);
- mGlobalDisable = SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false);
+ mGlobalDisable = isBackupDisabled();
+ mSuppressFile = getSuppressFile();
+ mSuppressFile.getParentFile().mkdirs();
+ }
+
+ protected BackupManagerServiceInterface createService() {
+ if (isRefactoredServiceEnabled()) {
+ Slog.i(TAG, "Instantiating RefactoredBackupManagerService");
+ return createRefactoredBackupManagerService();
+ }
+
+ Slog.i(TAG, "Instantiating BackupManagerService");
+ return createBackupManagerService();
+ }
+
+ protected boolean isBackupDisabled() {
+ return SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false);
+ }
+
+ protected boolean isRefactoredServiceEnabled() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.BACKUP_REFACTORED_SERVICE_DISABLED, 1) == 0;
+ }
+
+ protected int binderGetCallingUid() {
+ return Binder.getCallingUid();
+ }
+
+ protected File getSuppressFile() {
+ return new File(new File(Environment.getDataDirectory(), "backup"),
+ BACKUP_SUPPRESS_FILENAME);
+ }
+
+ protected BackupManagerServiceInterface createRefactoredBackupManagerService() {
+ return new RefactoredBackupManagerService(mContext, this);
+ }
+
+ protected BackupManagerServiceInterface createBackupManagerService() {
+ return new BackupManagerService(mContext, this);
}
// internal control API
@@ -79,7 +131,7 @@
synchronized (this) {
if (!mSuppressFile.exists()) {
- mService = new BackupManagerService(mContext, this);
+ mService = createService();
} else {
Slog.i(TAG, "Backup inactive in user " + whichUser);
}
@@ -89,7 +141,7 @@
public void setBackupServiceActive(final int userHandle, boolean makeActive) {
// Only the DPM should be changing the active state of backup
- final int caller = Binder.getCallingUid();
+ final int caller = binderGetCallingUid();
if (caller != Process.SYSTEM_UID
&& caller != Process.ROOT_UID) {
throw new SecurityException("No permission to configure backup activity");
@@ -106,7 +158,7 @@
Slog.i(TAG, "Making backup "
+ (makeActive ? "" : "in") + "active in user " + userHandle);
if (makeActive) {
- mService = new BackupManagerService(mContext, this);
+ mService = createService();
mSuppressFile.delete();
} else {
mService = null;
@@ -140,7 +192,7 @@
// IBackupManager binder API
@Override
public void dataChanged(String packageName) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.dataChanged(packageName);
}
@@ -149,7 +201,7 @@
@Override
public void clearBackupData(String transportName, String packageName)
throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.clearBackupData(transportName, packageName);
}
@@ -157,7 +209,7 @@
@Override
public void agentConnected(String packageName, IBinder agent) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.agentConnected(packageName, agent);
}
@@ -165,7 +217,7 @@
@Override
public void agentDisconnected(String packageName) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.agentDisconnected(packageName);
}
@@ -173,7 +225,7 @@
@Override
public void restoreAtInstall(String packageName, int token) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.restoreAtInstall(packageName, token);
}
@@ -181,7 +233,7 @@
@Override
public void setBackupEnabled(boolean isEnabled) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.setBackupEnabled(isEnabled);
}
@@ -189,7 +241,7 @@
@Override
public void setAutoRestore(boolean doAutoRestore) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.setAutoRestore(doAutoRestore);
}
@@ -197,7 +249,7 @@
@Override
public void setBackupProvisioned(boolean isProvisioned) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.setBackupProvisioned(isProvisioned);
}
@@ -205,25 +257,25 @@
@Override
public boolean isBackupEnabled() throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
return (svc != null) ? svc.isBackupEnabled() : false;
}
@Override
public boolean setBackupPassword(String currentPw, String newPw) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
return (svc != null) ? svc.setBackupPassword(currentPw, newPw) : false;
}
@Override
public boolean hasBackupPassword() throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
return (svc != null) ? svc.hasBackupPassword() : false;
}
@Override
public void backupNow() throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.backupNow();
}
@@ -234,7 +286,7 @@
boolean includeShared, boolean doWidgets, boolean allApps,
boolean allIncludesSystem, boolean doCompress, boolean doKeyValue, String[] packageNames)
throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.adbBackup(fd, includeApks, includeObbs, includeShared, doWidgets,
allApps, allIncludesSystem, doCompress, doKeyValue, packageNames);
@@ -243,7 +295,7 @@
@Override
public void fullTransportBackup(String[] packageNames) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.fullTransportBackup(packageNames);
}
@@ -251,7 +303,7 @@
@Override
public void adbRestore(ParcelFileDescriptor fd) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.adbRestore(fd);
}
@@ -261,7 +313,7 @@
public void acknowledgeFullBackupOrRestore(int token, boolean allow, String curPassword,
String encryptionPassword, IFullBackupRestoreObserver observer)
throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.acknowledgeAdbBackupOrRestore(token, allow,
curPassword, encryptionPassword, observer);
@@ -270,38 +322,38 @@
@Override
public String getCurrentTransport() throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
return (svc != null) ? svc.getCurrentTransport() : null;
}
@Override
public String[] listAllTransports() throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
return (svc != null) ? svc.listAllTransports() : null;
}
@Override
public ComponentName[] listAllTransportComponents() throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
return (svc != null) ? svc.listAllTransportComponents() : null;
}
@Override
public String[] getTransportWhitelist() {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
return (svc != null) ? svc.getTransportWhitelist() : null;
}
@Override
public String selectBackupTransport(String transport) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
return (svc != null) ? svc.selectBackupTransport(transport) : null;
}
@Override
public void selectBackupTransportAsync(ComponentName transport,
ISelectBackupTransportCallback listener) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.selectBackupTransportAsync(transport, listener);
} else {
@@ -309,7 +361,7 @@
try {
listener.onFailure(BackupManager.ERROR_BACKUP_NOT_ALLOWED);
} catch (RemoteException ex) {
- // Ignore
+ // ignore
}
}
}
@@ -317,38 +369,38 @@
@Override
public Intent getConfigurationIntent(String transport) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
return (svc != null) ? svc.getConfigurationIntent(transport) : null;
}
@Override
public String getDestinationString(String transport) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
return (svc != null) ? svc.getDestinationString(transport) : null;
}
@Override
public Intent getDataManagementIntent(String transport) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
return (svc != null) ? svc.getDataManagementIntent(transport) : null;
}
@Override
public String getDataManagementLabel(String transport) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
return (svc != null) ? svc.getDataManagementLabel(transport) : null;
}
@Override
public IRestoreSession beginRestoreSession(String packageName, String transportID)
throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
return (svc != null) ? svc.beginRestoreSession(packageName, transportID) : null;
}
@Override
public void opComplete(int token, long result) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.opComplete(token, result);
}
@@ -356,20 +408,20 @@
@Override
public long getAvailableRestoreToken(String packageName) {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
return (svc != null) ? svc.getAvailableRestoreToken(packageName) : 0;
}
@Override
public boolean isAppEligibleForBackup(String packageName) {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
return (svc != null) ? svc.isAppEligibleForBackup(packageName) : false;
}
@Override
public int requestBackup(String[] packages, IBackupObserver observer,
IBackupManagerMonitor monitor, int flags) throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc == null) {
return BackupManager.ERROR_BACKUP_NOT_ALLOWED;
}
@@ -378,7 +430,7 @@
@Override
public void cancelBackups() throws RemoteException {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.cancelBackups();
}
@@ -388,7 +440,7 @@
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.dump(fd, pw, args);
} else {
@@ -399,12 +451,12 @@
// Full backup/restore entry points - non-Binder; called directly
// by the full-backup scheduled job
/* package */ boolean beginFullBackup(FullBackupJob scheduledJob) {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
return (svc != null) ? svc.beginFullBackup(scheduledJob) : false;
}
/* package */ void endFullBackup() {
- BackupManagerService svc = mService;
+ BackupManagerServiceInterface svc = mService;
if (svc != null) {
svc.endFullBackup();
}
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index da1f32c..fb2982e 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -53,7 +53,7 @@
/**
* Handles in-memory bookkeeping of all BackupTransport objects.
*/
-class TransportManager {
+public class TransportManager {
private static final String TAG = "BackupTransportManager";
@@ -149,7 +149,7 @@
}
}
- IBackupTransport getTransportBinder(String transportName) {
+ public IBackupTransport getTransportBinder(String transportName) {
synchronized (mTransportLock) {
ComponentName component = mBoundTransports.get(transportName);
if (component == null) {
@@ -165,11 +165,11 @@
}
}
- IBackupTransport getCurrentTransportBinder() {
+ public IBackupTransport getCurrentTransportBinder() {
return getTransportBinder(mCurrentTransportName);
}
- String getTransportName(IBackupTransport binder) {
+ public String getTransportName(IBackupTransport binder) {
synchronized (mTransportLock) {
for (TransportConnection conn : mValidTransports.values()) {
if (conn.getBinder() == binder) {
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
new file mode 100644
index 0000000..3bf77e8
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.fullbackup;
+
+import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME;
+import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_METADATA_FILENAME;
+import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_METADATA_VERSION;
+import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_WIDGET_METADATA_TOKEN;
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_BACKUP_WAIT;
+import static com.android.server.backup.RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL;
+import static com.android.server.backup.RefactoredBackupManagerService
+ .TIMEOUT_SHARED_BACKUP_INTERVAL;
+
+import android.app.ApplicationThreadConstants;
+import android.app.IBackupAgent;
+import android.app.backup.BackupTransport;
+import android.app.backup.FullBackup;
+import android.app.backup.FullBackupDataOutput;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.os.Environment.UserEnvironment;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+import android.util.StringBuilderPrinter;
+
+import com.android.server.AppWidgetBackupBridge;
+import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.utils.FullBackupUtils;
+
+import java.io.BufferedOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Core logic for performing one package's full backup, gathering the tarball from the
+ * application and emitting it to the designated OutputStream.
+ */
+public class FullBackupEngine {
+
+ private RefactoredBackupManagerService backupManagerService;
+ OutputStream mOutput;
+ FullBackupPreflight mPreflightHook;
+ BackupRestoreTask mTimeoutMonitor;
+ IBackupAgent mAgent;
+ File mFilesDir;
+ File mManifestFile;
+ File mMetadataFile;
+ boolean mIncludeApks;
+ PackageInfo mPkg;
+ private final long mQuota;
+ private final int mOpToken;
+
+ class FullBackupRunner implements Runnable {
+
+ PackageInfo mPackage;
+ byte[] mWidgetData;
+ IBackupAgent mAgent;
+ ParcelFileDescriptor mPipe;
+ int mToken;
+ boolean mSendApk;
+ boolean mWriteManifest;
+
+ FullBackupRunner(PackageInfo pack, IBackupAgent agent, ParcelFileDescriptor pipe,
+ int token, boolean sendApk, boolean writeManifest, byte[] widgetData)
+ throws IOException {
+ mPackage = pack;
+ mWidgetData = widgetData;
+ mAgent = agent;
+ mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor());
+ mToken = token;
+ mSendApk = sendApk;
+ mWriteManifest = writeManifest;
+ }
+
+ @Override
+ public void run() {
+ try {
+ FullBackupDataOutput output = new FullBackupDataOutput(mPipe);
+
+ if (mWriteManifest) {
+ final boolean writeWidgetData = mWidgetData != null;
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
+ }
+ FullBackupUtils
+ .writeAppManifest(mPackage, backupManagerService.getPackageManager(),
+ mManifestFile, mSendApk,
+ writeWidgetData);
+ FullBackup.backupToTar(mPackage.packageName, null, null,
+ mFilesDir.getAbsolutePath(),
+ mManifestFile.getAbsolutePath(),
+ output);
+ mManifestFile.delete();
+
+ // We only need to write a metadata file if we have widget data to stash
+ if (writeWidgetData) {
+ writeMetadata(mPackage, mMetadataFile, mWidgetData);
+ FullBackup.backupToTar(mPackage.packageName, null, null,
+ mFilesDir.getAbsolutePath(),
+ mMetadataFile.getAbsolutePath(),
+ output);
+ mMetadataFile.delete();
+ }
+ }
+
+ if (mSendApk) {
+ writeApkToBackup(mPackage, output);
+ }
+
+ final boolean isSharedStorage =
+ mPackage.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
+ final long timeout = isSharedStorage ?
+ TIMEOUT_SHARED_BACKUP_INTERVAL :
+ TIMEOUT_FULL_BACKUP_INTERVAL;
+
+ if (DEBUG) {
+ Slog.d(TAG, "Calling doFullBackup() on " + mPackage.packageName);
+ }
+ backupManagerService
+ .prepareOperationTimeout(mToken,
+ timeout,
+ mTimeoutMonitor /* in parent class */,
+ OP_TYPE_BACKUP_WAIT);
+ mAgent.doFullBackup(mPipe, mQuota, mToken,
+ backupManagerService.getBackupManagerBinder());
+ } catch (IOException e) {
+ Slog.e(TAG, "Error running full backup for " + mPackage.packageName);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote agent vanished during full backup of " + mPackage.packageName);
+ } finally {
+ try {
+ mPipe.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ public FullBackupEngine(RefactoredBackupManagerService backupManagerService,
+ OutputStream output,
+ FullBackupPreflight preflightHook, PackageInfo pkg,
+ boolean alsoApks, BackupRestoreTask timeoutMonitor, long quota, int opToken) {
+ this.backupManagerService = backupManagerService;
+ mOutput = output;
+ mPreflightHook = preflightHook;
+ mPkg = pkg;
+ mIncludeApks = alsoApks;
+ mTimeoutMonitor = timeoutMonitor;
+ mFilesDir = new File("/data/system");
+ mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME);
+ mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME);
+ mQuota = quota;
+ mOpToken = opToken;
+ }
+
+ public int preflightCheck() throws RemoteException {
+ if (mPreflightHook == null) {
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "No preflight check");
+ }
+ return BackupTransport.TRANSPORT_OK;
+ }
+ if (initializeAgent()) {
+ int result = mPreflightHook.preflightFullBackup(mPkg, mAgent);
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "preflight returned " + result);
+ }
+ return result;
+ } else {
+ Slog.w(TAG, "Unable to bind to full agent for " + mPkg.packageName);
+ return BackupTransport.AGENT_ERROR;
+ }
+ }
+
+ public int backupOnePackage() throws RemoteException {
+ int result = BackupTransport.AGENT_ERROR;
+
+ if (initializeAgent()) {
+ ParcelFileDescriptor[] pipes = null;
+ try {
+ pipes = ParcelFileDescriptor.createPipe();
+
+ ApplicationInfo app = mPkg.applicationInfo;
+ final boolean isSharedStorage =
+ mPkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
+ final boolean sendApk = mIncludeApks
+ && !isSharedStorage
+ && ((app.privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) == 0)
+ && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
+ (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
+
+ // TODO: http://b/22388012
+ byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(mPkg.packageName,
+ UserHandle.USER_SYSTEM);
+
+ FullBackupRunner runner = new FullBackupRunner(mPkg, mAgent, pipes[1],
+ mOpToken, sendApk, !isSharedStorage, widgetBlob);
+ pipes[1].close(); // the runner has dup'd it
+ pipes[1] = null;
+ Thread t = new Thread(runner, "app-data-runner");
+ t.start();
+
+ // Now pull data from the app and stuff it into the output
+ FullBackupUtils.routeSocketDataToOutput(pipes[0], mOutput);
+
+ if (!backupManagerService.waitUntilOperationComplete(mOpToken)) {
+ Slog.e(TAG, "Full backup failed on package " + mPkg.packageName);
+ } else {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Full package backup success: " + mPkg.packageName);
+ }
+ result = BackupTransport.TRANSPORT_OK;
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Error backing up " + mPkg.packageName + ": " + e.getMessage());
+ result = BackupTransport.AGENT_ERROR;
+ } finally {
+ try {
+ // flush after every package
+ mOutput.flush();
+ if (pipes != null) {
+ if (pipes[0] != null) {
+ pipes[0].close();
+ }
+ if (pipes[1] != null) {
+ pipes[1].close();
+ }
+ }
+ } catch (IOException e) {
+ Slog.w(TAG, "Error bringing down backup stack");
+ result = BackupTransport.TRANSPORT_ERROR;
+ }
+ }
+ } else {
+ Slog.w(TAG, "Unable to bind to full agent for " + mPkg.packageName);
+ }
+ tearDown();
+ return result;
+ }
+
+ public void sendQuotaExceeded(final long backupDataBytes, final long quotaBytes) {
+ if (initializeAgent()) {
+ try {
+ mAgent.doQuotaExceeded(backupDataBytes, quotaBytes);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception while telling agent about quota exceeded");
+ }
+ }
+ }
+
+ private boolean initializeAgent() {
+ if (mAgent == null) {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Binding to full backup agent : " + mPkg.packageName);
+ }
+ mAgent = backupManagerService.bindToAgentSynchronous(mPkg.applicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL);
+ }
+ return mAgent != null;
+ }
+
+ private void writeApkToBackup(PackageInfo pkg, FullBackupDataOutput output) {
+ // Forward-locked apps, system-bundled .apks, etc are filtered out before we get here
+ // TODO: handle backing up split APKs
+ final String appSourceDir = pkg.applicationInfo.getBaseCodePath();
+ final String apkDir = new File(appSourceDir).getParent();
+ FullBackup.backupToTar(pkg.packageName, FullBackup.APK_TREE_TOKEN, null,
+ apkDir, appSourceDir, output);
+
+ // TODO: migrate this to SharedStorageBackup, since AID_SYSTEM
+ // doesn't have access to external storage.
+
+ // Save associated .obb content if it exists and we did save the apk
+ // check for .obb and save those too
+ // TODO: http://b/22388012
+ final UserEnvironment userEnv = new UserEnvironment(UserHandle.USER_SYSTEM);
+ final File obbDir = userEnv.buildExternalStorageAppObbDirs(pkg.packageName)[0];
+ if (obbDir != null) {
+ if (MORE_DEBUG) {
+ Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath());
+ }
+ File[] obbFiles = obbDir.listFiles();
+ if (obbFiles != null) {
+ final String obbDirName = obbDir.getAbsolutePath();
+ for (File obb : obbFiles) {
+ FullBackup.backupToTar(pkg.packageName, FullBackup.OBB_TREE_TOKEN, null,
+ obbDirName, obb.getAbsolutePath(), output);
+ }
+ }
+ }
+ }
+
+ // Widget metadata format. All header entries are strings ending in LF:
+ //
+ // Version 1 header:
+ // BACKUP_METADATA_VERSION, currently "1"
+ // package name
+ //
+ // File data (all integers are binary in network byte order)
+ // *N: 4 : integer token identifying which metadata blob
+ // 4 : integer size of this blob = N
+ // N : raw bytes of this metadata blob
+ //
+ // Currently understood blobs (always in network byte order):
+ //
+ // widgets : metadata token = 0x01FFED01 (BACKUP_WIDGET_METADATA_TOKEN)
+ //
+ // Unrecognized blobs are *ignored*, not errors.
+ private void writeMetadata(PackageInfo pkg, File destination, byte[] widgetData)
+ throws IOException {
+ StringBuilder b = new StringBuilder(512);
+ StringBuilderPrinter printer = new StringBuilderPrinter(b);
+ printer.println(Integer.toString(BACKUP_METADATA_VERSION));
+ printer.println(pkg.packageName);
+
+ FileOutputStream fout = new FileOutputStream(destination);
+ BufferedOutputStream bout = new BufferedOutputStream(fout);
+ DataOutputStream out = new DataOutputStream(bout);
+ bout.write(b.toString().getBytes()); // bypassing DataOutputStream
+
+ if (widgetData != null && widgetData.length > 0) {
+ out.writeInt(BACKUP_WIDGET_METADATA_TOKEN);
+ out.writeInt(widgetData.length);
+ out.write(widgetData);
+ }
+ bout.flush();
+ out.close();
+
+ // As with the manifest file, guarantee idempotence of the archive metadata
+ // for the widget block by using a fixed mtime on the transient file.
+ destination.setLastModified(0);
+ }
+
+ private void tearDown() {
+ if (mPkg != null) {
+ backupManagerService.tearDownAgentAndKill(mPkg.applicationInfo);
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEntry.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEntry.java
new file mode 100644
index 0000000..a62f1c0
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEntry.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.fullbackup;
+
+public class FullBackupEntry implements Comparable<FullBackupEntry> {
+
+ public String packageName;
+ public long lastBackup;
+
+ public FullBackupEntry(String pkg, long when) {
+ packageName = pkg;
+ lastBackup = when;
+ }
+
+ @Override
+ public int compareTo(FullBackupEntry other) {
+ if (lastBackup < other.lastBackup) {
+ return -1;
+ } else if (lastBackup > other.lastBackup) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java
new file mode 100644
index 0000000..da8a66a
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.fullbackup;
+
+import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_BACKUP_WAIT;
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL;
+
+import android.app.backup.IBackupManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageInfo;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.backup.IObbBackupService;
+import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.utils.FullBackupUtils;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Full backup/restore to a file/socket.
+ */
+public class FullBackupObbConnection implements ServiceConnection {
+
+ private RefactoredBackupManagerService backupManagerService;
+ volatile IObbBackupService mService;
+
+ public FullBackupObbConnection(RefactoredBackupManagerService backupManagerService) {
+ this.backupManagerService = backupManagerService;
+ mService = null;
+ }
+
+ public void establish() {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Initiating bind of OBB service on " + this);
+ }
+ Intent obbIntent = new Intent().setComponent(new ComponentName(
+ "com.android.sharedstoragebackup",
+ "com.android.sharedstoragebackup.ObbBackupService"));
+ backupManagerService.getContext().bindServiceAsUser(
+ obbIntent, this, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
+ }
+
+ public void tearDown() {
+ backupManagerService.getContext().unbindService(this);
+ }
+
+ public boolean backupObbs(PackageInfo pkg, OutputStream out) {
+ boolean success = false;
+ waitForConnection();
+
+ ParcelFileDescriptor[] pipes = null;
+ try {
+ pipes = ParcelFileDescriptor.createPipe();
+ int token = backupManagerService.generateRandomIntegerToken();
+ backupManagerService.prepareOperationTimeout(
+ token, TIMEOUT_FULL_BACKUP_INTERVAL, null, OP_TYPE_BACKUP_WAIT);
+ mService.backupObbs(pkg.packageName, pipes[1], token,
+ backupManagerService.getBackupManagerBinder());
+ FullBackupUtils.routeSocketDataToOutput(pipes[0], out);
+ success = backupManagerService.waitUntilOperationComplete(token);
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to back up OBBs for " + pkg, e);
+ } finally {
+ try {
+ out.flush();
+ if (pipes != null) {
+ if (pipes[0] != null) {
+ pipes[0].close();
+ }
+ if (pipes[1] != null) {
+ pipes[1].close();
+ }
+ }
+ } catch (IOException e) {
+ Slog.w(TAG, "I/O error closing down OBB backup", e);
+ }
+ }
+ return success;
+ }
+
+ public void restoreObbFile(String pkgName, ParcelFileDescriptor data,
+ long fileSize, int type, String path, long mode, long mtime,
+ int token, IBackupManager callbackBinder) {
+ waitForConnection();
+
+ try {
+ mService.restoreObbFile(pkgName, data, fileSize, type, path, mode, mtime,
+ token, callbackBinder);
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to restore OBBs for " + pkgName, e);
+ }
+ }
+
+ private void waitForConnection() {
+ synchronized (this) {
+ while (mService == null) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "...waiting for OBB service binding...");
+ }
+ try {
+ this.wait();
+ } catch (InterruptedException e) { /* never interrupted */ }
+ }
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Connected to OBB service; continuing");
+ }
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (this) {
+ mService = IObbBackupService.Stub.asInterface(service);
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "OBB service connection " + mService + " connected on " + this);
+ }
+ this.notifyAll();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (this) {
+ mService = null;
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "OBB service connection disconnected on " + this);
+ }
+ this.notifyAll();
+ }
+ }
+
+}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupPreflight.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupPreflight.java
new file mode 100644
index 0000000..f12587f
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupPreflight.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.fullbackup;
+
+import android.app.IBackupAgent;
+import android.content.pm.PackageInfo;
+
+/**
+ * Callout from the engine to an interested participant that might need to communicate with the
+ * agent prior to asking it to move data.
+ */
+public interface FullBackupPreflight {
+
+ /**
+ * Perform the preflight operation necessary for the given package.
+ *
+ * @param pkg The name of the package being proposed for full-data backup
+ * @param agent Live BackupAgent binding to the target app's agent
+ * @return BackupTransport.TRANSPORT_OK to proceed with the backup operation, or one of the
+ * other BackupTransport.* error codes as appropriate
+ */
+ int preflightFullBackup(PackageInfo pkg, IBackupAgent agent);
+
+ long getExpectedSizeOrErrorCode();
+}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupTask.java
new file mode 100644
index 0000000..37961f9
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupTask.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.fullbackup;
+
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+
+import android.app.backup.IFullBackupRestoreObserver;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * Generic driver skeleton for full backup operations.
+ */
+public abstract class FullBackupTask implements Runnable {
+
+ IFullBackupRestoreObserver mObserver;
+
+ FullBackupTask(IFullBackupRestoreObserver observer) {
+ mObserver = observer;
+ }
+
+ // wrappers for observer use
+ final void sendStartBackup() {
+ if (mObserver != null) {
+ try {
+ mObserver.onStartBackup();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "full backup observer went away: startBackup");
+ mObserver = null;
+ }
+ }
+ }
+
+ final void sendOnBackupPackage(String name) {
+ if (mObserver != null) {
+ try {
+ // TODO: use a more user-friendly name string
+ mObserver.onBackupPackage(name);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "full backup observer went away: backupPackage");
+ mObserver = null;
+ }
+ }
+ }
+
+ final void sendEndBackup() {
+ if (mObserver != null) {
+ try {
+ mObserver.onEndBackup();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "full backup observer went away: endBackup");
+ mObserver = null;
+ }
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
new file mode 100644
index 0000000..007d930
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.fullbackup;
+
+import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_FILE_HEADER_MAGIC;
+import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_FILE_VERSION;
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.PBKDF_CURRENT;
+import static com.android.server.backup.RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+
+import android.app.backup.IFullBackupRestoreObserver;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.server.AppWidgetBackupBridge;
+import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.KeyValueAdbBackupEngine;
+import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.utils.AppBackupUtils;
+import com.android.server.backup.utils.PasswordUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Full backup task variant used for adb backup.
+ */
+public class PerformAdbBackupTask extends FullBackupTask implements BackupRestoreTask {
+
+ private RefactoredBackupManagerService backupManagerService;
+ FullBackupEngine mBackupEngine;
+ final AtomicBoolean mLatch;
+
+ ParcelFileDescriptor mOutputFile;
+ DeflaterOutputStream mDeflater;
+ boolean mIncludeApks;
+ boolean mIncludeObbs;
+ boolean mIncludeShared;
+ boolean mDoWidgets;
+ boolean mAllApps;
+ boolean mIncludeSystem;
+ boolean mCompress;
+ boolean mKeyValue;
+ ArrayList<String> mPackages;
+ PackageInfo mCurrentTarget;
+ String mCurrentPassword;
+ String mEncryptPassword;
+ private final int mCurrentOpToken;
+
+ public PerformAdbBackupTask(RefactoredBackupManagerService backupManagerService,
+ ParcelFileDescriptor fd, IFullBackupRestoreObserver observer,
+ boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets,
+ String curPassword, String encryptPassword, boolean doAllApps, boolean doSystem,
+ boolean doCompress, boolean doKeyValue, String[] packages, AtomicBoolean latch) {
+ super(observer);
+ this.backupManagerService = backupManagerService;
+ mCurrentOpToken = backupManagerService.generateRandomIntegerToken();
+ mLatch = latch;
+
+ mOutputFile = fd;
+ mIncludeApks = includeApks;
+ mIncludeObbs = includeObbs;
+ mIncludeShared = includeShared;
+ mDoWidgets = doWidgets;
+ mAllApps = doAllApps;
+ mIncludeSystem = doSystem;
+ mPackages = (packages == null)
+ ? new ArrayList<String>()
+ : new ArrayList<>(Arrays.asList(packages));
+ mCurrentPassword = curPassword;
+ // when backing up, if there is a current backup password, we require that
+ // the user use a nonempty encryption password as well. if one is supplied
+ // in the UI we use that, but if the UI was left empty we fall back to the
+ // current backup password (which was supplied by the user as well).
+ if (encryptPassword == null || "".equals(encryptPassword)) {
+ mEncryptPassword = curPassword;
+ } else {
+ mEncryptPassword = encryptPassword;
+ }
+ if (MORE_DEBUG) {
+ Slog.w(TAG, "Encrypting backup with passphrase=" + mEncryptPassword);
+ }
+ mCompress = doCompress;
+ mKeyValue = doKeyValue;
+ }
+
+ void addPackagesToSet(TreeMap<String, PackageInfo> set, List<String> pkgNames) {
+ for (String pkgName : pkgNames) {
+ if (!set.containsKey(pkgName)) {
+ try {
+ PackageInfo info = backupManagerService.getPackageManager().getPackageInfo(
+ pkgName,
+ PackageManager.GET_SIGNATURES);
+ set.put(pkgName, info);
+ } catch (NameNotFoundException e) {
+ Slog.w(TAG, "Unknown package " + pkgName + ", skipping");
+ }
+ }
+ }
+ }
+
+ private OutputStream emitAesBackupHeader(StringBuilder headerbuf,
+ OutputStream ofstream) throws Exception {
+ // User key will be used to encrypt the master key.
+ byte[] newUserSalt = backupManagerService
+ .randomBytes(PasswordUtils.PBKDF2_SALT_SIZE);
+ SecretKey userKey = PasswordUtils
+ .buildPasswordKey(PBKDF_CURRENT, mEncryptPassword,
+ newUserSalt,
+ PasswordUtils.PBKDF2_HASH_ROUNDS);
+
+ // the master key is random for each backup
+ byte[] masterPw = new byte[256 / 8];
+ backupManagerService.getRng().nextBytes(masterPw);
+ byte[] checksumSalt = backupManagerService
+ .randomBytes(PasswordUtils.PBKDF2_SALT_SIZE);
+
+ // primary encryption of the datastream with the random key
+ Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ SecretKeySpec masterKeySpec = new SecretKeySpec(masterPw, "AES");
+ c.init(Cipher.ENCRYPT_MODE, masterKeySpec);
+ OutputStream finalOutput = new CipherOutputStream(ofstream, c);
+
+ // line 4: name of encryption algorithm
+ headerbuf.append(PasswordUtils.ENCRYPTION_ALGORITHM_NAME);
+ headerbuf.append('\n');
+ // line 5: user password salt [hex]
+ headerbuf.append(PasswordUtils.byteArrayToHex(newUserSalt));
+ headerbuf.append('\n');
+ // line 6: master key checksum salt [hex]
+ headerbuf.append(PasswordUtils.byteArrayToHex(checksumSalt));
+ headerbuf.append('\n');
+ // line 7: number of PBKDF2 rounds used [decimal]
+ headerbuf.append(PasswordUtils.PBKDF2_HASH_ROUNDS);
+ headerbuf.append('\n');
+
+ // line 8: IV of the user key [hex]
+ Cipher mkC = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ mkC.init(Cipher.ENCRYPT_MODE, userKey);
+
+ byte[] IV = mkC.getIV();
+ headerbuf.append(PasswordUtils.byteArrayToHex(IV));
+ headerbuf.append('\n');
+
+ // line 9: master IV + key blob, encrypted by the user key [hex]. Blob format:
+ // [byte] IV length = Niv
+ // [array of Niv bytes] IV itself
+ // [byte] master key length = Nmk
+ // [array of Nmk bytes] master key itself
+ // [byte] MK checksum hash length = Nck
+ // [array of Nck bytes] master key checksum hash
+ //
+ // The checksum is the (master key + checksum salt), run through the
+ // stated number of PBKDF2 rounds
+ IV = c.getIV();
+ byte[] mk = masterKeySpec.getEncoded();
+ byte[] checksum = PasswordUtils
+ .makeKeyChecksum(PBKDF_CURRENT,
+ masterKeySpec.getEncoded(),
+ checksumSalt, PasswordUtils.PBKDF2_HASH_ROUNDS);
+
+ ByteArrayOutputStream blob = new ByteArrayOutputStream(IV.length + mk.length
+ + checksum.length + 3);
+ DataOutputStream mkOut = new DataOutputStream(blob);
+ mkOut.writeByte(IV.length);
+ mkOut.write(IV);
+ mkOut.writeByte(mk.length);
+ mkOut.write(mk);
+ mkOut.writeByte(checksum.length);
+ mkOut.write(checksum);
+ mkOut.flush();
+ byte[] encryptedMk = mkC.doFinal(blob.toByteArray());
+ headerbuf.append(PasswordUtils.byteArrayToHex(encryptedMk));
+ headerbuf.append('\n');
+
+ return finalOutput;
+ }
+
+ private void finalizeBackup(OutputStream out) {
+ try {
+ // A standard 'tar' EOF sequence: two 512-byte blocks of all zeroes.
+ byte[] eof = new byte[512 * 2]; // newly allocated == zero filled
+ out.write(eof);
+ } catch (IOException e) {
+ Slog.w(TAG, "Error attempting to finalize backup stream");
+ }
+ }
+
+ @Override
+ public void run() {
+ String includeKeyValue = mKeyValue ? ", including key-value backups" : "";
+ Slog.i(TAG, "--- Performing adb backup" + includeKeyValue + " ---");
+
+ TreeMap<String, PackageInfo> packagesToBackup = new TreeMap<>();
+ FullBackupObbConnection obbConnection = new FullBackupObbConnection(
+ backupManagerService);
+ obbConnection.establish(); // we'll want this later
+
+ sendStartBackup();
+
+ // doAllApps supersedes the package set if any
+ if (mAllApps) {
+ List<PackageInfo> allPackages =
+ backupManagerService.getPackageManager().getInstalledPackages(
+ PackageManager.GET_SIGNATURES);
+ for (int i = 0; i < allPackages.size(); i++) {
+ PackageInfo pkg = allPackages.get(i);
+ // Exclude system apps if we've been asked to do so
+ if (mIncludeSystem == true
+ || ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0)) {
+ packagesToBackup.put(pkg.packageName, pkg);
+ }
+ }
+ }
+
+ // If we're doing widget state as well, ensure that we have all the involved
+ // host & provider packages in the set
+ if (mDoWidgets) {
+ // TODO: http://b/22388012
+ List<String> pkgs =
+ AppWidgetBackupBridge.getWidgetParticipants(UserHandle.USER_SYSTEM);
+ if (pkgs != null) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Adding widget participants to backup set:");
+ StringBuilder sb = new StringBuilder(128);
+ sb.append(" ");
+ for (String s : pkgs) {
+ sb.append(' ');
+ sb.append(s);
+ }
+ Slog.i(TAG, sb.toString());
+ }
+ addPackagesToSet(packagesToBackup, pkgs);
+ }
+ }
+
+ // Now process the command line argument packages, if any. Note that explicitly-
+ // named system-partition packages will be included even if includeSystem was
+ // set to false.
+ if (mPackages != null) {
+ addPackagesToSet(packagesToBackup, mPackages);
+ }
+
+ // Now we cull any inapplicable / inappropriate packages from the set. This
+ // includes the special shared-storage agent package; we handle that one
+ // explicitly at the end of the backup pass. Packages supporting key-value backup are
+ // added to their own queue, and handled after packages supporting fullbackup.
+ ArrayList<PackageInfo> keyValueBackupQueue = new ArrayList<>();
+ Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator();
+ while (iter.hasNext()) {
+ PackageInfo pkg = iter.next().getValue();
+ if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo)
+ || AppBackupUtils.appIsStopped(pkg.applicationInfo)) {
+ iter.remove();
+ if (DEBUG) {
+ Slog.i(TAG, "Package " + pkg.packageName
+ + " is not eligible for backup, removing.");
+ }
+ } else if (AppBackupUtils.appIsKeyValueOnly(pkg)) {
+ iter.remove();
+ if (DEBUG) {
+ Slog.i(TAG, "Package " + pkg.packageName
+ + " is key-value.");
+ }
+ keyValueBackupQueue.add(pkg);
+ }
+ }
+
+ // flatten the set of packages now so we can explicitly control the ordering
+ ArrayList<PackageInfo> backupQueue =
+ new ArrayList<>(packagesToBackup.values());
+ FileOutputStream ofstream = new FileOutputStream(mOutputFile.getFileDescriptor());
+ OutputStream out = null;
+
+ PackageInfo pkg = null;
+ try {
+ boolean encrypting = (mEncryptPassword != null && mEncryptPassword.length() > 0);
+
+ // Only allow encrypted backups of encrypted devices
+ if (backupManagerService.deviceIsEncrypted() && !encrypting) {
+ Slog.e(TAG, "Unencrypted backup of encrypted device; aborting");
+ return;
+ }
+
+ OutputStream finalOutput = ofstream;
+
+ // Verify that the given password matches the currently-active
+ // backup password, if any
+ if (!backupManagerService.backupPasswordMatches(mCurrentPassword)) {
+ if (DEBUG) {
+ Slog.w(TAG, "Backup password mismatch; aborting");
+ }
+ return;
+ }
+
+ // Write the global file header. All strings are UTF-8 encoded; lines end
+ // with a '\n' byte. Actual backup data begins immediately following the
+ // final '\n'.
+ //
+ // line 1: "ANDROID BACKUP"
+ // line 2: backup file format version, currently "5"
+ // line 3: compressed? "0" if not compressed, "1" if compressed.
+ // line 4: name of encryption algorithm [currently only "none" or "AES-256"]
+ //
+ // When line 4 is not "none", then additional header data follows:
+ //
+ // line 5: user password salt [hex]
+ // line 6: master key checksum salt [hex]
+ // line 7: number of PBKDF2 rounds to use (same for user & master) [decimal]
+ // line 8: IV of the user key [hex]
+ // line 9: master key blob [hex]
+ // IV of the master key, master key itself, master key checksum hash
+ //
+ // The master key checksum is the master key plus its checksum salt, run through
+ // 10k rounds of PBKDF2. This is used to verify that the user has supplied the
+ // correct password for decrypting the archive: the master key decrypted from
+ // the archive using the user-supplied password is also run through PBKDF2 in
+ // this way, and if the result does not match the checksum as stored in the
+ // archive, then we know that the user-supplied password does not match the
+ // archive's.
+ StringBuilder headerbuf = new StringBuilder(1024);
+
+ headerbuf.append(BACKUP_FILE_HEADER_MAGIC);
+ headerbuf.append(BACKUP_FILE_VERSION); // integer, no trailing \n
+ headerbuf.append(mCompress ? "\n1\n" : "\n0\n");
+
+ try {
+ // Set up the encryption stage if appropriate, and emit the correct header
+ if (encrypting) {
+ finalOutput = emitAesBackupHeader(headerbuf, finalOutput);
+ } else {
+ headerbuf.append("none\n");
+ }
+
+ byte[] header = headerbuf.toString().getBytes("UTF-8");
+ ofstream.write(header);
+
+ // Set up the compression stage feeding into the encryption stage (if any)
+ if (mCompress) {
+ Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
+ finalOutput = new DeflaterOutputStream(finalOutput, deflater, true);
+ }
+
+ out = finalOutput;
+ } catch (Exception e) {
+ // Should never happen!
+ Slog.e(TAG, "Unable to emit archive header", e);
+ return;
+ }
+
+ // Shared storage if requested
+ if (mIncludeShared) {
+ try {
+ pkg = backupManagerService.getPackageManager().getPackageInfo(
+ SHARED_BACKUP_AGENT_PACKAGE, 0);
+ backupQueue.add(pkg);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "Unable to find shared-storage backup handler");
+ }
+ }
+
+ // Now actually run the constructed backup sequence for full backup
+ int N = backupQueue.size();
+ for (int i = 0; i < N; i++) {
+ pkg = backupQueue.get(i);
+ if (DEBUG) {
+ Slog.i(TAG, "--- Performing full backup for package " + pkg.packageName
+ + " ---");
+ }
+ final boolean isSharedStorage =
+ pkg.packageName.equals(
+ SHARED_BACKUP_AGENT_PACKAGE);
+
+ mBackupEngine = new FullBackupEngine(backupManagerService, out,
+ null, pkg, mIncludeApks, this, Long.MAX_VALUE, mCurrentOpToken);
+ sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
+
+ // Don't need to check preflight result as there is no preflight hook.
+ mCurrentTarget = pkg;
+ mBackupEngine.backupOnePackage();
+
+ // after the app's agent runs to handle its private filesystem
+ // contents, back up any OBB content it has on its behalf.
+ if (mIncludeObbs) {
+ boolean obbOkay = obbConnection.backupObbs(pkg, out);
+ if (!obbOkay) {
+ throw new RuntimeException("Failure writing OBB stack for " + pkg);
+ }
+ }
+ }
+ // And for key-value backup if enabled
+ if (mKeyValue) {
+ for (PackageInfo keyValuePackage : keyValueBackupQueue) {
+ if (DEBUG) {
+ Slog.i(TAG, "--- Performing key-value backup for package "
+ + keyValuePackage.packageName + " ---");
+ }
+ KeyValueAdbBackupEngine kvBackupEngine =
+ new KeyValueAdbBackupEngine(out, keyValuePackage,
+ backupManagerService,
+ backupManagerService.getPackageManager(),
+ backupManagerService.getBaseStateDir(),
+ backupManagerService.getDataDir());
+ sendOnBackupPackage(keyValuePackage.packageName);
+ kvBackupEngine.backupOnePackage();
+ }
+ }
+
+ // Done!
+ finalizeBackup(out);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "App died during full backup");
+ } catch (Exception e) {
+ Slog.e(TAG, "Internal exception during full backup", e);
+ } finally {
+ try {
+ if (out != null) {
+ out.flush();
+ out.close();
+ }
+ mOutputFile.close();
+ } catch (IOException e) {
+ /* nothing we can do about this */
+ }
+ synchronized (mLatch) {
+ mLatch.set(true);
+ mLatch.notifyAll();
+ }
+ sendEndBackup();
+ obbConnection.tearDown();
+ if (DEBUG) {
+ Slog.d(TAG, "Full backup pass complete.");
+ }
+ backupManagerService.getWakelock().release();
+ }
+ }
+
+ // BackupRestoreTask methods, used for timeout handling
+ @Override
+ public void execute() {
+ // Unused
+ }
+
+ @Override
+ public void operationComplete(long result) {
+ // Unused
+ }
+
+ @Override
+ public void handleCancel(boolean cancelAll) {
+ final PackageInfo target = mCurrentTarget;
+ if (DEBUG) {
+ Slog.w(TAG, "adb backup cancel of " + target);
+ }
+ if (target != null) {
+ backupManagerService.tearDownAgentAndKill(mCurrentTarget.applicationInfo);
+ }
+ backupManagerService.removeOperation(mCurrentOpToken);
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
new file mode 100644
index 0000000..0f1e485
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -0,0 +1,855 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.fullbackup;
+
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG_SCHEDULING;
+import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.OP_PENDING;
+import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_BACKUP;
+import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_BACKUP_WAIT;
+import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL;
+
+import android.app.IBackupAgent;
+import android.app.backup.BackupManager;
+import android.app.backup.BackupManagerMonitor;
+import android.app.backup.BackupProgress;
+import android.app.backup.BackupTransport;
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IBackupObserver;
+import android.app.backup.IFullBackupRestoreObserver;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.EventLogTags;
+import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.FullBackupJob;
+import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.internal.Operation;
+import com.android.server.backup.utils.AppBackupUtils;
+import com.android.server.backup.utils.BackupManagerMonitorUtils;
+import com.android.server.backup.utils.BackupObserverUtils;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Full backup task extension used for transport-oriented operation.
+ *
+ * Flow:
+ * For each requested package:
+ * - Spin off a new SinglePackageBackupRunner (mBackupRunner) for the current package.
+ * - Wait until preflight is complete. (mBackupRunner.getPreflightResultBlocking())
+ * - If preflight data size is within limit, start reading data from agent pipe and writing
+ * to transport pipe. While there is data to send, call transport.sendBackupData(int) to
+ * tell the transport how many bytes to expect on its pipe.
+ * - After sending all data, call transport.finishBackup() if things went well. And
+ * transport.cancelFullBackup() otherwise.
+ *
+ * Interactions with mCurrentOperations:
+ * - An entry for this object is added to mCurrentOperations for the entire lifetime of this
+ * object. Used to cancel the operation.
+ * - SinglePackageBackupRunner and SinglePackageBackupPreflight will put ephemeral entries
+ * to get timeouts or operation complete callbacks.
+ *
+ * Handling cancels:
+ * - The contract we provide is that the task won't interact with the transport after
+ * handleCancel() is done executing.
+ * - This task blocks at 3 points: 1. Preflight result check 2. Reading on agent side pipe
+ * and 3. Get backup result from mBackupRunner.
+ * - Bubbling up handleCancel to mBackupRunner handles all 3: 1. Calls handleCancel on the
+ * preflight operation which counts down on the preflight latch. 2. Tears down the agent,
+ * so read() returns -1. 3. Notifies mCurrentOpLock which unblocks
+ * mBackupRunner.getBackupResultBlocking().
+ */
+public class PerformFullTransportBackupTask extends FullBackupTask implements BackupRestoreTask {
+ private static final String TAG = "PFTBT";
+
+ private RefactoredBackupManagerService backupManagerService;
+ private final Object mCancelLock = new Object();
+
+ ArrayList<PackageInfo> mPackages;
+ PackageInfo mCurrentPackage;
+ boolean mUpdateSchedule;
+ CountDownLatch mLatch;
+ FullBackupJob mJob; // if a scheduled job needs to be finished afterwards
+ IBackupObserver mBackupObserver;
+ IBackupManagerMonitor mMonitor;
+ boolean mUserInitiated;
+ private volatile IBackupTransport mTransport;
+ SinglePackageBackupRunner mBackupRunner;
+ private final int mBackupRunnerOpToken;
+
+ // This is true when a backup operation for some package is in progress.
+ private volatile boolean mIsDoingBackup;
+ private volatile boolean mCancelAll;
+ private final int mCurrentOpToken;
+
+ public PerformFullTransportBackupTask(RefactoredBackupManagerService backupManagerService,
+ IFullBackupRestoreObserver observer,
+ String[] whichPackages, boolean updateSchedule,
+ FullBackupJob runningJob, CountDownLatch latch, IBackupObserver backupObserver,
+ IBackupManagerMonitor monitor, boolean userInitiated) {
+ super(observer);
+ this.backupManagerService = backupManagerService;
+ mUpdateSchedule = updateSchedule;
+ mLatch = latch;
+ mJob = runningJob;
+ mPackages = new ArrayList<>(whichPackages.length);
+ mBackupObserver = backupObserver;
+ mMonitor = monitor;
+ mUserInitiated = userInitiated;
+ mCurrentOpToken = backupManagerService.generateRandomIntegerToken();
+ mBackupRunnerOpToken = backupManagerService.generateRandomIntegerToken();
+
+ if (backupManagerService.isBackupOperationInProgress()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Skipping full backup. A backup is already in progress.");
+ }
+ mCancelAll = true;
+ return;
+ }
+
+ registerTask();
+
+ for (String pkg : whichPackages) {
+ try {
+ PackageInfo info = backupManagerService.getPackageManager().getPackageInfo(pkg,
+ PackageManager.GET_SIGNATURES);
+ mCurrentPackage = info;
+ if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo)) {
+ // Cull any packages that have indicated that backups are not permitted,
+ // that run as system-domain uids but do not define their own backup agents,
+ // as well as any explicit mention of the 'special' shared-storage agent
+ // package (we handle that one at the end).
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Ignoring ineligible package " + pkg);
+ }
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_INELIGIBLE,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
+ BackupObserverUtils.sendBackupOnPackageResult(mBackupObserver, pkg,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
+ continue;
+ } else if (!AppBackupUtils.appGetsFullBackup(info)) {
+ // Cull any packages that are found in the queue but now aren't supposed
+ // to get full-data backup operations.
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Ignoring full-data backup of key/value participant "
+ + pkg);
+ }
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_KEY_VALUE_PARTICIPANT,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
+ BackupObserverUtils.sendBackupOnPackageResult(mBackupObserver, pkg,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
+ continue;
+ } else if (AppBackupUtils.appIsStopped(info.applicationInfo)) {
+ // Cull any packages in the 'stopped' state: they've either just been
+ // installed or have explicitly been force-stopped by the user. In both
+ // cases we do not want to launch them for backup.
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Ignoring stopped package " + pkg);
+ }
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_STOPPED,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
+ BackupObserverUtils.sendBackupOnPackageResult(mBackupObserver, pkg,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
+ continue;
+ }
+ mPackages.add(info);
+ } catch (NameNotFoundException e) {
+ Slog.i(TAG, "Requested package " + pkg + " not found; ignoring");
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_FOUND,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
+ }
+ }
+ }
+
+ private void registerTask() {
+ synchronized (backupManagerService.getCurrentOpLock()) {
+ Slog.d(TAG, "backupmanager pftbt token=" + Integer.toHexString(mCurrentOpToken));
+ backupManagerService.getCurrentOperations().put(
+ mCurrentOpToken,
+ new Operation(OP_PENDING, this, OP_TYPE_BACKUP));
+ }
+ }
+
+ public void unregisterTask() {
+ backupManagerService.removeOperation(mCurrentOpToken);
+ }
+
+ @Override
+ public void execute() {
+ // Nothing to do.
+ }
+
+ @Override
+ public void handleCancel(boolean cancelAll) {
+ synchronized (mCancelLock) {
+ // We only support 'cancelAll = true' case for this task. Cancelling of a single package
+
+ // due to timeout is handled by SinglePackageBackupRunner and
+ // SinglePackageBackupPreflight.
+
+ if (!cancelAll) {
+ Slog.wtf(TAG, "Expected cancelAll to be true.");
+ }
+
+ if (mCancelAll) {
+ Slog.d(TAG, "Ignoring duplicate cancel call.");
+ return;
+ }
+
+ mCancelAll = true;
+ if (mIsDoingBackup) {
+ backupManagerService.handleCancel(mBackupRunnerOpToken, cancelAll);
+ try {
+ mTransport.cancelFullBackup();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error calling cancelFullBackup() on transport: " + e);
+ // Can't do much.
+ }
+ }
+ }
+ }
+
+ @Override
+ public void operationComplete(long result) {
+ // Nothing to do.
+ }
+
+ @Override
+ public void run() {
+
+ // data from the app, passed to us for bridging to the transport
+ ParcelFileDescriptor[] enginePipes = null;
+
+ // Pipe through which we write data to the transport
+ ParcelFileDescriptor[] transportPipes = null;
+
+ long backoff = 0;
+ int backupRunStatus = BackupManager.SUCCESS;
+
+ try {
+ if (!backupManagerService.isEnabled() || !backupManagerService.isProvisioned()) {
+ // Backups are globally disabled, so don't proceed.
+ if (DEBUG) {
+ Slog.i(TAG, "full backup requested but enabled=" + backupManagerService
+ .isEnabled()
+ + " provisioned=" + backupManagerService.isProvisioned()
+ + "; ignoring");
+ }
+ int monitoringEvent;
+ if (!backupManagerService.isEnabled()) {
+ monitoringEvent = BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED;
+ } else {
+ monitoringEvent = BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED;
+ }
+ mMonitor = BackupManagerMonitorUtils
+ .monitorEvent(mMonitor, monitoringEvent, null,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
+ mUpdateSchedule = false;
+ backupRunStatus = BackupManager.ERROR_BACKUP_NOT_ALLOWED;
+ return;
+ }
+
+ mTransport = backupManagerService.getTransportManager().getCurrentTransportBinder();
+ if (mTransport == null) {
+ Slog.w(TAG, "Transport not present; full data backup not performed");
+ backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_TRANSPORT_NOT_PRESENT,
+ mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
+ null);
+ return;
+ }
+
+ // Set up to send data to the transport
+ final int N = mPackages.size();
+ final byte[] buffer = new byte[8192];
+ for (int i = 0; i < N; i++) {
+ PackageInfo currentPackage = mPackages.get(i);
+ String packageName = currentPackage.packageName;
+ if (DEBUG) {
+ Slog.i(TAG, "Initiating full-data transport backup of " + packageName
+ + " token: " + mCurrentOpToken);
+ }
+ EventLog.writeEvent(EventLogTags.FULL_BACKUP_PACKAGE, packageName);
+
+ transportPipes = ParcelFileDescriptor.createPipe();
+
+ // Tell the transport the data's coming
+ int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
+ int backupPackageStatus;
+ long quota = Long.MAX_VALUE;
+ synchronized (mCancelLock) {
+ if (mCancelAll) {
+ break;
+ }
+ backupPackageStatus = mTransport.performFullBackup(currentPackage,
+ transportPipes[0], flags);
+
+ if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
+ quota = mTransport.getBackupQuota(currentPackage.packageName,
+ true /* isFullBackup */);
+ // Now set up the backup engine / data source end of things
+ enginePipes = ParcelFileDescriptor.createPipe();
+ mBackupRunner =
+ new SinglePackageBackupRunner(enginePipes[1], currentPackage,
+ mTransport, quota, mBackupRunnerOpToken);
+ // The runner dup'd the pipe half, so we close it here
+ enginePipes[1].close();
+ enginePipes[1] = null;
+
+ mIsDoingBackup = true;
+ }
+ }
+ if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
+
+ // The transport has its own copy of the read end of the pipe,
+ // so close ours now
+ transportPipes[0].close();
+ transportPipes[0] = null;
+
+ // Spin off the runner to fetch the app's data and pipe it
+ // into the engine pipes
+ (new Thread(mBackupRunner, "package-backup-bridge")).start();
+
+ // Read data off the engine pipe and pass it to the transport
+ // pipe until we hit EOD on the input stream. We do not take
+ // close() responsibility for these FDs into these stream wrappers.
+ FileInputStream in = new FileInputStream(
+ enginePipes[0].getFileDescriptor());
+ FileOutputStream out = new FileOutputStream(
+ transportPipes[1].getFileDescriptor());
+ long totalRead = 0;
+ final long preflightResult = mBackupRunner.getPreflightResultBlocking();
+ // Preflight result is negative if some error happened on preflight.
+ if (preflightResult < 0) {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Backup error after preflight of package "
+ + packageName + ": " + preflightResult
+ + ", not running backup.");
+ }
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_ERROR_PREFLIGHT,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ BackupManagerMonitorUtils.putMonitoringExtra(null,
+ BackupManagerMonitor.EXTRA_LOG_PREFLIGHT_ERROR,
+ preflightResult));
+ backupPackageStatus = (int) preflightResult;
+ } else {
+ int nRead = 0;
+ do {
+ nRead = in.read(buffer);
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "in.read(buffer) from app: " + nRead);
+ }
+ if (nRead > 0) {
+ out.write(buffer, 0, nRead);
+ synchronized (mCancelLock) {
+ if (!mCancelAll) {
+ backupPackageStatus = mTransport.sendBackupData(nRead);
+ }
+ }
+ totalRead += nRead;
+ if (mBackupObserver != null && preflightResult > 0) {
+ BackupObserverUtils
+ .sendBackupOnUpdate(mBackupObserver, packageName,
+ new BackupProgress(preflightResult, totalRead));
+ }
+ }
+ } while (nRead > 0
+ && backupPackageStatus == BackupTransport.TRANSPORT_OK);
+ // Despite preflight succeeded, package still can hit quota on flight.
+ if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
+ Slog.w(TAG, "Package hit quota limit in-flight " + packageName
+ + ": " + totalRead + " of " + quota);
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_QUOTA_HIT_PREFLIGHT,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
+ null);
+ mBackupRunner.sendQuotaExceeded(totalRead, quota);
+ }
+ }
+
+ final int backupRunnerResult = mBackupRunner.getBackupResultBlocking();
+
+ synchronized (mCancelLock) {
+ mIsDoingBackup = false;
+ // If mCancelCurrent is true, we have already called cancelFullBackup().
+ if (!mCancelAll) {
+ if (backupRunnerResult == BackupTransport.TRANSPORT_OK) {
+ // If we were otherwise in a good state, now interpret the final
+ // result based on what finishBackup() returns. If we're in a
+ // failure case already, preserve that result and ignore whatever
+ // finishBackup() reports.
+ final int finishResult = mTransport.finishBackup();
+ if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
+ backupPackageStatus = finishResult;
+ }
+ } else {
+ mTransport.cancelFullBackup();
+ }
+ }
+ }
+
+ // A transport-originated error here means that we've hit an error that the
+ // runner doesn't know about, so it's still moving data but we're pulling the
+ // rug out from under it. Don't ask for its result: we already know better
+ // and we'll hang if we block waiting for it, since it relies on us to
+ // read back the data it's writing into the engine. Just proceed with
+ // a graceful failure. The runner/engine mechanism will tear itself
+ // down cleanly when we close the pipes from this end. Transport-level
+ // errors take precedence over agent/app-specific errors for purposes of
+ // determining our course of action.
+ if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
+ // We still could fail in backup runner thread.
+ if (backupRunnerResult != BackupTransport.TRANSPORT_OK) {
+ // If there was an error in runner thread and
+ // not TRANSPORT_ERROR here, overwrite it.
+ backupPackageStatus = backupRunnerResult;
+ }
+ } else {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Transport-level failure; cancelling agent work");
+ }
+ }
+
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Done delivering backup data: result="
+ + backupPackageStatus);
+ }
+
+ if (backupPackageStatus != BackupTransport.TRANSPORT_OK) {
+ Slog.e(TAG, "Error " + backupPackageStatus + " backing up "
+ + packageName);
+ }
+
+ // Also ask the transport how long it wants us to wait before
+ // moving on to the next package, if any.
+ backoff = mTransport.requestFullBackupTime();
+ if (DEBUG_SCHEDULING) {
+ Slog.i(TAG, "Transport suggested backoff=" + backoff);
+ }
+
+ }
+
+ // Roll this package to the end of the backup queue if we're
+ // in a queue-driven mode (regardless of success/failure)
+ if (mUpdateSchedule) {
+ backupManagerService.enqueueFullBackup(packageName, System.currentTimeMillis());
+ }
+
+ if (backupPackageStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
+ BackupObserverUtils
+ .sendBackupOnPackageResult(mBackupObserver, packageName,
+ BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED);
+ if (DEBUG) {
+ Slog.i(TAG, "Transport rejected backup of " + packageName
+ + ", skipping");
+ }
+ EventLog.writeEvent(EventLogTags.FULL_BACKUP_AGENT_FAILURE, packageName,
+ "transport rejected");
+ // Do nothing, clean up, and continue looping.
+ } else if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
+ BackupObserverUtils
+ .sendBackupOnPackageResult(mBackupObserver, packageName,
+ BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED);
+ if (DEBUG) {
+ Slog.i(TAG, "Transport quota exceeded for package: " + packageName);
+ EventLog.writeEvent(EventLogTags.FULL_BACKUP_QUOTA_EXCEEDED,
+ packageName);
+ }
+ // Do nothing, clean up, and continue looping.
+ } else if (backupPackageStatus == BackupTransport.AGENT_ERROR) {
+ BackupObserverUtils
+ .sendBackupOnPackageResult(mBackupObserver, packageName,
+ BackupManager.ERROR_AGENT_FAILURE);
+ Slog.w(TAG, "Application failure for package: " + packageName);
+ EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName);
+ backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+ // Do nothing, clean up, and continue looping.
+ } else if (backupPackageStatus == BackupManager.ERROR_BACKUP_CANCELLED) {
+ BackupObserverUtils
+ .sendBackupOnPackageResult(mBackupObserver, packageName,
+ BackupManager.ERROR_BACKUP_CANCELLED);
+ Slog.w(TAG, "Backup cancelled. package=" + packageName +
+ ", cancelAll=" + mCancelAll);
+ EventLog.writeEvent(EventLogTags.FULL_BACKUP_CANCELLED, packageName);
+ backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+ // Do nothing, clean up, and continue looping.
+ } else if (backupPackageStatus != BackupTransport.TRANSPORT_OK) {
+ BackupObserverUtils
+ .sendBackupOnPackageResult(mBackupObserver, packageName,
+ BackupManager.ERROR_TRANSPORT_ABORTED);
+ Slog.w(TAG, "Transport failed; aborting backup: " + backupPackageStatus);
+ EventLog.writeEvent(EventLogTags.FULL_BACKUP_TRANSPORT_FAILURE);
+ // Abort entire backup pass.
+ backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
+ return;
+ } else {
+ // Success!
+ BackupObserverUtils
+ .sendBackupOnPackageResult(mBackupObserver, packageName,
+ BackupManager.SUCCESS);
+ EventLog.writeEvent(EventLogTags.FULL_BACKUP_SUCCESS, packageName);
+ backupManagerService.logBackupComplete(packageName);
+ }
+ cleanUpPipes(transportPipes);
+ cleanUpPipes(enginePipes);
+ if (currentPackage.applicationInfo != null) {
+ Slog.i(TAG, "Unbinding agent in " + packageName);
+ backupManagerService.addBackupTrace("unbinding " + packageName);
+ try {
+ backupManagerService.getActivityManager().unbindBackupAgent(
+ currentPackage.applicationInfo);
+ } catch (RemoteException e) { /* can't happen; activity manager is local */ }
+ }
+ }
+ } catch (Exception e) {
+ backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
+ Slog.w(TAG, "Exception trying full transport backup", e);
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_EXCEPTION_FULL_BACKUP,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ BackupManagerMonitorUtils.putMonitoringExtra(null,
+ BackupManagerMonitor.EXTRA_LOG_EXCEPTION_FULL_BACKUP,
+ Log.getStackTraceString(e)));
+
+ } finally {
+
+ if (mCancelAll) {
+ backupRunStatus = BackupManager.ERROR_BACKUP_CANCELLED;
+ }
+
+ if (DEBUG) {
+ Slog.i(TAG, "Full backup completed with status: " + backupRunStatus);
+ }
+ BackupObserverUtils.sendBackupFinished(mBackupObserver, backupRunStatus);
+
+ cleanUpPipes(transportPipes);
+ cleanUpPipes(enginePipes);
+
+ unregisterTask();
+
+ if (mJob != null) {
+ mJob.finishBackupPass();
+ }
+
+ synchronized (backupManagerService.getQueueLock()) {
+ backupManagerService.setRunningFullBackupTask(null);
+ }
+
+ mLatch.countDown();
+
+ // Now that we're actually done with schedule-driven work, reschedule
+ // the next pass based on the new queue state.
+ if (mUpdateSchedule) {
+ backupManagerService.scheduleNextFullBackupJob(backoff);
+ }
+
+ Slog.i(TAG, "Full data backup pass finished.");
+ backupManagerService.getWakelock().release();
+ }
+ }
+
+ void cleanUpPipes(ParcelFileDescriptor[] pipes) {
+ if (pipes != null) {
+ if (pipes[0] != null) {
+ ParcelFileDescriptor fd = pipes[0];
+ pipes[0] = null;
+ try {
+ fd.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Unable to close pipe!");
+ }
+ }
+ if (pipes[1] != null) {
+ ParcelFileDescriptor fd = pipes[1];
+ pipes[1] = null;
+ try {
+ fd.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Unable to close pipe!");
+ }
+ }
+ }
+ }
+
+ // Run the backup and pipe it back to the given socket -- expects to run on
+ // a standalone thread. The runner owns this half of the pipe, and closes
+ // it to indicate EOD to the other end.
+ class SinglePackageBackupPreflight implements BackupRestoreTask, FullBackupPreflight {
+ final AtomicLong mResult = new AtomicLong(BackupTransport.AGENT_ERROR);
+ final CountDownLatch mLatch = new CountDownLatch(1);
+ final IBackupTransport mTransport;
+ final long mQuota;
+ private final int mCurrentOpToken;
+
+ SinglePackageBackupPreflight(IBackupTransport transport, long quota, int currentOpToken) {
+ mTransport = transport;
+ mQuota = quota;
+ mCurrentOpToken = currentOpToken;
+ }
+
+ @Override
+ public int preflightFullBackup(PackageInfo pkg, IBackupAgent agent) {
+ int result;
+ try {
+ backupManagerService.prepareOperationTimeout(
+ mCurrentOpToken, TIMEOUT_FULL_BACKUP_INTERVAL, this, OP_TYPE_BACKUP_WAIT);
+ backupManagerService.addBackupTrace("preflighting");
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Preflighting full payload of " + pkg.packageName);
+ }
+ agent.doMeasureFullBackup(mQuota, mCurrentOpToken,
+ backupManagerService.getBackupManagerBinder());
+
+ // Now wait to get our result back. If this backstop timeout is reached without
+ // the latch being thrown, flow will continue as though a result or "normal"
+ // timeout had been produced. In case of a real backstop timeout, mResult
+ // will still contain the value it was constructed with, AGENT_ERROR, which
+ // intentionaly falls into the "just report failure" code.
+ mLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS);
+
+ long totalSize = mResult.get();
+ // If preflight timed out, mResult will contain error code as int.
+ if (totalSize < 0) {
+ return (int) totalSize;
+ }
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "Got preflight response; size=" + totalSize);
+ }
+
+ result = mTransport.checkFullBackupSize(totalSize);
+ if (result == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Package hit quota limit on preflight " +
+ pkg.packageName + ": " + totalSize + " of " + mQuota);
+ }
+ agent.doQuotaExceeded(totalSize, mQuota);
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception preflighting " + pkg.packageName + ": " + e.getMessage());
+ result = BackupTransport.AGENT_ERROR;
+ }
+ return result;
+ }
+
+ @Override
+ public void execute() {
+ // Unused.
+ }
+
+ @Override
+ public void operationComplete(long result) {
+ // got the callback, and our preflightFullBackup() method is waiting for the result
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Preflight op complete, result=" + result);
+ }
+ mResult.set(result);
+ mLatch.countDown();
+ backupManagerService.removeOperation(mCurrentOpToken);
+ }
+
+ @Override
+ public void handleCancel(boolean cancelAll) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Preflight cancelled; failing");
+ }
+ mResult.set(BackupTransport.AGENT_ERROR);
+ mLatch.countDown();
+ backupManagerService.removeOperation(mCurrentOpToken);
+ }
+
+ @Override
+ public long getExpectedSizeOrErrorCode() {
+ try {
+ mLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS);
+ return mResult.get();
+ } catch (InterruptedException e) {
+ return BackupTransport.NO_MORE_DATA;
+ }
+ }
+ }
+
+ class SinglePackageBackupRunner implements Runnable, BackupRestoreTask {
+ final ParcelFileDescriptor mOutput;
+ final PackageInfo mTarget;
+ final SinglePackageBackupPreflight mPreflight;
+ final CountDownLatch mPreflightLatch;
+ final CountDownLatch mBackupLatch;
+ private final int mCurrentOpToken;
+ private final int mEphemeralToken;
+ private FullBackupEngine mEngine;
+ private volatile int mPreflightResult;
+ private volatile int mBackupResult;
+ private final long mQuota;
+ private volatile boolean mIsCancelled;
+
+ SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target,
+ IBackupTransport transport, long quota, int currentOpToken) throws IOException {
+ mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor());
+ mTarget = target;
+ mCurrentOpToken = currentOpToken;
+ mEphemeralToken = backupManagerService.generateRandomIntegerToken();
+ mPreflight = new SinglePackageBackupPreflight(transport, quota, mEphemeralToken);
+ mPreflightLatch = new CountDownLatch(1);
+ mBackupLatch = new CountDownLatch(1);
+ mPreflightResult = BackupTransport.AGENT_ERROR;
+ mBackupResult = BackupTransport.AGENT_ERROR;
+ mQuota = quota;
+ registerTask();
+ }
+
+ void registerTask() {
+ synchronized (backupManagerService.getCurrentOpLock()) {
+ backupManagerService.getCurrentOperations().put(
+ mCurrentOpToken, new Operation(OP_PENDING, this, OP_TYPE_BACKUP_WAIT));
+ }
+ }
+
+ void unregisterTask() {
+ synchronized (backupManagerService.getCurrentOpLock()) {
+ backupManagerService.getCurrentOperations().remove(mCurrentOpToken);
+ }
+ }
+
+ @Override
+ public void run() {
+ FileOutputStream out = new FileOutputStream(mOutput.getFileDescriptor());
+ mEngine = new FullBackupEngine(backupManagerService, out, mPreflight, mTarget, false,
+ this, mQuota, mCurrentOpToken);
+ try {
+ try {
+ if (!mIsCancelled) {
+ mPreflightResult = mEngine.preflightCheck();
+ }
+ } finally {
+ mPreflightLatch.countDown();
+ }
+ // If there is no error on preflight, continue backup.
+ if (mPreflightResult == BackupTransport.TRANSPORT_OK) {
+ if (!mIsCancelled) {
+ mBackupResult = mEngine.backupOnePackage();
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception during full package backup of " + mTarget.packageName);
+ } finally {
+ unregisterTask();
+ mBackupLatch.countDown();
+ try {
+ mOutput.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Error closing transport pipe in runner");
+ }
+ }
+ }
+
+ public void sendQuotaExceeded(final long backupDataBytes, final long quotaBytes) {
+ mEngine.sendQuotaExceeded(backupDataBytes, quotaBytes);
+ }
+
+ // If preflight succeeded, returns positive number - preflight size,
+ // otherwise return negative error code.
+ long getPreflightResultBlocking() {
+ try {
+ mPreflightLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS);
+ if (mIsCancelled) {
+ return BackupManager.ERROR_BACKUP_CANCELLED;
+ }
+ if (mPreflightResult == BackupTransport.TRANSPORT_OK) {
+ return mPreflight.getExpectedSizeOrErrorCode();
+ } else {
+ return mPreflightResult;
+ }
+ } catch (InterruptedException e) {
+ return BackupTransport.AGENT_ERROR;
+ }
+ }
+
+ int getBackupResultBlocking() {
+ try {
+ mBackupLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS);
+ if (mIsCancelled) {
+ return BackupManager.ERROR_BACKUP_CANCELLED;
+ }
+ return mBackupResult;
+ } catch (InterruptedException e) {
+ return BackupTransport.AGENT_ERROR;
+ }
+ }
+
+
+ // BackupRestoreTask interface: specifically, timeout detection
+
+ @Override
+ public void execute() { /* intentionally empty */ }
+
+ @Override
+ public void operationComplete(long result) { /* intentionally empty */ }
+
+ @Override
+ public void handleCancel(boolean cancelAll) {
+ if (DEBUG) {
+ Slog.w(TAG, "Full backup cancel of " + mTarget.packageName);
+ }
+
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_CANCEL,
+ mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
+ mIsCancelled = true;
+ // Cancel tasks spun off by this task.
+ backupManagerService.handleCancel(mEphemeralToken, cancelAll);
+ backupManagerService.tearDownAgentAndKill(mTarget.applicationInfo);
+ // Free up everyone waiting on this task and its children.
+ mPreflightLatch.countDown();
+ mBackupLatch.countDown();
+ // We are done with this operation.
+ backupManagerService.removeOperation(mCurrentOpToken);
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
new file mode 100644
index 0000000..886d1f8
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.internal;
+
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_RESTORE_INTERVAL;
+
+import android.app.AlarmManager;
+import android.app.backup.RestoreSet;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.EventLog;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.EventLogTags;
+import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.fullbackup.PerformAdbBackupTask;
+import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
+import com.android.server.backup.params.AdbBackupParams;
+import com.android.server.backup.params.AdbParams;
+import com.android.server.backup.params.AdbRestoreParams;
+import com.android.server.backup.params.BackupParams;
+import com.android.server.backup.params.ClearParams;
+import com.android.server.backup.params.ClearRetryParams;
+import com.android.server.backup.params.RestoreGetSetsParams;
+import com.android.server.backup.params.RestoreParams;
+import com.android.server.backup.restore.PerformAdbRestoreTask;
+import com.android.server.backup.restore.PerformUnifiedRestoreTask;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+
+/**
+ * Asynchronous backup/restore handler thread.
+ */
+public class BackupHandler extends Handler {
+
+ public static final int MSG_RUN_BACKUP = 1;
+ public static final int MSG_RUN_ADB_BACKUP = 2;
+ public static final int MSG_RUN_RESTORE = 3;
+ public static final int MSG_RUN_CLEAR = 4;
+ public static final int MSG_RUN_INITIALIZE = 5;
+ public static final int MSG_RUN_GET_RESTORE_SETS = 6;
+ public static final int MSG_RESTORE_SESSION_TIMEOUT = 8;
+ public static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9;
+ public static final int MSG_RUN_ADB_RESTORE = 10;
+ public static final int MSG_RETRY_INIT = 11;
+ public static final int MSG_RETRY_CLEAR = 12;
+ public static final int MSG_WIDGET_BROADCAST = 13;
+ public static final int MSG_RUN_FULL_TRANSPORT_BACKUP = 14;
+ public static final int MSG_REQUEST_BACKUP = 15;
+ public static final int MSG_SCHEDULE_BACKUP_PACKAGE = 16;
+ public static final int MSG_BACKUP_OPERATION_TIMEOUT = 17;
+ public static final int MSG_RESTORE_OPERATION_TIMEOUT = 18;
+ // backup task state machine tick
+ public static final int MSG_BACKUP_RESTORE_STEP = 20;
+ public static final int MSG_OP_COMPLETE = 21;
+
+ private RefactoredBackupManagerService backupManagerService;
+
+ public BackupHandler(
+ RefactoredBackupManagerService backupManagerService, Looper looper) {
+ super(looper);
+ this.backupManagerService = backupManagerService;
+ }
+
+ public void handleMessage(Message msg) {
+
+ switch (msg.what) {
+ case MSG_RUN_BACKUP: {
+ backupManagerService.setLastBackupPass(System.currentTimeMillis());
+
+ IBackupTransport transport =
+ backupManagerService.getTransportManager().getCurrentTransportBinder();
+ if (transport == null) {
+ Slog.v(TAG, "Backup requested but no transport available");
+ synchronized (backupManagerService.getQueueLock()) {
+ backupManagerService.setBackupRunning(false);
+ }
+ backupManagerService.getWakelock().release();
+ break;
+ }
+
+ // snapshot the pending-backup set and work on that
+ ArrayList<BackupRequest> queue = new ArrayList<>();
+ File oldJournal = backupManagerService.getJournal();
+ synchronized (backupManagerService.getQueueLock()) {
+ // Do we have any work to do? Construct the work queue
+ // then release the synchronization lock to actually run
+ // the backup.
+ if (backupManagerService.getPendingBackups().size() > 0) {
+ for (BackupRequest b : backupManagerService.getPendingBackups().values()) {
+ queue.add(b);
+ }
+ if (DEBUG) {
+ Slog.v(TAG, "clearing pending backups");
+ }
+ backupManagerService.getPendingBackups().clear();
+
+ // Start a new backup-queue journal file too
+ backupManagerService.setJournal(null);
+
+ }
+ }
+
+ // At this point, we have started a new journal file, and the old
+ // file identity is being passed to the backup processing task.
+ // When it completes successfully, that old journal file will be
+ // deleted. If we crash prior to that, the old journal is parsed
+ // at next boot and the journaled requests fulfilled.
+ boolean staged = true;
+ if (queue.size() > 0) {
+ // Spin up a backup state sequence and set it running
+ try {
+ String dirName = transport.transportDirName();
+ PerformBackupTask pbt = new PerformBackupTask(
+ backupManagerService, transport, dirName, queue,
+ oldJournal, null, null, Collections.<String>emptyList(), false,
+ false /* nonIncremental */);
+ Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
+ sendMessage(pbtMessage);
+ } catch (Exception e) {
+ // unable to ask the transport its dir name -- transient failure, since
+ // the above check succeeded. Try again next time.
+ Slog.e(TAG, "Transport became unavailable attempting backup"
+ + " or error initializing backup task", e);
+ staged = false;
+ }
+ } else {
+ Slog.v(TAG, "Backup requested but nothing pending");
+ staged = false;
+ }
+
+ if (!staged) {
+ // if we didn't actually hand off the wakelock, rewind until next time
+ synchronized (backupManagerService.getQueueLock()) {
+ backupManagerService.setBackupRunning(false);
+ }
+ backupManagerService.getWakelock().release();
+ }
+ break;
+ }
+
+ case MSG_BACKUP_RESTORE_STEP: {
+ try {
+ BackupRestoreTask task = (BackupRestoreTask) msg.obj;
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "Got next step for " + task + ", executing");
+ }
+ task.execute();
+ } catch (ClassCastException e) {
+ Slog.e(TAG, "Invalid backup task in flight, obj=" + msg.obj);
+ }
+ break;
+ }
+
+ case MSG_OP_COMPLETE: {
+ try {
+ Pair<BackupRestoreTask, Long> taskWithResult =
+ (Pair<BackupRestoreTask, Long>) msg.obj;
+ taskWithResult.first.operationComplete(taskWithResult.second);
+ } catch (ClassCastException e) {
+ Slog.e(TAG, "Invalid completion in flight, obj=" + msg.obj);
+ }
+ break;
+ }
+
+ case MSG_RUN_ADB_BACKUP: {
+ // TODO: refactor full backup to be a looper-based state machine
+ // similar to normal backup/restore.
+ AdbBackupParams params = (AdbBackupParams) msg.obj;
+ PerformAdbBackupTask task = new PerformAdbBackupTask(backupManagerService,
+ params.fd,
+ params.observer, params.includeApks, params.includeObbs,
+ params.includeShared, params.doWidgets, params.curPassword,
+ params.encryptPassword, params.allApps, params.includeSystem,
+ params.doCompress, params.includeKeyValue, params.packages, params.latch);
+ (new Thread(task, "adb-backup")).start();
+ break;
+ }
+
+ case MSG_RUN_FULL_TRANSPORT_BACKUP: {
+ PerformFullTransportBackupTask task = (PerformFullTransportBackupTask) msg.obj;
+ (new Thread(task, "transport-backup")).start();
+ break;
+ }
+
+ case MSG_RUN_RESTORE: {
+ RestoreParams params = (RestoreParams) msg.obj;
+ Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
+
+ PerformUnifiedRestoreTask task = new PerformUnifiedRestoreTask(backupManagerService,
+ params.transport,
+ params.observer, params.monitor, params.token, params.pkgInfo,
+ params.pmToken, params.isSystemRestore, params.filterSet);
+
+ synchronized (backupManagerService.getPendingRestores()) {
+ if (backupManagerService.isRestoreInProgress()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Restore in progress, queueing.");
+ }
+ backupManagerService.getPendingRestores().add(task);
+ // This task will be picked up and executed when the the currently running
+ // restore task finishes.
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "Starting restore.");
+ }
+ backupManagerService.setRestoreInProgress(true);
+ Message restoreMsg = obtainMessage(MSG_BACKUP_RESTORE_STEP, task);
+ sendMessage(restoreMsg);
+ }
+ }
+ break;
+ }
+
+ case MSG_RUN_ADB_RESTORE: {
+ // TODO: refactor full restore to be a looper-based state machine
+ // similar to normal backup/restore.
+ AdbRestoreParams params = (AdbRestoreParams) msg.obj;
+ PerformAdbRestoreTask task = new PerformAdbRestoreTask(backupManagerService,
+ params.fd,
+ params.curPassword, params.encryptPassword,
+ params.observer, params.latch);
+ (new Thread(task, "adb-restore")).start();
+ break;
+ }
+
+ case MSG_RUN_CLEAR: {
+ ClearParams params = (ClearParams) msg.obj;
+ (new PerformClearTask(backupManagerService, params.transport,
+ params.packageInfo)).run();
+ break;
+ }
+
+ case MSG_RETRY_CLEAR: {
+ // reenqueues if the transport remains unavailable
+ ClearRetryParams params = (ClearRetryParams) msg.obj;
+ backupManagerService.clearBackupData(params.transportName, params.packageName);
+ break;
+ }
+
+ case MSG_RUN_INITIALIZE: {
+ HashSet<String> queue;
+
+ // Snapshot the pending-init queue and work on that
+ synchronized (backupManagerService.getQueueLock()) {
+ queue = new HashSet<>(backupManagerService.getPendingInits());
+ backupManagerService.getPendingInits().clear();
+ }
+
+ (new PerformInitializeTask(backupManagerService, queue)).run();
+ break;
+ }
+
+ case MSG_RETRY_INIT: {
+ synchronized (backupManagerService.getQueueLock()) {
+ backupManagerService.recordInitPendingLocked(msg.arg1 != 0, (String) msg.obj);
+ backupManagerService.getAlarmManager().set(AlarmManager.RTC_WAKEUP,
+ System.currentTimeMillis(),
+ backupManagerService.getRunInitIntent());
+ }
+ break;
+ }
+
+ case MSG_RUN_GET_RESTORE_SETS: {
+ // Like other async operations, this is entered with the wakelock held
+ RestoreSet[] sets = null;
+ RestoreGetSetsParams params = (RestoreGetSetsParams) msg.obj;
+ try {
+ sets = params.transport.getAvailableRestoreSets();
+ // cache the result in the active session
+ synchronized (params.session) {
+ params.session.mRestoreSets = sets;
+ }
+ if (sets == null) {
+ EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Error from transport getting set list: " + e.getMessage());
+ } finally {
+ if (params.observer != null) {
+ try {
+ params.observer.restoreSetsAvailable(sets);
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to report listing to observer");
+ } catch (Exception e) {
+ Slog.e(TAG, "Restore observer threw: " + e.getMessage());
+ }
+ }
+
+ // Done: reset the session timeout clock
+ removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
+ sendEmptyMessageDelayed(MSG_RESTORE_SESSION_TIMEOUT, TIMEOUT_RESTORE_INTERVAL);
+
+ backupManagerService.getWakelock().release();
+ }
+ break;
+ }
+
+ case MSG_BACKUP_OPERATION_TIMEOUT:
+ case MSG_RESTORE_OPERATION_TIMEOUT: {
+ Slog.d(TAG, "Timeout message received for token=" + Integer.toHexString(msg.arg1));
+ backupManagerService.handleCancel(msg.arg1, false);
+ break;
+ }
+
+ case MSG_RESTORE_SESSION_TIMEOUT: {
+ synchronized (backupManagerService) {
+ if (backupManagerService.getActiveRestoreSession() != null) {
+ // Client app left the restore session dangling. We know that it
+ // can't be in the middle of an actual restore operation because
+ // the timeout is suspended while a restore is in progress. Clean
+ // up now.
+ Slog.w(TAG, "Restore session timed out; aborting");
+ backupManagerService.getActiveRestoreSession().markTimedOut();
+ post(backupManagerService.getActiveRestoreSession().new EndRestoreRunnable(
+ backupManagerService,
+ backupManagerService.getActiveRestoreSession()));
+ }
+ }
+ break;
+ }
+
+ case MSG_FULL_CONFIRMATION_TIMEOUT: {
+ synchronized (backupManagerService.getAdbBackupRestoreConfirmations()) {
+ AdbParams params = backupManagerService.getAdbBackupRestoreConfirmations().get(
+ msg.arg1);
+ if (params != null) {
+ Slog.i(TAG, "Full backup/restore timed out waiting for user confirmation");
+
+ // Release the waiter; timeout == completion
+ backupManagerService.signalAdbBackupRestoreCompletion(params);
+
+ // Remove the token from the set
+ backupManagerService.getAdbBackupRestoreConfirmations().delete(msg.arg1);
+
+ // Report a timeout to the observer, if any
+ if (params.observer != null) {
+ try {
+ params.observer.onTimeout();
+ } catch (RemoteException e) {
+ /* don't care if the app has gone away */
+ }
+ }
+ } else {
+ Slog.d(TAG, "couldn't find params for token " + msg.arg1);
+ }
+ }
+ break;
+ }
+
+ case MSG_WIDGET_BROADCAST: {
+ final Intent intent = (Intent) msg.obj;
+ backupManagerService.getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM);
+ break;
+ }
+
+ case MSG_REQUEST_BACKUP: {
+ BackupParams params = (BackupParams) msg.obj;
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "MSG_REQUEST_BACKUP observer=" + params.observer);
+ }
+ ArrayList<BackupRequest> kvQueue = new ArrayList<>();
+ for (String packageName : params.kvPackages) {
+ kvQueue.add(new BackupRequest(packageName));
+ }
+ backupManagerService.setBackupRunning(true);
+ backupManagerService.getWakelock().acquire();
+
+ PerformBackupTask pbt = new PerformBackupTask(
+ backupManagerService,
+ params.transport, params.dirName,
+ kvQueue, null, params.observer, params.monitor, params.fullPackages, true,
+ params.nonIncrementalBackup);
+ Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
+ sendMessage(pbtMessage);
+ break;
+ }
+
+ case MSG_SCHEDULE_BACKUP_PACKAGE: {
+ String pkgName = (String) msg.obj;
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "MSG_SCHEDULE_BACKUP_PACKAGE " + pkgName);
+ }
+ backupManagerService.dataChangedImpl(pkgName);
+ break;
+ }
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/internal/BackupRequest.java b/services/backup/java/com/android/server/backup/internal/BackupRequest.java
new file mode 100644
index 0000000..20c3cc4
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/internal/BackupRequest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.internal;
+
+/**
+ * Set of backup services that have pending changes.
+ */
+public class BackupRequest {
+
+ public String packageName;
+
+ public BackupRequest(String pkgName) {
+ packageName = pkgName;
+ }
+
+ public String toString() {
+ return "BackupRequest{pkg=" + packageName + "}";
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/internal/BackupState.java b/services/backup/java/com/android/server/backup/internal/BackupState.java
new file mode 100644
index 0000000..4d42c24
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/internal/BackupState.java
@@ -0,0 +1,10 @@
+package com.android.server.backup.internal;
+
+/**
+ * Current state of the backup.
+ */
+enum BackupState {
+ INITIAL,
+ RUNNING_QUEUE,
+ FINAL
+}
diff --git a/services/backup/java/com/android/server/backup/internal/ClearDataObserver.java b/services/backup/java/com/android/server/backup/internal/ClearDataObserver.java
new file mode 100644
index 0000000..d82c865
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/internal/ClearDataObserver.java
@@ -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
+ */
+
+package com.android.server.backup.internal;
+
+import android.content.pm.IPackageDataObserver;
+
+import com.android.server.backup.RefactoredBackupManagerService;
+
+public class ClearDataObserver extends IPackageDataObserver.Stub {
+
+ private RefactoredBackupManagerService backupManagerService;
+
+ public ClearDataObserver(RefactoredBackupManagerService backupManagerService) {
+ this.backupManagerService = backupManagerService;
+ }
+
+ public void onRemoveCompleted(String packageName, boolean succeeded) {
+ synchronized (backupManagerService.getClearDataLock()) {
+ backupManagerService.setClearingData(false);
+ backupManagerService.getClearDataLock().notifyAll();
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/internal/Operation.java b/services/backup/java/com/android/server/backup/internal/Operation.java
new file mode 100644
index 0000000..fd5ad92
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/internal/Operation.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.internal;
+
+import com.android.server.backup.BackupRestoreTask;
+
+public class Operation {
+
+ public int state;
+ public final BackupRestoreTask callback;
+ public final int type;
+
+ public Operation(int initialState, BackupRestoreTask callbackObj, int type) {
+ state = initialState;
+ callback = callbackObj;
+ this.type = type;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
new file mode 100644
index 0000000..a996e2d
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
@@ -0,0 +1,1118 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.internal;
+
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG_BACKUP_TRACE;
+import static com.android.server.backup.RefactoredBackupManagerService.KEY_WIDGET_STATE;
+import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.OP_PENDING;
+import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_BACKUP;
+import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_BACKUP_WAIT;
+import static com.android.server.backup.RefactoredBackupManagerService.PACKAGE_MANAGER_SENTINEL;
+import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_BACKUP_INTERVAL;
+import static com.android.server.backup.internal.BackupHandler.MSG_BACKUP_OPERATION_TIMEOUT;
+import static com.android.server.backup.internal.BackupHandler.MSG_BACKUP_RESTORE_STEP;
+
+import android.app.ApplicationThreadConstants;
+import android.app.IBackupAgent;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupManager;
+import android.app.backup.BackupManagerMonitor;
+import android.app.backup.BackupTransport;
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IBackupObserver;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.SELinux;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.EventLog;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.AppWidgetBackupBridge;
+import com.android.server.EventLogTags;
+import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.KeyValueBackupJob;
+import com.android.server.backup.PackageManagerBackupAgent;
+import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
+import com.android.server.backup.utils.AppBackupUtils;
+import com.android.server.backup.utils.BackupManagerMonitorUtils;
+import com.android.server.backup.utils.BackupObserverUtils;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * This class handles the process of backing up a given list of key/value backup packages.
+ * Also takes in a list of pending dolly backups and kicks them off when key/value backups
+ * are done.
+ *
+ * Flow:
+ * If required, backup @pm@.
+ * For each pending key/value backup package:
+ * - Bind to agent.
+ * - Call agent.doBackup()
+ * - Wait either for cancel/timeout or operationComplete() callback from the agent.
+ * Start task to perform dolly backups.
+ *
+ * There are three entry points into this class:
+ * - execute() [Called from the handler thread]
+ * - operationComplete(long result) [Called from the handler thread]
+ * - handleCancel(boolean cancelAll) [Can be called from any thread]
+ * These methods synchronize on mCancelLock.
+ *
+ * Interaction with mCurrentOperations:
+ * - An entry for this task is put into mCurrentOperations for the entire lifetime of the
+ * task. This is useful to cancel the task if required.
+ * - An ephemeral entry is put into mCurrentOperations each time we are waiting on for
+ * response from a backup agent. This is used to plumb timeouts and completion callbacks.
+ */
+public class PerformBackupTask implements BackupRestoreTask {
+ private static final String TAG = "PerformBackupTask";
+
+ private RefactoredBackupManagerService backupManagerService;
+ private final Object mCancelLock = new Object();
+
+ IBackupTransport mTransport;
+ ArrayList<BackupRequest> mQueue;
+ ArrayList<BackupRequest> mOriginalQueue;
+ File mStateDir;
+ File mJournal;
+ BackupState mCurrentState;
+ List<String> mPendingFullBackups;
+ IBackupObserver mObserver;
+ IBackupManagerMonitor mMonitor;
+
+ private final PerformFullTransportBackupTask mFullBackupTask;
+ private final int mCurrentOpToken;
+ private volatile int mEphemeralOpToken;
+
+ // carried information about the current in-flight operation
+ IBackupAgent mAgentBinder;
+ PackageInfo mCurrentPackage;
+ File mSavedStateName;
+ File mBackupDataName;
+ File mNewStateName;
+ ParcelFileDescriptor mSavedState;
+ ParcelFileDescriptor mBackupData;
+ ParcelFileDescriptor mNewState;
+ int mStatus;
+ boolean mFinished;
+ final boolean mUserInitiated;
+ final boolean mNonIncremental;
+
+ private volatile boolean mCancelAll;
+
+ public PerformBackupTask(RefactoredBackupManagerService backupManagerService,
+ IBackupTransport transport, String dirName,
+ ArrayList<BackupRequest> queue, File journal, IBackupObserver observer,
+ IBackupManagerMonitor monitor, List<String> pendingFullBackups,
+ boolean userInitiated, boolean nonIncremental) {
+ this.backupManagerService = backupManagerService;
+ mTransport = transport;
+ mOriginalQueue = queue;
+ mQueue = new ArrayList<>();
+ mJournal = journal;
+ mObserver = observer;
+ mMonitor = monitor;
+ mPendingFullBackups = pendingFullBackups;
+ mUserInitiated = userInitiated;
+ mNonIncremental = nonIncremental;
+
+ mStateDir = new File(backupManagerService.getBaseStateDir(), dirName);
+ mCurrentOpToken = backupManagerService.generateRandomIntegerToken();
+
+ mFinished = false;
+
+ synchronized (backupManagerService.getCurrentOpLock()) {
+ if (backupManagerService.isBackupOperationInProgress()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Skipping backup since one is already in progress.");
+ }
+ mCancelAll = true;
+ mFullBackupTask = null;
+ mCurrentState = BackupState.FINAL;
+ backupManagerService.addBackupTrace("Skipped. Backup already in progress.");
+ } else {
+ mCurrentState = BackupState.INITIAL;
+ CountDownLatch latch = new CountDownLatch(1);
+ String[] fullBackups =
+ mPendingFullBackups.toArray(new String[mPendingFullBackups.size()]);
+ mFullBackupTask =
+ new PerformFullTransportBackupTask(backupManagerService,
+ /*fullBackupRestoreObserver*/
+ null,
+ fullBackups, /*updateSchedule*/ false, /*runningJob*/ null,
+ latch,
+ mObserver, mMonitor, mUserInitiated);
+
+ registerTask();
+ backupManagerService.addBackupTrace("STATE => INITIAL");
+ }
+ }
+ }
+
+ /**
+ * Put this task in the repository of running tasks.
+ */
+ private void registerTask() {
+ synchronized (backupManagerService.getCurrentOpLock()) {
+ backupManagerService.getCurrentOperations().put(
+ mCurrentOpToken, new Operation(OP_PENDING, this, OP_TYPE_BACKUP));
+ }
+ }
+
+ /**
+ * Remove this task from repository of running tasks.
+ */
+ private void unregisterTask() {
+ backupManagerService.removeOperation(mCurrentOpToken);
+ }
+
+ // Main entry point: perform one chunk of work, updating the state as appropriate
+ // and reposting the next chunk to the primary backup handler thread.
+ @Override
+ @GuardedBy("mCancelLock")
+ public void execute() {
+ synchronized (mCancelLock) {
+ switch (mCurrentState) {
+ case INITIAL:
+ beginBackup();
+ break;
+
+ case RUNNING_QUEUE:
+ invokeNextAgent();
+ break;
+
+ case FINAL:
+ if (!mFinished) {
+ finalizeBackup();
+ } else {
+ Slog.e(TAG, "Duplicate finish");
+ }
+ mFinished = true;
+ break;
+ }
+ }
+ }
+
+ // We're starting a backup pass. Initialize the transport and send
+ // the PM metadata blob if we haven't already.
+ void beginBackup() {
+ if (DEBUG_BACKUP_TRACE) {
+ backupManagerService.clearBackupTrace();
+ StringBuilder b = new StringBuilder(256);
+ b.append("beginBackup: [");
+ for (BackupRequest req : mOriginalQueue) {
+ b.append(' ');
+ b.append(req.packageName);
+ }
+ b.append(" ]");
+ backupManagerService.addBackupTrace(b.toString());
+ }
+
+ mAgentBinder = null;
+ mStatus = BackupTransport.TRANSPORT_OK;
+
+ // Sanity check: if the queue is empty we have no work to do.
+ if (mOriginalQueue.isEmpty() && mPendingFullBackups.isEmpty()) {
+ Slog.w(TAG, "Backup begun with an empty queue - nothing to do.");
+ backupManagerService.addBackupTrace("queue empty at begin");
+ BackupObserverUtils.sendBackupFinished(mObserver, BackupManager.SUCCESS);
+ executeNextState(BackupState.FINAL);
+ return;
+ }
+
+ // We need to retain the original queue contents in case of transport
+ // failure, but we want a working copy that we can manipulate along
+ // the way.
+ mQueue = (ArrayList<BackupRequest>) mOriginalQueue.clone();
+
+ // When the transport is forcing non-incremental key/value payloads, we send the
+ // metadata only if it explicitly asks for it.
+ boolean skipPm = mNonIncremental;
+
+ // The app metadata pseudopackage might also be represented in the
+ // backup queue if apps have been added/removed since the last time
+ // we performed a backup. Drop it from the working queue now that
+ // we're committed to evaluating it for backup regardless.
+ for (int i = 0; i < mQueue.size(); i++) {
+ if (PACKAGE_MANAGER_SENTINEL.equals(
+ mQueue.get(i).packageName)) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Metadata in queue; eliding");
+ }
+ mQueue.remove(i);
+ skipPm = false;
+ break;
+ }
+ }
+
+ if (DEBUG) {
+ Slog.v(TAG, "Beginning backup of " + mQueue.size() + " targets");
+ }
+
+ File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL);
+ try {
+ final String transportName = mTransport.transportDirName();
+ EventLog.writeEvent(EventLogTags.BACKUP_START, transportName);
+
+ // If we haven't stored package manager metadata yet, we must init the transport.
+ if (mStatus == BackupTransport.TRANSPORT_OK && pmState.length() <= 0) {
+ Slog.i(TAG, "Initializing (wiping) backup state and transport storage");
+ backupManagerService.addBackupTrace("initializing transport " + transportName);
+ backupManagerService.resetBackupState(mStateDir); // Just to make sure.
+ mStatus = mTransport.initializeDevice();
+
+ backupManagerService.addBackupTrace("transport.initializeDevice() == " + mStatus);
+ if (mStatus == BackupTransport.TRANSPORT_OK) {
+ EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE);
+ } else {
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, "(initialize)");
+ Slog.e(TAG, "Transport error in initializeDevice()");
+ }
+ }
+
+ if (skipPm) {
+ Slog.d(TAG, "Skipping backup of package metadata.");
+ executeNextState(BackupState.RUNNING_QUEUE);
+ } else {
+ // The package manager doesn't have a proper <application> etc, but since
+ // it's running here in the system process we can just set up its agent
+ // directly and use a synthetic BackupRequest. We always run this pass
+ // because it's cheap and this way we guarantee that we don't get out of
+ // step even if we're selecting among various transports at run time.
+ if (mStatus == BackupTransport.TRANSPORT_OK) {
+ PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
+ backupManagerService.getPackageManager());
+ mStatus = invokeAgentForBackup(
+ PACKAGE_MANAGER_SENTINEL,
+ IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
+ backupManagerService.addBackupTrace("PMBA invoke: " + mStatus);
+
+ // Because the PMBA is a local instance, it has already executed its
+ // backup callback and returned. Blow away the lingering (spurious)
+ // pending timeout message for it.
+ backupManagerService.getBackupHandler().removeMessages(
+ MSG_BACKUP_OPERATION_TIMEOUT);
+ }
+ }
+
+ if (mStatus == BackupTransport.TRANSPORT_NOT_INITIALIZED) {
+ // The backend reports that our dataset has been wiped. Note this in
+ // the event log; the no-success code below will reset the backup
+ // state as well.
+ EventLog.writeEvent(EventLogTags.BACKUP_RESET, mTransport.transportDirName());
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Error in backup thread", e);
+ backupManagerService.addBackupTrace("Exception in backup thread: " + e);
+ mStatus = BackupTransport.TRANSPORT_ERROR;
+ } finally {
+ // If we've succeeded so far, invokeAgentForBackup() will have run the PM
+ // metadata and its completion/timeout callback will continue the state
+ // machine chain. If it failed that won't happen; we handle that now.
+ backupManagerService.addBackupTrace("exiting prelim: " + mStatus);
+ if (mStatus != BackupTransport.TRANSPORT_OK) {
+ // if things went wrong at this point, we need to
+ // restage everything and try again later.
+ backupManagerService.resetBackupState(mStateDir); // Just to make sure.
+ // In case of any other error, it's backup transport error.
+ BackupObserverUtils.sendBackupFinished(mObserver,
+ BackupManager.ERROR_TRANSPORT_ABORTED);
+ executeNextState(BackupState.FINAL);
+ }
+ }
+ }
+
+ // Transport has been initialized and the PM metadata submitted successfully
+ // if that was warranted. Now we process the single next thing in the queue.
+ void invokeNextAgent() {
+ mStatus = BackupTransport.TRANSPORT_OK;
+ backupManagerService.addBackupTrace("invoke q=" + mQueue.size());
+
+ // Sanity check that we have work to do. If not, skip to the end where
+ // we reestablish the wakelock invariants etc.
+ if (mQueue.isEmpty()) {
+ if (MORE_DEBUG) Slog.i(TAG, "queue now empty");
+ executeNextState(BackupState.FINAL);
+ return;
+ }
+
+ // pop the entry we're going to process on this step
+ BackupRequest request = mQueue.get(0);
+ mQueue.remove(0);
+
+ Slog.d(TAG, "starting key/value backup of " + request);
+ backupManagerService.addBackupTrace("launch agent for " + request.packageName);
+
+ // Verify that the requested app exists; it might be something that
+ // requested a backup but was then uninstalled. The request was
+ // journalled and rather than tamper with the journal it's safer
+ // to sanity-check here. This also gives us the classname of the
+ // package's backup agent.
+ try {
+ mCurrentPackage = backupManagerService.getPackageManager().getPackageInfo(
+ request.packageName,
+ PackageManager.GET_SIGNATURES);
+ if (!AppBackupUtils.appIsEligibleForBackup(
+ mCurrentPackage.applicationInfo)) {
+ // The manifest has changed but we had a stale backup request pending.
+ // This won't happen again because the app won't be requesting further
+ // backups.
+ Slog.i(TAG, "Package " + request.packageName
+ + " no longer supports backup; skipping");
+ backupManagerService.addBackupTrace("skipping - not eligible, completion is noop");
+ // Shouldn't happen in case of requested backup, as pre-check was done in
+ // #requestBackup(), except to app update done concurrently
+ BackupObserverUtils.sendBackupOnPackageResult(mObserver,
+ mCurrentPackage.packageName,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
+ executeNextState(BackupState.RUNNING_QUEUE);
+ return;
+ }
+
+ if (AppBackupUtils.appGetsFullBackup(mCurrentPackage)) {
+ // It's possible that this app *formerly* was enqueued for key/value backup,
+ // but has since been updated and now only supports the full-data path.
+ // Don't proceed with a key/value backup for it in this case.
+ Slog.i(TAG, "Package " + request.packageName
+ + " requests full-data rather than key/value; skipping");
+ backupManagerService.addBackupTrace(
+ "skipping - fullBackupOnly, completion is noop");
+ // Shouldn't happen in case of requested backup, as pre-check was done in
+ // #requestBackup()
+ BackupObserverUtils.sendBackupOnPackageResult(mObserver,
+ mCurrentPackage.packageName,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
+ executeNextState(BackupState.RUNNING_QUEUE);
+ return;
+ }
+
+ if (AppBackupUtils.appIsStopped(mCurrentPackage.applicationInfo)) {
+ // The app has been force-stopped or cleared or just installed,
+ // and not yet launched out of that state, so just as it won't
+ // receive broadcasts, we won't run it for backup.
+ backupManagerService.addBackupTrace("skipping - stopped");
+ BackupObserverUtils.sendBackupOnPackageResult(mObserver,
+ mCurrentPackage.packageName,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
+ executeNextState(BackupState.RUNNING_QUEUE);
+ return;
+ }
+
+ IBackupAgent agent = null;
+ try {
+ backupManagerService.getWakelock().setWorkSource(
+ new WorkSource(mCurrentPackage.applicationInfo.uid));
+ agent = backupManagerService.bindToAgentSynchronous(mCurrentPackage.applicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
+ backupManagerService.addBackupTrace("agent bound; a? = " + (agent != null));
+ if (agent != null) {
+ mAgentBinder = agent;
+ mStatus = invokeAgentForBackup(request.packageName, agent, mTransport);
+ // at this point we'll either get a completion callback from the
+ // agent, or a timeout message on the main handler. either way, we're
+ // done here as long as we're successful so far.
+ } else {
+ // Timeout waiting for the agent
+ mStatus = BackupTransport.AGENT_ERROR;
+ }
+ } catch (SecurityException ex) {
+ // Try for the next one.
+ Slog.d(TAG, "error in bind/backup", ex);
+ mStatus = BackupTransport.AGENT_ERROR;
+ backupManagerService.addBackupTrace("agent SE");
+ }
+ } catch (NameNotFoundException e) {
+ Slog.d(TAG, "Package does not exist; skipping");
+ backupManagerService.addBackupTrace("no such package");
+ mStatus = BackupTransport.AGENT_UNKNOWN;
+ } finally {
+ backupManagerService.getWakelock().setWorkSource(null);
+
+ // If there was an agent error, no timeout/completion handling will occur.
+ // That means we need to direct to the next state ourselves.
+ if (mStatus != BackupTransport.TRANSPORT_OK) {
+ BackupState nextState = BackupState.RUNNING_QUEUE;
+ mAgentBinder = null;
+
+ // An agent-level failure means we reenqueue this one agent for
+ // a later retry, but otherwise proceed normally.
+ if (mStatus == BackupTransport.AGENT_ERROR) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Agent failure for " + request.packageName
+ + " - restaging");
+ }
+ backupManagerService.dataChangedImpl(request.packageName);
+ mStatus = BackupTransport.TRANSPORT_OK;
+ if (mQueue.isEmpty()) nextState = BackupState.FINAL;
+ BackupObserverUtils
+ .sendBackupOnPackageResult(mObserver, mCurrentPackage.packageName,
+ BackupManager.ERROR_AGENT_FAILURE);
+ } else if (mStatus == BackupTransport.AGENT_UNKNOWN) {
+ // Failed lookup of the app, so we couldn't bring up an agent, but
+ // we're otherwise fine. Just drop it and go on to the next as usual.
+ mStatus = BackupTransport.TRANSPORT_OK;
+ BackupObserverUtils
+ .sendBackupOnPackageResult(mObserver, mCurrentPackage.packageName,
+ BackupManager.ERROR_PACKAGE_NOT_FOUND);
+ } else {
+ // Transport-level failure means we reenqueue everything
+ revertAndEndBackup();
+ nextState = BackupState.FINAL;
+ }
+
+ executeNextState(nextState);
+ } else {
+ // success case
+ backupManagerService.addBackupTrace("expecting completion/timeout callback");
+ }
+ }
+ }
+
+ void finalizeBackup() {
+ backupManagerService.addBackupTrace("finishing");
+
+ // Mark packages that we didn't backup (because backup was cancelled, etc.) as needing
+ // backup.
+ for (BackupRequest req : mQueue) {
+ backupManagerService.dataChangedImpl(req.packageName);
+ }
+
+ // Either backup was successful, in which case we of course do not need
+ // this pass's journal any more; or it failed, in which case we just
+ // re-enqueued all of these packages in the current active journal.
+ // Either way, we no longer need this pass's journal.
+ if (mJournal != null && !mJournal.delete()) {
+ Slog.e(TAG, "Unable to remove backup journal file " + mJournal);
+ }
+
+ // If everything actually went through and this is the first time we've
+ // done a backup, we can now record what the current backup dataset token
+ // is.
+ if ((backupManagerService.getCurrentToken() == 0) && (mStatus
+ == BackupTransport.TRANSPORT_OK)) {
+ backupManagerService.addBackupTrace("success; recording token");
+ try {
+ backupManagerService.setCurrentToken(mTransport.getCurrentRestoreSet());
+ backupManagerService.writeRestoreTokens();
+ } catch (Exception e) {
+ // nothing for it at this point, unfortunately, but this will be
+ // recorded the next time we fully succeed.
+ Slog.e(TAG, "Transport threw reporting restore set: " + e.getMessage());
+ backupManagerService.addBackupTrace("transport threw returning token");
+ }
+ }
+
+ // Set up the next backup pass - at this point we can set mBackupRunning
+ // to false to allow another pass to fire, because we're done with the
+ // state machine sequence and the wakelock is refcounted.
+ synchronized (backupManagerService.getQueueLock()) {
+ backupManagerService.setBackupRunning(false);
+ if (mStatus == BackupTransport.TRANSPORT_NOT_INITIALIZED) {
+ // Make sure we back up everything and perform the one-time init
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Server requires init; rerunning");
+ }
+ backupManagerService.addBackupTrace("init required; rerunning");
+ try {
+ final String name = backupManagerService.getTransportManager().getTransportName(
+ mTransport);
+ if (name != null) {
+ backupManagerService.getPendingInits().add(name);
+ } else {
+ if (DEBUG) {
+ Slog.w(TAG, "Couldn't find name of transport " + mTransport
+ + " for init");
+ }
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to query transport name for init: " + e.getMessage());
+ // swallow it and proceed; we don't rely on this
+ }
+ clearMetadata();
+ backupManagerService.backupNow();
+ }
+ }
+
+ backupManagerService.clearBackupTrace();
+
+ unregisterTask();
+
+ if (!mCancelAll && mStatus == BackupTransport.TRANSPORT_OK &&
+ mPendingFullBackups != null && !mPendingFullBackups.isEmpty()) {
+ Slog.d(TAG, "Starting full backups for: " + mPendingFullBackups);
+ // Acquiring wakelock for PerformFullTransportBackupTask before its start.
+ backupManagerService.getWakelock().acquire();
+ (new Thread(mFullBackupTask, "full-transport-requested")).start();
+ } else if (mCancelAll) {
+ if (mFullBackupTask != null) {
+ mFullBackupTask.unregisterTask();
+ }
+ BackupObserverUtils.sendBackupFinished(mObserver,
+ BackupManager.ERROR_BACKUP_CANCELLED);
+ } else {
+ mFullBackupTask.unregisterTask();
+ switch (mStatus) {
+ case BackupTransport.TRANSPORT_OK:
+ BackupObserverUtils.sendBackupFinished(mObserver,
+ BackupManager.SUCCESS);
+ break;
+ case BackupTransport.TRANSPORT_NOT_INITIALIZED:
+ BackupObserverUtils.sendBackupFinished(mObserver,
+ BackupManager.ERROR_TRANSPORT_ABORTED);
+ break;
+ case BackupTransport.TRANSPORT_ERROR:
+ default:
+ BackupObserverUtils.sendBackupFinished(mObserver,
+ BackupManager.ERROR_TRANSPORT_ABORTED);
+ break;
+ }
+ }
+ Slog.i(TAG, "K/V backup pass finished.");
+ // Only once we're entirely finished do we release the wakelock for k/v backup.
+ backupManagerService.getWakelock().release();
+ }
+
+ // Remove the PM metadata state. This will generate an init on the next pass.
+ void clearMetadata() {
+ final File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL);
+ if (pmState.exists()) pmState.delete();
+ }
+
+ // Invoke an agent's doBackup() and start a timeout message spinning on the main
+ // handler in case it doesn't get back to us.
+ int invokeAgentForBackup(String packageName, IBackupAgent agent,
+ IBackupTransport transport) {
+ if (DEBUG) {
+ Slog.d(TAG, "invokeAgentForBackup on " + packageName);
+ }
+ backupManagerService.addBackupTrace("invoking " + packageName);
+
+ File blankStateName = new File(mStateDir, "blank_state");
+ mSavedStateName = new File(mStateDir, packageName);
+ mBackupDataName = new File(backupManagerService.getDataDir(), packageName + ".data");
+ mNewStateName = new File(mStateDir, packageName + ".new");
+ if (MORE_DEBUG) Slog.d(TAG, "data file: " + mBackupDataName);
+
+ mSavedState = null;
+ mBackupData = null;
+ mNewState = null;
+
+ boolean callingAgent = false;
+ mEphemeralOpToken = backupManagerService.generateRandomIntegerToken();
+ try {
+ // Look up the package info & signatures. This is first so that if it
+ // throws an exception, there's no file setup yet that would need to
+ // be unraveled.
+ if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) {
+ // The metadata 'package' is synthetic; construct one and make
+ // sure our global state is pointed at it
+ mCurrentPackage = new PackageInfo();
+ mCurrentPackage.packageName = packageName;
+ }
+
+ // In a full backup, we pass a null ParcelFileDescriptor as
+ // the saved-state "file". For key/value backups we pass the old state if
+ // an incremental backup is required, and a blank state otherwise.
+ mSavedState = ParcelFileDescriptor.open(
+ mNonIncremental ? blankStateName : mSavedStateName,
+ ParcelFileDescriptor.MODE_READ_ONLY |
+ ParcelFileDescriptor.MODE_CREATE); // Make an empty file if necessary
+
+ mBackupData = ParcelFileDescriptor.open(mBackupDataName,
+ ParcelFileDescriptor.MODE_READ_WRITE |
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE);
+
+ if (!SELinux.restorecon(mBackupDataName)) {
+ Slog.e(TAG, "SELinux restorecon failed on " + mBackupDataName);
+ }
+
+ mNewState = ParcelFileDescriptor.open(mNewStateName,
+ ParcelFileDescriptor.MODE_READ_WRITE |
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE);
+
+ final long quota = mTransport.getBackupQuota(packageName, false /* isFullBackup */);
+ callingAgent = true;
+
+ // Initiate the target's backup pass
+ backupManagerService.addBackupTrace("setting timeout");
+ backupManagerService.prepareOperationTimeout(
+ mEphemeralOpToken, TIMEOUT_BACKUP_INTERVAL, this, OP_TYPE_BACKUP_WAIT);
+ backupManagerService.addBackupTrace("calling agent doBackup()");
+
+ agent.doBackup(mSavedState, mBackupData, mNewState, quota, mEphemeralOpToken,
+ backupManagerService.getBackupManagerBinder());
+ } catch (Exception e) {
+ Slog.e(TAG, "Error invoking for backup on " + packageName + ". " + e);
+ backupManagerService.addBackupTrace("exception: " + e);
+ EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName,
+ e.toString());
+ errorCleanup();
+ return callingAgent ? BackupTransport.AGENT_ERROR
+ : BackupTransport.TRANSPORT_ERROR;
+ } finally {
+ if (mNonIncremental) {
+ blankStateName.delete();
+ }
+ }
+
+ // At this point the agent is off and running. The next thing to happen will
+ // either be a callback from the agent, at which point we'll process its data
+ // for transport, or a timeout. Either way the next phase will happen in
+ // response to the TimeoutHandler interface callbacks.
+ backupManagerService.addBackupTrace("invoke success");
+ return BackupTransport.TRANSPORT_OK;
+ }
+
+ public void failAgent(IBackupAgent agent, String message) {
+ try {
+ agent.fail(message);
+ } catch (Exception e) {
+ Slog.w(TAG, "Error conveying failure to " + mCurrentPackage.packageName);
+ }
+ }
+
+ // SHA-1 a byte array and return the result in hex
+ private String SHA1Checksum(byte[] input) {
+ final byte[] checksum;
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ checksum = md.digest(input);
+ } catch (NoSuchAlgorithmException e) {
+ Slog.e(TAG, "Unable to use SHA-1!");
+ return "00";
+ }
+
+ StringBuffer sb = new StringBuffer(checksum.length * 2);
+ for (int i = 0; i < checksum.length; i++) {
+ sb.append(Integer.toHexString(checksum[i]));
+ }
+ return sb.toString();
+ }
+
+ private void writeWidgetPayloadIfAppropriate(FileDescriptor fd, String pkgName)
+ throws IOException {
+ // TODO: http://b/22388012
+ byte[] widgetState = AppWidgetBackupBridge.getWidgetState(pkgName,
+ UserHandle.USER_SYSTEM);
+ // has the widget state changed since last time?
+ final File widgetFile = new File(mStateDir, pkgName + "_widget");
+ final boolean priorStateExists = widgetFile.exists();
+
+ if (MORE_DEBUG) {
+ if (priorStateExists || widgetState != null) {
+ Slog.i(TAG, "Checking widget update: state=" + (widgetState != null)
+ + " prior=" + priorStateExists);
+ }
+ }
+
+ if (!priorStateExists && widgetState == null) {
+ // no prior state, no new state => nothing to do
+ return;
+ }
+
+ // if the new state is not null, we might need to compare checksums to
+ // determine whether to update the widget blob in the archive. If the
+ // widget state *is* null, we know a priori at this point that we simply
+ // need to commit a deletion for it.
+ String newChecksum = null;
+ if (widgetState != null) {
+ newChecksum = SHA1Checksum(widgetState);
+ if (priorStateExists) {
+ final String priorChecksum;
+ try (
+ FileInputStream fin = new FileInputStream(widgetFile);
+ DataInputStream in = new DataInputStream(fin)
+ ) {
+ priorChecksum = in.readUTF();
+ }
+ if (Objects.equals(newChecksum, priorChecksum)) {
+ // Same checksum => no state change => don't rewrite the widget data
+ return;
+ }
+ }
+ } // else widget state *became* empty, so we need to commit a deletion
+
+ BackupDataOutput out = new BackupDataOutput(fd);
+ if (widgetState != null) {
+ try (
+ FileOutputStream fout = new FileOutputStream(widgetFile);
+ DataOutputStream stateOut = new DataOutputStream(fout)
+ ) {
+ stateOut.writeUTF(newChecksum);
+ }
+
+ out.writeEntityHeader(KEY_WIDGET_STATE, widgetState.length);
+ out.writeEntityData(widgetState, widgetState.length);
+ } else {
+ // Widget state for this app has been removed; commit a deletion
+ out.writeEntityHeader(KEY_WIDGET_STATE, -1);
+ widgetFile.delete();
+ }
+ }
+
+ @Override
+ @GuardedBy("mCancelLock")
+ public void operationComplete(long unusedResult) {
+ backupManagerService.removeOperation(mEphemeralOpToken);
+ synchronized (mCancelLock) {
+ // The agent reported back to us!
+ if (mFinished) {
+ Slog.d(TAG, "operationComplete received after task finished.");
+ return;
+ }
+
+ if (mBackupData == null) {
+ // This callback was racing with our timeout, so we've cleaned up the
+ // agent state already and are on to the next thing. We have nothing
+ // further to do here: agent state having been cleared means that we've
+ // initiated the appropriate next operation.
+ final String pkg = (mCurrentPackage != null)
+ ? mCurrentPackage.packageName : "[none]";
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Callback after agent teardown: " + pkg);
+ }
+ backupManagerService.addBackupTrace("late opComplete; curPkg = " + pkg);
+ return;
+ }
+
+ final String pkgName = mCurrentPackage.packageName;
+ final long filepos = mBackupDataName.length();
+ FileDescriptor fd = mBackupData.getFileDescriptor();
+ try {
+ // If it's a 3rd party app, see whether they wrote any protected keys
+ // and complain mightily if they are attempting shenanigans.
+ if (mCurrentPackage.applicationInfo != null &&
+ (mCurrentPackage.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+ == 0) {
+ ParcelFileDescriptor readFd = ParcelFileDescriptor.open(mBackupDataName,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ BackupDataInput in = new BackupDataInput(readFd.getFileDescriptor());
+ try {
+ while (in.readNextHeader()) {
+ final String key = in.getKey();
+ if (key != null && key.charAt(0) >= 0xff00) {
+ // Not okay: crash them and bail.
+ failAgent(mAgentBinder, "Illegal backup key: " + key);
+ backupManagerService
+ .addBackupTrace("illegal key " + key + " from " + pkgName);
+ EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, pkgName,
+ "bad key");
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_ILLEGAL_KEY,
+ mCurrentPackage,
+ BackupManagerMonitor
+ .LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ BackupManagerMonitorUtils.putMonitoringExtra(null,
+ BackupManagerMonitor.EXTRA_LOG_ILLEGAL_KEY,
+ key));
+ backupManagerService.getBackupHandler().removeMessages(
+ MSG_BACKUP_OPERATION_TIMEOUT);
+ BackupObserverUtils
+ .sendBackupOnPackageResult(mObserver, pkgName,
+ BackupManager.ERROR_AGENT_FAILURE);
+ errorCleanup();
+ // agentErrorCleanup() implicitly executes next state properly
+ return;
+ }
+ in.skipEntityData();
+ }
+ } finally {
+ if (readFd != null) {
+ readFd.close();
+ }
+ }
+ }
+
+ // Piggyback the widget state payload, if any
+ writeWidgetPayloadIfAppropriate(fd, pkgName);
+ } catch (IOException e) {
+ // Hard disk error; recovery/failure policy TBD. For now roll back,
+ // but we may want to consider this a transport-level failure (i.e.
+ // we're in such a bad state that we can't contemplate doing backup
+ // operations any more during this pass).
+ Slog.w(TAG, "Unable to save widget state for " + pkgName);
+ try {
+ Os.ftruncate(fd, filepos);
+ } catch (ErrnoException ee) {
+ Slog.w(TAG, "Unable to roll back!");
+ }
+ }
+
+ // Spin the data off to the transport and proceed with the next stage.
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "operationComplete(): sending data to transport for "
+ + pkgName);
+ }
+ backupManagerService.getBackupHandler().removeMessages(MSG_BACKUP_OPERATION_TIMEOUT);
+ clearAgentState();
+ backupManagerService.addBackupTrace("operation complete");
+
+ ParcelFileDescriptor backupData = null;
+ mStatus = BackupTransport.TRANSPORT_OK;
+ long size = 0;
+ try {
+ size = mBackupDataName.length();
+ if (size > 0) {
+ if (mStatus == BackupTransport.TRANSPORT_OK) {
+ backupData = ParcelFileDescriptor.open(mBackupDataName,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ backupManagerService.addBackupTrace("sending data to transport");
+ int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
+ mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags);
+ }
+
+ // TODO - We call finishBackup() for each application backed up, because
+ // we need to know now whether it succeeded or failed. Instead, we should
+ // hold off on finishBackup() until the end, which implies holding off on
+ // renaming *all* the output state files (see below) until that happens.
+
+ backupManagerService.addBackupTrace("data delivered: " + mStatus);
+ if (mStatus == BackupTransport.TRANSPORT_OK) {
+ backupManagerService.addBackupTrace("finishing op on transport");
+ mStatus = mTransport.finishBackup();
+ backupManagerService.addBackupTrace("finished: " + mStatus);
+ } else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
+ backupManagerService.addBackupTrace("transport rejected package");
+ }
+ } else {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "no backup data written; not calling transport");
+ }
+ backupManagerService.addBackupTrace("no data to send");
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_NO_DATA_TO_SEND,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
+ }
+
+ if (mStatus == BackupTransport.TRANSPORT_OK) {
+ // After successful transport, delete the now-stale data
+ // and juggle the files so that next time we supply the agent
+ // with the new state file it just created.
+ mBackupDataName.delete();
+ mNewStateName.renameTo(mSavedStateName);
+ BackupObserverUtils
+ .sendBackupOnPackageResult(mObserver, pkgName, BackupManager.SUCCESS);
+ EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE, pkgName, size);
+ backupManagerService.logBackupComplete(pkgName);
+ } else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
+ // The transport has rejected backup of this specific package. Roll it
+ // back but proceed with running the rest of the queue.
+ mBackupDataName.delete();
+ mNewStateName.delete();
+ BackupObserverUtils.sendBackupOnPackageResult(mObserver, pkgName,
+ BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED);
+ EventLogTags.writeBackupAgentFailure(pkgName, "Transport rejected");
+ } else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
+ BackupObserverUtils.sendBackupOnPackageResult(mObserver, pkgName,
+ BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED);
+ EventLog.writeEvent(EventLogTags.BACKUP_QUOTA_EXCEEDED, pkgName);
+ } else {
+ // Actual transport-level failure to communicate the data to the backend
+ BackupObserverUtils.sendBackupOnPackageResult(mObserver, pkgName,
+ BackupManager.ERROR_TRANSPORT_ABORTED);
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
+ }
+ } catch (Exception e) {
+ BackupObserverUtils.sendBackupOnPackageResult(mObserver, pkgName,
+ BackupManager.ERROR_TRANSPORT_ABORTED);
+ Slog.e(TAG, "Transport error backing up " + pkgName, e);
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
+ mStatus = BackupTransport.TRANSPORT_ERROR;
+ } finally {
+ try {
+ if (backupData != null) backupData.close();
+ } catch (IOException e) {
+ }
+ }
+
+ final BackupState nextState;
+ if (mStatus == BackupTransport.TRANSPORT_OK
+ || mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
+ // Success or single-package rejection. Proceed with the next app if any,
+ // otherwise we're done.
+ nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
+ } else if (mStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Package " + mCurrentPackage.packageName +
+ " hit quota limit on k/v backup");
+ }
+ if (mAgentBinder != null) {
+ try {
+ long quota = mTransport.getBackupQuota(mCurrentPackage.packageName,
+ false);
+ mAgentBinder.doQuotaExceeded(size, quota);
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to notify about quota exceeded: " + e.getMessage());
+ }
+ }
+ nextState = (mQueue.isEmpty()) ? BackupState.FINAL : BackupState.RUNNING_QUEUE;
+ } else {
+ // Any other error here indicates a transport-level failure. That means
+ // we need to halt everything and reschedule everything for next time.
+ revertAndEndBackup();
+ nextState = BackupState.FINAL;
+ }
+
+ executeNextState(nextState);
+ }
+ }
+
+
+ @Override
+ @GuardedBy("mCancelLock")
+ public void handleCancel(boolean cancelAll) {
+ backupManagerService.removeOperation(mEphemeralOpToken);
+ synchronized (mCancelLock) {
+ if (mFinished) {
+ // We have already cancelled this operation.
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Ignoring stale cancel. cancelAll=" + cancelAll);
+ }
+ return;
+ }
+ mCancelAll = cancelAll;
+ final String logPackageName = (mCurrentPackage != null)
+ ? mCurrentPackage.packageName
+ : "no_package_yet";
+ Slog.i(TAG, "Cancel backing up " + logPackageName);
+ EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, logPackageName);
+ backupManagerService.addBackupTrace(
+ "cancel of " + logPackageName + ", cancelAll=" + cancelAll);
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_CANCEL,
+ mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
+ BackupManagerMonitorUtils.putMonitoringExtra(null,
+ BackupManagerMonitor.EXTRA_LOG_CANCEL_ALL,
+ mCancelAll));
+ errorCleanup();
+ if (!cancelAll) {
+ // The current agent either timed out or was cancelled running doBackup().
+ // Restage it for the next time we run a backup pass.
+ // !!! TODO: keep track of failure counts per agent, and blacklist those which
+ // fail repeatedly (i.e. have proved themselves to be buggy).
+ executeNextState(
+ mQueue.isEmpty() ? BackupState.FINAL : BackupState.RUNNING_QUEUE);
+ backupManagerService.dataChangedImpl(mCurrentPackage.packageName);
+ } else {
+ finalizeBackup();
+ }
+ }
+ }
+
+ void revertAndEndBackup() {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Reverting backup queue - restaging everything");
+ }
+ backupManagerService.addBackupTrace("transport error; reverting");
+
+ // We want to reset the backup schedule based on whatever the transport suggests
+ // by way of retry/backoff time.
+ long delay;
+ try {
+ delay = mTransport.requestBackupTime();
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to contact transport for recommended backoff: " + e.getMessage());
+ delay = 0; // use the scheduler's default
+ }
+ KeyValueBackupJob.schedule(backupManagerService.getContext(), delay);
+
+ for (BackupRequest request : mOriginalQueue) {
+ backupManagerService.dataChangedImpl(request.packageName);
+ }
+
+ }
+
+ void errorCleanup() {
+ mBackupDataName.delete();
+ mNewStateName.delete();
+ clearAgentState();
+ }
+
+ // Cleanup common to both success and failure cases
+ void clearAgentState() {
+ try {
+ if (mSavedState != null) mSavedState.close();
+ } catch (IOException e) {
+ }
+ try {
+ if (mBackupData != null) mBackupData.close();
+ } catch (IOException e) {
+ }
+ try {
+ if (mNewState != null) mNewState.close();
+ } catch (IOException e) {
+ }
+ synchronized (backupManagerService.getCurrentOpLock()) {
+ // Current-operation callback handling requires the validity of these various
+ // bits of internal state as an invariant of the operation still being live.
+ // This means we make sure to clear all of the state in unison inside the lock.
+ backupManagerService.getCurrentOperations().remove(mEphemeralOpToken);
+ mSavedState = mBackupData = mNewState = null;
+ }
+
+ // If this was a pseudopackage there's no associated Activity Manager state
+ if (mCurrentPackage.applicationInfo != null) {
+ backupManagerService.addBackupTrace("unbinding " + mCurrentPackage.packageName);
+ try { // unbind even on timeout, just in case
+ backupManagerService.getActivityManager().unbindBackupAgent(
+ mCurrentPackage.applicationInfo);
+ } catch (RemoteException e) { /* can't happen; activity manager is local */ }
+ }
+ }
+
+ void executeNextState(BackupState nextState) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, " => executing next step on "
+ + this + " nextState=" + nextState);
+ }
+ backupManagerService.addBackupTrace("executeNextState => " + nextState);
+ mCurrentState = nextState;
+ Message msg = backupManagerService.getBackupHandler().obtainMessage(
+ MSG_BACKUP_RESTORE_STEP, this);
+ backupManagerService.getBackupHandler().sendMessage(msg);
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/internal/PerformClearTask.java b/services/backup/java/com/android/server/backup/internal/PerformClearTask.java
new file mode 100644
index 0000000..7af01ea
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/internal/PerformClearTask.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.internal;
+
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+
+import android.content.pm.PackageInfo;
+import android.util.Slog;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.RefactoredBackupManagerService;
+
+import java.io.File;
+
+public class PerformClearTask implements Runnable {
+
+ private RefactoredBackupManagerService backupManagerService;
+ IBackupTransport mTransport;
+ PackageInfo mPackage;
+
+ PerformClearTask(RefactoredBackupManagerService backupManagerService,
+ IBackupTransport transport, PackageInfo packageInfo) {
+ this.backupManagerService = backupManagerService;
+ mTransport = transport;
+ mPackage = packageInfo;
+ }
+
+ public void run() {
+ try {
+ // Clear the on-device backup state to ensure a full backup next time
+ File stateDir = new File(backupManagerService.getBaseStateDir(),
+ mTransport.transportDirName());
+ File stateFile = new File(stateDir, mPackage.packageName);
+ stateFile.delete();
+
+ // Tell the transport to remove all the persistent storage for the app
+ // TODO - need to handle failures
+ mTransport.clearBackupData(mPackage);
+ } catch (Exception e) {
+ Slog.e(TAG, "Transport threw clearing data for " + mPackage + ": " + e.getMessage());
+ } finally {
+ try {
+ // TODO - need to handle failures
+ mTransport.finishBackup();
+ } catch (Exception e) {
+ // Nothing we can do here, alas
+ Slog.e(TAG, "Unable to mark clear operation finished: " + e.getMessage());
+ }
+
+ // Last but not least, release the cpu
+ backupManagerService.getWakelock().release();
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
new file mode 100644
index 0000000..5a75b66
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.internal;
+
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+
+import android.app.AlarmManager;
+import android.app.backup.BackupTransport;
+import android.os.SystemClock;
+import android.util.EventLog;
+import android.util.Slog;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.EventLogTags;
+import com.android.server.backup.RefactoredBackupManagerService;
+
+import java.io.File;
+import java.util.HashSet;
+
+public class PerformInitializeTask implements Runnable {
+
+ private RefactoredBackupManagerService backupManagerService;
+ HashSet<String> mQueue;
+
+ PerformInitializeTask(RefactoredBackupManagerService backupManagerService,
+ HashSet<String> transportNames) {
+ this.backupManagerService = backupManagerService;
+ mQueue = transportNames;
+ }
+
+ public void run() {
+ try {
+ for (String transportName : mQueue) {
+ IBackupTransport transport =
+ backupManagerService.getTransportManager().getTransportBinder(
+ transportName);
+ if (transport == null) {
+ Slog.e(TAG, "Requested init for " + transportName + " but not found");
+ continue;
+ }
+
+ Slog.i(TAG, "Initializing (wiping) backup transport storage: " + transportName);
+ EventLog.writeEvent(EventLogTags.BACKUP_START, transport.transportDirName());
+ long startRealtime = SystemClock.elapsedRealtime();
+ int status = transport.initializeDevice();
+
+ if (status == BackupTransport.TRANSPORT_OK) {
+ status = transport.finishBackup();
+ }
+
+ // Okay, the wipe really happened. Clean up our local bookkeeping.
+ if (status == BackupTransport.TRANSPORT_OK) {
+ Slog.i(TAG, "Device init successful");
+ int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
+ EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE);
+ backupManagerService
+ .resetBackupState(new File(backupManagerService.getBaseStateDir(),
+ transport.transportDirName()));
+ EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, 0, millis);
+ synchronized (backupManagerService.getQueueLock()) {
+ backupManagerService.recordInitPendingLocked(false, transportName);
+ }
+ } else {
+ // If this didn't work, requeue this one and try again
+ // after a suitable interval
+ Slog.e(TAG, "Transport error in initializeDevice()");
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, "(initialize)");
+ synchronized (backupManagerService.getQueueLock()) {
+ backupManagerService.recordInitPendingLocked(true, transportName);
+ }
+ // do this via another alarm to make sure of the wakelock states
+ long delay = transport.requestBackupTime();
+ Slog.w(TAG, "Init failed on " + transportName + " resched in " + delay);
+ backupManagerService.getAlarmManager().set(AlarmManager.RTC_WAKEUP,
+ System.currentTimeMillis() + delay,
+ backupManagerService.getRunInitIntent());
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Unexpected error performing init", e);
+ } finally {
+ // Done; release the wakelock
+ backupManagerService.getWakelock().release();
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/internal/ProvisionedObserver.java b/services/backup/java/com/android/server/backup/internal/ProvisionedObserver.java
new file mode 100644
index 0000000..e92b8be
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/internal/ProvisionedObserver.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.internal;
+
+import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.util.Slog;
+
+import com.android.server.backup.KeyValueBackupJob;
+import com.android.server.backup.RefactoredBackupManagerService;
+
+public class ProvisionedObserver extends ContentObserver {
+
+ private RefactoredBackupManagerService backupManagerService;
+
+ public ProvisionedObserver(
+ RefactoredBackupManagerService backupManagerService, Handler handler) {
+ super(handler);
+ this.backupManagerService = backupManagerService;
+ }
+
+ public void onChange(boolean selfChange) {
+ final boolean wasProvisioned = backupManagerService.isProvisioned();
+ final boolean isProvisioned = backupManagerService.deviceIsProvisioned();
+ // latch: never unprovision
+ backupManagerService.setProvisioned(wasProvisioned || isProvisioned);
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Provisioning change: was=" + wasProvisioned
+ + " is=" + isProvisioned + " now=" + backupManagerService.isProvisioned());
+ }
+
+ synchronized (backupManagerService.getQueueLock()) {
+ if (backupManagerService.isProvisioned() && !wasProvisioned
+ && backupManagerService.isEnabled()) {
+ // we're now good to go, so start the backup alarms
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Now provisioned, so starting backups");
+ }
+ KeyValueBackupJob.schedule(backupManagerService.getContext());
+ backupManagerService.scheduleNextFullBackupJob(0);
+ }
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java b/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java
new file mode 100644
index 0000000..f1fa7eb
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.internal;
+
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.RUN_BACKUP_ACTION;
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+import static com.android.server.backup.internal.BackupHandler.MSG_RUN_BACKUP;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Message;
+import android.util.Slog;
+
+import com.android.server.backup.RefactoredBackupManagerService;
+
+public class RunBackupReceiver extends BroadcastReceiver {
+
+ private RefactoredBackupManagerService backupManagerService;
+
+ public RunBackupReceiver(RefactoredBackupManagerService backupManagerService) {
+ this.backupManagerService = backupManagerService;
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ if (RUN_BACKUP_ACTION.equals(intent.getAction())) {
+ synchronized (backupManagerService.getQueueLock()) {
+ if (backupManagerService.getPendingInits().size() > 0) {
+ // If there are pending init operations, we process those
+ // and then settle into the usual periodic backup schedule.
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "Init pending at scheduled backup");
+ }
+ try {
+ backupManagerService.getAlarmManager().cancel(
+ backupManagerService.getRunInitIntent());
+ backupManagerService.getRunInitIntent().send();
+ } catch (PendingIntent.CanceledException ce) {
+ Slog.e(TAG, "Run init intent cancelled");
+ // can't really do more than bail here
+ }
+ } else {
+ // Don't run backups now if we're disabled or not yet
+ // fully set up.
+ if (backupManagerService.isEnabled() && backupManagerService.isProvisioned()) {
+ if (!backupManagerService.isBackupRunning()) {
+ if (DEBUG) {
+ Slog.v(TAG, "Running a backup pass");
+ }
+
+ // Acquire the wakelock and pass it to the backup thread. it will
+ // be released once backup concludes.
+ backupManagerService.setBackupRunning(true);
+ backupManagerService.getWakelock().acquire();
+
+ Message msg = backupManagerService.getBackupHandler().obtainMessage(
+ MSG_RUN_BACKUP);
+ backupManagerService.getBackupHandler().sendMessage(msg);
+ } else {
+ Slog.i(TAG, "Backup time but one already running");
+ }
+ } else {
+ Slog.w(TAG, "Backup pass but e=" + backupManagerService.isEnabled() + " p="
+ + backupManagerService.isProvisioned());
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java b/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java
new file mode 100644
index 0000000..1621a91
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.internal;
+
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.RUN_INITIALIZE_ACTION;
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+import static com.android.server.backup.internal.BackupHandler.MSG_RUN_INITIALIZE;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Message;
+import android.util.Slog;
+
+import com.android.server.backup.RefactoredBackupManagerService;
+
+public class RunInitializeReceiver extends BroadcastReceiver {
+
+ private RefactoredBackupManagerService backupManagerService;
+
+ public RunInitializeReceiver(RefactoredBackupManagerService backupManagerService) {
+ this.backupManagerService = backupManagerService;
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ if (RUN_INITIALIZE_ACTION.equals(intent.getAction())) {
+ synchronized (backupManagerService.getQueueLock()) {
+ if (DEBUG) {
+ Slog.v(TAG, "Running a device init");
+ }
+
+ // Acquire the wakelock and pass it to the init thread. it will
+ // be released once init concludes.
+ backupManagerService.getWakelock().acquire();
+
+ Message msg = backupManagerService.getBackupHandler().obtainMessage(
+ MSG_RUN_INITIALIZE);
+ backupManagerService.getBackupHandler().sendMessage(msg);
+ }
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/params/AdbBackupParams.java b/services/backup/java/com/android/server/backup/params/AdbBackupParams.java
new file mode 100644
index 0000000..5c1ba24
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/params/AdbBackupParams.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.params;
+
+import android.os.ParcelFileDescriptor;
+
+public class AdbBackupParams extends AdbParams {
+
+ public boolean includeApks;
+ public boolean includeObbs;
+ public boolean includeShared;
+ public boolean doWidgets;
+ public boolean allApps;
+ public boolean includeSystem;
+ public boolean doCompress;
+ public boolean includeKeyValue;
+ public String[] packages;
+
+ public AdbBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveObbs,
+ boolean saveShared, boolean alsoWidgets, boolean doAllApps, boolean doSystem,
+ boolean compress, boolean doKeyValue, String[] pkgList) {
+ fd = output;
+ includeApks = saveApks;
+ includeObbs = saveObbs;
+ includeShared = saveShared;
+ doWidgets = alsoWidgets;
+ allApps = doAllApps;
+ includeSystem = doSystem;
+ doCompress = compress;
+ includeKeyValue = doKeyValue;
+ packages = pkgList;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/params/AdbParams.java b/services/backup/java/com/android/server/backup/params/AdbParams.java
new file mode 100644
index 0000000..392f40d
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/params/AdbParams.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.params;
+
+import android.app.backup.IFullBackupRestoreObserver;
+import android.os.ParcelFileDescriptor;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Parameters used by adbBackup() and adbRestore().
+ */
+public class AdbParams {
+
+ public ParcelFileDescriptor fd;
+ public final AtomicBoolean latch;
+ public IFullBackupRestoreObserver observer;
+ public String curPassword; // filled in by the confirmation step
+ public String encryptPassword;
+
+ AdbParams() {
+ latch = new AtomicBoolean(false);
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/params/AdbRestoreParams.java b/services/backup/java/com/android/server/backup/params/AdbRestoreParams.java
new file mode 100644
index 0000000..0142fe0
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/params/AdbRestoreParams.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.params;
+
+import android.os.ParcelFileDescriptor;
+
+public class AdbRestoreParams extends AdbParams {
+
+ public AdbRestoreParams(ParcelFileDescriptor input) {
+ fd = input;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/params/BackupParams.java b/services/backup/java/com/android/server/backup/params/BackupParams.java
new file mode 100644
index 0000000..4fd7ddb
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/params/BackupParams.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.params;
+
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IBackupObserver;
+
+import com.android.internal.backup.IBackupTransport;
+
+import java.util.ArrayList;
+
+public class BackupParams {
+
+ public IBackupTransport transport;
+ public String dirName;
+ public ArrayList<String> kvPackages;
+ public ArrayList<String> fullPackages;
+ public IBackupObserver observer;
+ public IBackupManagerMonitor monitor;
+ public boolean userInitiated;
+ public boolean nonIncrementalBackup;
+
+ public BackupParams(IBackupTransport transport, String dirName, ArrayList<String> kvPackages,
+ ArrayList<String> fullPackages, IBackupObserver observer,
+ IBackupManagerMonitor monitor, boolean userInitiated, boolean nonIncrementalBackup) {
+ this.transport = transport;
+ this.dirName = dirName;
+ this.kvPackages = kvPackages;
+ this.fullPackages = fullPackages;
+ this.observer = observer;
+ this.monitor = monitor;
+ this.userInitiated = userInitiated;
+ this.nonIncrementalBackup = nonIncrementalBackup;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/params/ClearParams.java b/services/backup/java/com/android/server/backup/params/ClearParams.java
new file mode 100644
index 0000000..d744efc
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/params/ClearParams.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.params;
+
+import android.content.pm.PackageInfo;
+
+import com.android.internal.backup.IBackupTransport;
+
+public class ClearParams {
+
+ public IBackupTransport transport;
+ public PackageInfo packageInfo;
+
+ public ClearParams(IBackupTransport _transport, PackageInfo _info) {
+ transport = _transport;
+ packageInfo = _info;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/params/ClearRetryParams.java b/services/backup/java/com/android/server/backup/params/ClearRetryParams.java
new file mode 100644
index 0000000..fcf66e4
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/params/ClearRetryParams.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.params;
+
+public class ClearRetryParams {
+
+ public String transportName;
+ public String packageName;
+
+ public ClearRetryParams(String transport, String pkg) {
+ transportName = transport;
+ packageName = pkg;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/params/RestoreGetSetsParams.java b/services/backup/java/com/android/server/backup/params/RestoreGetSetsParams.java
new file mode 100644
index 0000000..bff476b
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/params/RestoreGetSetsParams.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.params;
+
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IRestoreObserver;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.restore.ActiveRestoreSession;
+
+public class RestoreGetSetsParams {
+
+ public IBackupTransport transport;
+ public ActiveRestoreSession session;
+ public IRestoreObserver observer;
+ public IBackupManagerMonitor monitor;
+
+ public RestoreGetSetsParams(IBackupTransport _transport, ActiveRestoreSession _session,
+ IRestoreObserver _observer, IBackupManagerMonitor _monitor) {
+ transport = _transport;
+ session = _session;
+ observer = _observer;
+ monitor = _monitor;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/params/RestoreParams.java b/services/backup/java/com/android/server/backup/params/RestoreParams.java
new file mode 100644
index 0000000..93ce00d
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/params/RestoreParams.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.params;
+
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IRestoreObserver;
+import android.content.pm.PackageInfo;
+
+import com.android.internal.backup.IBackupTransport;
+
+public class RestoreParams {
+
+ public IBackupTransport transport;
+ public String dirName;
+ public IRestoreObserver observer;
+ public IBackupManagerMonitor monitor;
+ public long token;
+ public PackageInfo pkgInfo;
+ public int pmToken; // in post-install restore, the PM's token for this transaction
+ public boolean isSystemRestore;
+ public String[] filterSet;
+
+ /**
+ * Restore a single package; no kill after restore
+ */
+ public RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
+ IBackupManagerMonitor _monitor, long _token, PackageInfo _pkg) {
+ transport = _transport;
+ dirName = _dirName;
+ observer = _obs;
+ monitor = _monitor;
+ token = _token;
+ pkgInfo = _pkg;
+ pmToken = 0;
+ isSystemRestore = false;
+ filterSet = null;
+ }
+
+ /**
+ * Restore at install: PM token needed, kill after restore
+ */
+ public RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
+ IBackupManagerMonitor _monitor, long _token, String _pkgName, int _pmToken) {
+ transport = _transport;
+ dirName = _dirName;
+ observer = _obs;
+ monitor = _monitor;
+ token = _token;
+ pkgInfo = null;
+ pmToken = _pmToken;
+ isSystemRestore = false;
+ filterSet = new String[]{_pkgName};
+ }
+
+ /**
+ * Restore everything possible. This is the form that Setup Wizard or similar
+ * restore UXes use.
+ */
+ public RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
+ IBackupManagerMonitor _monitor, long _token) {
+ transport = _transport;
+ dirName = _dirName;
+ observer = _obs;
+ monitor = _monitor;
+ token = _token;
+ pkgInfo = null;
+ pmToken = 0;
+ isSystemRestore = true;
+ filterSet = null;
+ }
+
+ /**
+ * Restore some set of packages. Leave this one up to the caller to specify
+ * whether it's to be considered a system-level restore.
+ */
+ public RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
+ IBackupManagerMonitor _monitor, long _token,
+ String[] _filterSet, boolean _isSystemRestore) {
+ transport = _transport;
+ dirName = _dirName;
+ observer = _obs;
+ monitor = _monitor;
+ token = _token;
+ pkgInfo = null;
+ pmToken = 0;
+ isSystemRestore = _isSystemRestore;
+ filterSet = _filterSet;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
new file mode 100644
index 0000000..8d8a013
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.restore;
+
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_SESSION_TIMEOUT;
+import static com.android.server.backup.internal.BackupHandler.MSG_RUN_GET_RESTORE_SETS;
+import static com.android.server.backup.internal.BackupHandler.MSG_RUN_RESTORE;
+
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IRestoreObserver;
+import android.app.backup.IRestoreSession;
+import android.app.backup.RestoreSet;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Binder;
+import android.os.Message;
+import android.util.Slog;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.params.RestoreGetSetsParams;
+import com.android.server.backup.params.RestoreParams;
+
+/**
+ * Restore session.
+ */
+public class ActiveRestoreSession extends IRestoreSession.Stub {
+
+ private static final String TAG = "RestoreSession";
+
+ private RefactoredBackupManagerService backupManagerService;
+ private String mPackageName;
+ private IBackupTransport mRestoreTransport = null;
+ public RestoreSet[] mRestoreSets = null;
+ boolean mEnded = false;
+ boolean mTimedOut = false;
+
+ public ActiveRestoreSession(RefactoredBackupManagerService backupManagerService,
+ String packageName, String transport) {
+ this.backupManagerService = backupManagerService;
+ mPackageName = packageName;
+ mRestoreTransport = backupManagerService.getTransportManager().getTransportBinder(
+ transport);
+ }
+
+ public void markTimedOut() {
+ mTimedOut = true;
+ }
+
+ // --- Binder interface ---
+ public synchronized int getAvailableRestoreSets(IRestoreObserver observer,
+ IBackupManagerMonitor monitor) {
+ backupManagerService.getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.BACKUP,
+ "getAvailableRestoreSets");
+ if (observer == null) {
+ throw new IllegalArgumentException("Observer must not be null");
+ }
+
+ if (mEnded) {
+ throw new IllegalStateException("Restore session already ended");
+ }
+
+ if (mTimedOut) {
+ Slog.i(TAG, "Session already timed out");
+ return -1;
+ }
+
+ long oldId = Binder.clearCallingIdentity();
+ try {
+ if (mRestoreTransport == null) {
+ Slog.w(TAG, "Null transport getting restore sets");
+ return -1;
+ }
+
+ // We know we're doing legit work now, so halt the timeout
+ // until we're done. It gets started again when the result
+ // comes in.
+ backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
+
+ // spin off the transport request to our service thread
+ backupManagerService.getWakelock().acquire();
+ Message msg = backupManagerService.getBackupHandler().obtainMessage(
+ MSG_RUN_GET_RESTORE_SETS,
+ new RestoreGetSetsParams(mRestoreTransport, this, observer,
+ monitor));
+ backupManagerService.getBackupHandler().sendMessage(msg);
+ return 0;
+ } catch (Exception e) {
+ Slog.e(TAG, "Error in getAvailableRestoreSets", e);
+ return -1;
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
+ }
+
+ public synchronized int restoreAll(long token, IRestoreObserver observer,
+ IBackupManagerMonitor monitor) {
+ backupManagerService.getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.BACKUP,
+ "performRestore");
+
+ if (DEBUG) {
+ Slog.d(TAG, "restoreAll token=" + Long.toHexString(token)
+ + " observer=" + observer);
+ }
+
+ if (mEnded) {
+ throw new IllegalStateException("Restore session already ended");
+ }
+
+ if (mTimedOut) {
+ Slog.i(TAG, "Session already timed out");
+ return -1;
+ }
+
+ if (mRestoreTransport == null || mRestoreSets == null) {
+ Slog.e(TAG, "Ignoring restoreAll() with no restore set");
+ return -1;
+ }
+
+ if (mPackageName != null) {
+ Slog.e(TAG, "Ignoring restoreAll() on single-package session");
+ return -1;
+ }
+
+ String dirName;
+ try {
+ dirName = mRestoreTransport.transportDirName();
+ } catch (Exception e) {
+ // Transport went AWOL; fail.
+ Slog.e(TAG, "Unable to get transport dir for restore: " + e.getMessage());
+ return -1;
+ }
+
+ synchronized (backupManagerService.getQueueLock()) {
+ for (int i = 0; i < mRestoreSets.length; i++) {
+ if (token == mRestoreSets[i].token) {
+ // Real work, so stop the session timeout until we finalize the restore
+ backupManagerService.getBackupHandler().removeMessages(
+ MSG_RESTORE_SESSION_TIMEOUT);
+
+ long oldId = Binder.clearCallingIdentity();
+ backupManagerService.getWakelock().acquire();
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "restoreAll() kicking off");
+ }
+ Message msg = backupManagerService.getBackupHandler().obtainMessage(
+ MSG_RUN_RESTORE);
+ msg.obj = new RestoreParams(mRestoreTransport, dirName,
+ observer, monitor, token);
+ backupManagerService.getBackupHandler().sendMessage(msg);
+ Binder.restoreCallingIdentity(oldId);
+ return 0;
+ }
+ }
+ }
+
+ Slog.w(TAG, "Restore token " + Long.toHexString(token) + " not found");
+ return -1;
+ }
+
+ // Restores of more than a single package are treated as 'system' restores
+ public synchronized int restoreSome(long token, IRestoreObserver observer,
+ IBackupManagerMonitor monitor, String[] packages) {
+ backupManagerService.getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.BACKUP,
+ "performRestore");
+
+ if (DEBUG) {
+ StringBuilder b = new StringBuilder(128);
+ b.append("restoreSome token=");
+ b.append(Long.toHexString(token));
+ b.append(" observer=");
+ b.append(observer.toString());
+ b.append(" monitor=");
+ if (monitor == null) {
+ b.append("null");
+ } else {
+ b.append(monitor.toString());
+ }
+ b.append(" packages=");
+ if (packages == null) {
+ b.append("null");
+ } else {
+ b.append('{');
+ boolean first = true;
+ for (String s : packages) {
+ if (!first) {
+ b.append(", ");
+ } else {
+ first = false;
+ }
+ b.append(s);
+ }
+ b.append('}');
+ }
+ Slog.d(TAG, b.toString());
+ }
+
+ if (mEnded) {
+ throw new IllegalStateException("Restore session already ended");
+ }
+
+ if (mTimedOut) {
+ Slog.i(TAG, "Session already timed out");
+ return -1;
+ }
+
+ if (mRestoreTransport == null || mRestoreSets == null) {
+ Slog.e(TAG, "Ignoring restoreAll() with no restore set");
+ return -1;
+ }
+
+ if (mPackageName != null) {
+ Slog.e(TAG, "Ignoring restoreAll() on single-package session");
+ return -1;
+ }
+
+ String dirName;
+ try {
+ dirName = mRestoreTransport.transportDirName();
+ } catch (Exception e) {
+ // Transport went AWOL; fail.
+ Slog.e(TAG, "Unable to get transport name for restoreSome: " + e.getMessage());
+ return -1;
+ }
+
+ synchronized (backupManagerService.getQueueLock()) {
+ for (int i = 0; i < mRestoreSets.length; i++) {
+ if (token == mRestoreSets[i].token) {
+ // Stop the session timeout until we finalize the restore
+ backupManagerService.getBackupHandler().removeMessages(
+ MSG_RESTORE_SESSION_TIMEOUT);
+
+ long oldId = Binder.clearCallingIdentity();
+ backupManagerService.getWakelock().acquire();
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "restoreSome() of " + packages.length + " packages");
+ }
+ Message msg = backupManagerService.getBackupHandler().obtainMessage(
+ MSG_RUN_RESTORE);
+ msg.obj = new RestoreParams(mRestoreTransport, dirName, observer, monitor,
+ token, packages, packages.length > 1);
+ backupManagerService.getBackupHandler().sendMessage(msg);
+ Binder.restoreCallingIdentity(oldId);
+ return 0;
+ }
+ }
+ }
+
+ Slog.w(TAG, "Restore token " + Long.toHexString(token) + " not found");
+ return -1;
+ }
+
+ public synchronized int restorePackage(String packageName, IRestoreObserver observer,
+ IBackupManagerMonitor monitor) {
+ if (DEBUG) {
+ Slog.v(TAG, "restorePackage pkg=" + packageName + " obs=" + observer
+ + "monitor=" + monitor);
+ }
+
+ if (mEnded) {
+ throw new IllegalStateException("Restore session already ended");
+ }
+
+ if (mTimedOut) {
+ Slog.i(TAG, "Session already timed out");
+ return -1;
+ }
+
+ if (mPackageName != null) {
+ if (!mPackageName.equals(packageName)) {
+ Slog.e(TAG, "Ignoring attempt to restore pkg=" + packageName
+ + " on session for package " + mPackageName);
+ return -1;
+ }
+ }
+
+ PackageInfo app = null;
+ try {
+ app = backupManagerService.getPackageManager().getPackageInfo(packageName, 0);
+ } catch (NameNotFoundException nnf) {
+ Slog.w(TAG, "Asked to restore nonexistent pkg " + packageName);
+ return -1;
+ }
+
+ // If the caller is not privileged and is not coming from the target
+ // app's uid, throw a permission exception back to the caller.
+ int perm = backupManagerService.getContext().checkPermission(
+ android.Manifest.permission.BACKUP,
+ Binder.getCallingPid(), Binder.getCallingUid());
+ if ((perm == PackageManager.PERMISSION_DENIED) &&
+ (app.applicationInfo.uid != Binder.getCallingUid())) {
+ Slog.w(TAG, "restorePackage: bad packageName=" + packageName
+ + " or calling uid=" + Binder.getCallingUid());
+ throw new SecurityException("No permission to restore other packages");
+ }
+
+ // So far so good; we're allowed to try to restore this package.
+ long oldId = Binder.clearCallingIdentity();
+ try {
+ // Check whether there is data for it in the current dataset, falling back
+ // to the ancestral dataset if not.
+ long token = backupManagerService.getAvailableRestoreToken(packageName);
+ if (DEBUG) {
+ Slog.v(TAG, "restorePackage pkg=" + packageName
+ + " token=" + Long.toHexString(token));
+ }
+
+ // If we didn't come up with a place to look -- no ancestral dataset and
+ // the app has never been backed up from this device -- there's nothing
+ // to do but return failure.
+ if (token == 0) {
+ if (DEBUG) {
+ Slog.w(TAG, "No data available for this package; not restoring");
+ }
+ return -1;
+ }
+
+ String dirName;
+ try {
+ dirName = mRestoreTransport.transportDirName();
+ } catch (Exception e) {
+ // Transport went AWOL; fail.
+ Slog.e(TAG, "Unable to get transport dir for restorePackage: " + e.getMessage());
+ return -1;
+ }
+
+ // Stop the session timeout until we finalize the restore
+ backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
+
+ // Ready to go: enqueue the restore request and claim success
+ backupManagerService.getWakelock().acquire();
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "restorePackage() : " + packageName);
+ }
+ Message msg = backupManagerService.getBackupHandler().obtainMessage(MSG_RUN_RESTORE);
+ msg.obj = new RestoreParams(mRestoreTransport, dirName, observer, monitor,
+ token, app);
+ backupManagerService.getBackupHandler().sendMessage(msg);
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
+ return 0;
+ }
+
+ // Posted to the handler to tear down a restore session in a cleanly synchronized way
+ public class EndRestoreRunnable implements Runnable {
+
+ RefactoredBackupManagerService mBackupManager;
+ ActiveRestoreSession mSession;
+
+ public EndRestoreRunnable(RefactoredBackupManagerService manager,
+ ActiveRestoreSession session) {
+ mBackupManager = manager;
+ mSession = session;
+ }
+
+ public void run() {
+ // clean up the session's bookkeeping
+ synchronized (mSession) {
+ mSession.mRestoreTransport = null;
+ mSession.mEnded = true;
+ }
+
+ // clean up the BackupManagerImpl side of the bookkeeping
+ // and cancel any pending timeout message
+ mBackupManager.clearRestoreSession(mSession);
+ }
+ }
+
+ public synchronized void endRestoreSession() {
+ if (DEBUG) {
+ Slog.d(TAG, "endRestoreSession");
+ }
+
+ if (mTimedOut) {
+ Slog.i(TAG, "Session already timed out");
+ return;
+ }
+
+ if (mEnded) {
+ throw new IllegalStateException("Restore session already ended");
+ }
+
+ backupManagerService.getBackupHandler().post(
+ new EndRestoreRunnable(backupManagerService, this));
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java b/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java
new file mode 100644
index 0000000..6353d1f
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.restore;
+
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL;
+
+import android.util.Slog;
+
+import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.RefactoredBackupManagerService;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Used for synchronizing doRestoreFinished during adb restore.
+ */
+public class AdbRestoreFinishedLatch implements BackupRestoreTask {
+
+ private static final String TAG = "AdbRestoreFinishedLatch";
+ private RefactoredBackupManagerService backupManagerService;
+ final CountDownLatch mLatch;
+ private final int mCurrentOpToken;
+
+ public AdbRestoreFinishedLatch(RefactoredBackupManagerService backupManagerService,
+ int currentOpToken) {
+ this.backupManagerService = backupManagerService;
+ mLatch = new CountDownLatch(1);
+ mCurrentOpToken = currentOpToken;
+ }
+
+ void await() {
+ boolean latched = false;
+ try {
+ latched = mLatch.await(TIMEOUT_FULL_BACKUP_INTERVAL, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "Interrupted!");
+ }
+ }
+
+ @Override
+ public void execute() {
+ // Unused
+ }
+
+ @Override
+ public void operationComplete(long result) {
+ if (MORE_DEBUG) {
+ Slog.w(TAG, "adb onRestoreFinished() complete");
+ }
+ mLatch.countDown();
+ backupManagerService.removeOperation(mCurrentOpToken);
+ }
+
+ @Override
+ public void handleCancel(boolean cancelAll) {
+ if (DEBUG) {
+ Slog.w(TAG, "adb onRestoreFinished() timed out");
+ }
+ mLatch.countDown();
+ backupManagerService.removeOperation(mCurrentOpToken);
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
new file mode 100644
index 0000000..888dcd7e
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -0,0 +1,634 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.restore;
+
+import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME;
+import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_METADATA_FILENAME;
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_RESTORE_WAIT;
+import static com.android.server.backup.RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_RESTORE_INTERVAL;
+import static com.android.server.backup.RefactoredBackupManagerService
+ .TIMEOUT_SHARED_BACKUP_INTERVAL;
+import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT;
+
+import android.app.ApplicationThreadConstants;
+import android.app.IBackupAgent;
+import android.app.backup.FullBackup;
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IFullBackupRestoreObserver;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.FileMetadata;
+import com.android.server.backup.KeyValueAdbRestoreEngine;
+import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.fullbackup.FullBackupObbConnection;
+import com.android.server.backup.utils.BytesReadListener;
+import com.android.server.backup.utils.FullBackupRestoreObserverUtils;
+import com.android.server.backup.utils.RestoreUtils;
+import com.android.server.backup.utils.TarBackupReader;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * Full restore engine, used by both adb restore and transport-based full restore.
+ */
+public class FullRestoreEngine extends RestoreEngine {
+
+ private final RefactoredBackupManagerService mBackupManagerService;
+ // Task in charge of monitoring timeouts
+ private final BackupRestoreTask mMonitorTask;
+
+ private final RestoreInstallObserver mInstallObserver = new RestoreInstallObserver();
+ private final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver();
+
+ // Dedicated observer, if any
+ private IFullBackupRestoreObserver mObserver;
+
+ final IBackupManagerMonitor mMonitor;
+
+ // Where we're delivering the file data as we go
+ private IBackupAgent mAgent;
+
+ // Are we permitted to only deliver a specific package's metadata?
+ final PackageInfo mOnlyPackage;
+
+ final boolean mAllowApks;
+ private final boolean mAllowObbs;
+
+ // Which package are we currently handling data for?
+ private String mAgentPackage;
+
+ // Info for working with the target app process
+ private ApplicationInfo mTargetApp;
+
+ // Machinery for restoring OBBs
+ private FullBackupObbConnection mObbConnection = null;
+
+ // possible handling states for a given package in the restore dataset
+ private final HashMap<String, RestorePolicy> mPackagePolicies
+ = new HashMap<>();
+
+ // installer package names for each encountered app, derived from the manifests
+ private final HashMap<String, String> mPackageInstallers = new HashMap<>();
+
+ // Signatures for a given package found in its manifest file
+ private final HashMap<String, Signature[]> mManifestSignatures
+ = new HashMap<>();
+
+ // Packages we've already wiped data on when restoring their first file
+ private final HashSet<String> mClearedPackages = new HashSet<>();
+
+ // How much data have we moved?
+ private long mBytes;
+
+ // Working buffer
+ final byte[] mBuffer;
+
+ // Pipes for moving data
+ private ParcelFileDescriptor[] mPipes = null;
+
+ // Widget blob to be restored out-of-band
+ private byte[] mWidgetData = null;
+
+ final int mEphemeralOpToken;
+
+ public FullRestoreEngine(RefactoredBackupManagerService backupManagerService,
+ BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer,
+ IBackupManagerMonitor monitor, PackageInfo onlyPackage, boolean allowApks,
+ boolean allowObbs, int ephemeralOpToken) {
+ mBackupManagerService = backupManagerService;
+ mEphemeralOpToken = ephemeralOpToken;
+ mMonitorTask = monitorTask;
+ mObserver = observer;
+ mMonitor = monitor;
+ mOnlyPackage = onlyPackage;
+ mAllowApks = allowApks;
+ mAllowObbs = allowObbs;
+ mBuffer = new byte[32 * 1024];
+ mBytes = 0;
+ }
+
+ public IBackupAgent getAgent() {
+ return mAgent;
+ }
+
+ public byte[] getWidgetData() {
+ return mWidgetData;
+ }
+
+ public boolean restoreOneFile(InputStream instream, boolean mustKillAgent, byte[] buffer,
+ PackageInfo onlyPackage, boolean allowApks, int token, IBackupManagerMonitor monitor) {
+ if (!isRunning()) {
+ Slog.w(TAG, "Restore engine used after halting");
+ return false;
+ }
+
+ BytesReadListener bytesReadListener = new BytesReadListener() {
+ @Override
+ public void onBytesRead(long bytesRead) {
+ mBytes += bytesRead;
+ }
+ };
+
+ TarBackupReader tarBackupReader = new TarBackupReader(instream,
+ bytesReadListener, monitor);
+
+ FileMetadata info;
+ try {
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "Reading tar header for restoring file");
+ }
+ info = tarBackupReader.readTarHeaders();
+ if (info != null) {
+ if (MORE_DEBUG) {
+ info.dump();
+ }
+
+ final String pkg = info.packageName;
+ if (!pkg.equals(mAgentPackage)) {
+ // In the single-package case, it's a semantic error to expect
+ // one app's data but see a different app's on the wire
+ if (onlyPackage != null) {
+ if (!pkg.equals(onlyPackage.packageName)) {
+ Slog.w(TAG, "Expected data for " + onlyPackage + " but saw " + pkg);
+ setResult(RestoreEngine.TRANSPORT_FAILURE);
+ setRunning(false);
+ return false;
+ }
+ }
+
+ // okay, change in package; set up our various
+ // bookkeeping if we haven't seen it yet
+ if (!mPackagePolicies.containsKey(pkg)) {
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ }
+
+ // Clean up the previous agent relationship if necessary,
+ // and let the observer know we're considering a new app.
+ if (mAgent != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Saw new package; finalizing old one");
+ }
+ // Now we're really done
+ tearDownPipes();
+ tearDownAgent(mTargetApp);
+ mTargetApp = null;
+ mAgentPackage = null;
+ }
+ }
+
+ if (info.path.equals(BACKUP_MANIFEST_FILENAME)) {
+ Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(
+ info);
+ RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
+ mBackupManagerService.getPackageManager(), allowApks, info, signatures);
+ mManifestSignatures.put(info.packageName, signatures);
+ mPackagePolicies.put(pkg, restorePolicy);
+ mPackageInstallers.put(pkg, info.installerPackageName);
+ // We've read only the manifest content itself at this point,
+ // so consume the footer before looping around to the next
+ // input file
+ tarBackupReader.skipTarPadding(info.size);
+ mObserver = FullBackupRestoreObserverUtils.sendOnRestorePackage(mObserver, pkg);
+ } else if (info.path.equals(BACKUP_METADATA_FILENAME)) {
+ // Metadata blobs!
+ tarBackupReader.readMetadata(info);
+
+ // The following only exist because we want to keep refactoring as safe as
+ // possible, without changing too much.
+ // TODO: Refactor, so that there are no funny things like this.
+ // This is read during TarBackupReader.readMetadata().
+ mWidgetData = tarBackupReader.getWidgetData();
+ // This can be nulled during TarBackupReader.readMetadata().
+ monitor = tarBackupReader.getMonitor();
+
+ tarBackupReader.skipTarPadding(info.size);
+ } else {
+ // Non-manifest, so it's actual file data. Is this a package
+ // we're ignoring?
+ boolean okay = true;
+ RestorePolicy policy = mPackagePolicies.get(pkg);
+ switch (policy) {
+ case IGNORE:
+ okay = false;
+ break;
+
+ case ACCEPT_IF_APK:
+ // If we're in accept-if-apk state, then the first file we
+ // see MUST be the apk.
+ if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
+ if (DEBUG) {
+ Slog.d(TAG, "APK file; installing");
+ }
+ // Try to install the app.
+ String installerName = mPackageInstallers.get(pkg);
+ boolean isSuccessfullyInstalled = RestoreUtils.installApk(
+ instream, mBackupManagerService.getPackageManager(),
+ mInstallObserver, mDeleteObserver, mManifestSignatures,
+ mPackagePolicies, info, installerName,
+ bytesReadListener, mBackupManagerService.getDataDir()
+ );
+ // good to go; promote to ACCEPT
+ mPackagePolicies.put(pkg, isSuccessfullyInstalled
+ ? RestorePolicy.ACCEPT
+ : RestorePolicy.IGNORE);
+ // At this point we've consumed this file entry
+ // ourselves, so just strip the tar footer and
+ // go on to the next file in the input stream
+ tarBackupReader.skipTarPadding(info.size);
+ return true;
+ } else {
+ // File data before (or without) the apk. We can't
+ // handle it coherently in this case so ignore it.
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ okay = false;
+ }
+ break;
+
+ case ACCEPT:
+ if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
+ if (DEBUG) {
+ Slog.d(TAG, "apk present but ACCEPT");
+ }
+ // we can take the data without the apk, so we
+ // *want* to do so. skip the apk by declaring this
+ // one file not-okay without changing the restore
+ // policy for the package.
+ okay = false;
+ }
+ break;
+
+ default:
+ // Something has gone dreadfully wrong when determining
+ // the restore policy from the manifest. Ignore the
+ // rest of this package's data.
+ Slog.e(TAG, "Invalid policy from manifest");
+ okay = false;
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ break;
+ }
+
+ // Is it a *file* we need to drop or is it not a canonical path?
+ if (!isRestorableFile(info) || !isCanonicalFilePath(info.path)) {
+ okay = false;
+ }
+
+ // If the policy is satisfied, go ahead and set up to pipe the
+ // data to the agent.
+ if (MORE_DEBUG && okay && mAgent != null) {
+ Slog.i(TAG, "Reusing existing agent instance");
+ }
+ if (okay && mAgent == null) {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Need to launch agent for " + pkg);
+ }
+
+ try {
+ mTargetApp =
+ mBackupManagerService.getPackageManager().getApplicationInfo(
+ pkg, 0);
+
+ // If we haven't sent any data to this app yet, we probably
+ // need to clear it first. Check that.
+ if (!mClearedPackages.contains(pkg)) {
+ // apps with their own backup agents are
+ // responsible for coherently managing a full
+ // restore.
+ if (mTargetApp.backupAgentName == null) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Clearing app data preparatory to full restore");
+ }
+ mBackupManagerService.clearApplicationDataSynchronous(pkg);
+ } else {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "backup agent ("
+ + mTargetApp.backupAgentName + ") => no clear");
+ }
+ }
+ mClearedPackages.add(pkg);
+ } else {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "We've initialized this app already; no clear "
+ + "required");
+ }
+ }
+
+ // All set; now set up the IPC and launch the agent
+ setUpPipes();
+ mAgent = mBackupManagerService.bindToAgentSynchronous(mTargetApp,
+ ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL);
+ mAgentPackage = pkg;
+ } catch (IOException e) {
+ // fall through to error handling
+ } catch (NameNotFoundException e) {
+ // fall through to error handling
+ }
+
+ if (mAgent == null) {
+ Slog.e(TAG, "Unable to create agent for " + pkg);
+ okay = false;
+ tearDownPipes();
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ }
+ }
+
+ // Sanity check: make sure we never give data to the wrong app. This
+ // should never happen but a little paranoia here won't go amiss.
+ if (okay && !pkg.equals(mAgentPackage)) {
+ Slog.e(TAG, "Restoring data for " + pkg
+ + " but agent is for " + mAgentPackage);
+ okay = false;
+ }
+
+ // At this point we have an agent ready to handle the full
+ // restore data as well as a pipe for sending data to
+ // that agent. Tell the agent to start reading from the
+ // pipe.
+ if (okay) {
+ boolean agentSuccess = true;
+ long toCopy = info.size;
+ final boolean isSharedStorage = pkg.equals(SHARED_BACKUP_AGENT_PACKAGE);
+ final long timeout = isSharedStorage ?
+ TIMEOUT_SHARED_BACKUP_INTERVAL :
+ TIMEOUT_RESTORE_INTERVAL;
+ try {
+ mBackupManagerService.prepareOperationTimeout(token,
+ timeout,
+ mMonitorTask,
+ OP_TYPE_RESTORE_WAIT);
+
+ if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Restoring OBB file for " + pkg
+ + " : " + info.path);
+ }
+ mObbConnection.restoreObbFile(pkg, mPipes[0],
+ info.size, info.type, info.path, info.mode,
+ info.mtime, token,
+ mBackupManagerService.getBackupManagerBinder());
+ } else if (FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)) {
+ // This is only possible during adb restore.
+ // TODO: Refactor to clearly separate the flows.
+ if (DEBUG) {
+ Slog.d(TAG, "Restoring key-value file for " + pkg
+ + " : " + info.path);
+ }
+ KeyValueAdbRestoreEngine restoreEngine =
+ new KeyValueAdbRestoreEngine(
+ mBackupManagerService,
+ mBackupManagerService.getDataDir(), info, mPipes[0],
+ mAgent, token);
+ new Thread(restoreEngine, "restore-key-value-runner").start();
+ } else {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Invoking agent to restore file " + info.path);
+ }
+ // fire up the app's agent listening on the socket. If
+ // the agent is running in the system process we can't
+ // just invoke it asynchronously, so we provide a thread
+ // for it here.
+ if (mTargetApp.processName.equals("system")) {
+ Slog.d(TAG, "system process agent - spinning a thread");
+ RestoreFileRunnable runner = new RestoreFileRunnable(
+ mBackupManagerService, mAgent, info, mPipes[0], token);
+ new Thread(runner, "restore-sys-runner").start();
+ } else {
+ mAgent.doRestoreFile(mPipes[0], info.size, info.type,
+ info.domain, info.path, info.mode, info.mtime,
+ token, mBackupManagerService.getBackupManagerBinder());
+ }
+ }
+ } catch (IOException e) {
+ // couldn't dup the socket for a process-local restore
+ Slog.d(TAG, "Couldn't establish restore");
+ agentSuccess = false;
+ okay = false;
+ } catch (RemoteException e) {
+ // whoops, remote entity went away. We'll eat the content
+ // ourselves, then, and not copy it over.
+ Slog.e(TAG, "Agent crashed during full restore");
+ agentSuccess = false;
+ okay = false;
+ }
+
+ // Copy over the data if the agent is still good
+ if (okay) {
+ if (MORE_DEBUG) {
+ Slog.v(TAG, " copying to restore agent: " + toCopy + " bytes");
+ }
+ boolean pipeOkay = true;
+ FileOutputStream pipe = new FileOutputStream(
+ mPipes[1].getFileDescriptor());
+ while (toCopy > 0) {
+ int toRead = (toCopy > buffer.length)
+ ? buffer.length : (int) toCopy;
+ int nRead = instream.read(buffer, 0, toRead);
+ if (nRead >= 0) {
+ mBytes += nRead;
+ }
+ if (nRead <= 0) {
+ break;
+ }
+ toCopy -= nRead;
+
+ // send it to the output pipe as long as things
+ // are still good
+ if (pipeOkay) {
+ try {
+ pipe.write(buffer, 0, nRead);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write to restore pipe: "
+ + e.getMessage());
+ pipeOkay = false;
+ }
+ }
+ }
+
+ // done sending that file! Now we just need to consume
+ // the delta from info.size to the end of block.
+ tarBackupReader.skipTarPadding(info.size);
+
+ // and now that we've sent it all, wait for the remote
+ // side to acknowledge receipt
+ agentSuccess = mBackupManagerService.waitUntilOperationComplete(token);
+ }
+
+ // okay, if the remote end failed at any point, deal with
+ // it by ignoring the rest of the restore on it
+ if (!agentSuccess) {
+ Slog.w(TAG, "Agent failure restoring " + pkg + "; ending restore");
+ mBackupManagerService.getBackupHandler().removeMessages(
+ MSG_RESTORE_OPERATION_TIMEOUT);
+ tearDownPipes();
+ tearDownAgent(mTargetApp);
+ mAgent = null;
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+
+ // If this was a single-package restore, we halt immediately
+ // with an agent error under these circumstances
+ if (onlyPackage != null) {
+ setResult(RestoreEngine.TARGET_FAILURE);
+ setRunning(false);
+ return false;
+ }
+ }
+ }
+
+ // Problems setting up the agent communication, an explicitly
+ // dropped file, or an already-ignored package: skip to the
+ // next stream entry by reading and discarding this file.
+ if (!okay) {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "[discarding file content]");
+ }
+ long bytesToConsume = (info.size + 511) & ~511;
+ while (bytesToConsume > 0) {
+ int toRead = (bytesToConsume > buffer.length)
+ ? buffer.length : (int) bytesToConsume;
+ long nRead = instream.read(buffer, 0, toRead);
+ if (nRead >= 0) {
+ mBytes += nRead;
+ }
+ if (nRead <= 0) {
+ break;
+ }
+ bytesToConsume -= nRead;
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "io exception on restore socket read: " + e.getMessage());
+ }
+ setResult(RestoreEngine.TRANSPORT_FAILURE);
+ info = null;
+ }
+
+ // If we got here we're either running smoothly or we've finished
+ if (info == null) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "No [more] data for this package; tearing down");
+ }
+ tearDownPipes();
+ setRunning(false);
+ if (mustKillAgent) {
+ tearDownAgent(mTargetApp);
+ }
+ }
+ return (info != null);
+ }
+
+ private void setUpPipes() throws IOException {
+ mPipes = ParcelFileDescriptor.createPipe();
+ }
+
+ private void tearDownPipes() {
+ // Teardown might arise from the inline restore processing or from the asynchronous
+ // timeout mechanism, and these might race. Make sure we don't try to close and
+ // null out the pipes twice.
+ synchronized (this) {
+ if (mPipes != null) {
+ try {
+ mPipes[0].close();
+ mPipes[0] = null;
+ mPipes[1].close();
+ mPipes[1] = null;
+ } catch (IOException e) {
+ Slog.w(TAG, "Couldn't close agent pipes", e);
+ }
+ mPipes = null;
+ }
+ }
+ }
+
+ private void tearDownAgent(ApplicationInfo app) {
+ if (mAgent != null) {
+ mBackupManagerService.tearDownAgentAndKill(app);
+ mAgent = null;
+ }
+ }
+
+ void handleTimeout() {
+ tearDownPipes();
+ setResult(RestoreEngine.TARGET_FAILURE);
+ setRunning(false);
+ }
+
+ private static boolean isRestorableFile(FileMetadata info) {
+ if (FullBackup.CACHE_TREE_TOKEN.equals(info.domain)) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Dropping cache file path " + info.path);
+ }
+ return false;
+ }
+
+ if (FullBackup.ROOT_TREE_TOKEN.equals(info.domain)) {
+ // It's possible this is "no-backup" dir contents in an archive stream
+ // produced on a device running a version of the OS that predates that
+ // API. Respect the no-backup intention and don't let the data get to
+ // the app.
+ if (info.path.startsWith("no_backup/")) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Dropping no_backup file path " + info.path);
+ }
+ return false;
+ }
+ }
+
+ // Otherwise we think this file is good to go
+ return true;
+ }
+
+ private static boolean isCanonicalFilePath(String path) {
+ if (path.contains("..") || path.contains("//")) {
+ if (MORE_DEBUG) {
+ Slog.w(TAG, "Dropping invalid path " + path);
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ void sendOnRestorePackage(String name) {
+ if (mObserver != null) {
+ try {
+ // TODO: use a more user-friendly name string
+ mObserver.onRestorePackage(name);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "full restore observer went away: restorePackage");
+ mObserver = null;
+ }
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
new file mode 100644
index 0000000..d5c62af
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -0,0 +1,848 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.restore;
+
+import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_FILE_HEADER_MAGIC;
+import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_FILE_VERSION;
+import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME;
+import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_METADATA_FILENAME;
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_RESTORE_WAIT;
+import static com.android.server.backup.RefactoredBackupManagerService.PBKDF_CURRENT;
+import static com.android.server.backup.RefactoredBackupManagerService.PBKDF_FALLBACK;
+import static com.android.server.backup.RefactoredBackupManagerService.SETTINGS_PACKAGE;
+import static com.android.server.backup.RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL;
+import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_RESTORE_INTERVAL;
+import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT;
+
+import android.app.ApplicationThreadConstants;
+import android.app.IBackupAgent;
+import android.app.backup.FullBackup;
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IFullBackupRestoreObserver;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.backup.FileMetadata;
+import com.android.server.backup.KeyValueAdbRestoreEngine;
+import com.android.server.backup.PackageManagerBackupAgent;
+import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.fullbackup.FullBackupObbConnection;
+import com.android.server.backup.utils.BytesReadListener;
+import com.android.server.backup.utils.FullBackupRestoreObserverUtils;
+import com.android.server.backup.utils.PasswordUtils;
+import com.android.server.backup.utils.RestoreUtils;
+import com.android.server.backup.utils.TarBackupReader;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.zip.InflaterInputStream;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class PerformAdbRestoreTask implements Runnable {
+
+ private final RefactoredBackupManagerService mBackupManagerService;
+ private final ParcelFileDescriptor mInputFile;
+ private final String mCurrentPassword;
+ private final String mDecryptPassword;
+ private final AtomicBoolean mLatchObject;
+ private final PackageManagerBackupAgent mPackageManagerBackupAgent;
+ private final RestoreInstallObserver mInstallObserver = new RestoreInstallObserver();
+ private final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver();
+
+ private IFullBackupRestoreObserver mObserver;
+ private IBackupAgent mAgent;
+ private String mAgentPackage;
+ private ApplicationInfo mTargetApp;
+ private FullBackupObbConnection mObbConnection = null;
+ private ParcelFileDescriptor[] mPipes = null;
+ private byte[] mWidgetData = null;
+
+ private long mBytes;
+
+ // Runner that can be placed on a separate thread to do in-process invocation
+ // of the "restore finished" API asynchronously. Used by adb restore.
+ private static class RestoreFinishedRunnable implements Runnable {
+
+ private final IBackupAgent mAgent;
+ private final int mToken;
+ private final RefactoredBackupManagerService mBackupManagerService;
+
+ RestoreFinishedRunnable(IBackupAgent agent, int token,
+ RefactoredBackupManagerService backupManagerService) {
+ mAgent = agent;
+ mToken = token;
+ mBackupManagerService = backupManagerService;
+ }
+
+ @Override
+ public void run() {
+ try {
+ mAgent.doRestoreFinished(mToken, mBackupManagerService.getBackupManagerBinder());
+ } catch (RemoteException e) {
+ // never happens; this is used only for local binder calls
+ }
+ }
+ }
+
+ // possible handling states for a given package in the restore dataset
+ private final HashMap<String, RestorePolicy> mPackagePolicies
+ = new HashMap<>();
+
+ // installer package names for each encountered app, derived from the manifests
+ private final HashMap<String, String> mPackageInstallers = new HashMap<>();
+
+ // Signatures for a given package found in its manifest file
+ private final HashMap<String, Signature[]> mManifestSignatures
+ = new HashMap<>();
+
+ // Packages we've already wiped data on when restoring their first file
+ private final HashSet<String> mClearedPackages = new HashSet<>();
+
+ public PerformAdbRestoreTask(RefactoredBackupManagerService backupManagerService,
+ ParcelFileDescriptor fd, String curPassword, String decryptPassword,
+ IFullBackupRestoreObserver observer, AtomicBoolean latch) {
+ this.mBackupManagerService = backupManagerService;
+ mInputFile = fd;
+ mCurrentPassword = curPassword;
+ mDecryptPassword = decryptPassword;
+ mObserver = observer;
+ mLatchObject = latch;
+ mAgent = null;
+ mPackageManagerBackupAgent = new PackageManagerBackupAgent(
+ backupManagerService.getPackageManager());
+ mAgentPackage = null;
+ mTargetApp = null;
+ mObbConnection = new FullBackupObbConnection(backupManagerService);
+
+ // Which packages we've already wiped data on. We prepopulate this
+ // with a whitelist of packages known to be unclearable.
+ mClearedPackages.add("android");
+ mClearedPackages.add(SETTINGS_PACKAGE);
+ }
+
+ @Override
+ public void run() {
+ Slog.i(TAG, "--- Performing full-dataset restore ---");
+ mObbConnection.establish();
+ mObserver = FullBackupRestoreObserverUtils.sendStartRestore(mObserver);
+
+ // Are we able to restore shared-storage data?
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ mPackagePolicies.put(SHARED_BACKUP_AGENT_PACKAGE, RestorePolicy.ACCEPT);
+ }
+
+ FileInputStream rawInStream = null;
+ try {
+ if (!mBackupManagerService.backupPasswordMatches(mCurrentPassword)) {
+ if (DEBUG) {
+ Slog.w(TAG, "Backup password mismatch; aborting");
+ }
+ return;
+ }
+
+ mBytes = 0;
+
+ rawInStream = new FileInputStream(mInputFile.getFileDescriptor());
+
+ InputStream tarInputStream = parseBackupFileHeaderAndReturnTarStream(rawInStream,
+ mDecryptPassword);
+ if (tarInputStream == null) {
+ // There was an error reading the backup file, which is already handled and logged.
+ // Just abort.
+ return;
+ }
+
+ byte[] buffer = new byte[32 * 1024];
+ boolean didRestore;
+ do {
+ didRestore = restoreOneFile(tarInputStream, false /* mustKillAgent */, buffer,
+ null /* onlyPackage */, true /* allowApks */,
+ mBackupManagerService.generateRandomIntegerToken(), null /* monitor */);
+ } while (didRestore);
+
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "Done consuming input tarfile, total bytes=" + mBytes);
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to read restore input");
+ } finally {
+ tearDownPipes();
+ tearDownAgent(mTargetApp, true);
+
+ try {
+ if (rawInStream != null) {
+ rawInStream.close();
+ }
+ mInputFile.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Close of restore data pipe threw", e);
+ /* nothing we can do about this */
+ }
+ synchronized (mLatchObject) {
+ mLatchObject.set(true);
+ mLatchObject.notifyAll();
+ }
+ mObbConnection.tearDown();
+ mObserver = FullBackupRestoreObserverUtils.sendEndRestore(mObserver);
+ Slog.d(TAG, "Full restore pass complete.");
+ mBackupManagerService.getWakelock().release();
+ }
+ }
+
+ private static void readFullyOrThrow(InputStream in, byte[] buffer) throws IOException {
+ int offset = 0;
+ while (offset < buffer.length) {
+ int bytesRead = in.read(buffer, offset, buffer.length - offset);
+ if (bytesRead <= 0) {
+ throw new IOException("Couldn't fully read data");
+ }
+ offset += bytesRead;
+ }
+ }
+
+ @VisibleForTesting
+ public static InputStream parseBackupFileHeaderAndReturnTarStream(
+ InputStream rawInputStream,
+ String decryptPassword)
+ throws IOException {
+ // First, parse out the unencrypted/uncompressed header
+ boolean compressed = false;
+ InputStream preCompressStream = rawInputStream;
+
+ boolean okay = false;
+ final int headerLen = BACKUP_FILE_HEADER_MAGIC.length();
+ byte[] streamHeader = new byte[headerLen];
+ readFullyOrThrow(rawInputStream, streamHeader);
+ byte[] magicBytes = BACKUP_FILE_HEADER_MAGIC.getBytes(
+ "UTF-8");
+ if (Arrays.equals(magicBytes, streamHeader)) {
+ // okay, header looks good. now parse out the rest of the fields.
+ String s = readHeaderLine(rawInputStream);
+ final int archiveVersion = Integer.parseInt(s);
+ if (archiveVersion <= BACKUP_FILE_VERSION) {
+ // okay, it's a version we recognize. if it's version 1, we may need
+ // to try two different PBKDF2 regimes to compare checksums.
+ final boolean pbkdf2Fallback = (archiveVersion == 1);
+
+ s = readHeaderLine(rawInputStream);
+ compressed = (Integer.parseInt(s) != 0);
+ s = readHeaderLine(rawInputStream);
+ if (s.equals("none")) {
+ // no more header to parse; we're good to go
+ okay = true;
+ } else if (decryptPassword != null && decryptPassword.length() > 0) {
+ preCompressStream = decodeAesHeaderAndInitialize(
+ decryptPassword, s, pbkdf2Fallback,
+ rawInputStream);
+ if (preCompressStream != null) {
+ okay = true;
+ }
+ } else {
+ Slog.w(TAG, "Archive is encrypted but no password given");
+ }
+ } else {
+ Slog.w(TAG, "Wrong header version: " + s);
+ }
+ } else {
+ Slog.w(TAG, "Didn't read the right header magic");
+ }
+
+ if (!okay) {
+ Slog.w(TAG, "Invalid restore data; aborting.");
+ return null;
+ }
+
+ // okay, use the right stream layer based on compression
+ return compressed ? new InflaterInputStream(preCompressStream) : preCompressStream;
+ }
+
+ private static String readHeaderLine(InputStream in) throws IOException {
+ int c;
+ StringBuilder buffer = new StringBuilder(80);
+ while ((c = in.read()) >= 0) {
+ if (c == '\n') {
+ break; // consume and discard the newlines
+ }
+ buffer.append((char) c);
+ }
+ return buffer.toString();
+ }
+
+ private static InputStream attemptMasterKeyDecryption(String decryptPassword, String algorithm,
+ byte[] userSalt, byte[] ckSalt,
+ int rounds, String userIvHex, String masterKeyBlobHex, InputStream rawInStream,
+ boolean doLog) {
+ InputStream result = null;
+
+ try {
+ Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ SecretKey userKey = PasswordUtils
+ .buildPasswordKey(algorithm, decryptPassword, userSalt,
+ rounds);
+ byte[] IV = PasswordUtils.hexToByteArray(userIvHex);
+ IvParameterSpec ivSpec = new IvParameterSpec(IV);
+ c.init(Cipher.DECRYPT_MODE,
+ new SecretKeySpec(userKey.getEncoded(), "AES"),
+ ivSpec);
+ byte[] mkCipher = PasswordUtils.hexToByteArray(masterKeyBlobHex);
+ byte[] mkBlob = c.doFinal(mkCipher);
+
+ // first, the master key IV
+ int offset = 0;
+ int len = mkBlob[offset++];
+ IV = Arrays.copyOfRange(mkBlob, offset, offset + len);
+ offset += len;
+ // then the master key itself
+ len = mkBlob[offset++];
+ byte[] mk = Arrays.copyOfRange(mkBlob,
+ offset, offset + len);
+ offset += len;
+ // and finally the master key checksum hash
+ len = mkBlob[offset++];
+ byte[] mkChecksum = Arrays.copyOfRange(mkBlob,
+ offset, offset + len);
+
+ // now validate the decrypted master key against the checksum
+ byte[] calculatedCk = PasswordUtils.makeKeyChecksum(algorithm, mk, ckSalt,
+ rounds);
+ if (Arrays.equals(calculatedCk, mkChecksum)) {
+ ivSpec = new IvParameterSpec(IV);
+ c.init(Cipher.DECRYPT_MODE,
+ new SecretKeySpec(mk, "AES"),
+ ivSpec);
+ // Only if all of the above worked properly will 'result' be assigned
+ result = new CipherInputStream(rawInStream, c);
+ } else if (doLog) {
+ Slog.w(TAG, "Incorrect password");
+ }
+ } catch (InvalidAlgorithmParameterException e) {
+ if (doLog) {
+ Slog.e(TAG, "Needed parameter spec unavailable!", e);
+ }
+ } catch (BadPaddingException e) {
+ // This case frequently occurs when the wrong password is used to decrypt
+ // the master key. Use the identical "incorrect password" log text as is
+ // used in the checksum failure log in order to avoid providing additional
+ // information to an attacker.
+ if (doLog) {
+ Slog.w(TAG, "Incorrect password");
+ }
+ } catch (IllegalBlockSizeException e) {
+ if (doLog) {
+ Slog.w(TAG, "Invalid block size in master key");
+ }
+ } catch (NoSuchAlgorithmException e) {
+ if (doLog) {
+ Slog.e(TAG, "Needed decryption algorithm unavailable!");
+ }
+ } catch (NoSuchPaddingException e) {
+ if (doLog) {
+ Slog.e(TAG, "Needed padding mechanism unavailable!");
+ }
+ } catch (InvalidKeyException e) {
+ if (doLog) {
+ Slog.w(TAG, "Illegal password; aborting");
+ }
+ }
+
+ return result;
+ }
+
+ private static InputStream decodeAesHeaderAndInitialize(String decryptPassword,
+ String encryptionName,
+ boolean pbkdf2Fallback,
+ InputStream rawInStream) {
+ InputStream result = null;
+ try {
+ if (encryptionName.equals(PasswordUtils.ENCRYPTION_ALGORITHM_NAME)) {
+
+ String userSaltHex = readHeaderLine(rawInStream); // 5
+ byte[] userSalt = PasswordUtils.hexToByteArray(userSaltHex);
+
+ String ckSaltHex = readHeaderLine(rawInStream); // 6
+ byte[] ckSalt = PasswordUtils.hexToByteArray(ckSaltHex);
+
+ int rounds = Integer.parseInt(readHeaderLine(rawInStream)); // 7
+ String userIvHex = readHeaderLine(rawInStream); // 8
+
+ String masterKeyBlobHex = readHeaderLine(rawInStream); // 9
+
+ // decrypt the master key blob
+ result = attemptMasterKeyDecryption(decryptPassword, PBKDF_CURRENT,
+ userSalt, ckSalt, rounds, userIvHex, masterKeyBlobHex, rawInStream, false);
+ if (result == null && pbkdf2Fallback) {
+ result = attemptMasterKeyDecryption(
+ decryptPassword, PBKDF_FALLBACK, userSalt, ckSalt,
+ rounds, userIvHex, masterKeyBlobHex, rawInStream, true);
+ }
+ } else {
+ Slog.w(TAG, "Unsupported encryption method: " + encryptionName);
+ }
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Can't parse restore data header");
+ } catch (IOException e) {
+ Slog.w(TAG, "Can't read input header");
+ }
+
+ return result;
+ }
+
+ boolean restoreOneFile(InputStream instream, boolean mustKillAgent, byte[] buffer,
+ PackageInfo onlyPackage, boolean allowApks, int token, IBackupManagerMonitor monitor) {
+ BytesReadListener bytesReadListener = new BytesReadListener() {
+ @Override
+ public void onBytesRead(long bytesRead) {
+ mBytes += bytesRead;
+ }
+ };
+ TarBackupReader tarBackupReader = new TarBackupReader(instream,
+ bytesReadListener, monitor);
+ FileMetadata info;
+ try {
+ info = tarBackupReader.readTarHeaders();
+ if (info != null) {
+ if (MORE_DEBUG) {
+ info.dump();
+ }
+
+ final String pkg = info.packageName;
+ if (!pkg.equals(mAgentPackage)) {
+ // okay, change in package; set up our various
+ // bookkeeping if we haven't seen it yet
+ if (!mPackagePolicies.containsKey(pkg)) {
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ }
+
+ // Clean up the previous agent relationship if necessary,
+ // and let the observer know we're considering a new app.
+ if (mAgent != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Saw new package; finalizing old one");
+ }
+ // Now we're really done
+ tearDownPipes();
+ tearDownAgent(mTargetApp, true);
+ mTargetApp = null;
+ mAgentPackage = null;
+ }
+ }
+
+ if (info.path.equals(BACKUP_MANIFEST_FILENAME)) {
+ Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(
+ info);
+ RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
+ mBackupManagerService.getPackageManager(), allowApks,
+ info, signatures);
+ mManifestSignatures.put(info.packageName, signatures);
+ mPackagePolicies.put(pkg, restorePolicy);
+ mPackageInstallers.put(pkg, info.installerPackageName);
+ // We've read only the manifest content itself at this point,
+ // so consume the footer before looping around to the next
+ // input file
+ tarBackupReader.skipTarPadding(info.size);
+ mObserver = FullBackupRestoreObserverUtils.sendOnRestorePackage(mObserver, pkg);
+ } else if (info.path.equals(BACKUP_METADATA_FILENAME)) {
+ // Metadata blobs!
+ tarBackupReader.readMetadata(info);
+
+ // The following only exist because we want to keep refactoring as safe as
+ // possible, without changing too much.
+ // TODO: Refactor, so that there are no funny things like this.
+ // This is read during TarBackupReader.readMetadata().
+ mWidgetData = tarBackupReader.getWidgetData();
+ // This can be nulled during TarBackupReader.readMetadata().
+ monitor = tarBackupReader.getMonitor();
+
+ tarBackupReader.skipTarPadding(info.size);
+ } else {
+ // Non-manifest, so it's actual file data. Is this a package
+ // we're ignoring?
+ boolean okay = true;
+ RestorePolicy policy = mPackagePolicies.get(pkg);
+ switch (policy) {
+ case IGNORE:
+ okay = false;
+ break;
+
+ case ACCEPT_IF_APK:
+ // If we're in accept-if-apk state, then the first file we
+ // see MUST be the apk.
+ if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
+ if (DEBUG) {
+ Slog.d(TAG, "APK file; installing");
+ }
+ // Try to install the app.
+ String installerName = mPackageInstallers.get(pkg);
+ boolean isSuccessfullyInstalled = RestoreUtils.installApk(
+ instream, mBackupManagerService.getPackageManager(),
+ mInstallObserver, mDeleteObserver, mManifestSignatures,
+ mPackagePolicies, info, installerName,
+ bytesReadListener, mBackupManagerService.getDataDir()
+ );
+ // good to go; promote to ACCEPT
+ mPackagePolicies.put(pkg, isSuccessfullyInstalled
+ ? RestorePolicy.ACCEPT
+ : RestorePolicy.IGNORE);
+ // At this point we've consumed this file entry
+ // ourselves, so just strip the tar footer and
+ // go on to the next file in the input stream
+ tarBackupReader.skipTarPadding(info.size);
+ return true;
+ } else {
+ // File data before (or without) the apk. We can't
+ // handle it coherently in this case so ignore it.
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ okay = false;
+ }
+ break;
+
+ case ACCEPT:
+ if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
+ if (DEBUG) {
+ Slog.d(TAG, "apk present but ACCEPT");
+ }
+ // we can take the data without the apk, so we
+ // *want* to do so. skip the apk by declaring this
+ // one file not-okay without changing the restore
+ // policy for the package.
+ okay = false;
+ }
+ break;
+
+ default:
+ // Something has gone dreadfully wrong when determining
+ // the restore policy from the manifest. Ignore the
+ // rest of this package's data.
+ Slog.e(TAG, "Invalid policy from manifest");
+ okay = false;
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ break;
+ }
+
+ // The path needs to be canonical
+ if (!isCanonicalFilePath(info.path)) {
+ okay = false;
+ }
+
+ // If the policy is satisfied, go ahead and set up to pipe the
+ // data to the agent.
+ if (DEBUG && okay && mAgent != null) {
+ Slog.i(TAG, "Reusing existing agent instance");
+ }
+ if (okay && mAgent == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Need to launch agent for " + pkg);
+ }
+
+ try {
+ mTargetApp =
+ mBackupManagerService.getPackageManager().getApplicationInfo(
+ pkg, 0);
+
+ // If we haven't sent any data to this app yet, we probably
+ // need to clear it first. Check that.
+ if (!mClearedPackages.contains(pkg)) {
+ // apps with their own backup agents are
+ // responsible for coherently managing a full
+ // restore.
+ if (mTargetApp.backupAgentName == null) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Clearing app data preparatory to full restore");
+ }
+ mBackupManagerService.clearApplicationDataSynchronous(pkg);
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "backup agent ("
+ + mTargetApp.backupAgentName + ") => no clear");
+ }
+ }
+ mClearedPackages.add(pkg);
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "We've initialized this app already; no clear "
+ + "required");
+ }
+ }
+
+ // All set; now set up the IPC and launch the agent
+ setUpPipes();
+ mAgent = mBackupManagerService.bindToAgentSynchronous(mTargetApp,
+ ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL);
+ mAgentPackage = pkg;
+ } catch (IOException e) {
+ // fall through to error handling
+ } catch (NameNotFoundException e) {
+ // fall through to error handling
+ }
+
+ if (mAgent == null) {
+ Slog.e(TAG, "Unable to create agent for " + pkg);
+ okay = false;
+ tearDownPipes();
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ }
+ }
+
+ // Sanity check: make sure we never give data to the wrong app. This
+ // should never happen but a little paranoia here won't go amiss.
+ if (okay && !pkg.equals(mAgentPackage)) {
+ Slog.e(TAG, "Restoring data for " + pkg
+ + " but agent is for " + mAgentPackage);
+ okay = false;
+ }
+
+ // At this point we have an agent ready to handle the full
+ // restore data as well as a pipe for sending data to
+ // that agent. Tell the agent to start reading from the
+ // pipe.
+ if (okay) {
+ boolean agentSuccess = true;
+ long toCopy = info.size;
+ try {
+ mBackupManagerService.prepareOperationTimeout(
+ token, TIMEOUT_RESTORE_INTERVAL, null, OP_TYPE_RESTORE_WAIT);
+
+ if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Restoring OBB file for " + pkg
+ + " : " + info.path);
+ }
+ mObbConnection.restoreObbFile(pkg, mPipes[0],
+ info.size, info.type, info.path, info.mode,
+ info.mtime, token,
+ mBackupManagerService.getBackupManagerBinder());
+ } else if (FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Restoring key-value file for " + pkg
+ + " : " + info.path);
+ }
+ KeyValueAdbRestoreEngine restoreEngine =
+ new KeyValueAdbRestoreEngine(
+ mBackupManagerService,
+ mBackupManagerService.getDataDir(), info, mPipes[0],
+ mAgent, token);
+ new Thread(restoreEngine, "restore-key-value-runner").start();
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "Invoking agent to restore file " + info.path);
+ }
+ // fire up the app's agent listening on the socket. If
+ // the agent is running in the system process we can't
+ // just invoke it asynchronously, so we provide a thread
+ // for it here.
+ if (mTargetApp.processName.equals("system")) {
+ Slog.d(TAG, "system process agent - spinning a thread");
+ RestoreFileRunnable runner = new RestoreFileRunnable(
+ mBackupManagerService, mAgent, info, mPipes[0], token);
+ new Thread(runner, "restore-sys-runner").start();
+ } else {
+ mAgent.doRestoreFile(mPipes[0], info.size, info.type,
+ info.domain, info.path, info.mode, info.mtime,
+ token, mBackupManagerService.getBackupManagerBinder());
+ }
+ }
+ } catch (IOException e) {
+ // couldn't dup the socket for a process-local restore
+ Slog.d(TAG, "Couldn't establish restore");
+ agentSuccess = false;
+ okay = false;
+ } catch (RemoteException e) {
+ // whoops, remote entity went away. We'll eat the content
+ // ourselves, then, and not copy it over.
+ Slog.e(TAG, "Agent crashed during full restore");
+ agentSuccess = false;
+ okay = false;
+ }
+
+ // Copy over the data if the agent is still good
+ if (okay) {
+ boolean pipeOkay = true;
+ FileOutputStream pipe = new FileOutputStream(
+ mPipes[1].getFileDescriptor());
+ while (toCopy > 0) {
+ int toRead = (toCopy > buffer.length)
+ ? buffer.length : (int) toCopy;
+ int nRead = instream.read(buffer, 0, toRead);
+ if (nRead >= 0) {
+ mBytes += nRead;
+ }
+ if (nRead <= 0) {
+ break;
+ }
+ toCopy -= nRead;
+
+ // send it to the output pipe as long as things
+ // are still good
+ if (pipeOkay) {
+ try {
+ pipe.write(buffer, 0, nRead);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write to restore pipe", e);
+ pipeOkay = false;
+ }
+ }
+ }
+
+ // done sending that file! Now we just need to consume
+ // the delta from info.size to the end of block.
+ tarBackupReader.skipTarPadding(info.size);
+
+ // and now that we've sent it all, wait for the remote
+ // side to acknowledge receipt
+ agentSuccess = mBackupManagerService.waitUntilOperationComplete(token);
+ }
+
+ // okay, if the remote end failed at any point, deal with
+ // it by ignoring the rest of the restore on it
+ if (!agentSuccess) {
+ if (DEBUG) {
+ Slog.d(TAG, "Agent failure restoring " + pkg + "; now ignoring");
+ }
+ mBackupManagerService.getBackupHandler().removeMessages(
+ MSG_RESTORE_OPERATION_TIMEOUT);
+ tearDownPipes();
+ tearDownAgent(mTargetApp, false);
+ mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
+ }
+ }
+
+ // Problems setting up the agent communication, or an already-
+ // ignored package: skip to the next tar stream entry by
+ // reading and discarding this file.
+ if (!okay) {
+ if (DEBUG) {
+ Slog.d(TAG, "[discarding file content]");
+ }
+ long bytesToConsume = (info.size + 511) & ~511;
+ while (bytesToConsume > 0) {
+ int toRead = (bytesToConsume > buffer.length)
+ ? buffer.length : (int) bytesToConsume;
+ long nRead = instream.read(buffer, 0, toRead);
+ if (nRead >= 0) {
+ mBytes += nRead;
+ }
+ if (nRead <= 0) {
+ break;
+ }
+ bytesToConsume -= nRead;
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "io exception on restore socket read", e);
+ }
+ // treat as EOF
+ info = null;
+ }
+
+ return (info != null);
+ }
+
+ private static boolean isCanonicalFilePath(String path) {
+ if (path.contains("..") || path.contains("//")) {
+ if (MORE_DEBUG) {
+ Slog.w(TAG, "Dropping invalid path " + path);
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ private void setUpPipes() throws IOException {
+ mPipes = ParcelFileDescriptor.createPipe();
+ }
+
+ private void tearDownPipes() {
+ if (mPipes != null) {
+ try {
+ mPipes[0].close();
+ mPipes[0] = null;
+ mPipes[1].close();
+ mPipes[1] = null;
+ } catch (IOException e) {
+ Slog.w(TAG, "Couldn't close agent pipes", e);
+ }
+ mPipes = null;
+ }
+ }
+
+ private void tearDownAgent(ApplicationInfo app, boolean doRestoreFinished) {
+ if (mAgent != null) {
+ try {
+ // In the adb restore case, we do restore-finished here
+ if (doRestoreFinished) {
+ final int token = mBackupManagerService.generateRandomIntegerToken();
+ final AdbRestoreFinishedLatch latch = new AdbRestoreFinishedLatch(
+ mBackupManagerService, token);
+ mBackupManagerService.prepareOperationTimeout(
+ token, TIMEOUT_FULL_BACKUP_INTERVAL, latch, OP_TYPE_RESTORE_WAIT);
+ if (mTargetApp.processName.equals("system")) {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "system agent - restoreFinished on thread");
+ }
+ Runnable runner = new RestoreFinishedRunnable(mAgent, token,
+ mBackupManagerService);
+ new Thread(runner, "restore-sys-finished-runner").start();
+ } else {
+ mAgent.doRestoreFinished(token,
+ mBackupManagerService.getBackupManagerBinder());
+ }
+
+ latch.await();
+ }
+
+ mBackupManagerService.tearDownAgentAndKill(app);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Lost app trying to shut down");
+ }
+ mAgent = null;
+ }
+ }
+
+}
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
new file mode 100644
index 0000000..21d5dc2
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -0,0 +1,1324 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.restore;
+
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.KEY_WIDGET_STATE;
+import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_RESTORE_WAIT;
+import static com.android.server.backup.RefactoredBackupManagerService.PACKAGE_MANAGER_SENTINEL;
+import static com.android.server.backup.RefactoredBackupManagerService.SETTINGS_PACKAGE;
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+import static com.android.server.backup.RefactoredBackupManagerService
+ .TIMEOUT_RESTORE_FINISHED_INTERVAL;
+import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_RESTORE_INTERVAL;
+import static com.android.server.backup.internal.BackupHandler.MSG_BACKUP_RESTORE_STEP;
+import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT;
+import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_SESSION_TIMEOUT;
+
+import android.app.ApplicationThreadConstants;
+import android.app.IBackupAgent;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupManagerMonitor;
+import android.app.backup.BackupTransport;
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IRestoreObserver;
+import android.app.backup.RestoreDescription;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.EventLog;
+import android.util.Slog;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.AppWidgetBackupBridge;
+import com.android.server.EventLogTags;
+import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.BackupUtils;
+import com.android.server.backup.PackageManagerBackupAgent;
+import com.android.server.backup.PackageManagerBackupAgent.Metadata;
+import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.utils.AppBackupUtils;
+import com.android.server.backup.utils.BackupManagerMonitorUtils;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class PerformUnifiedRestoreTask implements BackupRestoreTask {
+
+ private RefactoredBackupManagerService backupManagerService;
+ // Transport we're working with to do the restore
+ private IBackupTransport mTransport;
+
+ // Where per-transport saved state goes
+ File mStateDir;
+
+ // Restore observer; may be null
+ private IRestoreObserver mObserver;
+
+ // BackuoManagerMonitor; may be null
+ private IBackupManagerMonitor mMonitor;
+
+ // Token identifying the dataset to the transport
+ private long mToken;
+
+ // When this is a restore-during-install, this is the token identifying the
+ // operation to the Package Manager, and we must ensure that we let it know
+ // when we're finished.
+ private int mPmToken;
+
+ // When this is restore-during-install, we need to tell the package manager
+ // whether we actually launched the app, because this affects notifications
+ // around externally-visible state transitions.
+ private boolean mDidLaunch;
+
+ // Is this a whole-system restore, i.e. are we establishing a new ancestral
+ // dataset to base future restore-at-install operations from?
+ private boolean mIsSystemRestore;
+
+ // If this is a single-package restore, what package are we interested in?
+ private PackageInfo mTargetPackage;
+
+ // In all cases, the calculated list of packages that we are trying to restore
+ private List<PackageInfo> mAcceptSet;
+
+ // Our bookkeeping about the ancestral dataset
+ private PackageManagerBackupAgent mPmAgent;
+
+ // Currently-bound backup agent for restore + restoreFinished purposes
+ private IBackupAgent mAgent;
+
+ // What sort of restore we're doing now
+ private RestoreDescription mRestoreDescription;
+
+ // The package we're currently restoring
+ private PackageInfo mCurrentPackage;
+
+ // Widget-related data handled as part of this restore operation
+ private byte[] mWidgetData;
+
+ // Number of apps restored in this pass
+ private int mCount;
+
+ // When did we start?
+ private long mStartRealtime;
+
+ // State machine progress
+ private UnifiedRestoreState mState;
+
+ // How are things going?
+ private int mStatus;
+
+ // Done?
+ private boolean mFinished;
+
+ // Key/value: bookkeeping about staged data and files for agent access
+ private File mBackupDataName;
+ private File mStageName;
+ private File mSavedStateName;
+ private File mNewStateName;
+ ParcelFileDescriptor mBackupData;
+ ParcelFileDescriptor mNewState;
+
+ private final int mEphemeralOpToken;
+
+ // Invariant: mWakelock is already held, and this task is responsible for
+ // releasing it at the end of the restore operation.
+ public PerformUnifiedRestoreTask(RefactoredBackupManagerService backupManagerService,
+ IBackupTransport transport, IRestoreObserver observer,
+ IBackupManagerMonitor monitor, long restoreSetToken, PackageInfo targetPackage,
+ int pmToken, boolean isFullSystemRestore, String[] filterSet) {
+ this.backupManagerService = backupManagerService;
+ mEphemeralOpToken = backupManagerService.generateRandomIntegerToken();
+ mState = UnifiedRestoreState.INITIAL;
+ mStartRealtime = SystemClock.elapsedRealtime();
+
+ mTransport = transport;
+ mObserver = observer;
+ mMonitor = monitor;
+ mToken = restoreSetToken;
+ mPmToken = pmToken;
+ mTargetPackage = targetPackage;
+ mIsSystemRestore = isFullSystemRestore;
+ mFinished = false;
+ mDidLaunch = false;
+
+ if (targetPackage != null) {
+ // Single package restore
+ mAcceptSet = new ArrayList<>();
+ mAcceptSet.add(targetPackage);
+ } else {
+ // Everything possible, or a target set
+ if (filterSet == null) {
+ // We want everything and a pony
+ List<PackageInfo> apps =
+ PackageManagerBackupAgent.getStorableApplications(
+ backupManagerService.getPackageManager());
+ filterSet = packagesToNames(apps);
+ if (DEBUG) {
+ Slog.i(TAG, "Full restore; asking about " + filterSet.length + " apps");
+ }
+ }
+
+ mAcceptSet = new ArrayList<>(filterSet.length);
+
+ // Pro tem, we insist on moving the settings provider package to last place.
+ // Keep track of whether it's in the list, and bump it down if so. We also
+ // want to do the system package itself first if it's called for.
+ boolean hasSystem = false;
+ boolean hasSettings = false;
+ for (int i = 0; i < filterSet.length; i++) {
+ try {
+ PackageInfo info = backupManagerService.getPackageManager().getPackageInfo(
+ filterSet[i], 0);
+ if ("android".equals(info.packageName)) {
+ hasSystem = true;
+ continue;
+ }
+ if (SETTINGS_PACKAGE.equals(info.packageName)) {
+ hasSettings = true;
+ continue;
+ }
+
+ if (AppBackupUtils.appIsEligibleForBackup(
+ info.applicationInfo)) {
+ mAcceptSet.add(info);
+ }
+ } catch (NameNotFoundException e) {
+ // requested package name doesn't exist; ignore it
+ }
+ }
+ if (hasSystem) {
+ try {
+ mAcceptSet.add(0,
+ backupManagerService.getPackageManager().getPackageInfo("android", 0));
+ } catch (NameNotFoundException e) {
+ // won't happen; we know a priori that it's valid
+ }
+ }
+ if (hasSettings) {
+ try {
+ mAcceptSet.add(backupManagerService.getPackageManager().getPackageInfo(
+ SETTINGS_PACKAGE, 0));
+ } catch (NameNotFoundException e) {
+ // this one is always valid too
+ }
+ }
+ }
+
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "Restore; accept set size is " + mAcceptSet.size());
+ for (PackageInfo info : mAcceptSet) {
+ Slog.v(TAG, " " + info.packageName);
+ }
+ }
+ }
+
+ private String[] packagesToNames(List<PackageInfo> apps) {
+ final int N = apps.size();
+ String[] names = new String[N];
+ for (int i = 0; i < N; i++) {
+ names[i] = apps.get(i).packageName;
+ }
+ return names;
+ }
+
+ // Execute one tick of whatever state machine the task implements
+ @Override
+ public void execute() {
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "*** Executing restore step " + mState);
+ }
+ switch (mState) {
+ case INITIAL:
+ startRestore();
+ break;
+
+ case RUNNING_QUEUE:
+ dispatchNextRestore();
+ break;
+
+ case RESTORE_KEYVALUE:
+ restoreKeyValue();
+ break;
+
+ case RESTORE_FULL:
+ restoreFull();
+ break;
+
+ case RESTORE_FINISHED:
+ restoreFinished();
+ break;
+
+ case FINAL:
+ if (!mFinished) {
+ finalizeRestore();
+ } else {
+ Slog.e(TAG, "Duplicate finish");
+ }
+ mFinished = true;
+ break;
+ }
+ }
+
+ /*
+ * SKETCH OF OPERATION
+ *
+ * create one of these PerformUnifiedRestoreTask objects, telling it which
+ * dataset & transport to address, and then parameters within the restore
+ * operation: single target package vs many, etc.
+ *
+ * 1. transport.startRestore(token, list-of-packages). If we need @pm@ it is
+ * always placed first and the settings provider always placed last [for now].
+ *
+ * 1a [if we needed @pm@ then nextRestorePackage() and restore the PMBA inline]
+ *
+ * [ state change => RUNNING_QUEUE ]
+ *
+ * NOW ITERATE:
+ *
+ * { 3. t.nextRestorePackage()
+ * 4. does the metadata for this package allow us to restore it?
+ * does the on-disk app permit us to restore it? [re-check allowBackup etc]
+ * 5. is this a key/value dataset? => key/value agent restore
+ * [ state change => RESTORE_KEYVALUE ]
+ * 5a. spin up agent
+ * 5b. t.getRestoreData() to stage it properly
+ * 5c. call into agent to perform restore
+ * 5d. tear down agent
+ * [ state change => RUNNING_QUEUE ]
+ *
+ * 6. else it's a stream dataset:
+ * [ state change => RESTORE_FULL ]
+ * 6a. instantiate the engine for a stream restore: engine handles agent lifecycles
+ * 6b. spin off engine runner on separate thread
+ * 6c. ITERATE getNextFullRestoreDataChunk() and copy data to engine runner socket
+ * [ state change => RUNNING_QUEUE ]
+ * }
+ *
+ * [ state change => FINAL ]
+ *
+ * 7. t.finishRestore(), release wakelock, etc.
+ *
+ *
+ */
+
+ // state INITIAL : set up for the restore and read the metadata if necessary
+ private void startRestore() {
+ sendStartRestore(mAcceptSet.size());
+
+ // If we're starting a full-system restore, set up to begin widget ID remapping
+ if (mIsSystemRestore) {
+ // TODO: http://b/22388012
+ AppWidgetBackupBridge.restoreStarting(UserHandle.USER_SYSTEM);
+ }
+
+ try {
+ String transportDir = mTransport.transportDirName();
+ mStateDir = new File(backupManagerService.getBaseStateDir(), transportDir);
+
+ // Fetch the current metadata from the dataset first
+ PackageInfo pmPackage = new PackageInfo();
+ pmPackage.packageName = PACKAGE_MANAGER_SENTINEL;
+ mAcceptSet.add(0, pmPackage);
+
+ PackageInfo[] packages = mAcceptSet.toArray(new PackageInfo[0]);
+ mStatus = mTransport.startRestore(mToken, packages);
+ if (mStatus != BackupTransport.TRANSPORT_OK) {
+ Slog.e(TAG, "Transport error " + mStatus + "; no restore possible");
+ mStatus = BackupTransport.TRANSPORT_ERROR;
+ executeNextState(UnifiedRestoreState.FINAL);
+ return;
+ }
+
+ RestoreDescription desc = mTransport.nextRestorePackage();
+ if (desc == null) {
+ Slog.e(TAG, "No restore metadata available; halting");
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_NO_RESTORE_METADATA_AVAILABLE,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
+ mStatus = BackupTransport.TRANSPORT_ERROR;
+ executeNextState(UnifiedRestoreState.FINAL);
+ return;
+ }
+ if (!PACKAGE_MANAGER_SENTINEL.equals(
+ desc.getPackageName())) {
+ Slog.e(TAG, "Required package metadata but got "
+ + desc.getPackageName());
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_NO_PM_METADATA_RECEIVED,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
+ mStatus = BackupTransport.TRANSPORT_ERROR;
+ executeNextState(UnifiedRestoreState.FINAL);
+ return;
+ }
+
+ // Pull the Package Manager metadata from the restore set first
+ mCurrentPackage = new PackageInfo();
+ mCurrentPackage.packageName = PACKAGE_MANAGER_SENTINEL;
+ mPmAgent = new PackageManagerBackupAgent(backupManagerService.getPackageManager(),
+ null);
+ mAgent = IBackupAgent.Stub.asInterface(mPmAgent.onBind());
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "initiating restore for PMBA");
+ }
+ initiateOneRestore(mCurrentPackage, 0);
+ // The PM agent called operationComplete() already, because our invocation
+ // of it is process-local and therefore synchronous. That means that the
+ // next-state message (RUNNING_QUEUE) is already enqueued. Only if we're
+ // unable to proceed with running the queue do we remove that pending
+ // message and jump straight to the FINAL state. Because this was
+ // synchronous we also know that we should cancel the pending timeout
+ // message.
+ backupManagerService.getBackupHandler().removeMessages(
+ MSG_RESTORE_OPERATION_TIMEOUT);
+
+ // Verify that the backup set includes metadata. If not, we can't do
+ // signature/version verification etc, so we simply do not proceed with
+ // the restore operation.
+ if (!mPmAgent.hasMetadata()) {
+ Slog.e(TAG, "PM agent has no metadata, so not restoring");
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_PM_AGENT_HAS_NO_METADATA,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
+ PACKAGE_MANAGER_SENTINEL,
+ "Package manager restore metadata missing");
+ mStatus = BackupTransport.TRANSPORT_ERROR;
+ backupManagerService.getBackupHandler().removeMessages(
+ MSG_BACKUP_RESTORE_STEP, this);
+ executeNextState(UnifiedRestoreState.FINAL);
+ return;
+ }
+
+ // Success; cache the metadata and continue as expected with the
+ // next state already enqueued
+
+ } catch (Exception e) {
+ // If we lost the transport at any time, halt
+ Slog.e(TAG, "Unable to contact transport for restore: " + e.getMessage());
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_LOST_TRANSPORT,
+ null,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null);
+ mStatus = BackupTransport.TRANSPORT_ERROR;
+ backupManagerService.getBackupHandler().removeMessages(
+ MSG_BACKUP_RESTORE_STEP, this);
+ executeNextState(UnifiedRestoreState.FINAL);
+ return;
+ }
+ }
+
+ // state RUNNING_QUEUE : figure out what the next thing to be restored is,
+ // and fire the appropriate next step
+ private void dispatchNextRestore() {
+ UnifiedRestoreState nextState = UnifiedRestoreState.FINAL;
+ try {
+ mRestoreDescription = mTransport.nextRestorePackage();
+ final String pkgName = (mRestoreDescription != null)
+ ? mRestoreDescription.getPackageName() : null;
+ if (pkgName == null) {
+ Slog.e(TAG, "Failure getting next package name");
+ EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ nextState = UnifiedRestoreState.FINAL;
+ return;
+ } else if (mRestoreDescription == RestoreDescription.NO_MORE_PACKAGES) {
+ // Yay we've reached the end cleanly
+ if (DEBUG) {
+ Slog.v(TAG, "No more packages; finishing restore");
+ }
+ int millis = (int) (SystemClock.elapsedRealtime() - mStartRealtime);
+ EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, mCount, millis);
+ nextState = UnifiedRestoreState.FINAL;
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.i(TAG, "Next restore package: " + mRestoreDescription);
+ }
+ sendOnRestorePackage(pkgName);
+
+ Metadata metaInfo = mPmAgent.getRestoredMetadata(pkgName);
+ if (metaInfo == null) {
+ Slog.e(TAG, "No metadata for " + pkgName);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName,
+ "Package metadata missing");
+ nextState = UnifiedRestoreState.RUNNING_QUEUE;
+ return;
+ }
+
+ try {
+ mCurrentPackage = backupManagerService.getPackageManager().getPackageInfo(
+ pkgName, PackageManager.GET_SIGNATURES);
+ } catch (NameNotFoundException e) {
+ // Whoops, we thought we could restore this package but it
+ // turns out not to be present. Skip it.
+ Slog.e(TAG, "Package not present: " + pkgName);
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_PRESENT,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName,
+ "Package missing on device");
+ nextState = UnifiedRestoreState.RUNNING_QUEUE;
+ return;
+ }
+
+ if (metaInfo.versionCode > mCurrentPackage.versionCode) {
+ // Data is from a "newer" version of the app than we have currently
+ // installed. If the app has not declared that it is prepared to
+ // handle this case, we do not attempt the restore.
+ if ((mCurrentPackage.applicationInfo.flags
+ & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) == 0) {
+ String message = "Source version " + metaInfo.versionCode
+ + " > installed version " + mCurrentPackage.versionCode;
+ Slog.w(TAG, "Package " + pkgName + ": " + message);
+ Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
+ BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
+ metaInfo.versionCode);
+ monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
+ monitoringExtras,
+ BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY, false);
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ monitoringExtras);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
+ pkgName, message);
+ nextState = UnifiedRestoreState.RUNNING_QUEUE;
+ return;
+ } else {
+ if (DEBUG) {
+ Slog.v(TAG, "Source version " + metaInfo.versionCode
+ + " > installed version " + mCurrentPackage.versionCode
+ + " but restoreAnyVersion");
+ }
+ Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
+ BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
+ metaInfo.versionCode);
+ monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
+ monitoringExtras,
+ BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY, true);
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ monitoringExtras);
+ }
+ }
+
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "Package " + pkgName
+ + " restore version [" + metaInfo.versionCode
+ + "] is compatible with installed version ["
+ + mCurrentPackage.versionCode + "]");
+ }
+
+ // Reset per-package preconditions and fire the appropriate next state
+ mWidgetData = null;
+ final int type = mRestoreDescription.getDataType();
+ if (type == RestoreDescription.TYPE_KEY_VALUE) {
+ nextState = UnifiedRestoreState.RESTORE_KEYVALUE;
+ } else if (type == RestoreDescription.TYPE_FULL_STREAM) {
+ nextState = UnifiedRestoreState.RESTORE_FULL;
+ } else {
+ // Unknown restore type; ignore this package and move on
+ Slog.e(TAG, "Unrecognized restore type " + type);
+ nextState = UnifiedRestoreState.RUNNING_QUEUE;
+ return;
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Can't get next restore target from transport; halting: "
+ + e.getMessage());
+ EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ nextState = UnifiedRestoreState.FINAL;
+ return;
+ } finally {
+ executeNextState(nextState);
+ }
+ }
+
+ // state RESTORE_KEYVALUE : restore one package via key/value API set
+ private void restoreKeyValue() {
+ // Initiating the restore will pass responsibility for the state machine's
+ // progress to the agent callback, so we do not always execute the
+ // next state here.
+ final String packageName = mCurrentPackage.packageName;
+ // Validate some semantic requirements that apply in this way
+ // only to the key/value restore API flow
+ if (mCurrentPackage.applicationInfo.backupAgentName == null
+ || "".equals(mCurrentPackage.applicationInfo.backupAgentName)) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Data exists for package " + packageName
+ + " but app has no agent; skipping");
+ }
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_APP_HAS_NO_AGENT, mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
+ "Package has no agent");
+ executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
+ return;
+ }
+
+ Metadata metaInfo = mPmAgent.getRestoredMetadata(packageName);
+ if (!BackupUtils.signaturesMatch(metaInfo.sigHashes, mCurrentPackage)) {
+ Slog.w(TAG, "Signature mismatch restoring " + packageName);
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_SIGNATURE_MISMATCH, mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
+ "Signature mismatch");
+ executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
+ return;
+ }
+
+ // Good to go! Set up and bind the agent...
+ mAgent = backupManagerService.bindToAgentSynchronous(
+ mCurrentPackage.applicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
+ if (mAgent == null) {
+ Slog.w(TAG, "Can't find backup agent for " + packageName);
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_CANT_FIND_AGENT, mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
+ "Restore agent missing");
+ executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
+ return;
+ }
+
+ // Whatever happens next, we've launched the target app now; remember that.
+ mDidLaunch = true;
+
+ // And then finally start the restore on this agent
+ try {
+ initiateOneRestore(mCurrentPackage, metaInfo.versionCode);
+ ++mCount;
+ } catch (Exception e) {
+ Slog.e(TAG, "Error when attempting restore: " + e.toString());
+ keyValueAgentErrorCleanup();
+ executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
+ }
+ }
+
+ // Guts of a key/value restore operation
+ void initiateOneRestore(PackageInfo app, int appVersionCode) {
+ final String packageName = app.packageName;
+
+ if (DEBUG) {
+ Slog.d(TAG, "initiateOneRestore packageName=" + packageName);
+ }
+
+ // !!! TODO: get the dirs from the transport
+ mBackupDataName = new File(backupManagerService.getDataDir(), packageName + ".restore");
+ mStageName = new File(backupManagerService.getDataDir(), packageName + ".stage");
+ mNewStateName = new File(mStateDir, packageName + ".new");
+ mSavedStateName = new File(mStateDir, packageName);
+
+ // don't stage the 'android' package where the wallpaper data lives. this is
+ // an optimization: we know there's no widget data hosted/published by that
+ // package, and this way we avoid doing a spurious copy of MB-sized wallpaper
+ // data following the download.
+ boolean staging = !packageName.equals("android");
+ ParcelFileDescriptor stage;
+ File downloadFile = (staging) ? mStageName : mBackupDataName;
+
+ try {
+ // Run the transport's restore pass
+ stage = ParcelFileDescriptor.open(downloadFile,
+ ParcelFileDescriptor.MODE_READ_WRITE |
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE);
+
+ if (mTransport.getRestoreData(stage) != BackupTransport.TRANSPORT_OK) {
+ // Transport-level failure, so we wind everything up and
+ // terminate the restore operation.
+ Slog.e(TAG, "Error getting restore data for " + packageName);
+ EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ stage.close();
+ downloadFile.delete();
+ executeNextState(UnifiedRestoreState.FINAL);
+ return;
+ }
+
+ // We have the data from the transport. Now we extract and strip
+ // any per-package metadata (typically widget-related information)
+ // if appropriate
+ if (staging) {
+ stage.close();
+ stage = ParcelFileDescriptor.open(downloadFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ mBackupData = ParcelFileDescriptor.open(mBackupDataName,
+ ParcelFileDescriptor.MODE_READ_WRITE |
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE);
+
+ BackupDataInput in = new BackupDataInput(stage.getFileDescriptor());
+ BackupDataOutput out = new BackupDataOutput(mBackupData.getFileDescriptor());
+ byte[] buffer = new byte[8192]; // will grow when needed
+ while (in.readNextHeader()) {
+ final String key = in.getKey();
+ final int size = in.getDataSize();
+
+ // is this a special key?
+ if (key.equals(KEY_WIDGET_STATE)) {
+ if (DEBUG) {
+ Slog.i(TAG, "Restoring widget state for " + packageName);
+ }
+ mWidgetData = new byte[size];
+ in.readEntityData(mWidgetData, 0, size);
+ } else {
+ if (size > buffer.length) {
+ buffer = new byte[size];
+ }
+ in.readEntityData(buffer, 0, size);
+ out.writeEntityHeader(key, size);
+ out.writeEntityData(buffer, size);
+ }
+ }
+
+ mBackupData.close();
+ }
+
+ // Okay, we have the data. Now have the agent do the restore.
+ stage.close();
+
+ mBackupData = ParcelFileDescriptor.open(mBackupDataName,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ mNewState = ParcelFileDescriptor.open(mNewStateName,
+ ParcelFileDescriptor.MODE_READ_WRITE |
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE);
+
+ // Kick off the restore, checking for hung agents. The timeout or
+ // the operationComplete() callback will schedule the next step,
+ // so we do not do that here.
+ backupManagerService.prepareOperationTimeout(
+ mEphemeralOpToken, TIMEOUT_RESTORE_INTERVAL, this, OP_TYPE_RESTORE_WAIT);
+ mAgent.doRestore(mBackupData, appVersionCode, mNewState,
+ mEphemeralOpToken, backupManagerService.getBackupManagerBinder());
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to call app for restore: " + packageName, e);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
+ packageName, e.toString());
+ keyValueAgentErrorCleanup(); // clears any pending timeout messages as well
+
+ // After a restore failure we go back to running the queue. If there
+ // are no more packages to be restored that will be handled by the
+ // next step.
+ executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
+ }
+ }
+
+ // state RESTORE_FULL : restore one package via streaming engine
+ private void restoreFull() {
+ // None of this can run on the work looper here, so we spin asynchronous
+ // work like this:
+ //
+ // StreamFeederThread: read data from mTransport.getNextFullRestoreDataChunk()
+ // write it into the pipe to the engine
+ // EngineThread: FullRestoreEngine thread communicating with the target app
+ //
+ // When finished, StreamFeederThread executes next state as appropriate on the
+ // backup looper, and the overall unified restore task resumes
+ try {
+ StreamFeederThread feeder = new StreamFeederThread();
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Spinning threads for stream restore of "
+ + mCurrentPackage.packageName);
+ }
+ new Thread(feeder, "unified-stream-feeder").start();
+
+ // At this point the feeder is responsible for advancing the restore
+ // state, so we're done here.
+ } catch (IOException e) {
+ // Unable to instantiate the feeder thread -- we need to bail on the
+ // current target. We haven't asked the transport for data yet, though,
+ // so we can do that simply by going back to running the restore queue.
+ Slog.e(TAG, "Unable to construct pipes for stream restore!");
+ executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
+ }
+ }
+
+ // state RESTORE_FINISHED : provide the "no more data" signpost callback at the end
+ private void restoreFinished() {
+ try {
+ backupManagerService
+ .prepareOperationTimeout(mEphemeralOpToken,
+ TIMEOUT_RESTORE_FINISHED_INTERVAL, this,
+ OP_TYPE_RESTORE_WAIT);
+ mAgent.doRestoreFinished(mEphemeralOpToken,
+ backupManagerService.getBackupManagerBinder());
+ // If we get this far, the callback or timeout will schedule the
+ // next restore state, so we're done
+ } catch (Exception e) {
+ final String packageName = mCurrentPackage.packageName;
+ Slog.e(TAG, "Unable to finalize restore of " + packageName);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
+ packageName, e.toString());
+ keyValueAgentErrorCleanup();
+ executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
+ }
+ }
+
+ class StreamFeederThread extends RestoreEngine implements Runnable, BackupRestoreTask {
+
+ final String TAG = "StreamFeederThread";
+ FullRestoreEngine mEngine;
+ EngineThread mEngineThread;
+
+ // pipe through which we read data from the transport. [0] read, [1] write
+ ParcelFileDescriptor[] mTransportPipes;
+
+ // pipe through which the engine will read data. [0] read, [1] write
+ ParcelFileDescriptor[] mEnginePipes;
+
+ private final int mEphemeralOpToken;
+
+ public StreamFeederThread() throws IOException {
+ mEphemeralOpToken = backupManagerService.generateRandomIntegerToken();
+ mTransportPipes = ParcelFileDescriptor.createPipe();
+ mEnginePipes = ParcelFileDescriptor.createPipe();
+ setRunning(true);
+ }
+
+ @Override
+ public void run() {
+ UnifiedRestoreState nextState = UnifiedRestoreState.RUNNING_QUEUE;
+ int status = BackupTransport.TRANSPORT_OK;
+
+ EventLog.writeEvent(EventLogTags.FULL_RESTORE_PACKAGE,
+ mCurrentPackage.packageName);
+
+ mEngine = new FullRestoreEngine(backupManagerService, this, null,
+ mMonitor, mCurrentPackage, false, false, mEphemeralOpToken);
+ mEngineThread = new EngineThread(mEngine, mEnginePipes[0]);
+
+ ParcelFileDescriptor eWriteEnd = mEnginePipes[1];
+ ParcelFileDescriptor tReadEnd = mTransportPipes[0];
+ ParcelFileDescriptor tWriteEnd = mTransportPipes[1];
+
+ int bufferSize = 32 * 1024;
+ byte[] buffer = new byte[bufferSize];
+ FileOutputStream engineOut = new FileOutputStream(eWriteEnd.getFileDescriptor());
+ FileInputStream transportIn = new FileInputStream(tReadEnd.getFileDescriptor());
+
+ // spin up the engine and start moving data to it
+ new Thread(mEngineThread, "unified-restore-engine").start();
+
+ try {
+ while (status == BackupTransport.TRANSPORT_OK) {
+ // have the transport write some of the restoring data to us
+ int result = mTransport.getNextFullRestoreDataChunk(tWriteEnd);
+ if (result > 0) {
+ // The transport wrote this many bytes of restore data to the
+ // pipe, so pass it along to the engine.
+ if (MORE_DEBUG) {
+ Slog.v(TAG, " <- transport provided chunk size " + result);
+ }
+ if (result > bufferSize) {
+ bufferSize = result;
+ buffer = new byte[bufferSize];
+ }
+ int toCopy = result;
+ while (toCopy > 0) {
+ int n = transportIn.read(buffer, 0, toCopy);
+ engineOut.write(buffer, 0, n);
+ toCopy -= n;
+ if (MORE_DEBUG) {
+ Slog.v(TAG, " -> wrote " + n + " to engine, left=" + toCopy);
+ }
+ }
+ } else if (result == BackupTransport.NO_MORE_DATA) {
+ // Clean finish. Wind up and we're done!
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Got clean full-restore EOF for "
+ + mCurrentPackage.packageName);
+ }
+ status = BackupTransport.TRANSPORT_OK;
+ break;
+ } else {
+ // Transport reported some sort of failure; the fall-through
+ // handling will deal properly with that.
+ Slog.e(TAG, "Error " + result + " streaming restore for "
+ + mCurrentPackage.packageName);
+ EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ status = result;
+ }
+ }
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "Done copying to engine, falling through");
+ }
+ } catch (IOException e) {
+ // We lost our ability to communicate via the pipes. That's worrying
+ // but potentially recoverable; abandon this package's restore but
+ // carry on with the next restore target.
+ Slog.e(TAG, "Unable to route data for restore");
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
+ mCurrentPackage.packageName, "I/O error on pipes");
+ status = BackupTransport.AGENT_ERROR;
+ } catch (Exception e) {
+ // The transport threw; terminate the whole operation. Closing
+ // the sockets will wake up the engine and it will then tidy up the
+ // remote end.
+ Slog.e(TAG, "Transport failed during restore: " + e.getMessage());
+ EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
+ status = BackupTransport.TRANSPORT_ERROR;
+ } finally {
+ // Close the transport pipes and *our* end of the engine pipe,
+ // but leave the engine thread's end open so that it properly
+ // hits EOF and winds up its operations.
+ IoUtils.closeQuietly(mEnginePipes[1]);
+ IoUtils.closeQuietly(mTransportPipes[0]);
+ IoUtils.closeQuietly(mTransportPipes[1]);
+
+ // Don't proceed until the engine has wound up operations
+ mEngineThread.waitForResult();
+
+ // Now we're really done with this one too
+ IoUtils.closeQuietly(mEnginePipes[0]);
+
+ // In all cases we want to remember whether we launched
+ // the target app as part of our work so far.
+ mDidLaunch = (mEngine.getAgent() != null);
+
+ // If we hit a transport-level error, we are done with everything;
+ // if we hit an agent error we just go back to running the queue.
+ if (status == BackupTransport.TRANSPORT_OK) {
+ // Clean finish means we issue the restore-finished callback
+ nextState = UnifiedRestoreState.RESTORE_FINISHED;
+
+ // the engine bound the target's agent, so recover that binding
+ // to use for the callback.
+ mAgent = mEngine.getAgent();
+
+ // and the restored widget data, if any
+ mWidgetData = mEngine.getWidgetData();
+ } else {
+ // Something went wrong somewhere. Whether it was at the transport
+ // level is immaterial; we need to tell the transport to bail
+ try {
+ mTransport.abortFullRestore();
+ } catch (Exception e) {
+ // transport itself is dead; make sure we handle this as a
+ // fatal error
+ Slog.e(TAG, "Transport threw from abortFullRestore: " + e.getMessage());
+ status = BackupTransport.TRANSPORT_ERROR;
+ }
+
+ // We also need to wipe the current target's data, as it's probably
+ // in an incoherent state.
+ backupManagerService.clearApplicationDataSynchronous(
+ mCurrentPackage.packageName);
+
+ // Schedule the next state based on the nature of our failure
+ if (status == BackupTransport.TRANSPORT_ERROR) {
+ nextState = UnifiedRestoreState.FINAL;
+ } else {
+ nextState = UnifiedRestoreState.RUNNING_QUEUE;
+ }
+ }
+ executeNextState(nextState);
+ setRunning(false);
+ }
+ }
+
+ // BackupRestoreTask interface, specifically for timeout handling
+
+ @Override
+ public void execute() { /* intentionally empty */ }
+
+ @Override
+ public void operationComplete(long result) { /* intentionally empty */ }
+
+ // The app has timed out handling a restoring file
+ @Override
+ public void handleCancel(boolean cancelAll) {
+ backupManagerService.removeOperation(mEphemeralOpToken);
+ if (DEBUG) {
+ Slog.w(TAG, "Full-data restore target timed out; shutting down");
+ }
+
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_TIMEOUT,
+ mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
+ mEngineThread.handleTimeout();
+
+ IoUtils.closeQuietly(mEnginePipes[1]);
+ mEnginePipes[1] = null;
+ IoUtils.closeQuietly(mEnginePipes[0]);
+ mEnginePipes[0] = null;
+ }
+ }
+
+ class EngineThread implements Runnable {
+
+ FullRestoreEngine mEngine;
+ FileInputStream mEngineStream;
+
+ EngineThread(FullRestoreEngine engine, ParcelFileDescriptor engineSocket) {
+ mEngine = engine;
+ engine.setRunning(true);
+ // We *do* want this FileInputStream to own the underlying fd, so that
+ // when we are finished with it, it closes this end of the pipe in a way
+ // that signals its other end.
+ mEngineStream = new FileInputStream(engineSocket.getFileDescriptor(), true);
+ }
+
+ public boolean isRunning() {
+ return mEngine.isRunning();
+ }
+
+ public int waitForResult() {
+ return mEngine.waitForResult();
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (mEngine.isRunning()) {
+ // Tell it to be sure to leave the agent instance up after finishing
+ mEngine.restoreOneFile(mEngineStream, false, mEngine.mBuffer,
+ mEngine.mOnlyPackage, mEngine.mAllowApks, mEngine.mEphemeralOpToken,
+ mEngine.mMonitor);
+ }
+ } finally {
+ // Because mEngineStream adopted its underlying FD, this also
+ // closes this end of the pipe.
+ IoUtils.closeQuietly(mEngineStream);
+ }
+ }
+
+ public void handleTimeout() {
+ IoUtils.closeQuietly(mEngineStream);
+ mEngine.handleTimeout();
+ }
+ }
+
+ // state FINAL : tear everything down and we're done.
+ private void finalizeRestore() {
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "finishing restore mObserver=" + mObserver);
+ }
+
+ try {
+ mTransport.finishRestore();
+ } catch (Exception e) {
+ Slog.e(TAG, "Error finishing restore", e);
+ }
+
+ // Tell the observer we're done
+ if (mObserver != null) {
+ try {
+ mObserver.restoreFinished(mStatus);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Restore observer died at restoreFinished");
+ }
+ }
+
+ // Clear any ongoing session timeout.
+ backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
+
+ // If we have a PM token, we must under all circumstances be sure to
+ // handshake when we've finished.
+ if (mPmToken > 0) {
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "finishing PM token " + mPmToken);
+ }
+ try {
+ backupManagerService.getPackageManagerBinder().finishPackageInstall(mPmToken,
+ mDidLaunch);
+ } catch (RemoteException e) { /* can't happen */ }
+ } else {
+ // We were invoked via an active restore session, not by the Package
+ // Manager, so start up the session timeout again.
+ backupManagerService.getBackupHandler().sendEmptyMessageDelayed(
+ MSG_RESTORE_SESSION_TIMEOUT,
+ TIMEOUT_RESTORE_INTERVAL);
+ }
+
+ // Kick off any work that may be needed regarding app widget restores
+ // TODO: http://b/22388012
+ AppWidgetBackupBridge.restoreFinished(UserHandle.USER_SYSTEM);
+
+ // If this was a full-system restore, record the ancestral
+ // dataset information
+ if (mIsSystemRestore && mPmAgent != null) {
+ backupManagerService.setAncestralPackages(mPmAgent.getRestoredPackages());
+ backupManagerService.setAncestralToken(mToken);
+ backupManagerService.writeRestoreTokens();
+ }
+
+ // done; we can finally release the wakelock and be legitimately done.
+ Slog.i(TAG, "Restore complete.");
+
+ synchronized (backupManagerService.getPendingRestores()) {
+ if (backupManagerService.getPendingRestores().size() > 0) {
+ if (DEBUG) {
+ Slog.d(TAG, "Starting next pending restore.");
+ }
+ PerformUnifiedRestoreTask task = backupManagerService.getPendingRestores().remove();
+ backupManagerService.getBackupHandler().sendMessage(
+ backupManagerService.getBackupHandler().obtainMessage(
+ MSG_BACKUP_RESTORE_STEP, task));
+
+ } else {
+ backupManagerService.setRestoreInProgress(false);
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "No pending restores.");
+ }
+ }
+ }
+
+ backupManagerService.getWakelock().release();
+ }
+
+ void keyValueAgentErrorCleanup() {
+ // If the agent fails restore, it might have put the app's data
+ // into an incoherent state. For consistency we wipe its data
+ // again in this case before continuing with normal teardown
+ backupManagerService.clearApplicationDataSynchronous(mCurrentPackage.packageName);
+ keyValueAgentCleanup();
+ }
+
+ // TODO: clean up naming; this is now used at finish by both k/v and stream restores
+ void keyValueAgentCleanup() {
+ mBackupDataName.delete();
+ mStageName.delete();
+ try {
+ if (mBackupData != null) {
+ mBackupData.close();
+ }
+ } catch (IOException e) {
+ }
+ try {
+ if (mNewState != null) {
+ mNewState.close();
+ }
+ } catch (IOException e) {
+ }
+ mBackupData = mNewState = null;
+
+ // if everything went okay, remember the recorded state now
+ //
+ // !!! TODO: the restored data could be migrated on the server
+ // side into the current dataset. In that case the new state file
+ // we just created would reflect the data already extant in the
+ // backend, so there'd be nothing more to do. Until that happens,
+ // however, we need to make sure that we record the data to the
+ // current backend dataset. (Yes, this means shipping the data over
+ // the wire in both directions. That's bad, but consistency comes
+ // first, then efficiency.) Once we introduce server-side data
+ // migration to the newly-restored device's dataset, we will change
+ // the following from a discard of the newly-written state to the
+ // "correct" operation of renaming into the canonical state blob.
+ mNewStateName.delete(); // TODO: remove; see above comment
+ //mNewStateName.renameTo(mSavedStateName); // TODO: replace with this
+
+ // If this wasn't the PM pseudopackage, tear down the agent side
+ if (mCurrentPackage.applicationInfo != null) {
+ // unbind and tidy up even on timeout or failure
+ try {
+ backupManagerService.getActivityManager().unbindBackupAgent(
+ mCurrentPackage.applicationInfo);
+
+ // The agent was probably running with a stub Application object,
+ // which isn't a valid run mode for the main app logic. Shut
+ // down the app so that next time it's launched, it gets the
+ // usual full initialization. Note that this is only done for
+ // full-system restores: when a single app has requested a restore,
+ // it is explicitly not killed following that operation.
+ //
+ // We execute this kill when these conditions hold:
+ // 1. it's not a system-uid process,
+ // 2. the app did not request its own restore (mTargetPackage == null), and
+ // either
+ // 3a. the app is a full-data target (TYPE_FULL_STREAM) or
+ // b. the app does not state android:killAfterRestore="false" in its manifest
+ final int appFlags = mCurrentPackage.applicationInfo.flags;
+ final boolean killAfterRestore =
+ (mCurrentPackage.applicationInfo.uid >= Process.FIRST_APPLICATION_UID)
+ && ((mRestoreDescription.getDataType()
+ == RestoreDescription.TYPE_FULL_STREAM)
+ || ((appFlags & ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0));
+
+ if (mTargetPackage == null && killAfterRestore) {
+ if (DEBUG) {
+ Slog.d(TAG, "Restore complete, killing host process of "
+ + mCurrentPackage.applicationInfo.processName);
+ }
+ backupManagerService.getActivityManager().killApplicationProcess(
+ mCurrentPackage.applicationInfo.processName,
+ mCurrentPackage.applicationInfo.uid);
+ }
+ } catch (RemoteException e) {
+ // can't happen; we run in the same process as the activity manager
+ }
+ }
+
+ // The caller is responsible for reestablishing the state machine; our
+ // responsibility here is to clear the decks for whatever comes next.
+ backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_OPERATION_TIMEOUT, this);
+ }
+
+ @Override
+ public void operationComplete(long unusedResult) {
+ backupManagerService.removeOperation(mEphemeralOpToken);
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "operationComplete() during restore: target="
+ + mCurrentPackage.packageName
+ + " state=" + mState);
+ }
+
+ final UnifiedRestoreState nextState;
+ switch (mState) {
+ case INITIAL:
+ // We've just (manually) restored the PMBA. It doesn't need the
+ // additional restore-finished callback so we bypass that and go
+ // directly to running the queue.
+ nextState = UnifiedRestoreState.RUNNING_QUEUE;
+ break;
+
+ case RESTORE_KEYVALUE:
+ case RESTORE_FULL: {
+ // Okay, we've just heard back from the agent that it's done with
+ // the restore itself. We now have to send the same agent its
+ // doRestoreFinished() callback, so roll into that state.
+ nextState = UnifiedRestoreState.RESTORE_FINISHED;
+ break;
+ }
+
+ case RESTORE_FINISHED: {
+ // Okay, we're done with this package. Tidy up and go on to the next
+ // app in the queue.
+ int size = (int) mBackupDataName.length();
+ EventLog.writeEvent(EventLogTags.RESTORE_PACKAGE,
+ mCurrentPackage.packageName, size);
+
+ // Just go back to running the restore queue
+ keyValueAgentCleanup();
+
+ // If there was widget state associated with this app, get the OS to
+ // incorporate it into current bookeeping and then pass that along to
+ // the app as part of the restore-time work.
+ if (mWidgetData != null) {
+ backupManagerService.restoreWidgetData(mCurrentPackage.packageName,
+ mWidgetData);
+ }
+
+ nextState = UnifiedRestoreState.RUNNING_QUEUE;
+ break;
+ }
+
+ default: {
+ // Some kind of horrible semantic error; we're in an unexpected state.
+ // Back off hard and wind up.
+ Slog.e(TAG, "Unexpected restore callback into state " + mState);
+ keyValueAgentErrorCleanup();
+ nextState = UnifiedRestoreState.FINAL;
+ break;
+ }
+ }
+
+ executeNextState(nextState);
+ }
+
+ // A call to agent.doRestore() or agent.doRestoreFinished() has timed out
+ @Override
+ public void handleCancel(boolean cancelAll) {
+ backupManagerService.removeOperation(mEphemeralOpToken);
+ Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT,
+ mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
+ EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
+ mCurrentPackage.packageName, "restore timeout");
+ // Handle like an agent that threw on invocation: wipe it and go on to the next
+ keyValueAgentErrorCleanup();
+ executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
+ }
+
+ void executeNextState(UnifiedRestoreState nextState) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, " => executing next step on "
+ + this + " nextState=" + nextState);
+ }
+ mState = nextState;
+ Message msg = backupManagerService.getBackupHandler().obtainMessage(
+ MSG_BACKUP_RESTORE_STEP, this);
+ backupManagerService.getBackupHandler().sendMessage(msg);
+ }
+
+ // restore observer support
+ void sendStartRestore(int numPackages) {
+ if (mObserver != null) {
+ try {
+ mObserver.restoreStarting(numPackages);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Restore observer went away: startRestore");
+ mObserver = null;
+ }
+ }
+ }
+
+ void sendOnRestorePackage(String name) {
+ if (mObserver != null) {
+ if (mObserver != null) {
+ try {
+ mObserver.onUpdate(mCount, name);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Restore observer died in onUpdate");
+ mObserver = null;
+ }
+ }
+ }
+ }
+
+ void sendEndRestore() {
+ if (mObserver != null) {
+ try {
+ mObserver.restoreFinished(mStatus);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Restore observer went away: endRestore");
+ mObserver = null;
+ }
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/restore/RestoreDeleteObserver.java b/services/backup/java/com/android/server/backup/restore/RestoreDeleteObserver.java
new file mode 100644
index 0000000..12be84c
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/restore/RestoreDeleteObserver.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.restore;
+
+import android.content.pm.IPackageDeleteObserver;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Synchronous implementation of IPackageDeleteObserver.Stub.
+ *
+ * Allows the caller to synchronously wait for package deleted event.
+ */
+public class RestoreDeleteObserver extends IPackageDeleteObserver.Stub {
+
+ @GuardedBy("mDone")
+ private final AtomicBoolean mDone = new AtomicBoolean();
+
+ public RestoreDeleteObserver() {
+ }
+
+ /**
+ * Resets the observer to prepare for another removal.
+ */
+ public void reset() {
+ synchronized (mDone) {
+ mDone.set(false);
+ }
+ }
+
+ /**
+ * Synchronously waits for completion.
+ */
+ public void waitForCompletion() {
+ synchronized (mDone) {
+ while (mDone.get() == false) {
+ try {
+ mDone.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+
+ @Override
+ public void packageDeleted(String packageName, int returnCode) throws RemoteException {
+ synchronized (mDone) {
+ mDone.set(true);
+ mDone.notifyAll();
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/restore/RestoreEngine.java b/services/backup/java/com/android/server/backup/restore/RestoreEngine.java
new file mode 100644
index 0000000..b2fdbb8
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/restore/RestoreEngine.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.restore;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Restore infrastructure.
+ */
+public abstract class RestoreEngine {
+
+ private static final String TAG = "RestoreEngine";
+
+ public static final int SUCCESS = 0;
+ public static final int TARGET_FAILURE = -2;
+ public static final int TRANSPORT_FAILURE = -3;
+
+ private AtomicBoolean mRunning = new AtomicBoolean(false);
+ private AtomicInteger mResult = new AtomicInteger(SUCCESS);
+
+ public boolean isRunning() {
+ return mRunning.get();
+ }
+
+ public void setRunning(boolean stillRunning) {
+ synchronized (mRunning) {
+ mRunning.set(stillRunning);
+ mRunning.notifyAll();
+ }
+ }
+
+ public int waitForResult() {
+ synchronized (mRunning) {
+ while (isRunning()) {
+ try {
+ mRunning.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ return getResult();
+ }
+
+ public int getResult() {
+ return mResult.get();
+ }
+
+ public void setResult(int result) {
+ mResult.set(result);
+ }
+
+ // TODO: abstract restore state and APIs
+}
diff --git a/services/backup/java/com/android/server/backup/restore/RestoreFileRunnable.java b/services/backup/java/com/android/server/backup/restore/RestoreFileRunnable.java
new file mode 100644
index 0000000..6c9a222
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/restore/RestoreFileRunnable.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.restore;
+
+import android.app.IBackupAgent;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import com.android.server.backup.FileMetadata;
+import com.android.server.backup.RefactoredBackupManagerService;
+
+import java.io.IOException;
+
+/**
+ * Runner that can be placed in a separate thread to do in-process invocations of the full restore
+ * API asynchronously. Used by adb restore.
+ */
+class RestoreFileRunnable implements Runnable {
+
+ private final IBackupAgent mAgent;
+ private final FileMetadata mInfo;
+ private final ParcelFileDescriptor mSocket;
+ private final int mToken;
+ private final RefactoredBackupManagerService mBackupManagerService;
+
+ RestoreFileRunnable(RefactoredBackupManagerService backupManagerService, IBackupAgent agent,
+ FileMetadata info, ParcelFileDescriptor socket, int token) throws IOException {
+ mAgent = agent;
+ mInfo = info;
+ mToken = token;
+
+ // This class is used strictly for process-local binder invocations. The
+ // semantics of ParcelFileDescriptor differ in this case; in particular, we
+ // do not automatically get a 'dup'ed descriptor that we can can continue
+ // to use asynchronously from the caller. So, we make sure to dup it ourselves
+ // before proceeding to do the restore.
+ mSocket = ParcelFileDescriptor.dup(socket.getFileDescriptor());
+ this.mBackupManagerService = backupManagerService;
+ }
+
+ @Override
+ public void run() {
+ try {
+ mAgent.doRestoreFile(mSocket, mInfo.size, mInfo.type,
+ mInfo.domain, mInfo.path, mInfo.mode, mInfo.mtime,
+ mToken, mBackupManagerService.getBackupManagerBinder());
+ } catch (RemoteException e) {
+ // never happens; this is used strictly for local binder calls
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/restore/RestoreInstallObserver.java b/services/backup/java/com/android/server/backup/restore/RestoreInstallObserver.java
new file mode 100644
index 0000000..3d506f1
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/restore/RestoreInstallObserver.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.restore;
+
+import android.app.PackageInstallObserver;
+import android.os.Bundle;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Synchronous implementation of PackageInstallObserver.
+ *
+ * Allows the caller to synchronously wait for package install event.
+ */
+public class RestoreInstallObserver extends PackageInstallObserver {
+
+ @GuardedBy("mDone")
+ private final AtomicBoolean mDone = new AtomicBoolean();
+
+ private String mPackageName;
+ private int mResult;
+
+ public RestoreInstallObserver() {
+ }
+
+ /**
+ * Resets the observer to prepare for another installation.
+ */
+ public void reset() {
+ synchronized (mDone) {
+ mDone.set(false);
+ }
+ }
+
+ /**
+ * Synchronously waits for completion.
+ */
+ public void waitForCompletion() {
+ synchronized (mDone) {
+ while (mDone.get() == false) {
+ try {
+ mDone.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns result code.
+ */
+ public int getResult() {
+ return mResult;
+ }
+
+ /**
+ * Returns installed package name.
+ */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @Override
+ public void onPackageInstalled(String packageName, int returnCode,
+ String msg, Bundle extras) {
+ synchronized (mDone) {
+ mResult = returnCode;
+ mPackageName = packageName;
+ mDone.set(true);
+ mDone.notifyAll();
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/restore/RestorePolicy.java b/services/backup/java/com/android/server/backup/restore/RestorePolicy.java
new file mode 100644
index 0000000..db25472
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/restore/RestorePolicy.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.restore;
+
+/**
+ * Full restore from a file/socket.
+ */
+public enum RestorePolicy {
+ IGNORE,
+ ACCEPT,
+ ACCEPT_IF_APK
+}
diff --git a/services/backup/java/com/android/server/backup/restore/UnifiedRestoreState.java b/services/backup/java/com/android/server/backup/restore/UnifiedRestoreState.java
new file mode 100644
index 0000000..f5bff5e
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/restore/UnifiedRestoreState.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.restore;
+
+/**
+ * States of the unified-restore state machine.
+ */
+public enum UnifiedRestoreState {
+ INITIAL,
+ RUNNING_QUEUE,
+ RESTORE_KEYVALUE,
+ RESTORE_FULL,
+ RESTORE_FINISHED,
+ FINAL
+}
diff --git a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
new file mode 100644
index 0000000..c033d98
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.utils;
+
+import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.Signature;
+import android.os.Process;
+import android.util.Slog;
+
+/**
+ * Utility methods wrapping operations on ApplicationInfo and PackageInfo.
+ */
+public class AppBackupUtils {
+ /**
+ * Returns whether app is eligible for backup.
+ *
+ * High level policy: apps are generally ineligible for backup if certain conditions apply. The
+ * conditions are:
+ *
+ * <ol>
+ * <li>their manifest states android:allowBackup="false"
+ * <li>they run as a system-level uid but do not supply their own backup agent
+ * <li>it is the special shared-storage backup package used for 'adb backup'
+ * </ol>
+ */
+ public static boolean appIsEligibleForBackup(ApplicationInfo app) {
+ // 1. their manifest states android:allowBackup="false"
+ if ((app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) {
+ return false;
+ }
+
+ // 2. they run as a system-level uid but do not supply their own backup agent
+ if ((app.uid < Process.FIRST_APPLICATION_UID) && (app.backupAgentName == null)) {
+ return false;
+ }
+
+ // 3. it is the special shared-storage backup package used for 'adb backup'
+ if (app.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if the app is in a stopped state, that means it won't receive broadcasts.
+ */
+ public static boolean appIsStopped(ApplicationInfo app) {
+ return ((app.flags & ApplicationInfo.FLAG_STOPPED) != 0);
+ }
+
+ /**
+ * Returns whether the app can get full backup. Does *not* check overall backup eligibility
+ * policy!
+ */
+ public static boolean appGetsFullBackup(PackageInfo pkg) {
+ if (pkg.applicationInfo.backupAgentName != null) {
+ // If it has an agent, it gets full backups only if it says so
+ return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FULL_BACKUP_ONLY) != 0;
+ }
+
+ // No agent or fullBackupOnly="true" means we do indeed perform full-data backups for it
+ return true;
+ }
+
+ /**
+ * Returns whether the app is only capable of doing key/value. We say it's not if it allows full
+ * backup, and it is otherwise.
+ */
+ public static boolean appIsKeyValueOnly(PackageInfo pkg) {
+ return !appGetsFullBackup(pkg);
+ }
+
+ /**
+ * Old style: directly match the stored vs on device signature blocks.
+ */
+ // TODO(b/37977154): Resolve questionable policies.
+ public static boolean signaturesMatch(Signature[] storedSigs, PackageInfo target) {
+ if (target == null) {
+ return false;
+ }
+
+ // If the target resides on the system partition, we allow it to restore
+ // data from the like-named package in a restore set even if the signatures
+ // do not match. (Unlike general applications, those flashed to the system
+ // partition will be signed with the device's platform certificate, so on
+ // different phones the same system app will have different signatures.)
+ if ((target.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "System app " + target.packageName + " - skipping sig check");
+ }
+ return true;
+ }
+
+ // Allow unsigned apps, but not signed on one device and unsigned on the other
+ // TODO(b/37977154): is this the right policy?
+ Signature[] deviceSigs = target.signatures;
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "signaturesMatch(): stored=" + storedSigs + " device=" + deviceSigs);
+ }
+ if ((storedSigs == null || storedSigs.length == 0)
+ && (deviceSigs == null || deviceSigs.length == 0)) {
+ return true;
+ }
+ // TODO(b/37977154): This allows empty stored signature, is this right?
+ if (storedSigs == null || deviceSigs == null) {
+ return false;
+ }
+
+ // TODO(b/37977154): this demands that every stored signature match one
+ // that is present on device, and does not demand the converse.
+ // Is this this right policy?
+ int nStored = storedSigs.length;
+ int nDevice = deviceSigs.length;
+
+ for (int i = 0; i < nStored; i++) {
+ boolean match = false;
+ for (int j = 0; j < nDevice; j++) {
+ if (storedSigs[i].equals(deviceSigs[j])) {
+ match = true;
+ break;
+ }
+ }
+ if (!match) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java
new file mode 100644
index 0000000..734fa1d
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.utils;
+
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME;
+
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+
+import android.app.backup.BackupManagerMonitor;
+import android.app.backup.IBackupManagerMonitor;
+import android.content.pm.PackageInfo;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * Utility methods to communicate with BackupManagerMonitor.
+ */
+public class BackupManagerMonitorUtils {
+ /**
+ * Notifies monitor about the event.
+ *
+ * Calls {@link IBackupManagerMonitor#onEvent(Bundle)} with a bundle representing current event.
+ *
+ * @param monitor - implementation of {@link IBackupManagerMonitor} to notify.
+ * @param id - event id.
+ * @param pkg - package event is related to.
+ * @param category - event category.
+ * @param extras - additional event data.
+ * @return <code>monitor</code> if call succeeded and <code>null</code> otherwise.
+ */
+ public static IBackupManagerMonitor monitorEvent(IBackupManagerMonitor monitor, int id,
+ PackageInfo pkg, int category, Bundle extras) {
+ if (monitor != null) {
+ try {
+ Bundle bundle = new Bundle();
+ bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID, id);
+ bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY, category);
+ if (pkg != null) {
+ bundle.putString(EXTRA_LOG_EVENT_PACKAGE_NAME,
+ pkg.packageName);
+ bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION,
+ pkg.versionCode);
+ }
+ if (extras != null) {
+ bundle.putAll(extras);
+ }
+ monitor.onEvent(bundle);
+ return monitor;
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "backup manager monitor went away");
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds given key-value pair in the bundle and returns the bundle. If bundle was null it will
+ * be created.
+ *
+ * @param extras - bundle where to add key-value to, if null a new bundle will be created.
+ * @param key - key.
+ * @param value - value.
+ * @return extras if it was not null and new bundle otherwise.
+ */
+ public static Bundle putMonitoringExtra(Bundle extras, String key, String value) {
+ if (extras == null) {
+ extras = new Bundle();
+ }
+ extras.putString(key, value);
+ return extras;
+ }
+
+ /**
+ * Adds given key-value pair in the bundle and returns the bundle. If bundle was null it will
+ * be created.
+ *
+ * @param extras - bundle where to add key-value to, if null a new bundle will be created.
+ * @param key - key.
+ * @param value - value.
+ * @return extras if it was not null and new bundle otherwise.
+ */
+ public static Bundle putMonitoringExtra(Bundle extras, String key, long value) {
+ if (extras == null) {
+ extras = new Bundle();
+ }
+ extras.putLong(key, value);
+ return extras;
+ }
+
+ /**
+ * Adds given key-value pair in the bundle and returns the bundle. If bundle was null it will
+ * be created.
+ *
+ * @param extras - bundle where to add key-value to, if null a new bundle will be created.
+ * @param key - key.
+ * @param value - value.
+ * @return extras if it was not null and new bundle otherwise.
+ */
+ public static Bundle putMonitoringExtra(Bundle extras, String key, boolean value) {
+ if (extras == null) {
+ extras = new Bundle();
+ }
+ extras.putBoolean(key, value);
+ return extras;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/utils/BackupObserverUtils.java b/services/backup/java/com/android/server/backup/utils/BackupObserverUtils.java
new file mode 100644
index 0000000..3be1e33
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/utils/BackupObserverUtils.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.utils;
+
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+
+import android.app.backup.BackupProgress;
+import android.app.backup.IBackupObserver;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * Utility methods to communicate with BackupObserver.
+ */
+public class BackupObserverUtils {
+ /**
+ * Wraps {@link IBackupObserver#onUpdate(String, BackupProgress)} to handle RemoteException,
+ * so that the caller doesn't have to.
+ */
+ public static void sendBackupOnUpdate(IBackupObserver observer, String packageName,
+ BackupProgress progress) {
+ if (observer != null) {
+ try {
+ observer.onUpdate(packageName, progress);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "Backup observer went away: onUpdate");
+ }
+ }
+ }
+ }
+
+ /**
+ * Wraps {@link IBackupObserver#onResult(String, int)} to handle RemoteException, so that the
+ * caller doesn't have to.
+ */
+ public static void sendBackupOnPackageResult(IBackupObserver observer, String packageName,
+ int status) {
+ if (observer != null) {
+ try {
+ observer.onResult(packageName, status);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "Backup observer went away: onResult");
+ }
+ }
+ }
+ }
+
+ /**
+ * Wraps {@link IBackupObserver#backupFinished(int)} to handle RemoteException, so that the
+ * caller doesn't have to.
+ */
+ public static void sendBackupFinished(IBackupObserver observer, int status) {
+ if (observer != null) {
+ try {
+ observer.backupFinished(status);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "Backup observer went away: backupFinished");
+ }
+ }
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/utils/BytesReadListener.java b/services/backup/java/com/android/server/backup/utils/BytesReadListener.java
new file mode 100644
index 0000000..34d85ff
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/utils/BytesReadListener.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.utils;
+
+/**
+ * Listener for bytes reading.
+ */
+public interface BytesReadListener {
+ /**
+ * Will be called on each read operation.
+ * @param bytesRead - number of bytes read with the most recent read operation.
+ */
+ void onBytesRead(long bytesRead);
+}
diff --git a/services/backup/java/com/android/server/backup/utils/FullBackupRestoreObserverUtils.java b/services/backup/java/com/android/server/backup/utils/FullBackupRestoreObserverUtils.java
new file mode 100644
index 0000000..089109e
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/utils/FullBackupRestoreObserverUtils.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.utils;
+
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+
+import android.app.backup.IFullBackupRestoreObserver;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * Utility methods to communicate with FullBackupRestoreObserver.
+ */
+public class FullBackupRestoreObserverUtils {
+ /**
+ * Wraps {@link IFullBackupRestoreObserver#onStartRestore()} to handle RemoteException, so that
+ * the caller doesn't have to.
+ *
+ * @param observer - IFullBackupRestoreObserver to communicate with.
+ * @return observer if the call worked and null if there was a communication problem.
+ */
+ public static IFullBackupRestoreObserver sendStartRestore(IFullBackupRestoreObserver observer) {
+ if (observer != null) {
+ try {
+ observer.onStartRestore();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "full restore observer went away: startRestore");
+ observer = null;
+ }
+ }
+ return observer;
+ }
+
+ /**
+ * Wraps {@link IFullBackupRestoreObserver#onRestorePackage(String)} to handle RemoteException,
+ * so that the caller doesn't have to.
+ *
+ * @param observer - IFullBackupRestoreObserver to communicate with.
+ * @param name - package name.
+ * @return observer if the call worked and null if there was a communication problem.
+ */
+ public static IFullBackupRestoreObserver sendOnRestorePackage(
+ IFullBackupRestoreObserver observer, String name) {
+ if (observer != null) {
+ try {
+ // TODO: use a more user-friendly name string
+ observer.onRestorePackage(name);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "full restore observer went away: restorePackage");
+ observer = null;
+ }
+ }
+ return observer;
+ }
+
+ /**
+ * Wraps {@link IFullBackupRestoreObserver#onEndRestore()} ()} to handle RemoteException, so
+ * that the caller doesn't have to.
+ *
+ * @param observer - IFullBackupRestoreObserver to communicate with.
+ * @return observer if the call worked and null if there was a communication problem.
+ */
+ public static IFullBackupRestoreObserver sendEndRestore(IFullBackupRestoreObserver observer) {
+ if (observer != null) {
+ try {
+ observer.onEndRestore();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "full restore observer went away: endRestore");
+ observer = null;
+ }
+ }
+ return observer;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java b/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java
new file mode 100644
index 0000000..b3e20dc
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.utils;
+
+import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_MANIFEST_VERSION;
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Slog;
+import android.util.StringBuilderPrinter;
+
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Low-level utility methods for full backup.
+ */
+public class FullBackupUtils {
+ /**
+ * Reads data from pipe and writes it to the stream in chunks of up to 32KB.
+ *
+ * @param inPipe - pipe to read the data from.
+ * @param out - stream to write the data to.
+ * @throws IOException - in case of an error.
+ */
+ public static void routeSocketDataToOutput(ParcelFileDescriptor inPipe, OutputStream out)
+ throws IOException {
+ // We do not take close() responsibility for the pipe FD
+ FileInputStream raw = new FileInputStream(inPipe.getFileDescriptor());
+ DataInputStream in = new DataInputStream(raw);
+
+ byte[] buffer = new byte[32 * 1024];
+ int chunkTotal;
+ while ((chunkTotal = in.readInt()) > 0) {
+ while (chunkTotal > 0) {
+ int toRead = (chunkTotal > buffer.length) ? buffer.length : chunkTotal;
+ int nRead = in.read(buffer, 0, toRead);
+ if (nRead < 0) {
+ Slog.e(TAG, "Unexpectedly reached end of file while reading data");
+ throw new EOFException();
+ }
+ out.write(buffer, 0, nRead);
+ chunkTotal -= nRead;
+ }
+ }
+ }
+
+ /**
+ * Writes app manifest to the given manifest file.
+ *
+ * @param pkg - app package, which manifest to write.
+ * @param packageManager - {@link PackageManager} instance.
+ * @param manifestFile - target manifest file.
+ * @param withApk - whether include apk or not.
+ * @param withWidgets - whether to write widgets data.
+ * @throws IOException - in case of an error.
+ */
+ // TODO: withWidgets is not used, decide whether it is needed.
+ public static void writeAppManifest(PackageInfo pkg, PackageManager packageManager,
+ File manifestFile, boolean withApk, boolean withWidgets) throws IOException {
+ // Manifest format. All data are strings ending in LF:
+ // BACKUP_MANIFEST_VERSION, currently 1
+ //
+ // Version 1:
+ // package name
+ // package's versionCode
+ // platform versionCode
+ // getInstallerPackageName() for this package (maybe empty)
+ // boolean: "1" if archive includes .apk; any other string means not
+ // number of signatures == N
+ // N*: signature byte array in ascii format per Signature.toCharsString()
+ StringBuilder builder = new StringBuilder(4096);
+ StringBuilderPrinter printer = new StringBuilderPrinter(builder);
+
+ printer.println(Integer.toString(BACKUP_MANIFEST_VERSION));
+ printer.println(pkg.packageName);
+ printer.println(Integer.toString(pkg.versionCode));
+ printer.println(Integer.toString(Build.VERSION.SDK_INT));
+
+ String installerName = packageManager.getInstallerPackageName(pkg.packageName);
+ printer.println((installerName != null) ? installerName : "");
+
+ printer.println(withApk ? "1" : "0");
+ if (pkg.signatures == null) {
+ printer.println("0");
+ } else {
+ printer.println(Integer.toString(pkg.signatures.length));
+ for (Signature sig : pkg.signatures) {
+ printer.println(sig.toCharsString());
+ }
+ }
+
+ FileOutputStream outstream = new FileOutputStream(manifestFile);
+ outstream.write(builder.toString().getBytes());
+ outstream.close();
+
+ // We want the manifest block in the archive stream to be idempotent:
+ // each time we generate a backup stream for the app, we want the manifest
+ // block to be identical. The underlying tar mechanism sees it as a file,
+ // though, and will propagate its mtime, causing the tar header to vary.
+ // Avoid this problem by pinning the mtime to zero.
+ manifestFile.setLastModified(0);
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/utils/PasswordUtils.java b/services/backup/java/com/android/server/backup/utils/PasswordUtils.java
new file mode 100644
index 0000000..12fc927
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/utils/PasswordUtils.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.utils;
+
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+
+import android.util.Slog;
+
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+/**
+ * Passwords related utility methods.
+ */
+public class PasswordUtils {
+ // Configuration of PBKDF2 that we use for generating pw hashes and intermediate keys
+ public static final int PBKDF2_HASH_ROUNDS = 10000;
+ private static final int PBKDF2_KEY_SIZE = 256; // bits
+ public static final int PBKDF2_SALT_SIZE = 512; // bits
+ public static final String ENCRYPTION_ALGORITHM_NAME = "AES-256";
+
+ /**
+ * Creates {@link SecretKey} instance from given parameters.
+ *
+ * @param algorithm - key generation algorithm.
+ * @param pw - password.
+ * @param salt - salt.
+ * @param rounds - number of rounds to run in key generation.
+ * @return {@link SecretKey} instance or null in case of an error.
+ */
+ public static SecretKey buildPasswordKey(String algorithm, String pw, byte[] salt, int rounds) {
+ return buildCharArrayKey(algorithm, pw.toCharArray(), salt, rounds);
+ }
+
+ /**
+ * Generates {@link SecretKey} instance from given parameters and returns it's hex
+ * representation.
+ *
+ * @param algorithm - key generation algorithm.
+ * @param pw - password.
+ * @param salt - salt.
+ * @param rounds - number of rounds to run in key generation.
+ * @return Hex representation of the generated key, or null if generation failed.
+ */
+ public static String buildPasswordHash(String algorithm, String pw, byte[] salt, int rounds) {
+ SecretKey key = buildPasswordKey(algorithm, pw, salt, rounds);
+ if (key != null) {
+ return byteArrayToHex(key.getEncoded());
+ }
+ return null;
+ }
+
+ /**
+ * Creates hex string representation of the byte array.
+ */
+ public static String byteArrayToHex(byte[] data) {
+ StringBuilder buf = new StringBuilder(data.length * 2);
+ for (int i = 0; i < data.length; i++) {
+ buf.append(Byte.toHexString(data[i], true));
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Creates byte array from it's hex string representation.
+ */
+ public static byte[] hexToByteArray(String digits) {
+ final int bytes = digits.length() / 2;
+ if (2 * bytes != digits.length()) {
+ throw new IllegalArgumentException("Hex string must have an even number of digits");
+ }
+
+ byte[] result = new byte[bytes];
+ for (int i = 0; i < digits.length(); i += 2) {
+ result[i / 2] = (byte) Integer.parseInt(digits.substring(i, i + 2), 16);
+ }
+ return result;
+ }
+
+ /**
+ * Generates {@link SecretKey} instance from given parameters and returns it's checksum.
+ *
+ * Current implementation returns the key in its primary encoding format.
+ *
+ * @param algorithm - key generation algorithm.
+ * @param pwBytes - password.
+ * @param salt - salt.
+ * @param rounds - number of rounds to run in key generation.
+ * @return Hex representation of the generated key, or null if generation failed.
+ */
+ public static byte[] makeKeyChecksum(String algorithm, byte[] pwBytes, byte[] salt,
+ int rounds) {
+ char[] mkAsChar = new char[pwBytes.length];
+ for (int i = 0; i < pwBytes.length; i++) {
+ mkAsChar[i] = (char) pwBytes[i];
+ }
+
+ Key checksum = buildCharArrayKey(algorithm, mkAsChar, salt, rounds);
+ return checksum.getEncoded();
+ }
+
+ private static SecretKey buildCharArrayKey(String algorithm, char[] pwArray, byte[] salt,
+ int rounds) {
+ try {
+ SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
+ KeySpec
+ ks = new PBEKeySpec(pwArray, salt, rounds, PBKDF2_KEY_SIZE);
+ return keyFactory.generateSecret(ks);
+ } catch (InvalidKeySpecException e) {
+ Slog.e(TAG, "Invalid key spec for PBKDF2!");
+ } catch (NoSuchAlgorithmException e) {
+ Slog.e(TAG, "PBKDF2 unavailable!");
+ }
+ return null;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/utils/RestoreUtils.java b/services/backup/java/com/android/server/backup/utils/RestoreUtils.java
new file mode 100644
index 0000000..ee4201e
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/utils/RestoreUtils.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.utils;
+
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.net.Uri;
+import android.os.Process;
+import android.util.Slog;
+
+import com.android.server.backup.FileMetadata;
+import com.android.server.backup.restore.RestoreDeleteObserver;
+import com.android.server.backup.restore.RestoreInstallObserver;
+import com.android.server.backup.restore.RestorePolicy;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+
+/**
+ * Utility methods used by {@link com.android.server.backup.restore.PerformAdbRestoreTask} and
+ * {@link com.android.server.backup.restore.FullRestoreEngine}.
+ */
+public class RestoreUtils {
+ /**
+ * Reads apk contents from input stream and installs the apk.
+ *
+ * @param instream - input stream to read apk data from.
+ * @param packageManager - {@link PackageManager} instance.
+ * @param installObserver - {@link RestoreInstallObserver} instance.
+ * @param deleteObserver - {@link RestoreDeleteObserver} instance.
+ * @param manifestSignatures - manifest signatures.
+ * @param packagePolicies - package policies.
+ * @param info - backup file info.
+ * @param installerPackage - installer package.
+ * @param bytesReadListener - listener to be called for counting bytes read.
+ * @param dataDir - directory where to create apk file.
+ * @return true if apk was successfully read and installed and false otherwise.
+ */
+ // TODO: Refactor to get rid of unneeded params.
+ public static boolean installApk(InputStream instream, PackageManager packageManager,
+ RestoreInstallObserver installObserver, RestoreDeleteObserver deleteObserver,
+ HashMap<String, Signature[]> manifestSignatures,
+ HashMap<String, RestorePolicy> packagePolicies,
+ FileMetadata info,
+ String installerPackage, BytesReadListener bytesReadListener,
+ File dataDir) {
+ boolean okay = true;
+
+ if (DEBUG) {
+ Slog.d(TAG, "Installing from backup: " + info.packageName);
+ }
+
+ // The file content is an .apk file. Copy it out to a staging location and
+ // attempt to install it.
+ File apkFile = new File(dataDir, info.packageName);
+ try {
+ FileOutputStream apkStream = new FileOutputStream(apkFile);
+ byte[] buffer = new byte[32 * 1024];
+ long size = info.size;
+ while (size > 0) {
+ long toRead = (buffer.length < size) ? buffer.length : size;
+ int didRead = instream.read(buffer, 0, (int) toRead);
+ if (didRead >= 0) {
+ bytesReadListener.onBytesRead(didRead);
+ }
+ apkStream.write(buffer, 0, didRead);
+ size -= didRead;
+ }
+ apkStream.close();
+
+ // make sure the installer can read it
+ apkFile.setReadable(true, false);
+
+ // Now install it
+ Uri packageUri = Uri.fromFile(apkFile);
+ installObserver.reset();
+ // TODO: PackageManager.installPackage() is deprecated, refactor.
+ packageManager.installPackage(packageUri, installObserver,
+ PackageManager.INSTALL_REPLACE_EXISTING | PackageManager.INSTALL_FROM_ADB,
+ installerPackage);
+ installObserver.waitForCompletion();
+
+ if (installObserver.getResult() != PackageManager.INSTALL_SUCCEEDED) {
+ // The only time we continue to accept install of data even if the
+ // apk install failed is if we had already determined that we could
+ // accept the data regardless.
+ if (packagePolicies.get(info.packageName) != RestorePolicy.ACCEPT) {
+ okay = false;
+ }
+ } else {
+ // Okay, the install succeeded. Make sure it was the right app.
+ boolean uninstall = false;
+ if (!installObserver.getPackageName().equals(info.packageName)) {
+ Slog.w(TAG, "Restore stream claimed to include apk for "
+ + info.packageName + " but apk was really "
+ + installObserver.getPackageName());
+ // delete the package we just put in place; it might be fraudulent
+ okay = false;
+ uninstall = true;
+ } else {
+ try {
+ PackageInfo pkg = packageManager.getPackageInfo(
+ info.packageName,
+ PackageManager.GET_SIGNATURES);
+ if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP)
+ == 0) {
+ Slog.w(TAG, "Restore stream contains apk of package "
+ + info.packageName
+ + " but it disallows backup/restore");
+ okay = false;
+ } else {
+ // So far so good -- do the signatures match the manifest?
+ Signature[] sigs = manifestSignatures.get(info.packageName);
+ if (AppBackupUtils.signaturesMatch(sigs, pkg)) {
+ // If this is a system-uid app without a declared backup agent,
+ // don't restore any of the file data.
+ if ((pkg.applicationInfo.uid < Process.FIRST_APPLICATION_UID)
+ && (pkg.applicationInfo.backupAgentName == null)) {
+ Slog.w(TAG, "Installed app " + info.packageName
+ + " has restricted uid and no agent");
+ okay = false;
+ }
+ } else {
+ Slog.w(TAG, "Installed app " + info.packageName
+ + " signatures do not match restore manifest");
+ okay = false;
+ uninstall = true;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Install of package " + info.packageName
+ + " succeeded but now not found");
+ okay = false;
+ }
+ }
+
+ // If we're not okay at this point, we need to delete the package
+ // that we just installed.
+ if (uninstall) {
+ deleteObserver.reset();
+ packageManager.deletePackage(
+ installObserver.getPackageName(),
+ deleteObserver, 0);
+ deleteObserver.waitForCompletion();
+ }
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to transcribe restored apk for install");
+ okay = false;
+ } finally {
+ apkFile.delete();
+ }
+
+ return okay;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
new file mode 100644
index 0000000..2910ba2
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
@@ -0,0 +1,798 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.utils;
+
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME;
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION;
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_MANIFEST_PACKAGE_NAME;
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_OLD_VERSION;
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_POLICY_ALLOW_APKS;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_APK_NOT_INSTALLED;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_MISSING_SIGNATURE;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_RESTORE_ANY_VERSION;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_SYSTEM_APP_NO_AGENT;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSIONS_MATCH;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER;
+
+import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME;
+import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_MANIFEST_VERSION;
+import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_METADATA_FILENAME;
+import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_WIDGET_METADATA_TOKEN;
+import static com.android.server.backup.RefactoredBackupManagerService.DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
+import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+
+import android.app.backup.BackupAgent;
+import android.app.backup.BackupManagerMonitor;
+import android.app.backup.FullBackup;
+import android.app.backup.IBackupManagerMonitor;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.os.Bundle;
+import android.os.Process;
+import android.util.Slog;
+
+import com.android.server.backup.FileMetadata;
+import com.android.server.backup.restore.RestorePolicy;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Utility methods to read backup tar file.
+ */
+public class TarBackupReader {
+ private static final int TAR_HEADER_OFFSET_TYPE_CHAR = 156;
+ private static final int TAR_HEADER_LENGTH_PATH = 100;
+ private static final int TAR_HEADER_OFFSET_PATH = 0;
+ private static final int TAR_HEADER_LENGTH_PATH_PREFIX = 155;
+ private static final int TAR_HEADER_OFFSET_PATH_PREFIX = 345;
+ private static final int TAR_HEADER_LENGTH_MODE = 8;
+ private static final int TAR_HEADER_OFFSET_MODE = 100;
+ private static final int TAR_HEADER_LENGTH_MODTIME = 12;
+ private static final int TAR_HEADER_OFFSET_MODTIME = 136;
+ private static final int TAR_HEADER_LENGTH_FILESIZE = 12;
+ private static final int TAR_HEADER_OFFSET_FILESIZE = 124;
+ private static final int TAR_HEADER_LONG_RADIX = 8;
+
+ private final InputStream mInputStream;
+ private final BytesReadListener mBytesReadListener;
+
+ private IBackupManagerMonitor mMonitor;
+
+ // Widget blob to be restored out-of-band.
+ private byte[] mWidgetData = null;
+
+ public TarBackupReader(InputStream inputStream, BytesReadListener bytesReadListener,
+ IBackupManagerMonitor monitor) {
+ mInputStream = inputStream;
+ mBytesReadListener = bytesReadListener;
+ mMonitor = monitor;
+ }
+
+ /**
+ * Consumes a tar file header block [sequence] and accumulates the relevant metadata.
+ */
+ public FileMetadata readTarHeaders() throws IOException {
+ byte[] block = new byte[512];
+ FileMetadata info = null;
+
+ boolean gotHeader = readTarHeader(block);
+ if (gotHeader) {
+ try {
+ // okay, presume we're okay, and extract the various metadata
+ info = new FileMetadata();
+ info.size = extractRadix(block,
+ TAR_HEADER_OFFSET_FILESIZE,
+ TAR_HEADER_LENGTH_FILESIZE,
+ TAR_HEADER_LONG_RADIX);
+ info.mtime = extractRadix(block,
+ TAR_HEADER_OFFSET_MODTIME,
+ TAR_HEADER_LENGTH_MODTIME,
+ TAR_HEADER_LONG_RADIX);
+ info.mode = extractRadix(block,
+ TAR_HEADER_OFFSET_MODE,
+ TAR_HEADER_LENGTH_MODE,
+ TAR_HEADER_LONG_RADIX);
+
+ info.path = extractString(block,
+ TAR_HEADER_OFFSET_PATH_PREFIX,
+ TAR_HEADER_LENGTH_PATH_PREFIX);
+ String path = extractString(block,
+ TAR_HEADER_OFFSET_PATH,
+ TAR_HEADER_LENGTH_PATH);
+ if (path.length() > 0) {
+ if (info.path.length() > 0) {
+ info.path += '/';
+ }
+ info.path += path;
+ }
+
+ // tar link indicator field: 1 byte at offset 156 in the header.
+ int typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR];
+ if (typeChar == 'x') {
+ // pax extended header, so we need to read that
+ gotHeader = readPaxExtendedHeader(info);
+ if (gotHeader) {
+ // and after a pax extended header comes another real header -- read
+ // that to find the real file type
+ gotHeader = readTarHeader(block);
+ }
+ if (!gotHeader) {
+ throw new IOException("Bad or missing pax header");
+ }
+
+ typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR];
+ }
+
+ switch (typeChar) {
+ case '0':
+ info.type = BackupAgent.TYPE_FILE;
+ break;
+ case '5': {
+ info.type = BackupAgent.TYPE_DIRECTORY;
+ if (info.size != 0) {
+ Slog.w(TAG, "Directory entry with nonzero size in header");
+ info.size = 0;
+ }
+ break;
+ }
+ case 0: {
+ // presume EOF
+ if (MORE_DEBUG) {
+ Slog.w(TAG, "Saw type=0 in tar header block, info=" + info);
+ }
+ return null;
+ }
+ default: {
+ Slog.e(TAG, "Unknown tar entity type: " + typeChar);
+ throw new IOException("Unknown entity type " + typeChar);
+ }
+ }
+
+ // Parse out the path
+ //
+ // first: apps/shared/unrecognized
+ if (FullBackup.SHARED_PREFIX.regionMatches(0,
+ info.path, 0, FullBackup.SHARED_PREFIX.length())) {
+ // File in shared storage. !!! TODO: implement this.
+ info.path = info.path.substring(FullBackup.SHARED_PREFIX.length());
+ info.packageName = SHARED_BACKUP_AGENT_PACKAGE;
+ info.domain = FullBackup.SHARED_STORAGE_TOKEN;
+ if (DEBUG) {
+ Slog.i(TAG, "File in shared storage: " + info.path);
+ }
+ } else if (FullBackup.APPS_PREFIX.regionMatches(0,
+ info.path, 0, FullBackup.APPS_PREFIX.length())) {
+ // App content! Parse out the package name and domain
+
+ // strip the apps/ prefix
+ info.path = info.path.substring(FullBackup.APPS_PREFIX.length());
+
+ // extract the package name
+ int slash = info.path.indexOf('/');
+ if (slash < 0) {
+ throw new IOException("Illegal semantic path in " + info.path);
+ }
+ info.packageName = info.path.substring(0, slash);
+ info.path = info.path.substring(slash + 1);
+
+ // if it's a manifest or metadata payload we're done, otherwise parse
+ // out the domain into which the file will be restored
+ if (!info.path.equals(BACKUP_MANIFEST_FILENAME) &&
+ !info.path.equals(BACKUP_METADATA_FILENAME)) {
+ slash = info.path.indexOf('/');
+ if (slash < 0) {
+ throw new IOException("Illegal semantic path in non-manifest "
+ + info.path);
+ }
+ info.domain = info.path.substring(0, slash);
+ info.path = info.path.substring(slash + 1);
+ }
+ }
+ } catch (IOException e) {
+ if (DEBUG) {
+ Slog.e(TAG, "Parse error in header: " + e.getMessage());
+ if (MORE_DEBUG) {
+ hexLog(block);
+ }
+ }
+ throw e;
+ }
+ }
+ return info;
+ }
+
+ /**
+ * Tries to read exactly the given number of bytes into a buffer at the stated offset.
+ *
+ * @param in - input stream to read bytes from..
+ * @param buffer - where to write bytes to.
+ * @param offset - offset in buffer to write bytes to.
+ * @param size - number of bytes to read.
+ * @return number of bytes actually read.
+ * @throws IOException in case of an error.
+ */
+ private static int readExactly(InputStream in, byte[] buffer, int offset, int size)
+ throws IOException {
+ if (size <= 0) {
+ throw new IllegalArgumentException("size must be > 0");
+ }
+ if (MORE_DEBUG) {
+ Slog.i(TAG, " ... readExactly(" + size + ") called");
+ }
+ int soFar = 0;
+ while (soFar < size) {
+ int nRead = in.read(buffer, offset + soFar, size - soFar);
+ if (nRead <= 0) {
+ if (MORE_DEBUG) {
+ Slog.w(TAG, "- wanted exactly " + size + " but got only " + soFar);
+ }
+ break;
+ }
+ soFar += nRead;
+ if (MORE_DEBUG) {
+ Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soFar));
+ }
+ }
+ return soFar;
+ }
+
+ /**
+ * Reads app manifest, filling version and hasApk fields in the metadata, and returns array of
+ * signatures.
+ *
+ * @param info - file metadata.
+ * @return array of signatures or null, in case of an error.
+ * @throws IOException in case of an error.
+ */
+ public Signature[] readAppManifestAndReturnSignatures(FileMetadata info)
+ throws IOException {
+ // Fail on suspiciously large manifest files
+ if (info.size > 64 * 1024) {
+ throw new IOException("Restore manifest too big; corrupt? size=" + info.size);
+ }
+
+ byte[] buffer = new byte[(int) info.size];
+ if (MORE_DEBUG) {
+ Slog.i(TAG,
+ " readAppManifestAndReturnSignatures() looking for " + info.size + " bytes");
+ }
+ if (readExactly(mInputStream, buffer, 0, (int) info.size) == info.size) {
+ mBytesReadListener.onBytesRead(info.size);
+ } else {
+ throw new IOException("Unexpected EOF in manifest");
+ }
+
+ String[] str = new String[1];
+ int offset = 0;
+
+ try {
+ offset = extractLine(buffer, offset, str);
+ int version = Integer.parseInt(str[0]);
+ if (version == BACKUP_MANIFEST_VERSION) {
+ offset = extractLine(buffer, offset, str);
+ String manifestPackage = str[0];
+ // TODO: handle <original-package>
+ if (manifestPackage.equals(info.packageName)) {
+ offset = extractLine(buffer, offset, str);
+ info.version = Integer.parseInt(str[0]); // app version
+ offset = extractLine(buffer, offset, str);
+ // This is the platform version, which we don't use, but we parse it
+ // as a safety against corruption in the manifest.
+ Integer.parseInt(str[0]);
+ offset = extractLine(buffer, offset, str);
+ info.installerPackageName = (str[0].length() > 0) ? str[0] : null;
+ offset = extractLine(buffer, offset, str);
+ info.hasApk = str[0].equals("1");
+ offset = extractLine(buffer, offset, str);
+ int numSigs = Integer.parseInt(str[0]);
+ if (numSigs > 0) {
+ Signature[] sigs = new Signature[numSigs];
+ for (int i = 0; i < numSigs; i++) {
+ offset = extractLine(buffer, offset, str);
+ sigs[i] = new Signature(str[0]);
+ }
+ return sigs;
+ } else {
+ Slog.i(TAG, "Missing signature on backed-up package " + info.packageName);
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(
+ mMonitor,
+ LOG_EVENT_ID_MISSING_SIGNATURE,
+ null,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ BackupManagerMonitorUtils.putMonitoringExtra(null,
+ EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName));
+ }
+ } else {
+ Slog.i(TAG, "Expected package " + info.packageName
+ + " but restore manifest claims " + manifestPackage);
+ Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
+ EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
+ monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
+ monitoringExtras,
+ EXTRA_LOG_MANIFEST_PACKAGE_NAME, manifestPackage);
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(
+ mMonitor,
+ LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE,
+ null,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ monitoringExtras);
+ }
+ } else {
+ Slog.i(TAG, "Unknown restore manifest version " + version
+ + " for package " + info.packageName);
+ Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
+ EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
+ monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
+ EXTRA_LOG_EVENT_PACKAGE_VERSION, version);
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(
+ mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_UNKNOWN_VERSION,
+ null,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ monitoringExtras);
+
+ }
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Corrupt restore manifest for package " + info.packageName);
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(
+ mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_CORRUPT_MANIFEST,
+ null,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ BackupManagerMonitorUtils.putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME,
+ info.packageName));
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, e.getMessage());
+ }
+
+ return null;
+ }
+
+ /**
+ * Chooses restore policy.
+ *
+ * @param packageManager - PackageManager instance.
+ * @param allowApks - allow restore set to include apks.
+ * @param info - file metadata.
+ * @param signatures - array of signatures parsed from backup file.
+ * @return a restore policy constant.
+ */
+ public RestorePolicy chooseRestorePolicy(PackageManager packageManager,
+ boolean allowApks, FileMetadata info, Signature[] signatures) {
+ if (signatures == null) {
+ return RestorePolicy.IGNORE;
+ }
+
+ RestorePolicy policy = RestorePolicy.IGNORE;
+
+ // Okay, got the manifest info we need...
+ try {
+ PackageInfo pkgInfo = packageManager.getPackageInfo(
+ info.packageName, PackageManager.GET_SIGNATURES);
+ // Fall through to IGNORE if the app explicitly disallows backup
+ final int flags = pkgInfo.applicationInfo.flags;
+ if ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
+ // Restore system-uid-space packages only if they have
+ // defined a custom backup agent
+ if ((pkgInfo.applicationInfo.uid
+ >= Process.FIRST_APPLICATION_UID)
+ || (pkgInfo.applicationInfo.backupAgentName != null)) {
+ // Verify signatures against any installed version; if they
+ // don't match, then we fall though and ignore the data. The
+ // signatureMatch() method explicitly ignores the signature
+ // check for packages installed on the system partition, because
+ // such packages are signed with the platform cert instead of
+ // the app developer's cert, so they're different on every
+ // device.
+ if (AppBackupUtils.signaturesMatch(signatures, pkgInfo)) {
+ if ((pkgInfo.applicationInfo.flags
+ & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) != 0) {
+ Slog.i(TAG, "Package has restoreAnyVersion; taking data");
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(
+ mMonitor,
+ LOG_EVENT_ID_RESTORE_ANY_VERSION,
+ pkgInfo,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
+ policy = RestorePolicy.ACCEPT;
+ } else if (pkgInfo.versionCode >= info.version) {
+ Slog.i(TAG, "Sig + version match; taking data");
+ policy = RestorePolicy.ACCEPT;
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(
+ mMonitor,
+ LOG_EVENT_ID_VERSIONS_MATCH,
+ pkgInfo,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
+ } else {
+ // The data is from a newer version of the app than
+ // is presently installed. That means we can only
+ // use it if the matching apk is also supplied.
+ if (allowApks) {
+ Slog.i(TAG, "Data version " + info.version
+ + " is newer than installed "
+ + "version "
+ + pkgInfo.versionCode
+ + " - requiring apk");
+ policy = RestorePolicy.ACCEPT_IF_APK;
+ } else {
+ Slog.i(TAG, "Data requires newer version "
+ + info.version + "; ignoring");
+ mMonitor = BackupManagerMonitorUtils
+ .monitorEvent(mMonitor,
+ LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER,
+ pkgInfo,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ BackupManagerMonitorUtils
+ .putMonitoringExtra(
+ null,
+ EXTRA_LOG_OLD_VERSION,
+ info.version));
+
+ policy = RestorePolicy.IGNORE;
+ }
+ }
+ } else {
+ Slog.w(TAG, "Restore manifest signatures do not match "
+ + "installed application for "
+ + info.packageName);
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(
+ mMonitor,
+ LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH,
+ pkgInfo,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
+ }
+ } else {
+ Slog.w(TAG, "Package " + info.packageName
+ + " is system level with no agent");
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(
+ mMonitor,
+ LOG_EVENT_ID_SYSTEM_APP_NO_AGENT,
+ pkgInfo,
+ LOG_EVENT_CATEGORY_AGENT,
+ null);
+ }
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG,
+ "Restore manifest from " + info.packageName + " but allowBackup=false");
+ }
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(
+ mMonitor,
+ LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE,
+ pkgInfo,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Okay, the target app isn't installed. We can process
+ // the restore properly only if the dataset provides the
+ // apk file and we can successfully install it.
+ if (allowApks) {
+ if (DEBUG) {
+ Slog.i(TAG, "Package " + info.packageName
+ + " not installed; requiring apk in dataset");
+ }
+ policy = RestorePolicy.ACCEPT_IF_APK;
+ } else {
+ policy = RestorePolicy.IGNORE;
+ }
+ Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
+ null,
+ EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
+ monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
+ monitoringExtras,
+ EXTRA_LOG_POLICY_ALLOW_APKS, allowApks);
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(
+ mMonitor,
+ LOG_EVENT_ID_APK_NOT_INSTALLED,
+ null,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ monitoringExtras);
+ }
+
+ if (policy == RestorePolicy.ACCEPT_IF_APK && !info.hasApk) {
+ Slog.i(TAG, "Cannot restore package " + info.packageName
+ + " without the matching .apk");
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(
+ mMonitor,
+ LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK,
+ null,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ BackupManagerMonitorUtils.putMonitoringExtra(null,
+ EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName));
+ }
+
+ return policy;
+ }
+
+ // Given an actual file content size, consume the post-content padding mandated
+ // by the tar format.
+ public void skipTarPadding(long size) throws IOException {
+ long partial = (size + 512) % 512;
+ if (partial > 0) {
+ final int needed = 512 - (int) partial;
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Skipping tar padding: " + needed + " bytes");
+ }
+ byte[] buffer = new byte[needed];
+ if (readExactly(mInputStream, buffer, 0, needed) == needed) {
+ mBytesReadListener.onBytesRead(needed);
+ } else {
+ throw new IOException("Unexpected EOF in padding");
+ }
+ }
+ }
+
+ /**
+ * Read a widget metadata file, returning the restored blob.
+ */
+ public void readMetadata(FileMetadata info) throws IOException {
+ // Fail on suspiciously large widget dump files
+ if (info.size > 64 * 1024) {
+ throw new IOException("Metadata too big; corrupt? size=" + info.size);
+ }
+
+ byte[] buffer = new byte[(int) info.size];
+ if (readExactly(mInputStream, buffer, 0, (int) info.size) == info.size) {
+ mBytesReadListener.onBytesRead(info.size);
+ } else {
+ throw new IOException("Unexpected EOF in widget data");
+ }
+
+ String[] str = new String[1];
+ int offset = extractLine(buffer, 0, str);
+ int version = Integer.parseInt(str[0]);
+ if (version == BACKUP_MANIFEST_VERSION) {
+ offset = extractLine(buffer, offset, str);
+ final String pkg = str[0];
+ if (info.packageName.equals(pkg)) {
+ // Data checks out -- the rest of the buffer is a concatenation of
+ // binary blobs as described in the comment at writeAppWidgetData()
+ ByteArrayInputStream bin = new ByteArrayInputStream(buffer,
+ offset, buffer.length - offset);
+ DataInputStream in = new DataInputStream(bin);
+ while (bin.available() > 0) {
+ int token = in.readInt();
+ int size = in.readInt();
+ if (size > 64 * 1024) {
+ throw new IOException("Datum " + Integer.toHexString(token)
+ + " too big; corrupt? size=" + info.size);
+ }
+ switch (token) {
+ case BACKUP_WIDGET_METADATA_TOKEN: {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Got widget metadata for " + info.packageName);
+ }
+ mWidgetData = new byte[size];
+ in.read(mWidgetData);
+ break;
+ }
+ default: {
+ if (DEBUG) {
+ Slog.i(TAG, "Ignoring metadata blob " + Integer.toHexString(token)
+ + " for " + info.packageName);
+ }
+ in.skipBytes(size);
+ break;
+ }
+ }
+ }
+ } else {
+ Slog.w(TAG,
+ "Metadata mismatch: package " + info.packageName + " but widget data for "
+ + pkg);
+
+ Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
+ EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
+ monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
+ BackupManagerMonitor.EXTRA_LOG_WIDGET_PACKAGE_NAME, pkg);
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(
+ mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_WIDGET_METADATA_MISMATCH,
+ null,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ monitoringExtras);
+ }
+ } else {
+ Slog.w(TAG, "Unsupported metadata version " + version);
+
+ Bundle monitoringExtras = BackupManagerMonitorUtils
+ .putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME,
+ info.packageName);
+ monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
+ EXTRA_LOG_EVENT_PACKAGE_VERSION, version);
+ mMonitor = BackupManagerMonitorUtils.monitorEvent(
+ mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_WIDGET_UNKNOWN_VERSION,
+ null,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ monitoringExtras);
+ }
+ }
+
+ /**
+ * Builds a line from a byte buffer starting at 'offset'.
+ *
+ * @param buffer - where to read a line from.
+ * @param offset - offset in buffer to read a line from.
+ * @param outStr - an output parameter, the result will be put in outStr.
+ * @return the index of the next unconsumed data in the buffer.
+ * @throws IOException in case of an error.
+ */
+ private static int extractLine(byte[] buffer, int offset, String[] outStr) throws IOException {
+ final int end = buffer.length;
+ if (offset >= end) {
+ throw new IOException("Incomplete data");
+ }
+
+ int pos;
+ for (pos = offset; pos < end; pos++) {
+ byte c = buffer[pos];
+ // at LF we declare end of line, and return the next char as the
+ // starting point for the next time through
+ if (c == '\n') {
+ break;
+ }
+ }
+ outStr[0] = new String(buffer, offset, pos - offset);
+ pos++; // may be pointing an extra byte past the end but that's okay
+ return pos;
+ }
+
+ private boolean readTarHeader(byte[] block) throws IOException {
+ final int got = readExactly(mInputStream, block, 0, 512);
+ if (got == 0) {
+ return false; // Clean EOF
+ }
+ if (got < 512) {
+ throw new IOException("Unable to read full block header");
+ }
+ mBytesReadListener.onBytesRead(512);
+ return true;
+ }
+
+ // overwrites 'info' fields based on the pax extended header
+ private boolean readPaxExtendedHeader(FileMetadata info)
+ throws IOException {
+ // We should never see a pax extended header larger than this
+ if (info.size > 32 * 1024) {
+ Slog.w(TAG, "Suspiciously large pax header size " + info.size + " - aborting");
+ throw new IOException("Sanity failure: pax header size " + info.size);
+ }
+
+ // read whole blocks, not just the content size
+ int numBlocks = (int) ((info.size + 511) >> 9);
+ byte[] data = new byte[numBlocks * 512];
+ if (readExactly(mInputStream, data, 0, data.length) < data.length) {
+ throw new IOException("Unable to read full pax header");
+ }
+ mBytesReadListener.onBytesRead(data.length);
+
+ final int contentSize = (int) info.size;
+ int offset = 0;
+ do {
+ // extract the line at 'offset'
+ int eol = offset + 1;
+ while (eol < contentSize && data[eol] != ' ') {
+ eol++;
+ }
+ if (eol >= contentSize) {
+ // error: we just hit EOD looking for the end of the size field
+ throw new IOException("Invalid pax data");
+ }
+ // eol points to the space between the count and the key
+ int linelen = (int) extractRadix(data, offset, eol - offset, 10);
+ int key = eol + 1; // start of key=value
+ eol = offset + linelen - 1; // trailing LF
+ int value;
+ for (value = key + 1; data[value] != '=' && value <= eol; value++) {
+ ;
+ }
+ if (value > eol) {
+ throw new IOException("Invalid pax declaration");
+ }
+
+ // pax requires that key/value strings be in UTF-8
+ String keyStr = new String(data, key, value - key, "UTF-8");
+ // -1 to strip the trailing LF
+ String valStr = new String(data, value + 1, eol - value - 1, "UTF-8");
+
+ if ("path".equals(keyStr)) {
+ info.path = valStr;
+ } else if ("size".equals(keyStr)) {
+ info.size = Long.parseLong(valStr);
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "Unhandled pax key: " + key);
+ }
+ }
+
+ offset += linelen;
+ } while (offset < contentSize);
+
+ return true;
+ }
+
+ private static long extractRadix(byte[] data, int offset, int maxChars, int radix)
+ throws IOException {
+ long value = 0;
+ final int end = offset + maxChars;
+ for (int i = offset; i < end; i++) {
+ final byte b = data[i];
+ // Numeric fields in tar can terminate with either NUL or SPC
+ if (b == 0 || b == ' ') {
+ break;
+ }
+ if (b < '0' || b > ('0' + radix - 1)) {
+ throw new IOException("Invalid number in header: '" + (char) b
+ + "' for radix " + radix);
+ }
+ value = radix * value + (b - '0');
+ }
+ return value;
+ }
+
+ private static String extractString(byte[] data, int offset, int maxChars) throws IOException {
+ final int end = offset + maxChars;
+ int eos = offset;
+ // tar string fields terminate early with a NUL
+ while (eos < end && data[eos] != 0) {
+ eos++;
+ }
+ return new String(data, offset, eos - offset, "US-ASCII");
+ }
+
+ private static void hexLog(byte[] block) {
+ int offset = 0;
+ int todo = block.length;
+ StringBuilder buf = new StringBuilder(64);
+ while (todo > 0) {
+ buf.append(String.format("%04x ", offset));
+ int numThisLine = (todo > 16) ? 16 : todo;
+ for (int i = 0; i < numThisLine; i++) {
+ buf.append(String.format("%02x ", block[offset + i]));
+ }
+ Slog.i("hexdump", buf.toString());
+ buf.setLength(0);
+ todo -= numThisLine;
+ offset += numThisLine;
+ }
+ }
+
+ public IBackupManagerMonitor getMonitor() {
+ return mMonitor;
+ }
+
+ public byte[] getWidgetData() {
+ return mWidgetData;
+ }
+}
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 15493467..4d080e9 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -28,7 +28,10 @@
tzdata_shared2 \
tzdata_update2 \
android.hidl.base-V1.0-java-static \
+ android.hardware.weaver-V1.0-java-static \
android.hardware.biometrics.fingerprint-V2.1-java-static \
+ android.hardware.oemlock-V1.0-java-static \
+ android.hardware.tetheroffload.control-V1.0-java-static \
android.hardware.vibrator-V1.0-java-constants \
ifneq ($(INCREMENTAL_BUILDS),)
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index a378d02..a15992d 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -31,7 +31,11 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PermissionInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
@@ -154,6 +158,20 @@
long mAllowWhileIdleMinTime;
int mNumTimeChanged;
+ // Bookkeeping about the identity of the "System UI" package, determined at runtime.
+
+ /**
+ * This permission must be defined by the canonical System UI package,
+ * with protection level "signature".
+ */
+ private static final String SYSTEM_UI_SELF_PERMISSION =
+ "android.permission.systemui.IDENTITY";
+
+ /**
+ * At boot we use SYSTEM_UI_SELF_PERMISSION to look up the definer's uid.
+ */
+ int mSystemUiUid;
+
/**
* The current set of user whitelisted apps for device idle mode, meaning these are allowed
* to freely schedule alarms.
@@ -961,6 +979,25 @@
}
}
+ // Determine SysUI's uid
+ final PackageManager packMan = getContext().getPackageManager();
+ try {
+ PermissionInfo sysUiPerm = packMan.getPermissionInfo(SYSTEM_UI_SELF_PERMISSION, 0);
+ ApplicationInfo sysUi = packMan.getApplicationInfo(sysUiPerm.packageName, 0);
+ if ((sysUi.privateFlags&ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
+ mSystemUiUid = sysUi.uid;
+ } else {
+ Slog.e(TAG, "SysUI permission " + SYSTEM_UI_SELF_PERMISSION
+ + " defined by non-privileged app " + sysUi.packageName
+ + " - ignoring");
+ }
+ } catch (NameNotFoundException e) {
+ }
+
+ if (mSystemUiUid <= 0) {
+ Slog.wtf(TAG, "SysUI package not found!");
+ }
+
PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*alarm*");
@@ -1335,6 +1372,7 @@
// This means we will allow these alarms to go off as normal even while idle, with no
// timing restrictions.
} else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
+ || callingUid == mSystemUiUid
|| Arrays.binarySearch(mDeviceIdleUserWhitelist,
UserHandle.getAppId(callingUid)) >= 0)) {
flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
diff --git a/services/core/java/com/android/server/CommonTimeManagementService.java b/services/core/java/com/android/server/CommonTimeManagementService.java
index 07c8679..5cebfa5 100644
--- a/services/core/java/com/android/server/CommonTimeManagementService.java
+++ b/services/core/java/com/android/server/CommonTimeManagementService.java
@@ -92,12 +92,12 @@
* Internal state
*/
private final Context mContext;
+ private final Object mLock = new Object();
private INetworkManagementService mNetMgr;
private CommonTimeConfig mCTConfig;
private String mCurIface;
private Handler mReconnectHandler = new Handler();
private Handler mNoInterfaceHandler = new Handler();
- private Object mLock = new Object();
private boolean mDetectedAtStartup = false;
private byte mEffectivePrio = BASE_SERVER_PRIO;
@@ -105,15 +105,19 @@
* Callback handler implementations.
*/
private INetworkManagementEventObserver mIfaceObserver = new BaseNetworkObserver() {
+ @Override
public void interfaceStatusChanged(String iface, boolean up) {
reevaluateServiceState();
}
+ @Override
public void interfaceLinkStateChanged(String iface, boolean up) {
reevaluateServiceState();
}
+ @Override
public void interfaceAdded(String iface) {
reevaluateServiceState();
}
+ @Override
public void interfaceRemoved(String iface) {
reevaluateServiceState();
}
@@ -127,19 +131,11 @@
};
private CommonTimeConfig.OnServerDiedListener mCTServerDiedListener =
- new CommonTimeConfig.OnServerDiedListener() {
- public void onServerDied() {
- scheduleTimeConfigReconnect();
- }
- };
+ () -> scheduleTimeConfigReconnect();
- private Runnable mReconnectRunnable = new Runnable() {
- public void run() { connectToTimeConfig(); }
- };
+ private Runnable mReconnectRunnable = () -> connectToTimeConfig();
- private Runnable mNoInterfaceRunnable = new Runnable() {
- public void run() { handleNoInterfaceTimeout(); }
- };
+ private Runnable mNoInterfaceRunnable = () -> handleNoInterfaceTimeout();
/*
* Public interface (constructor, systemReady and dump)
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4b12bc4..224845c 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -29,6 +29,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.Nullable;
import android.app.BroadcastOptions;
@@ -41,7 +42,6 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.PacketKeepalive;
@@ -62,6 +62,7 @@
import android.net.NetworkMisc;
import android.net.NetworkQuotaInfo;
import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
import android.net.NetworkState;
import android.net.NetworkUtils;
import android.net.Proxy;
@@ -85,10 +86,12 @@
import android.os.Message;
import android.os.Messenger;
import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
@@ -100,7 +103,6 @@
import android.util.LocalLog;
import android.util.LocalLog.ReadOnlyLocalLog;
import android.util.Log;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -137,6 +139,7 @@
import com.android.server.connectivity.PacManager;
import com.android.server.connectivity.PermissionMonitor;
import com.android.server.connectivity.Tethering;
+import com.android.server.connectivity.tethering.TetheringDependencies;
import com.android.server.connectivity.Vpn;
import com.android.server.net.BaseNetworkObserver;
import com.android.server.net.LockdownVpnTracker;
@@ -177,6 +180,7 @@
public static final String DIAG_ARG = "--diag";
public static final String SHORT_ARG = "--short";
+ public static final String TETHERING_ARG = "tethering";
private static final boolean DBG = true;
private static final boolean VDBG = false;
@@ -206,6 +210,13 @@
// See Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
private final int mReleasePendingIntentDelayMs;
+ // Driver specific constants used to select packets received via
+ // WiFi that caused the phone to exit sleep state. Currently there
+ // is only one kernel implementation so we can get away with
+ // constants.
+ private static final int mWakeupPacketMark = 0x80000000;
+ private static final int mWakeupPacketMask = 0x80000000;
+
private MockableSystemProperties mSystemProperties;
private Tethering mTethering;
@@ -363,11 +374,6 @@
private static final int EVENT_SET_ACCEPT_UNVALIDATED = 28;
/**
- * used to specify whether a network should not be penalized when it becomes unvalidated.
- */
- private static final int EVENT_SET_AVOID_UNVALIDATED = 35;
-
- /**
* used to ask the user to confirm a connection to an unvalidated network.
* obj = network
*/
@@ -384,6 +390,20 @@
*/
private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31;
+ /**
+ * used to specify whether a network should not be penalized when it becomes unvalidated.
+ */
+ private static final int EVENT_SET_AVOID_UNVALIDATED = 35;
+
+ /**
+ * used to trigger revalidation of a network.
+ */
+ private static final int EVENT_REVALIDATE_NETWORK = 36;
+
+ private static String eventName(int what) {
+ return sMagicDecoderRing.get(what, Integer.toString(what));
+ }
+
/** Handler thread used for both of the handlers below. */
@VisibleForTesting
protected final HandlerThread mHandlerThread;
@@ -396,15 +416,9 @@
private Intent mInitialBroadcast;
private PowerManager.WakeLock mNetTransitionWakeLock;
- private String mNetTransitionWakeLockCausedBy = "";
- private int mNetTransitionWakeLockSerialNumber;
private int mNetTransitionWakeLockTimeout;
private final PowerManager.WakeLock mPendingIntentWakeLock;
- // used in DBG mode to track inet condition reports
- private static final int INET_CONDITION_LOG_MAX_SIZE = 15;
- private ArrayList mInetLog;
-
// track the current default http proxy - tell the world if we get a new one (real change)
private volatile ProxyInfo mDefaultProxy = null;
private Object mProxyLock = new Object();
@@ -446,10 +460,12 @@
private final LocalLog mNetworkRequestInfoLogs = new LocalLog(MAX_NETWORK_REQUEST_LOGS);
// NetworkInfo blocked and unblocked String log entries
- // TODO: consider reducing memory usage. Each log line is ~40 2B chars, for a total of ~8kB.
- private static final int MAX_NETWORK_INFO_LOGS = 100;
+ private static final int MAX_NETWORK_INFO_LOGS = 40;
private final LocalLog mNetworkInfoBlockingLogs = new LocalLog(MAX_NETWORK_INFO_LOGS);
+ private static final int MAX_WAKELOCK_LOGS = 20;
+ private final LocalLog mWakelockLogs = new LocalLog(MAX_WAKELOCK_LOGS);
+
// Array of <Network,ReadOnlyLocalLogs> tracking network validation and results
private static final int MAX_VALIDATION_LOGS = 10;
private static class ValidationLog {
@@ -467,7 +483,7 @@
new ArrayDeque<ValidationLog>(MAX_VALIDATION_LOGS);
private void addValidationLogs(ReadOnlyLocalLog log, Network network, String networkExtraInfo) {
- synchronized(mValidationLogs) {
+ synchronized (mValidationLogs) {
while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) {
mValidationLogs.removeLast();
}
@@ -785,8 +801,7 @@
mTestMode = mSystemProperties.get("cm.test.mode").equals("true")
&& mSystemProperties.get("ro.build.type").equals("eng");
- mTethering = new Tethering(mContext, mNetd, statsService, mPolicyManager,
- IoThread.get().getLooper(), new MockableSystemProperties());
+ mTethering = makeTethering();
mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
@@ -809,10 +824,6 @@
loge("Error registering observer :" + e);
}
- if (DBG) {
- mInetLog = new ArrayList();
- }
-
mSettingsObserver = new SettingsObserver(mContext, mHandler);
registerSettingsCallbacks();
@@ -840,6 +851,14 @@
mMultinetworkPolicyTracker.start();
}
+ private Tethering makeTethering() {
+ // TODO: Move other elements into @Overridden getters.
+ final TetheringDependencies deps = new TetheringDependencies();
+ return new Tethering(mContext, mNetd, mStatsService, mPolicyManager,
+ IoThread.get().getLooper(), new MockableSystemProperties(),
+ deps);
+ }
+
private NetworkRequest createInternetRequestForTransport(
int transportType, NetworkRequest.Type type) {
NetworkCapabilities netCap = new NetworkCapabilities();
@@ -863,8 +882,8 @@
}
private void handleMobileDataAlwaysOn() {
- final boolean enable = (Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 1) == 1);
+ final boolean enable = toBool(Settings.Global.getInt(
+ mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 1));
final boolean isEnabled = (mNetworkRequests.get(mDefaultMobileDataRequest) != null);
if (enable == isEnabled) {
return; // Nothing to do.
@@ -1006,22 +1025,19 @@
if (ni == null || !LOGD_BLOCKED_NETWORKINFO) {
return;
}
- boolean removed = false;
- boolean added = false;
+ final boolean blocked;
synchronized (mBlockedAppUids) {
if (ni.getDetailedState() == DetailedState.BLOCKED && mBlockedAppUids.add(uid)) {
- added = true;
+ blocked = true;
} else if (ni.isConnected() && mBlockedAppUids.remove(uid)) {
- removed = true;
+ blocked = false;
+ } else {
+ return;
}
}
- if (added) {
- log("Returning blocked NetworkInfo to uid=" + uid);
- mNetworkInfoBlockingLogs.log("BLOCKED " + uid);
- } else if (removed) {
- log("Returning unblocked NetworkInfo to uid=" + uid);
- mNetworkInfoBlockingLogs.log("UNBLOCKED " + uid);
- }
+ String action = blocked ? "BLOCKED" : "UNBLOCKED";
+ log(String.format("Returning %s NetworkInfo to uid=%d", action, uid));
+ mNetworkInfoBlockingLogs.log(action + " " + uid);
}
/**
@@ -1580,7 +1596,7 @@
}
private void sendStickyBroadcast(Intent intent) {
- synchronized(this) {
+ synchronized (this) {
if (!mSystemReady) {
mInitialBroadcast = new Intent(intent);
}
@@ -1621,7 +1637,7 @@
void systemReady() {
loadGlobalProxy();
- synchronized(this) {
+ synchronized (this) {
mSystemReady = true;
if (mInitialBroadcast != null) {
mContext.sendStickyBroadcastAsUser(mInitialBroadcast, UserHandle.ALL);
@@ -1827,7 +1843,7 @@
private boolean argsContain(String[] args, String target) {
for (String arg : args) {
- if (arg.equals(target)) return true;
+ if (target.equals(arg)) return true;
}
return false;
}
@@ -1858,6 +1874,9 @@
if (argsContain(args, DIAG_ARG)) {
dumpNetworkDiagnostics(pw);
return;
+ } else if (argsContain(args, TETHERING_ARG)) {
+ mTethering.dump(fd, pw, args);
+ return;
}
pw.print("NetworkFactories for:");
@@ -1911,16 +1930,6 @@
mLegacyTypeTracker.dump(pw);
- synchronized (this) {
- pw.print("mNetTransitionWakeLock: currently " +
- (mNetTransitionWakeLock.isHeld() ? "" : "not ") + "held");
- if (!TextUtils.isEmpty(mNetTransitionWakeLockCausedBy)) {
- pw.println(", last requested for " + mNetTransitionWakeLockCausedBy);
- } else {
- pw.println(", last requested never");
- }
- }
-
pw.println();
mTethering.dump(fd, pw, args);
@@ -1930,17 +1939,6 @@
pw.println();
dumpAvoidBadWifiSettings(pw);
- pw.println();
- if (mInetLog != null && mInetLog.size() > 0) {
- pw.println();
- pw.println("Inet condition reports:");
- pw.increaseIndent();
- for(int i = 0; i < mInetLog.size(); i++) {
- pw.println(mInetLog.get(i));
- }
- pw.decreaseIndent();
- }
-
if (argsContain(args, SHORT_ARG) == false) {
pw.println();
synchronized (mValidationLogs) {
@@ -1964,6 +1962,12 @@
pw.increaseIndent();
mNetworkInfoBlockingLogs.reverseDump(fd, pw, args);
pw.decreaseIndent();
+
+ pw.println();
+ pw.println("NetTransition WakeLock activity (most recent first):");
+ pw.increaseIndent();
+ mWakelockLogs.reverseDump(fd, pw, args);
+ pw.decreaseIndent();
}
}
@@ -1972,8 +1976,7 @@
final NetworkAgentInfo officialNai = getNetworkAgentInfoForNetwork(nai.network);
if (officialNai != null && officialNai.equals(nai)) return true;
if (officialNai != null || VDBG) {
- final String msg = sMagicDecoderRing.get(what, Integer.toString(what));
- loge(msg + " - isLiveNetworkAgent found mismatched netId: " + officialNai +
+ loge(eventName(what) + " - isLiveNetworkAgent found mismatched netId: " + officialNai +
" - " + nai);
}
return false;
@@ -2010,8 +2013,7 @@
NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
if (nai == null) {
if (VDBG) {
- final String what = sMagicDecoderRing.get(msg.what, Integer.toString(msg.what));
- log(String.format("%s from unknown NetworkAgent", what));
+ log(String.format("%s from unknown NetworkAgent", eventName(msg.what)));
}
return;
}
@@ -2122,7 +2124,7 @@
}
case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
final int netId = msg.arg2;
- final boolean visible = (msg.arg1 != 0);
+ final boolean visible = toBool(msg.arg1);
final NetworkAgentInfo nai;
synchronized (mNetworkForNetId) {
nai = mNetworkForNetId.get(netId);
@@ -2301,7 +2303,7 @@
if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) {
removeDataActivityTracking(nai);
notifyLockdownVpn(nai);
- requestNetworkTransitionWakelock(nai.name());
+ ensureNetworkTransitionWakelock(nai.name());
}
mLegacyTypeTracker.remove(nai, wasDefault);
rematchAllNetworksAndRequests(null, 0);
@@ -2448,25 +2450,32 @@
}
private void handleTimedOutNetworkRequest(final NetworkRequestInfo nri) {
- if (mNetworkRequests.get(nri.request) != null && mNetworkForRequestId.get(
- nri.request.requestId) == null) {
- handleRemoveNetworkRequest(nri, ConnectivityManager.CALLBACK_UNAVAIL);
+ if (mNetworkRequests.get(nri.request) == null) {
+ return;
}
+ if (mNetworkForRequestId.get(nri.request.requestId) != null) {
+ return;
+ }
+ if (VDBG || (DBG && nri.request.isRequest())) {
+ log("releasing " + nri.request + " (timeout)");
+ }
+ handleRemoveNetworkRequest(nri);
+ callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
}
private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
- final NetworkRequestInfo nri = getNriForAppRequest(
- request, callingUid, "release NetworkRequest");
- if (nri != null) {
- handleRemoveNetworkRequest(nri, ConnectivityManager.CALLBACK_RELEASED);
+ final NetworkRequestInfo nri =
+ getNriForAppRequest(request, callingUid, "release NetworkRequest");
+ if (nri == null) {
+ return;
}
+ if (VDBG || (DBG && nri.request.isRequest())) {
+ log("releasing " + nri.request + " (release request)");
+ }
+ handleRemoveNetworkRequest(nri);
}
- private void handleRemoveNetworkRequest(final NetworkRequestInfo nri, final int whichCallback) {
- final String logCallbackType = ConnectivityManager.getCallbackName(whichCallback);
- if (VDBG || (DBG && nri.request.isRequest())) {
- log("releasing " + nri.request + " (" + logCallbackType + ")");
- }
+ private void handleRemoveNetworkRequest(final NetworkRequestInfo nri) {
nri.unlinkDeathRecipient();
mNetworkRequests.remove(nri.request);
synchronized (mUidToNetworkRequestCount) {
@@ -2562,14 +2571,13 @@
}
}
}
- callCallbackForRequest(nri, null, whichCallback, 0);
}
@Override
public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
enforceConnectivityInternalPermission();
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_ACCEPT_UNVALIDATED,
- accept ? 1 : 0, always ? 1: 0, network));
+ encodeBool(accept), encodeBool(always), network));
}
@Override
@@ -2606,7 +2614,7 @@
if (always) {
nai.asyncChannel.sendMessage(
- NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, accept ? 1 : 0);
+ NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, encodeBool(accept));
}
if (!accept) {
@@ -2771,25 +2779,7 @@
switch (msg.what) {
case EVENT_EXPIRE_NET_TRANSITION_WAKELOCK:
case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: {
- String causedBy = null;
- synchronized (ConnectivityService.this) {
- if (msg.arg1 == mNetTransitionWakeLockSerialNumber &&
- mNetTransitionWakeLock.isHeld()) {
- mNetTransitionWakeLock.release();
- causedBy = mNetTransitionWakeLockCausedBy;
- } else {
- break;
- }
- }
- if (VDBG) {
- if (msg.what == EVENT_EXPIRE_NET_TRANSITION_WAKELOCK) {
- log("Failed to find a new network - expiring NetTransition Wakelock");
- } else {
- log("NetTransition Wakelock (" +
- (causedBy == null ? "unknown" : causedBy) +
- " cleared because we found a replacement network");
- }
- }
+ handleReleaseNetworkTransitionWakelock(msg.what);
break;
}
case EVENT_APPLY_GLOBAL_HTTP_PROXY: {
@@ -2836,7 +2826,8 @@
break;
}
case EVENT_SET_ACCEPT_UNVALIDATED: {
- handleSetAcceptUnvalidated((Network) msg.obj, msg.arg1 != 0, msg.arg2 != 0);
+ Network network = (Network) msg.obj;
+ handleSetAcceptUnvalidated(network, toBool(msg.arg1), toBool(msg.arg2));
break;
}
case EVENT_SET_AVOID_UNVALIDATED: {
@@ -2870,14 +2861,18 @@
}
break;
}
+ case EVENT_REVALIDATE_NETWORK: {
+ handleReportNetworkConnectivity((Network) msg.obj, msg.arg1, toBool(msg.arg2));
+ break;
+ }
}
}
}
// javadoc from interface
@Override
- public int tether(String iface) {
- ConnectivityManager.enforceTetherChangePermission(mContext);
+ public int tether(String iface, String callerPkg) {
+ ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
if (isTetheringSupported()) {
final int status = mTethering.tether(iface);
return status;
@@ -2888,8 +2883,8 @@
// javadoc from interface
@Override
- public int untether(String iface) {
- ConnectivityManager.enforceTetherChangePermission(mContext);
+ public int untether(String iface, String callerPkg) {
+ ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
if (isTetheringSupported()) {
final int status = mTethering.untether(iface);
@@ -2943,8 +2938,8 @@
}
@Override
- public int setUsbTethering(boolean enable) {
- ConnectivityManager.enforceTetherChangePermission(mContext);
+ public int setUsbTethering(boolean enable, String callerPkg) {
+ ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
if (isTetheringSupported()) {
return mTethering.setUsbTethering(enable);
} else {
@@ -2984,9 +2979,10 @@
@Override
public boolean isTetheringSupported() {
enforceTetherAccessPermission();
- int defaultVal = (mSystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1);
- boolean tetherEnabledInSettings = (Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.TETHER_SUPPORTED, defaultVal) != 0)
+ int defaultVal = encodeBool(!mSystemProperties.get("ro.tether.denied").equals("true"));
+ boolean tetherSupported = toBool(Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.TETHER_SUPPORTED, defaultVal));
+ boolean tetherEnabledInSettings = tetherSupported
&& !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
// Elevate to system UID to avoid caller requiring MANAGE_USERS permission.
@@ -2998,13 +2994,13 @@
Binder.restoreCallingIdentity(token);
}
- return tetherEnabledInSettings && adminUser &&
- mTethering.hasTetherableConfiguration();
+ return tetherEnabledInSettings && adminUser && mTethering.hasTetherableConfiguration();
}
@Override
- public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) {
- ConnectivityManager.enforceTetherChangePermission(mContext);
+ public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi,
+ String callerPkg) {
+ ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
if (!isTetheringSupported()) {
receiver.send(ConnectivityManager.TETHER_ERROR_UNSUPPORTED, null);
return;
@@ -3013,8 +3009,8 @@
}
@Override
- public void stopTethering(int type) {
- ConnectivityManager.enforceTetherChangePermission(mContext);
+ public void stopTethering(int type, String callerPkg) {
+ ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
mTethering.stopTethering(type);
}
@@ -3022,18 +3018,46 @@
// This will automatically be cleared after X seconds or a new default network
// becomes CONNECTED, whichever happens first. The timer is started by the
// first caller and not restarted by subsequent callers.
- private void requestNetworkTransitionWakelock(String forWhom) {
- int serialNum = 0;
+ private void ensureNetworkTransitionWakelock(String forWhom) {
synchronized (this) {
- if (mNetTransitionWakeLock.isHeld()) return;
- serialNum = ++mNetTransitionWakeLockSerialNumber;
+ if (mNetTransitionWakeLock.isHeld()) {
+ return;
+ }
mNetTransitionWakeLock.acquire();
- mNetTransitionWakeLockCausedBy = forWhom;
}
- mHandler.sendMessageDelayed(mHandler.obtainMessage(
- EVENT_EXPIRE_NET_TRANSITION_WAKELOCK, serialNum, 0),
- mNetTransitionWakeLockTimeout);
- return;
+ mWakelockLogs.log("ACQUIRE for " + forWhom);
+ Message msg = mHandler.obtainMessage(EVENT_EXPIRE_NET_TRANSITION_WAKELOCK);
+ mHandler.sendMessageDelayed(msg, mNetTransitionWakeLockTimeout);
+ }
+
+ // Called when we gain a new default network to release the network transition wakelock in a
+ // second, to allow a grace period for apps to reconnect over the new network. Pending expiry
+ // message is cancelled.
+ private void scheduleReleaseNetworkTransitionWakelock() {
+ synchronized (this) {
+ if (!mNetTransitionWakeLock.isHeld()) {
+ return; // expiry message released the lock first.
+ }
+ }
+ // Cancel self timeout on wakelock hold.
+ mHandler.removeMessages(EVENT_EXPIRE_NET_TRANSITION_WAKELOCK);
+ Message msg = mHandler.obtainMessage(EVENT_CLEAR_NET_TRANSITION_WAKELOCK);
+ mHandler.sendMessageDelayed(msg, 1000);
+ }
+
+ // Called when either message of ensureNetworkTransitionWakelock or
+ // scheduleReleaseNetworkTransitionWakelock is processed.
+ private void handleReleaseNetworkTransitionWakelock(int eventId) {
+ String event = eventName(eventId);
+ synchronized (this) {
+ if (!mNetTransitionWakeLock.isHeld()) {
+ mWakelockLogs.log(String.format("RELEASE: already released (%s)", event));
+ Slog.w(TAG, "expected Net Transition WakeLock to be held");
+ return;
+ }
+ mNetTransitionWakeLock.release();
+ }
+ mWakelockLogs.log(String.format("RELEASE (%s)", event));
}
// 100 percent is full good, 0 is full bad.
@@ -3048,8 +3072,14 @@
public void reportNetworkConnectivity(Network network, boolean hasConnectivity) {
enforceAccessPermission();
enforceInternetPermission();
+ final int uid = Binder.getCallingUid();
+ final int connectivityInfo = encodeBool(hasConnectivity);
+ mHandler.sendMessage(
+ mHandler.obtainMessage(EVENT_REVALIDATE_NETWORK, uid, connectivityInfo, network));
+ }
- // TODO: execute this logic on ConnectivityService handler.
+ private void handleReportNetworkConnectivity(
+ Network network, int uid, boolean hasConnectivity) {
final NetworkAgentInfo nai;
if (network == null) {
nai = getDefaultNetwork();
@@ -3064,10 +3094,9 @@
if (hasConnectivity == nai.lastValidated) {
return;
}
- final int uid = Binder.getCallingUid();
if (DBG) {
- log("reportNetworkConnectivity(" + nai.network.netId + ", " + hasConnectivity +
- ") by " + uid);
+ int netid = nai.network.netId;
+ log("reportNetworkConnectivity(" + netid + ", " + hasConnectivity + ") by " + uid);
}
// Validating a network that has not yet connected could result in a call to
// rematchNetworkAndRequests() which is not meant to work on such networks.
@@ -3354,13 +3383,6 @@
Slog.e(TAG, s, t);
}
- private static <T> T checkNotNull(T value, String message) {
- if (value == null) {
- throw new NullPointerException(message);
- }
- return value;
- }
-
/**
* Prepare for a VPN application.
* VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
@@ -3381,7 +3403,7 @@
enforceCrossUserPermission(userId);
throwIfLockdownEnabled();
- synchronized(mVpns) {
+ synchronized (mVpns) {
Vpn vpn = mVpns.get(userId);
if (vpn != null) {
return vpn.prepare(oldPackage, newPackage);
@@ -3408,7 +3430,7 @@
public void setVpnPackageAuthorization(String packageName, int userId, boolean authorized) {
enforceCrossUserPermission(userId);
- synchronized(mVpns) {
+ synchronized (mVpns) {
Vpn vpn = mVpns.get(userId);
if (vpn != null) {
vpn.setPackageAuthorization(packageName, authorized);
@@ -3427,7 +3449,7 @@
public ParcelFileDescriptor establishVpn(VpnConfig config) {
throwIfLockdownEnabled();
int user = UserHandle.getUserId(Binder.getCallingUid());
- synchronized(mVpns) {
+ synchronized (mVpns) {
return mVpns.get(user).establish(config);
}
}
@@ -3444,7 +3466,7 @@
throw new IllegalStateException("Missing active network connection");
}
int user = UserHandle.getUserId(Binder.getCallingUid());
- synchronized(mVpns) {
+ synchronized (mVpns) {
mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress);
}
}
@@ -3458,7 +3480,7 @@
public LegacyVpnInfo getLegacyVpnInfo(int userId) {
enforceCrossUserPermission(userId);
- synchronized(mVpns) {
+ synchronized (mVpns) {
return mVpns.get(userId).getLegacyVpnInfo();
}
}
@@ -3474,7 +3496,7 @@
return new VpnInfo[0];
}
- synchronized(mVpns) {
+ synchronized (mVpns) {
List<VpnInfo> infoList = new ArrayList<>();
for (int i = 0; i < mVpns.size(); i++) {
VpnInfo info = createVpnInfo(mVpns.valueAt(i));
@@ -3522,7 +3544,7 @@
@Override
public VpnConfig getVpnConfig(int userId) {
enforceCrossUserPermission(userId);
- synchronized(mVpns) {
+ synchronized (mVpns) {
Vpn vpn = mVpns.get(userId);
if (vpn != null) {
return vpn.getVpnConfig();
@@ -3556,7 +3578,7 @@
return true;
}
int user = UserHandle.getUserId(Binder.getCallingUid());
- synchronized(mVpns) {
+ synchronized (mVpns) {
Vpn vpn = mVpns.get(user);
if (vpn == null) {
Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown");
@@ -3583,17 +3605,9 @@
existing.shutdown();
}
- try {
- if (tracker != null) {
- mNetd.setFirewallEnabled(true);
- mNetd.setFirewallInterfaceRule("lo", true);
- mLockdownTracker = tracker;
- mLockdownTracker.init();
- } else {
- mNetd.setFirewallEnabled(false);
- }
- } catch (RemoteException e) {
- // ignored; NMS lives inside system_server
+ if (tracker != null) {
+ mLockdownTracker = tracker;
+ mLockdownTracker.init();
}
}
@@ -3647,8 +3661,6 @@
vpn.setAlwaysOnPackage(null, false);
return false;
}
-
- vpn.saveAlwaysOnPackage();
}
return true;
}
@@ -3791,7 +3803,7 @@
final long ident = Binder.clearCallingIdentity();
try {
final ContentResolver cr = mContext.getContentResolver();
- Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, enable ? 1 : 0);
+ Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, encodeBool(enable));
Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
intent.putExtra("state", enable);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
@@ -3801,7 +3813,7 @@
}
private void onUserStart(int userId) {
- synchronized(mVpns) {
+ synchronized (mVpns) {
Vpn userVpn = mVpns.get(userId);
if (userVpn != null) {
loge("Starting user already has a VPN");
@@ -3809,15 +3821,6 @@
}
userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId);
mVpns.put(userId, userVpn);
-
- final ContentResolver cr = mContext.getContentResolver();
- String alwaysOnPackage = Settings.Secure.getStringForUser(cr,
- Settings.Secure.ALWAYS_ON_VPN_APP, userId);
- final boolean alwaysOnLockdown = Settings.Secure.getIntForUser(cr,
- Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, /* default */ 0, userId) != 0;
- if (alwaysOnPackage != null) {
- userVpn.setAlwaysOnPackage(alwaysOnPackage, alwaysOnLockdown);
- }
}
if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
updateLockdownVpn();
@@ -3825,7 +3828,7 @@
}
private void onUserStop(int userId) {
- synchronized(mVpns) {
+ synchronized (mVpns) {
Vpn userVpn = mVpns.get(userId);
if (userVpn == null) {
loge("Stopped user has no VPN");
@@ -3837,7 +3840,7 @@
}
private void onUserAdded(int userId) {
- synchronized(mVpns) {
+ synchronized (mVpns) {
final int vpnsSize = mVpns.size();
for (int i = 0; i < vpnsSize; i++) {
Vpn vpn = mVpns.valueAt(i);
@@ -3847,7 +3850,7 @@
}
private void onUserRemoved(int userId) {
- synchronized(mVpns) {
+ synchronized (mVpns) {
final int vpnsSize = mVpns.size();
for (int i = 0; i < vpnsSize; i++) {
Vpn vpn = mVpns.valueAt(i);
@@ -3971,7 +3974,8 @@
synchronized (mUidToNetworkRequestCount) {
int networkRequests = mUidToNetworkRequestCount.get(mUid, 0) + 1;
if (networkRequests >= MAX_NETWORK_REQUESTS_PER_UID) {
- throw new IllegalArgumentException("Too many NetworkRequests filed");
+ throw new ServiceSpecificException(
+ ConnectivityManager.Errors.TOO_MANY_REQUESTS);
}
mUidToNetworkRequestCount.put(mUid, networkRequests);
}
@@ -4037,6 +4041,18 @@
0, 0, thresholds);
}
+ private void ensureValidNetworkSpecifier(NetworkCapabilities nc) {
+ if (nc == null) {
+ return;
+ }
+ NetworkSpecifier ns = nc.getNetworkSpecifier();
+ if (ns == null) {
+ return;
+ }
+ MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(ns);
+ ns.assertValidFromUid(Binder.getCallingUid());
+ }
+
@Override
public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
@@ -4062,9 +4078,7 @@
if (timeoutMs < 0) {
throw new IllegalArgumentException("Bad timeout specified");
}
-
- MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(
- networkCapabilities.getNetworkSpecifier());
+ ensureValidNetworkSpecifier(networkCapabilities);
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
nextNetworkRequestId(), type);
@@ -4132,9 +4146,7 @@
enforceNetworkRequestPermissions(networkCapabilities);
enforceMeteredApnPolicy(networkCapabilities);
ensureRequestableCapabilities(networkCapabilities);
-
- MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(
- networkCapabilities.getNetworkSpecifier());
+ ensureValidNetworkSpecifier(networkCapabilities);
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
@@ -4196,9 +4208,7 @@
// can't request networks.
nc.addCapability(NET_CAPABILITY_FOREGROUND);
}
-
- MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(
- networkCapabilities.getNetworkSpecifier());
+ ensureValidNetworkSpecifier(networkCapabilities);
NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
NetworkRequest.Type.LISTEN);
@@ -4216,9 +4226,7 @@
if (!hasWifiNetworkListenPermission(networkCapabilities)) {
enforceAccessPermission();
}
-
- MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(
- networkCapabilities.getNetworkSpecifier());
+ ensureValidNetworkSpecifier(networkCapabilities);
NetworkRequest networkRequest = new NetworkRequest(
new NetworkCapabilities(networkCapabilities), TYPE_NONE, nextNetworkRequestId(),
@@ -4355,7 +4363,7 @@
networkAgent.clatd.fixupLinkProperties(oldLp);
}
- updateInterfaces(newLp, oldLp, netId);
+ updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities);
updateMtu(newLp, oldLp);
// TODO - figure out what to do for clat
// for (LinkProperties lp : newLp.getStackedLinks()) {
@@ -4393,7 +4401,26 @@
}
}
- private void updateInterfaces(LinkProperties newLp, LinkProperties oldLp, int netId) {
+ private void wakeupAddInterface(String iface, NetworkCapabilities caps) throws RemoteException {
+ // Marks are only available on WiFi interaces. Checking for
+ // marks on unsupported interfaces is harmless.
+ if (!caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+ return;
+ }
+ mNetd.getNetdService().wakeupAddInterface(
+ iface, "iface:" + iface, mWakeupPacketMark, mWakeupPacketMask);
+ }
+
+ private void wakeupDelInterface(String iface, NetworkCapabilities caps) throws RemoteException {
+ if (!caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+ return;
+ }
+ mNetd.getNetdService().wakeupDelInterface(
+ iface, "iface:" + iface, mWakeupPacketMark, mWakeupPacketMask);
+ }
+
+ private void updateInterfaces(LinkProperties newLp, LinkProperties oldLp, int netId,
+ NetworkCapabilities caps) {
CompareResult<String> interfaceDiff = new CompareResult<String>();
if (oldLp != null) {
interfaceDiff = oldLp.compareAllInterfaceNames(newLp);
@@ -4404,6 +4431,7 @@
try {
if (DBG) log("Adding iface " + iface + " to network " + netId);
mNetd.addInterfaceToNetwork(iface, netId);
+ wakeupAddInterface(iface, caps);
} catch (Exception e) {
loge("Exception adding interface: " + e);
}
@@ -4412,6 +4440,7 @@
try {
if (DBG) log("Removing iface " + iface + " from network " + netId);
mNetd.removeInterfaceFromNetwork(iface, netId);
+ wakeupDelInterface(iface, caps);
} catch (Exception e) {
loge("Exception removing interface: " + e);
}
@@ -4639,16 +4668,17 @@
releasePendingNetworkRequestWithDelay(pendingIntent);
}
- private void callCallbackForRequest(NetworkRequestInfo nri,
+ private static void callCallbackForRequest(NetworkRequestInfo nri,
NetworkAgentInfo networkAgent, int notificationType, int arg1) {
- if (nri.messenger == null) return; // Default request has no msgr
+ if (nri.messenger == null) {
+ return; // Default request has no msgr
+ }
Bundle bundle = new Bundle();
- bundle.putParcelable(NetworkRequest.class.getSimpleName(),
- new NetworkRequest(nri.request));
+ // TODO: check if defensive copies of data is needed.
+ putParcelable(bundle, new NetworkRequest(nri.request));
Message msg = Message.obtain();
- if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL &&
- notificationType != ConnectivityManager.CALLBACK_RELEASED) {
- bundle.putParcelable(Network.class.getSimpleName(), networkAgent.network);
+ if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) {
+ putParcelable(bundle, networkAgent.network);
}
switch (notificationType) {
case ConnectivityManager.CALLBACK_LOSING: {
@@ -4656,13 +4686,11 @@
break;
}
case ConnectivityManager.CALLBACK_CAP_CHANGED: {
- bundle.putParcelable(NetworkCapabilities.class.getSimpleName(),
- new NetworkCapabilities(networkAgent.networkCapabilities));
+ putParcelable(bundle, new NetworkCapabilities(networkAgent.networkCapabilities));
break;
}
case ConnectivityManager.CALLBACK_IP_CHANGED: {
- bundle.putParcelable(LinkProperties.class.getSimpleName(),
- new LinkProperties(networkAgent.linkProperties));
+ putParcelable(bundle, new LinkProperties(networkAgent.linkProperties));
break;
}
}
@@ -4670,8 +4698,8 @@
msg.setData(bundle);
try {
if (VDBG) {
- log("sending notification " + notifyTypeToName(notificationType) +
- " for " + nri.request);
+ String notification = ConnectivityManager.getCallbackName(notificationType);
+ log("sending notification " + notification + " for " + nri.request);
}
nri.messenger.send(msg);
} catch (RemoteException e) {
@@ -4680,6 +4708,10 @@
}
}
+ private static <T extends Parcelable> void putParcelable(Bundle bundle, T t) {
+ bundle.putParcelable(t.getClass().getSimpleName(), t);
+ }
+
private void teardownUnneededNetwork(NetworkAgentInfo nai) {
if (nai.numRequestNetworkRequests() != 0) {
for (int i = 0; i < nai.numNetworkRequests(); i++) {
@@ -4893,17 +4925,8 @@
makeDefault(newNetwork);
// Log 0 -> X and Y -> X default network transitions, where X is the new default.
logDefaultNetworkEvent(newNetwork, oldDefaultNetwork);
- synchronized (ConnectivityService.this) {
- // have a new default network, release the transition wakelock in
- // a second if it's held. The second pause is to allow apps
- // to reconnect over the new network
- if (mNetTransitionWakeLock.isHeld()) {
- mHandler.sendMessageDelayed(mHandler.obtainMessage(
- EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
- mNetTransitionWakeLockSerialNumber, 0),
- 1000);
- }
- }
+ // Have a new default network, release the transition wakelock in
+ scheduleReleaseNetworkTransitionWakelock();
}
if (!newNetwork.networkCapabilities.equalRequestableCapabilities(nc)) {
@@ -5290,7 +5313,10 @@
}
protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType, int arg1) {
- if (VDBG) log("notifyType " + notifyTypeToName(notifyType) + " for " + networkAgent.name());
+ if (VDBG) {
+ String notification = ConnectivityManager.getCallbackName(notifyType);
+ log("notifyType " + notification + " for " + networkAgent.name());
+ }
for (int i = 0; i < networkAgent.numNetworkRequests(); i++) {
NetworkRequest nr = networkAgent.requestAt(i);
NetworkRequestInfo nri = mNetworkRequests.get(nr);
@@ -5309,20 +5335,6 @@
notifyNetworkCallbacks(networkAgent, notifyType, 0);
}
- private String notifyTypeToName(int notifyType) {
- switch (notifyType) {
- case ConnectivityManager.CALLBACK_PRECHECK: return "PRECHECK";
- case ConnectivityManager.CALLBACK_AVAILABLE: return "AVAILABLE";
- case ConnectivityManager.CALLBACK_LOSING: return "LOSING";
- case ConnectivityManager.CALLBACK_LOST: return "LOST";
- case ConnectivityManager.CALLBACK_UNAVAIL: return "UNAVAILABLE";
- case ConnectivityManager.CALLBACK_CAP_CHANGED: return "CAP_CHANGED";
- case ConnectivityManager.CALLBACK_IP_CHANGED: return "IP_CHANGED";
- case ConnectivityManager.CALLBACK_RELEASED: return "RELEASED";
- }
- return "UNKNOWN";
- }
-
/**
* Notify NetworkStatsService that the set of active ifaces has changed, or that one of the
* properties tracked by NetworkStatsService on an active iface has changed.
@@ -5402,8 +5414,9 @@
if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
// Untether
+ String pkgName = mContext.getOpPackageName();
for (String tether : getTetheredIfaces()) {
- untether(tether);
+ untether(tether, pkgName);
}
}
@@ -5488,4 +5501,12 @@
private void logNetworkEvent(NetworkAgentInfo nai, int evtype) {
mMetricsLog.log(new NetworkEvent(nai.network.netId, evtype));
}
+
+ private static boolean toBool(int encodedBoolean) {
+ return encodedBoolean != 0; // Only 0 means false.
+ }
+
+ private static int encodeBool(boolean b) {
+ return b ? 1 : 0;
+ }
}
diff --git a/services/core/java/com/android/server/FgThread.java b/services/core/java/com/android/server/FgThread.java
index 5f85cba..18fb477 100644
--- a/services/core/java/com/android/server/FgThread.java
+++ b/services/core/java/com/android/server/FgThread.java
@@ -45,14 +45,14 @@
}
public static FgThread get() {
- synchronized (UiThread.class) {
+ synchronized (FgThread.class) {
ensureThreadLocked();
return sInstance;
}
}
public static Handler getHandler() {
- synchronized (UiThread.class) {
+ synchronized (FgThread.class) {
ensureThreadLocked();
return sHandler;
}
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index 830a6ed..b408da8 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -17,6 +17,7 @@
package com.android.server;
import android.app.ActivityManager;
+import android.app.KeyguardManager;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -28,6 +29,8 @@
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.hardware.TriggerEvent;
+import android.hardware.TriggerEventListener;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
@@ -39,6 +42,7 @@
import android.util.Slog;
import android.view.KeyEvent;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -58,17 +62,32 @@
* Time in milliseconds in which the power button must be pressed twice so it will be considered
* as a camera launch.
*/
- private static final long CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS = 300;
+ @VisibleForTesting static final long CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS = 300;
+
+ /**
+ * Interval in milliseconds in which the power button must be depressed in succession to be
+ * considered part of an extended sequence of taps. Note that this is a looser threshold than
+ * the camera launch gesture, because the purpose of this threshold is to measure the
+ * frequency of consecutive taps, for evaluation for future gestures.
+ */
+ @VisibleForTesting static final long POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS = 500;
/** The listener that receives the gesture event. */
private final GestureEventListener mGestureListener = new GestureEventListener();
+ private final CameraLiftTriggerEventListener mCameraLiftTriggerListener =
+ new CameraLiftTriggerEventListener();
private Sensor mCameraLaunchSensor;
+ private Sensor mCameraLiftTriggerSensor;
private Context mContext;
+ private final MetricsLogger mMetricsLogger;
+ private PowerManager mPowerManager;
+ private KeyguardManager mKeyguardManager;
/** The wake lock held when a gesture is detected. */
private WakeLock mWakeLock;
- private boolean mRegistered;
+ private boolean mCameraLaunchRegistered;
+ private boolean mCameraLiftRegistered;
private int mUserId;
// Below are fields used for event logging only.
@@ -106,10 +125,17 @@
*/
private boolean mCameraDoubleTapPowerEnabled;
private long mLastPowerDown;
+ private int mPowerButtonConsecutiveTaps;
public GestureLauncherService(Context context) {
+ this(context, new MetricsLogger());
+ }
+
+ @VisibleForTesting
+ GestureLauncherService(Context context, MetricsLogger metricsLogger) {
super(context);
mContext = context;
+ mMetricsLogger = metricsLogger;
}
public void onStart() {
@@ -124,9 +150,11 @@
return;
}
- PowerManager powerManager = (PowerManager) mContext.getSystemService(
+ mKeyguardManager = (KeyguardManager) mContext.getSystemService(
+ Context.KEYGUARD_SERVICE);
+ mPowerManager = (PowerManager) mContext.getSystemService(
Context.POWER_SERVICE);
- mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"GestureLauncherService");
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
@@ -144,6 +172,9 @@
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED),
false, mSettingObserver, mUserId);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED),
+ false, mSettingObserver, mUserId);
}
private void updateCameraRegistered() {
@@ -153,9 +184,16 @@
} else {
unregisterCameraLaunchGesture();
}
+
+ if (isCameraLiftTriggerSettingEnabled(mContext, mUserId)) {
+ registerCameraLiftTrigger(resources);
+ } else {
+ unregisterCameraLiftTrigger();
+ }
}
- private void updateCameraDoubleTapPowerEnabled() {
+ @VisibleForTesting
+ void updateCameraDoubleTapPowerEnabled() {
boolean enabled = isCameraDoubleTapPowerSettingEnabled(mContext, mUserId);
synchronized (this) {
mCameraDoubleTapPowerEnabled = enabled;
@@ -163,8 +201,8 @@
}
private void unregisterCameraLaunchGesture() {
- if (mRegistered) {
- mRegistered = false;
+ if (mCameraLaunchRegistered) {
+ mCameraLaunchRegistered = false;
mCameraGestureOnTimeMs = 0L;
mCameraGestureLastEventTime = 0L;
mCameraGestureSensor1LastOnTimeMs = 0;
@@ -181,7 +219,7 @@
* Registers for the camera launch gesture.
*/
private void registerCameraLaunchGesture(Resources resources) {
- if (mRegistered) {
+ if (mCameraLaunchRegistered) {
return;
}
mCameraGestureOnTimeMs = SystemClock.elapsedRealtime();
@@ -191,7 +229,7 @@
int cameraLaunchGestureId = resources.getInteger(
com.android.internal.R.integer.config_cameraLaunchGestureSensorType);
if (cameraLaunchGestureId != -1) {
- mRegistered = false;
+ mCameraLaunchRegistered = false;
String sensorName = resources.getString(
com.android.internal.R.string.config_cameraLaunchGestureSensorStringType);
mCameraLaunchSensor = sensorManager.getDefaultSensor(
@@ -203,7 +241,7 @@
// makes the code more robust.
if (mCameraLaunchSensor != null) {
if (sensorName.equals(mCameraLaunchSensor.getStringType())) {
- mRegistered = sensorManager.registerListener(mGestureListener,
+ mCameraLaunchRegistered = sensorManager.registerListener(mGestureListener,
mCameraLaunchSensor, 0);
} else {
String message = String.format("Wrong configuration. Sensor type and sensor "
@@ -212,12 +250,61 @@
throw new RuntimeException(message);
}
}
- if (DBG) Slog.d(TAG, "Camera launch sensor registered: " + mRegistered);
+ if (DBG) Slog.d(TAG, "Camera launch sensor registered: " + mCameraLaunchRegistered);
} else {
if (DBG) Slog.d(TAG, "Camera launch sensor is not specified.");
}
}
+ private void unregisterCameraLiftTrigger() {
+ if (mCameraLiftRegistered) {
+ mCameraLiftRegistered = false;
+
+ SensorManager sensorManager = (SensorManager) mContext.getSystemService(
+ Context.SENSOR_SERVICE);
+ sensorManager.cancelTriggerSensor(mCameraLiftTriggerListener, mCameraLiftTriggerSensor);
+ }
+ }
+
+ /**
+ * Registers for the camera lift trigger.
+ */
+ private void registerCameraLiftTrigger(Resources resources) {
+ if (mCameraLiftRegistered) {
+ return;
+ }
+ SensorManager sensorManager = (SensorManager) mContext.getSystemService(
+ Context.SENSOR_SERVICE);
+ int cameraLiftTriggerId = resources.getInteger(
+ com.android.internal.R.integer.config_cameraLiftTriggerSensorType);
+ if (cameraLiftTriggerId != -1) {
+ mCameraLiftRegistered = false;
+ String sensorName = resources.getString(
+ com.android.internal.R.string.config_cameraLiftTriggerSensorStringType);
+ mCameraLiftTriggerSensor = sensorManager.getDefaultSensor(
+ cameraLiftTriggerId,
+ true /*wakeUp*/);
+
+ // Compare the camera lift trigger string type to that in the resource file to make
+ // sure we are registering the correct sensor. This is redundant check, it
+ // makes the code more robust.
+ if (mCameraLiftTriggerSensor != null) {
+ if (sensorName.equals(mCameraLiftTriggerSensor.getStringType())) {
+ mCameraLiftRegistered = sensorManager.requestTriggerSensor(mCameraLiftTriggerListener,
+ mCameraLiftTriggerSensor);
+ } else {
+ String message = String.format("Wrong configuration. Sensor type and sensor "
+ + "string type don't match: %s in resources, %s in the sensor.",
+ sensorName, mCameraLiftTriggerSensor.getStringType());
+ throw new RuntimeException(message);
+ }
+ }
+ if (DBG) Slog.d(TAG, "Camera lift trigger sensor registered: " + mCameraLiftRegistered);
+ } else {
+ if (DBG) Slog.d(TAG, "Camera lift trigger sensor is not specified.");
+ }
+ }
+
public static boolean isCameraLaunchSettingEnabled(Context context, int userId) {
return isCameraLaunchEnabled(context.getResources())
&& (Settings.Secure.getIntForUser(context.getContentResolver(),
@@ -230,6 +317,13 @@
Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0);
}
+ public static boolean isCameraLiftTriggerSettingEnabled(Context context, int userId) {
+ return isCameraLiftTriggerEnabled(context.getResources())
+ && (Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED,
+ Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED_DEFAULT, userId) != 0);
+ }
+
/**
* Whether to enable the camera launch gesture.
*/
@@ -245,38 +339,55 @@
com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled);
}
+ public static boolean isCameraLiftTriggerEnabled(Resources resources) {
+ boolean configSet = resources.getInteger(
+ com.android.internal.R.integer.config_cameraLiftTriggerSensorType) != -1;
+ return configSet;
+ }
+
/**
* Whether GestureLauncherService should be enabled according to system properties.
*/
public static boolean isGestureLauncherEnabled(Resources resources) {
- return isCameraLaunchEnabled(resources) || isCameraDoubleTapPowerEnabled(resources);
+ return isCameraLaunchEnabled(resources) || isCameraDoubleTapPowerEnabled(resources) ||
+ isCameraLiftTriggerEnabled(resources);
}
public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive,
MutableBoolean outLaunched) {
boolean launched = false;
boolean intercept = false;
- long doubleTapInterval;
+ long powerTapInterval;
synchronized (this) {
- doubleTapInterval = event.getEventTime() - mLastPowerDown;
+ powerTapInterval = event.getEventTime() - mLastPowerDown;
if (mCameraDoubleTapPowerEnabled
- && doubleTapInterval < CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS) {
+ && powerTapInterval < CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS) {
launched = true;
intercept = interactive;
+ mPowerButtonConsecutiveTaps++;
+ } else if (powerTapInterval < POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS) {
+ mPowerButtonConsecutiveTaps++;
+ } else {
+ mPowerButtonConsecutiveTaps = 1;
}
mLastPowerDown = event.getEventTime();
}
+ if (DBG && mPowerButtonConsecutiveTaps > 1) {
+ Slog.i(TAG, Long.valueOf(mPowerButtonConsecutiveTaps) +
+ " consecutive power button taps detected");
+ }
if (launched) {
Slog.i(TAG, "Power button double tap gesture detected, launching camera. Interval="
- + doubleTapInterval + "ms");
- launched = handleCameraLaunchGesture(false /* useWakelock */,
+ + powerTapInterval + "ms");
+ launched = handleCameraGesture(false /* useWakelock */,
StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
if (launched) {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE,
- (int) doubleTapInterval);
+ mMetricsLogger.action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE,
+ (int) powerTapInterval);
}
}
- MetricsLogger.histogram(mContext, "power_double_tap_interval", (int) doubleTapInterval);
+ mMetricsLogger.histogram("power_consecutive_short_tap_count", mPowerButtonConsecutiveTaps);
+ mMetricsLogger.histogram("power_double_tap_interval", (int) powerTapInterval);
outLaunched.value = launched;
return intercept && launched;
}
@@ -284,17 +395,18 @@
/**
* @return true if camera was launched, false otherwise.
*/
- private boolean handleCameraLaunchGesture(boolean useWakelock, int source) {
+ @VisibleForTesting
+ boolean handleCameraGesture(boolean useWakelock, int source) {
boolean userSetupComplete = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
if (!userSetupComplete) {
if (DBG) Slog.d(TAG, String.format(
- "userSetupComplete = %s, ignoring camera launch gesture.",
+ "userSetupComplete = %s, ignoring camera gesture.",
userSetupComplete));
return false;
}
if (DBG) Slog.d(TAG, String.format(
- "userSetupComplete = %s, performing camera launch gesture.",
+ "userSetupComplete = %s, performing camera gesture.",
userSetupComplete));
if (useWakelock) {
@@ -332,7 +444,7 @@
private final class GestureEventListener implements SensorEventListener {
@Override
public void onSensorChanged(SensorEvent event) {
- if (!mRegistered) {
+ if (!mCameraLaunchRegistered) {
if (DBG) Slog.d(TAG, "Ignoring gesture event because it's unregistered.");
return;
}
@@ -342,9 +454,9 @@
Slog.d(TAG, String.format("Received a camera launch event: " +
"values=[%.4f, %.4f, %.4f].", values[0], values[1], values[2]));
}
- if (handleCameraLaunchGesture(true /* useWakelock */,
+ if (handleCameraGesture(true /* useWakelock */,
StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)) {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_WIGGLE_CAMERA_GESTURE);
+ mMetricsLogger.action(MetricsEvent.ACTION_WIGGLE_CAMERA_GESTURE);
trackCameraLaunchEvent(event);
}
return;
@@ -401,4 +513,39 @@
mCameraLaunchLastEventExtra = extra;
}
}
+
+ private final class CameraLiftTriggerEventListener extends TriggerEventListener {
+ @Override
+ public void onTrigger(TriggerEvent event) {
+ if (!mCameraLiftRegistered) {
+ if (DBG) Slog.d(TAG, "Ignoring camera lift event because it's unregistered.");
+ return;
+ }
+ if (event.sensor == mCameraLiftTriggerSensor) {
+ Resources resources = mContext.getResources();
+ SensorManager sensorManager = (SensorManager) mContext.getSystemService(
+ Context.SENSOR_SERVICE);
+
+ if (DBG) {
+ float[] values = event.values;
+ Slog.d(TAG, String.format("Received a camera lift trigger event: " +
+ "values=[%.4f].", values[0]));
+ }
+ if (mKeyguardManager.isKeyguardLocked() || !mPowerManager.isInteractive()) {
+ if (handleCameraGesture(true /* useWakelock */,
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER)) {
+ MetricsLogger.action(mContext, MetricsEvent.ACTION_CAMERA_LIFT_TRIGGER);
+ }
+ } else if (DBG) {
+ Slog.d(TAG, "Ignoring lift event because device is awake");
+ }
+
+ mCameraLiftRegistered = sensorManager.requestTriggerSensor(mCameraLiftTriggerListener,
+ mCameraLiftTriggerSensor);
+
+ if (DBG) Slog.d(TAG, "Camera lift trigger sensor re-registered: " + mCameraLiftRegistered);
+ return;
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index a7ce95b..ec275cc 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -18,9 +18,10 @@
import static android.Manifest.permission.DUMP;
import static android.net.IpSecManager.INVALID_RESOURCE_ID;
-import static android.net.IpSecManager.KEY_RESOURCE_ID;
-import static android.net.IpSecManager.KEY_SPI;
-import static android.net.IpSecManager.KEY_STATUS;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static com.android.internal.util.Preconditions.checkNotNull;
import android.content.Context;
import android.net.IIpSecService;
@@ -28,47 +29,92 @@
import android.net.IpSecAlgorithm;
import android.net.IpSecConfig;
import android.net.IpSecManager;
+import android.net.IpSecSpiResponse;
import android.net.IpSecTransform;
+import android.net.IpSecTransformResponse;
+import android.net.IpSecUdpEncapResponse;
import android.net.util.NetdService;
import android.os.Binder;
-import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import java.io.FileDescriptor;
+import java.io.IOException;
import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
import java.util.concurrent.atomic.AtomicInteger;
+import libcore.io.IoUtils;
/** @hide */
public class IpSecService extends IIpSecService.Stub {
private static final String TAG = "IpSecService";
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+
private static final String NETD_SERVICE_NAME = "netd";
private static final int[] DIRECTIONS =
new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN};
- /** Binder context for this service */
+ private static final int NETD_FETCH_TIMEOUT = 5000; //ms
+ private static final int MAX_PORT_BIND_ATTEMPTS = 10;
+ private static final InetAddress INADDR_ANY;
+
+ static {
+ try {
+ INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
+ } catch (UnknownHostException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static final int FREE_PORT_MIN = 1024; // ports 1-1023 are reserved
+ static final int PORT_MAX = 0xFFFF; // ports are an unsigned 16-bit integer
+
+ /* Binder context for this service */
private final Context mContext;
- private Object mLock = new Object();
+ /** Should be a never-repeating global ID for resources */
+ private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
- private static final int NETD_FETCH_TIMEOUT = 5000; //ms
+ @GuardedBy("this")
+ private final ManagedResourceArray<SpiRecord> mSpiRecords = new ManagedResourceArray<>();
- private AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
+ @GuardedBy("this")
+ private final ManagedResourceArray<TransformRecord> mTransformRecords =
+ new ManagedResourceArray<>();
+ @GuardedBy("this")
+ private final ManagedResourceArray<UdpSocketRecord> mUdpSocketRecords =
+ new ManagedResourceArray<>();
+
+ /**
+ * The ManagedResource class provides a facility to cleanly and reliably release system
+ * resources. It relies on two things: an IBinder that allows ManagedResource to automatically
+ * clean up in the event that the Binder dies and a user-provided resourceId that should
+ * uniquely identify the managed resource. To use this class, the user should implement the
+ * releaseResources() method that is responsible for releasing system resources when invoked.
+ */
private abstract class ManagedResource implements IBinder.DeathRecipient {
final int pid;
final int uid;
private IBinder mBinder;
+ protected int mResourceId;
- ManagedResource(IBinder binder) {
+ private AtomicInteger mReferenceCount = new AtomicInteger(0);
+
+ ManagedResource(int resourceId, IBinder binder) {
super();
mBinder = binder;
+ mResourceId = resourceId;
pid = Binder.getCallingPid();
uid = Binder.getCallingUid();
@@ -79,21 +125,53 @@
}
}
+ public void addReference() {
+ mReferenceCount.incrementAndGet();
+ }
+
+ public void removeReference() {
+ if (mReferenceCount.decrementAndGet() < 0) {
+ Log.wtf(TAG, "Programming error: negative reference count");
+ }
+ }
+
+ public boolean isReferenced() {
+ return (mReferenceCount.get() > 0);
+ }
+
+ public void checkOwnerOrSystemAndThrow() {
+ if (uid != Binder.getCallingUid()
+ && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
+ throw new SecurityException("Only the owner may access managed resources!");
+ }
+ }
+
/**
* When this record is no longer needed for managing system resources this function should
- * unlink all references held by the record to allow efficient garbage collection.
+ * clean up all system resources and nullify the record. This function shall perform all
+ * necessary cleanup of the resources managed by this record.
+ *
+ * <p>NOTE: this function verifies ownership before allowing resources to be freed.
*/
- public final void release() {
- //Release all the underlying system resources first
- releaseResources();
+ public final void release() throws RemoteException {
+ synchronized (IpSecService.this) {
+ if (isReferenced()) {
+ throw new IllegalStateException(
+ "Cannot release a resource that has active references!");
+ }
- if (mBinder != null) {
- mBinder.unlinkToDeath(this, 0);
+ if (mResourceId == INVALID_RESOURCE_ID) {
+ return;
+ }
+
+ releaseResources();
+ if (mBinder != null) {
+ mBinder.unlinkToDeath(this, 0);
+ }
+ mBinder = null;
+
+ mResourceId = INVALID_RESOURCE_ID;
}
- mBinder = null;
-
- //remove this record so that it can be cleaned up
- nullifyRecord();
}
/**
@@ -102,41 +180,85 @@
* collection
*/
public final void binderDied() {
- release();
+ try {
+ release();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to release resource: " + e);
+ }
}
/**
- * Implement this method to release all object references contained in the subclass to allow
- * efficient garbage collection of the record. This should remove any references to the
- * record from all other locations that hold a reference as the record is no longer valid.
- */
- protected abstract void nullifyRecord();
-
- /**
* Implement this method to release all system resources that are being protected by this
* record. Once the resources are released, the record should be invalidated and no longer
- * used by calling releaseRecord()
+ * used by calling release(). This should NEVER be called directly.
+ *
+ * <p>Calls to this are always guarded by IpSecService#this
*/
- protected abstract void releaseResources();
+ protected abstract void releaseResources() throws RemoteException;
};
- private final class TransformRecord extends ManagedResource {
- private IpSecConfig mConfig;
- private int mResourceId;
+ /**
+ * Minimal wrapper around SparseArray that performs ownership
+ * validation on element accesses.
+ */
+ private class ManagedResourceArray<T extends ManagedResource> {
+ SparseArray<T> mArray = new SparseArray<>();
- TransformRecord(IpSecConfig config, int resourceId, IBinder binder) {
- super(binder);
+ T get(int key) {
+ T val = mArray.get(key);
+ val.checkOwnerOrSystemAndThrow();
+ return val;
+ }
+
+ void put(int key, T obj) {
+ checkNotNull(obj, "Null resources cannot be added");
+ mArray.put(key, obj);
+ }
+
+ void remove(int key) {
+ mArray.remove(key);
+ }
+ }
+
+ private final class TransformRecord extends ManagedResource {
+ private final IpSecConfig mConfig;
+ private final SpiRecord[] mSpis;
+ private final UdpSocketRecord mSocket;
+
+ TransformRecord(
+ int resourceId,
+ IBinder binder,
+ IpSecConfig config,
+ SpiRecord[] spis,
+ UdpSocketRecord socket) {
+ super(resourceId, binder);
mConfig = config;
- mResourceId = resourceId;
+ mSpis = spis;
+ mSocket = socket;
+
+ for (int direction : DIRECTIONS) {
+ mSpis[direction].addReference();
+ mSpis[direction].setOwnedByTransform();
+ }
+
+ if (mSocket != null) {
+ mSocket.addReference();
+ }
}
public IpSecConfig getConfig() {
return mConfig;
}
+ public SpiRecord getSpiRecord(int direction) {
+ return mSpis[direction];
+ }
+
+ /** always guarded by IpSecService#this */
@Override
protected void releaseResources() {
for (int direction : DIRECTIONS) {
+ int spi = mSpis[direction].getSpi();
try {
getNetdInstance()
.ipSecDeleteSecurityAssociation(
@@ -148,19 +270,21 @@
(mConfig.getRemoteAddress() != null)
? mConfig.getRemoteAddress().getHostAddress()
: "",
- mConfig.getSpi(direction));
+ spi);
} catch (ServiceSpecificException e) {
// FIXME: get the error code and throw is at an IOException from Errno Exception
} catch (RemoteException e) {
Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
}
}
- }
- @Override
- protected void nullifyRecord() {
- mConfig = null;
- mResourceId = INVALID_RESOURCE_ID;
+ for (int direction : DIRECTIONS) {
+ mSpis[direction].removeReference();
+ }
+
+ if (mSocket != null) {
+ mSocket.removeReference();
+ }
}
}
@@ -168,27 +292,37 @@
private final int mDirection;
private final String mLocalAddress;
private final String mRemoteAddress;
- private final IBinder mBinder;
private int mSpi;
- private int mResourceId;
+
+ private boolean mOwnedByTransform = false;
SpiRecord(
int resourceId,
+ IBinder binder,
int direction,
String localAddress,
String remoteAddress,
- int spi,
- IBinder binder) {
- super(binder);
- mResourceId = resourceId;
+ int spi) {
+ super(resourceId, binder);
mDirection = direction;
mLocalAddress = localAddress;
mRemoteAddress = remoteAddress;
mSpi = spi;
- mBinder = binder;
}
+ /** always guarded by IpSecService#this */
+ @Override
protected void releaseResources() {
+ if (mOwnedByTransform) {
+ Log.d(TAG, "Cannot release Spi " + mSpi + ": Currently locked by a Transform");
+ // Because SPIs are "handed off" to transform, objects, they should never be
+ // freed from the SpiRecord once used in a transform. (They refer to the same SA,
+ // thus ownership and responsibility for freeing these resources passes to the
+ // Transform object). Thus, we should let the user free them without penalty once
+ // they are applied in a Transform object.
+ return;
+ }
+
try {
getNetdInstance()
.ipSecDeleteSecurityAssociation(
@@ -198,19 +332,50 @@
} catch (RemoteException e) {
Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId);
}
+
+ mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
}
- protected void nullifyRecord() {
- mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
- mResourceId = INVALID_RESOURCE_ID;
+ public int getSpi() {
+ return mSpi;
+ }
+
+ public void setOwnedByTransform() {
+ if (mOwnedByTransform) {
+ // Programming error
+ new IllegalStateException("Cannot own an SPI twice!");
+ }
+
+ mOwnedByTransform = true;
}
}
- @GuardedBy("mSpiRecords")
- private final SparseArray<SpiRecord> mSpiRecords = new SparseArray<>();
+ private final class UdpSocketRecord extends ManagedResource {
+ private FileDescriptor mSocket;
+ private final int mPort;
- @GuardedBy("mTransformRecords")
- private final SparseArray<TransformRecord> mTransformRecords = new SparseArray<>();
+ UdpSocketRecord(int resourceId, IBinder binder, FileDescriptor socket, int port) {
+ super(resourceId, binder);
+ mSocket = socket;
+ mPort = port;
+ }
+
+ /** always guarded by IpSecService#this */
+ @Override
+ protected void releaseResources() {
+ Log.d(TAG, "Closing port " + mPort);
+ IoUtils.closeQuietly(mSocket);
+ mSocket = null;
+ }
+
+ public int getPort() {
+ return mPort;
+ }
+
+ public FileDescriptor getSocket() {
+ return mSocket;
+ }
+ }
/**
* Constructs a new IpSecService instance
@@ -242,7 +407,7 @@
new Runnable() {
@Override
public void run() {
- synchronized (mLock) {
+ synchronized (IpSecService.this) {
NetdService.get(NETD_FETCH_TIMEOUT);
}
}
@@ -258,30 +423,27 @@
return netd;
}
- boolean isNetdAlive() {
- synchronized (mLock) {
- try {
- final INetd netd = getNetdInstance();
- if (netd == null) {
- return false;
- }
- return netd.isAlive();
- } catch (RemoteException re) {
+ synchronized boolean isNetdAlive() {
+ try {
+ final INetd netd = getNetdInstance();
+ if (netd == null) {
return false;
}
+ return netd.isAlive();
+ } catch (RemoteException re) {
+ return false;
}
}
@Override
/** Get a new SPI and maintain the reservation in the system server */
- public Bundle reserveSecurityParameterIndex(
+ public synchronized IpSecSpiResponse reserveSecurityParameterIndex(
int direction, String remoteAddress, int requestedSpi, IBinder binder)
throws RemoteException {
int resourceId = mNextResourceId.getAndIncrement();
int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
String localAddress = "";
- Bundle retBundle = new Bundle(3);
try {
spi =
getNetdInstance()
@@ -292,29 +454,78 @@
remoteAddress,
requestedSpi);
Log.d(TAG, "Allocated SPI " + spi);
- retBundle.putInt(KEY_STATUS, IpSecManager.Status.OK);
- retBundle.putInt(KEY_RESOURCE_ID, resourceId);
- retBundle.putInt(KEY_SPI, spi);
- synchronized (mSpiRecords) {
- mSpiRecords.put(
- resourceId,
- new SpiRecord(
- resourceId, direction, localAddress, remoteAddress, spi, binder));
- }
+ mSpiRecords.put(
+ resourceId,
+ new SpiRecord(resourceId, binder, direction, localAddress, remoteAddress, spi));
} catch (ServiceSpecificException e) {
// TODO: Add appropriate checks when other ServiceSpecificException types are supported
- retBundle.putInt(KEY_STATUS, IpSecManager.Status.SPI_UNAVAILABLE);
- retBundle.putInt(KEY_RESOURCE_ID, resourceId);
- retBundle.putInt(KEY_SPI, spi);
+ return new IpSecSpiResponse(
+ IpSecManager.Status.SPI_UNAVAILABLE, IpSecManager.INVALID_RESOURCE_ID, spi);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- return retBundle;
+ return new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, spi);
+ }
+
+ /* This method should only be called from Binder threads. Do not call this from
+ * within the system server as it will crash the system on failure.
+ */
+ private synchronized <T extends ManagedResource> void releaseManagedResource(
+ ManagedResourceArray<T> resArray, int resourceId, String typeName)
+ throws RemoteException {
+ // We want to non-destructively get so that we can check credentials before removing
+ // this from the records.
+ T record = resArray.get(resourceId);
+
+ if (record == null) {
+ throw new IllegalArgumentException(
+ typeName + " " + resourceId + " is not available to be deleted");
+ }
+
+ record.release();
+ resArray.remove(resourceId);
}
/** Release a previously allocated SPI that has been registered with the system server */
@Override
- public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {}
+ public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
+ releaseManagedResource(mSpiRecords, resourceId, "SecurityParameterIndex");
+ }
+
+ /**
+ * This function finds and forcibly binds to a random system port, ensuring that the port cannot
+ * be unbound.
+ *
+ * <p>A socket cannot be un-bound from a port if it was bound to that port by number. To select
+ * a random open port and then bind by number, this function creates a temp socket, binds to a
+ * random port (specifying 0), gets that port number, and then uses is to bind the user's UDP
+ * Encapsulation Socket forcibly, so that it cannot be un-bound by the user with the returned
+ * FileHandle.
+ *
+ * <p>The loop in this function handles the inherent race window between un-binding to a port
+ * and re-binding, during which the system could *technically* hand that port out to someone
+ * else.
+ */
+ private void bindToRandomPort(FileDescriptor sockFd) throws IOException {
+ for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
+ try {
+ FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ Os.bind(probeSocket, INADDR_ANY, 0);
+ int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
+ Os.close(probeSocket);
+ Log.v(TAG, "Binding to port " + port);
+ Os.bind(sockFd, INADDR_ANY, port);
+ return;
+ } catch (ErrnoException e) {
+ // Someone miraculously claimed the port just after we closed probeSocket.
+ if (e.errno == OsConstants.EADDRINUSE) {
+ continue;
+ }
+ throw e.rethrowAsIOException();
+ }
+ }
+ throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
+ }
/**
* Open a socket via the system server and bind it to the specified port (random if port=0).
@@ -323,13 +534,47 @@
* needed.
*/
@Override
- public Bundle openUdpEncapsulationSocket(int port, IBinder binder) throws RemoteException {
- return null;
+ public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
+ throws RemoteException {
+ if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) {
+ throw new IllegalArgumentException(
+ "Specified port number must be a valid non-reserved UDP port");
+ }
+ int resourceId = mNextResourceId.getAndIncrement();
+ FileDescriptor sockFd = null;
+ try {
+ sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+ if (port != 0) {
+ Log.v(TAG, "Binding to port " + port);
+ Os.bind(sockFd, INADDR_ANY, port);
+ } else {
+ bindToRandomPort(sockFd);
+ }
+ // This code is common to both the unspecified and specified port cases
+ Os.setsockoptInt(
+ sockFd,
+ OsConstants.IPPROTO_UDP,
+ OsConstants.UDP_ENCAP,
+ OsConstants.UDP_ENCAP_ESPINUDP);
+
+ mUdpSocketRecords.put(
+ resourceId, new UdpSocketRecord(resourceId, binder, sockFd, port));
+ return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd);
+ } catch (IOException | ErrnoException e) {
+ IoUtils.closeQuietly(sockFd);
+ }
+ // If we make it to here, then something has gone wrong and we couldn't open a socket.
+ // The only reasonable condition that would cause that is resource unavailable.
+ return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
}
/** close a socket that has been been allocated by and registered with the system server */
@Override
- public void closeUdpEncapsulationSocket(ParcelFileDescriptor socket) {}
+ public void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {
+
+ releaseManagedResource(mUdpSocketRecords, resourceId, "UdpEncapsulationSocket");
+ }
/**
* Create a transport mode transform, which represent two security associations (one in each
@@ -339,13 +584,26 @@
* receive data.
*/
@Override
- public Bundle createTransportModeTransform(IpSecConfig c, IBinder binder)
- throws RemoteException {
- // TODO: Basic input validation here since it's coming over the Binder
+ public synchronized IpSecTransformResponse createTransportModeTransform(
+ IpSecConfig c, IBinder binder) throws RemoteException {
int resourceId = mNextResourceId.getAndIncrement();
+ SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
+ // TODO: Basic input validation here since it's coming over the Binder
+ int encapType, encapLocalPort = 0, encapRemotePort = 0;
+ UdpSocketRecord socketRecord = null;
+ encapType = c.getEncapType();
+ if (encapType != IpSecTransform.ENCAP_NONE) {
+ socketRecord = mUdpSocketRecords.get(c.getEncapLocalResourceId());
+ encapLocalPort = socketRecord.getPort();
+ encapRemotePort = c.getEncapRemotePort();
+ }
+
for (int direction : DIRECTIONS) {
IpSecAlgorithm auth = c.getAuthentication(direction);
IpSecAlgorithm crypt = c.getEncryption(direction);
+
+ spis[direction] = mSpiRecords.get(c.getSpiResourceId(direction));
+ int spi = spis[direction].getSpi();
try {
int result =
getNetdInstance()
@@ -362,35 +620,29 @@
(c.getNetwork() != null)
? c.getNetwork().getNetworkHandle()
: 0,
- c.getSpi(direction),
+ spi,
(auth != null) ? auth.getName() : "",
(auth != null) ? auth.getKey() : null,
(auth != null) ? auth.getTruncationLengthBits() : 0,
(crypt != null) ? crypt.getName() : "",
(crypt != null) ? crypt.getKey() : null,
(crypt != null) ? crypt.getTruncationLengthBits() : 0,
- c.getEncapType(),
- c.getEncapLocalPort(),
- c.getEncapRemotePort());
- if (result != c.getSpi(direction)) {
+ encapType,
+ encapLocalPort,
+ encapRemotePort);
+ if (result != spi) {
// TODO: cleanup the first SA if creation of second SA fails
- Bundle retBundle = new Bundle(2);
- retBundle.putInt(KEY_STATUS, IpSecManager.Status.SPI_UNAVAILABLE);
- retBundle.putInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID);
- return retBundle;
+ return new IpSecTransformResponse(
+ IpSecManager.Status.SPI_UNAVAILABLE, INVALID_RESOURCE_ID);
}
} catch (ServiceSpecificException e) {
// FIXME: get the error code and throw is at an IOException from Errno Exception
}
}
- synchronized (mTransformRecords) {
- mTransformRecords.put(resourceId, new TransformRecord(c, resourceId, binder));
- }
-
- Bundle retBundle = new Bundle(2);
- retBundle.putInt(KEY_STATUS, IpSecManager.Status.OK);
- retBundle.putInt(KEY_RESOURCE_ID, resourceId);
- return retBundle;
+ // Both SAs were created successfully, time to construct a record and lock it away
+ mTransformRecords.put(
+ resourceId, new TransformRecord(resourceId, binder, c, spis, socketRecord));
+ return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
}
/**
@@ -401,27 +653,7 @@
*/
@Override
public void deleteTransportModeTransform(int resourceId) throws RemoteException {
- synchronized (mTransformRecords) {
- TransformRecord record;
- // We want to non-destructively get so that we can check credentials before removing
- // this from the records.
- record = mTransformRecords.get(resourceId);
-
- if (record == null) {
- throw new IllegalArgumentException(
- "Transform " + resourceId + " is not available to be deleted");
- }
-
- if (record.pid != Binder.getCallingPid() || record.uid != Binder.getCallingUid()) {
- throw new SecurityException("Only the owner of an IpSec Transform may delete it!");
- }
-
- // TODO: if releaseResources() throws RemoteException, we can try again to clean up on
- // binder death. Need to make sure that path is actually functional.
- record.releaseResources();
- mTransformRecords.remove(resourceId);
- record.nullifyRecord();
- }
+ releaseManagedResource(mTransformRecords, resourceId, "IpSecTransform");
}
/**
@@ -429,44 +661,43 @@
* association as a correspondent policy to the provided socket
*/
@Override
- public void applyTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
- throws RemoteException {
+ public synchronized void applyTransportModeTransform(
+ ParcelFileDescriptor socket, int resourceId) throws RemoteException {
+ // Synchronize liberally here because we are using ManagedResources in this block
+ TransformRecord info;
+ // FIXME: this code should be factored out into a security check + getter
+ info = mTransformRecords.get(resourceId);
- synchronized (mTransformRecords) {
- TransformRecord info;
- // FIXME: this code should be factored out into a security check + getter
- info = mTransformRecords.get(resourceId);
+ if (info == null) {
+ throw new IllegalArgumentException("Transform " + resourceId + " is not active");
+ }
- if (info == null) {
- throw new IllegalArgumentException("Transform " + resourceId + " is not active");
+ // TODO: make this a function.
+ if (info.pid != getCallingPid() || info.uid != getCallingUid()) {
+ throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
+ }
+
+ IpSecConfig c = info.getConfig();
+ try {
+ for (int direction : DIRECTIONS) {
+ getNetdInstance()
+ .ipSecApplyTransportModeTransform(
+ socket.getFileDescriptor(),
+ resourceId,
+ direction,
+ (c.getLocalAddress() != null)
+ ? c.getLocalAddress().getHostAddress()
+ : "",
+ (c.getRemoteAddress() != null)
+ ? c.getRemoteAddress().getHostAddress()
+ : "",
+ info.getSpiRecord(direction).getSpi());
}
-
- // TODO: make this a function.
- if (info.pid != getCallingPid() || info.uid != getCallingUid()) {
- throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
- }
-
- IpSecConfig c = info.getConfig();
- try {
- for (int direction : DIRECTIONS) {
- getNetdInstance()
- .ipSecApplyTransportModeTransform(
- socket.getFileDescriptor(),
- resourceId,
- direction,
- (c.getLocalAddress() != null)
- ? c.getLocalAddress().getHostAddress()
- : "",
- (c.getRemoteAddress() != null)
- ? c.getRemoteAddress().getHostAddress()
- : "",
- c.getSpi(direction));
- }
- } catch (ServiceSpecificException e) {
- // FIXME: get the error code and throw is at an IOException from Errno Exception
- }
+ } catch (ServiceSpecificException e) {
+ // FIXME: get the error code and throw is at an IOException from Errno Exception
}
}
+
/**
* Remove a transport mode transform from a socket, applying the default (empty) policy. This
* will ensure that NO IPsec policy is applied to the socket (would be the equivalent of
@@ -486,7 +717,7 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(DUMP, TAG);
-
+ // TODO: Add dump code to print out a log of all the resources being tracked
pw.println("IpSecService Log:");
pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
pw.println();
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 3460419..7275461 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -244,6 +244,7 @@
private GnssLocationProvider.GnssSystemInfoProvider mGnssSystemInfoProvider;
private GnssLocationProvider.GnssBatchingProvider mGnssBatchingProvider;
+ private GnssLocationProvider.GnssMetricsProvider mGnssMetricsProvider;
private IBatchedLocationCallback mGnssBatchingCallback;
private LinkedCallback mGnssBatchingDeathCallback;
private boolean mGnssBatchingInProgress = false;
@@ -587,6 +588,7 @@
mLocationHandler.getLooper());
mGnssSystemInfoProvider = gnssProvider.getGnssSystemInfoProvider();
mGnssBatchingProvider = gnssProvider.getGnssBatchingProvider();
+ mGnssMetricsProvider = gnssProvider.getGnssMetricsProvider();
mGnssStatusProvider = gnssProvider.getGnssStatusProvider();
mNetInitiatedListener = gnssProvider.getNetInitiatedListener();
addProviderLocked(gnssProvider);
@@ -3036,6 +3038,12 @@
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
synchronized (mLock) {
+ if (args.length > 0 && args[0].equals("--gnssmetrics")) {
+ if (mGnssMetricsProvider != null) {
+ pw.append(mGnssMetricsProvider.getGnssMetricsAsProtoString());
+ }
+ return;
+ }
pw.println("Current Location Manager state:");
pw.println(" Location Listeners:");
for (Receiver receiver : mReceivers.values()) {
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
deleted file mode 100644
index 773a1ee..0000000
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ /dev/null
@@ -1,2259 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
-import static android.Manifest.permission.READ_CONTACTS;
-import static android.content.Context.KEYGUARD_SERVICE;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
-import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
-import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
-
-import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.app.IActivityManager;
-import android.app.KeyguardManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
-import android.app.admin.PasswordMetrics;
-import android.app.backup.BackupManager;
-import android.app.trust.IStrongAuthTracker;
-import android.app.trust.TrustManager;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.database.sqlite.SQLiteDatabase;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IProgressListener;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.ServiceManager;
-import android.os.ShellCallback;
-import android.os.StrictMode;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.os.storage.IStorageManager;
-import android.os.storage.StorageManager;
-import android.provider.Settings;
-import android.provider.Settings.Secure;
-import android.provider.Settings.SettingNotFoundException;
-import android.security.GateKeeper;
-import android.security.KeyStore;
-import android.security.keystore.AndroidKeyStoreProvider;
-import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
-import android.service.gatekeeper.GateKeeperResponse;
-import android.service.gatekeeper.IGateKeeperService;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.DumpUtils;
-import com.android.internal.widget.ICheckCredentialProgressCallback;
-import com.android.internal.widget.ILockSettings;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.server.LockSettingsStorage.CredentialHash;
-import com.android.server.SyntheticPasswordManager.AuthenticationResult;
-import com.android.server.SyntheticPasswordManager.AuthenticationToken;
-
-import libcore.util.HexEncoding;
-
-import java.io.ByteArrayOutputStream;
-import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.KeyStoreException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.CertificateException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.KeyGenerator;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.GCMParameterSpec;
-
-/**
- * Keeps the lock pattern/password data and related settings for each user. Used by
- * LockPatternUtils. Needs to be a service because Settings app also needs to be able to save
- * lockscreen information for secondary users.
- *
- * @hide
- */
-public class LockSettingsService extends ILockSettings.Stub {
- private static final String TAG = "LockSettingsService";
- private static final String PERMISSION = ACCESS_KEYGUARD_SECURE_STORAGE;
- private static final boolean DEBUG = false;
-
- private static final int PROFILE_KEY_IV_SIZE = 12;
- private static final String SEPARATE_PROFILE_CHALLENGE_KEY = "lockscreen.profilechallenge";
-
- // Order of holding lock: mSeparateChallengeLock -> mSpManager -> this
- private final Object mSeparateChallengeLock = new Object();
-
- private final Injector mInjector;
- private final Context mContext;
- private final Handler mHandler;
- @VisibleForTesting
- protected final LockSettingsStorage mStorage;
- private final LockSettingsStrongAuth mStrongAuth;
- private final SynchronizedStrongAuthTracker mStrongAuthTracker;
-
- private final LockPatternUtils mLockPatternUtils;
- private final NotificationManager mNotificationManager;
- private final UserManager mUserManager;
- private final IActivityManager mActivityManager;
-
- private final KeyStore mKeyStore;
-
- private boolean mFirstCallToVold;
- protected IGateKeeperService mGateKeeperService;
- private SyntheticPasswordManager mSpManager;
-
- /**
- * The UIDs that are used for system credential storage in keystore.
- */
- private static final int[] SYSTEM_CREDENTIAL_UIDS = {
- Process.WIFI_UID, Process.VPN_UID,
- Process.ROOT_UID, Process.SYSTEM_UID };
-
- // This class manages life cycle events for encrypted users on File Based Encryption (FBE)
- // devices. The most basic of these is to show/hide notifications about missing features until
- // the user unlocks the account and credential-encrypted storage is available.
- public static final class Lifecycle extends SystemService {
- private LockSettingsService mLockSettingsService;
-
- public Lifecycle(Context context) {
- super(context);
- }
-
- @Override
- public void onStart() {
- AndroidKeyStoreProvider.install();
- mLockSettingsService = new LockSettingsService(getContext());
- publishBinderService("lock_settings", mLockSettingsService);
- }
-
- @Override
- public void onStartUser(int userHandle) {
- mLockSettingsService.onStartUser(userHandle);
- }
-
- @Override
- public void onUnlockUser(int userHandle) {
- mLockSettingsService.onUnlockUser(userHandle);
- }
-
- @Override
- public void onCleanupUser(int userHandle) {
- mLockSettingsService.onCleanupUser(userHandle);
- }
- }
-
- @VisibleForTesting
- protected static class SynchronizedStrongAuthTracker
- extends LockPatternUtils.StrongAuthTracker {
- public SynchronizedStrongAuthTracker(Context context) {
- super(context);
- }
-
- @Override
- protected void handleStrongAuthRequiredChanged(int strongAuthFlags, int userId) {
- synchronized (this) {
- super.handleStrongAuthRequiredChanged(strongAuthFlags, userId);
- }
- }
-
- @Override
- public int getStrongAuthForUser(int userId) {
- synchronized (this) {
- return super.getStrongAuthForUser(userId);
- }
- }
-
- void register(LockSettingsStrongAuth strongAuth) {
- strongAuth.registerStrongAuthTracker(this.mStub);
- }
- }
-
- /**
- * Tie managed profile to primary profile if it is in unified mode and not tied before.
- *
- * @param managedUserId Managed profile user Id
- * @param managedUserPassword Managed profile original password (when it has separated lock).
- * NULL when it does not have a separated lock before.
- */
- public void tieManagedProfileLockIfNecessary(int managedUserId, String managedUserPassword) {
- if (DEBUG) Slog.v(TAG, "Check child profile lock for user: " + managedUserId);
- // Only for managed profile
- if (!mUserManager.getUserInfo(managedUserId).isManagedProfile()) {
- return;
- }
- // Do not tie managed profile when work challenge is enabled
- if (mLockPatternUtils.isSeparateProfileChallengeEnabled(managedUserId)) {
- return;
- }
- // Do not tie managed profile to parent when it's done already
- if (mStorage.hasChildProfileLock(managedUserId)) {
- return;
- }
- // Do not tie it to parent when parent does not have a screen lock
- final int parentId = mUserManager.getProfileParent(managedUserId).id;
- if (!isUserSecure(parentId)) {
- if (DEBUG) Slog.v(TAG, "Parent does not have a screen lock");
- return;
- }
- // Do not tie when the parent has no SID (but does have a screen lock).
- // This can only happen during an upgrade path where SID is yet to be
- // generated when the user unlocks for the first time.
- try {
- if (getGateKeeperService().getSecureUserId(parentId) == 0) {
- return;
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to talk to GateKeeper service", e);
- return;
- }
- if (DEBUG) Slog.v(TAG, "Tie managed profile to parent now!");
- byte[] randomLockSeed = new byte[] {};
- try {
- randomLockSeed = SecureRandom.getInstance("SHA1PRNG").generateSeed(40);
- String newPassword = String.valueOf(HexEncoding.encode(randomLockSeed));
- setLockCredentialInternal(newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- managedUserPassword, managedUserId);
- // We store a private credential for the managed user that's unlocked by the primary
- // account holder's credential. As such, the user will never be prompted to enter this
- // password directly, so we always store a password.
- setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, managedUserId);
- tieProfileLockToParent(managedUserId, newPassword);
- } catch (NoSuchAlgorithmException | RemoteException e) {
- Slog.e(TAG, "Fail to tie managed profile", e);
- // Nothing client can do to fix this issue, so we do not throw exception out
- }
- }
-
- static class Injector {
-
- protected Context mContext;
-
- public Injector(Context context) {
- mContext = context;
- }
-
- public Context getContext() {
- return mContext;
- }
-
- public Handler getHandler() {
- return new Handler();
- }
-
- public LockSettingsStorage getStorage() {
- final LockSettingsStorage storage = new LockSettingsStorage(mContext);
- storage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() {
- @Override
- public void initialize(SQLiteDatabase db) {
- // Get the lockscreen default from a system property, if available
- boolean lockScreenDisable = SystemProperties.getBoolean(
- "ro.lockscreen.disable.default", false);
- if (lockScreenDisable) {
- storage.writeKeyValue(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0);
- }
- }
- });
- return storage;
- }
-
- public LockSettingsStrongAuth getStrongAuth() {
- return new LockSettingsStrongAuth(mContext);
- }
-
- public SynchronizedStrongAuthTracker getStrongAuthTracker() {
- return new SynchronizedStrongAuthTracker(mContext);
- }
-
- public IActivityManager getActivityManager() {
- return ActivityManager.getService();
- }
-
- public LockPatternUtils getLockPatternUtils() {
- return new LockPatternUtils(mContext);
- }
-
- public NotificationManager getNotificationManager() {
- return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- }
-
- public UserManager getUserManager() {
- return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- }
-
- public DevicePolicyManager getDevicePolicyManager() {
- return (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- }
-
- public KeyStore getKeyStore() {
- return KeyStore.getInstance();
- }
-
- public IStorageManager getStorageManager() {
- final IBinder service = ServiceManager.getService("mount");
- if (service != null) {
- return IStorageManager.Stub.asInterface(service);
- }
- return null;
- }
-
- public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) {
- return new SyntheticPasswordManager(storage);
- }
-
- public int binderGetCallingUid() {
- return Binder.getCallingUid();
- }
- }
-
- public LockSettingsService(Context context) {
- this(new Injector(context));
- }
-
- @VisibleForTesting
- protected LockSettingsService(Injector injector) {
- mInjector = injector;
- mContext = injector.getContext();
- mKeyStore = injector.getKeyStore();
- mHandler = injector.getHandler();
- mStrongAuth = injector.getStrongAuth();
- mActivityManager = injector.getActivityManager();
-
- mLockPatternUtils = injector.getLockPatternUtils();
- mFirstCallToVold = true;
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_ADDED);
- filter.addAction(Intent.ACTION_USER_STARTING);
- filter.addAction(Intent.ACTION_USER_REMOVED);
- injector.getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter,
- null, null);
-
- mStorage = injector.getStorage();
- mNotificationManager = injector.getNotificationManager();
- mUserManager = injector.getUserManager();
- mStrongAuthTracker = injector.getStrongAuthTracker();
- mStrongAuthTracker.register(mStrongAuth);
-
- mSpManager = injector.getSyntheticPasswordManager(mStorage);
- }
-
- /**
- * If the account is credential-encrypted, show notification requesting the user to unlock the
- * device.
- */
- private void maybeShowEncryptionNotificationForUser(@UserIdInt int userId) {
- final UserInfo user = mUserManager.getUserInfo(userId);
- if (!user.isManagedProfile()) {
- // When the user is locked, we communicate it loud-and-clear
- // on the lockscreen; we only show a notification below for
- // locked managed profiles.
- return;
- }
-
- final UserHandle userHandle = user.getUserHandle();
- final boolean isSecure = isUserSecure(userId);
- if (isSecure && !mUserManager.isUserUnlockingOrUnlocked(userHandle)) {
- UserInfo parent = mUserManager.getProfileParent(userId);
- if (parent != null &&
- mUserManager.isUserUnlockingOrUnlocked(parent.getUserHandle()) &&
- !mUserManager.isQuietModeEnabled(userHandle)) {
- // Only show notifications for managed profiles once their parent
- // user is unlocked.
- showEncryptionNotificationForProfile(userHandle);
- }
- }
- }
-
- private void showEncryptionNotificationForProfile(UserHandle user) {
- Resources r = mContext.getResources();
- CharSequence title = r.getText(
- com.android.internal.R.string.user_encrypted_title);
- CharSequence message = r.getText(
- com.android.internal.R.string.profile_encrypted_message);
- CharSequence detail = r.getText(
- com.android.internal.R.string.profile_encrypted_detail);
-
- final KeyguardManager km = (KeyguardManager) mContext.getSystemService(KEYGUARD_SERVICE);
- final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null,
- user.getIdentifier());
- if (unlockIntent == null) {
- return;
- }
- unlockIntent.setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- PendingIntent intent = PendingIntent.getActivity(mContext, 0, unlockIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
-
- showEncryptionNotification(user, title, message, detail, intent);
- }
-
- private void showEncryptionNotification(UserHandle user, CharSequence title,
- CharSequence message, CharSequence detail, PendingIntent intent) {
- if (DEBUG) Slog.v(TAG, "showing encryption notification, user: " + user.getIdentifier());
-
- // Suppress all notifications on non-FBE devices for now
- if (!StorageManager.isFileEncryptedNativeOrEmulated()) return;
-
- Notification notification =
- new Notification.Builder(mContext, SystemNotificationChannels.SECURITY)
- .setSmallIcon(com.android.internal.R.drawable.ic_user_secure)
- .setWhen(0)
- .setOngoing(true)
- .setTicker(title)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color))
- .setContentTitle(title)
- .setContentText(message)
- .setSubText(detail)
- .setVisibility(Notification.VISIBILITY_PUBLIC)
- .setContentIntent(intent)
- .build();
- mNotificationManager.notifyAsUser(null, SystemMessage.NOTE_FBE_ENCRYPTED_NOTIFICATION,
- notification, user);
- }
-
- private void hideEncryptionNotification(UserHandle userHandle) {
- if (DEBUG) Slog.v(TAG, "hide encryption notification, user: " + userHandle.getIdentifier());
- mNotificationManager.cancelAsUser(null, SystemMessage.NOTE_FBE_ENCRYPTED_NOTIFICATION,
- userHandle);
- }
-
- public void onCleanupUser(int userId) {
- hideEncryptionNotification(new UserHandle(userId));
- }
-
- public void onStartUser(final int userId) {
- maybeShowEncryptionNotificationForUser(userId);
- }
-
- public void onUnlockUser(final int userId) {
- // Perform tasks which require locks in LSS on a handler, as we are callbacks from
- // ActivityManager.unlockUser()
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- // Hide notification first, as tie managed profile lock takes time
- hideEncryptionNotification(new UserHandle(userId));
-
- // Now we have unlocked the parent user we should show notifications
- // about any profiles that exist.
- List<UserInfo> profiles = mUserManager.getProfiles(userId);
- for (int i = 0; i < profiles.size(); i++) {
- UserInfo profile = profiles.get(i);
- final boolean isSecure = isUserSecure(profile.id);
- if (isSecure && profile.isManagedProfile()) {
- UserHandle userHandle = profile.getUserHandle();
- if (!mUserManager.isUserUnlockingOrUnlocked(userHandle) &&
- !mUserManager.isQuietModeEnabled(userHandle)) {
- showEncryptionNotificationForProfile(userHandle);
- }
- }
- }
-
- if (mUserManager.getUserInfo(userId).isManagedProfile()) {
- tieManagedProfileLockIfNecessary(userId, null);
- }
- }
- });
- }
-
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_USER_ADDED.equals(intent.getAction())) {
- // Notify keystore that a new user was added.
- final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
- if (userHandle > UserHandle.USER_SYSTEM) {
- removeUser(userHandle, /* unknownUser= */ true);
- }
- final KeyStore ks = KeyStore.getInstance();
- final UserInfo parentInfo = mUserManager.getProfileParent(userHandle);
- final int parentHandle = parentInfo != null ? parentInfo.id : -1;
- ks.onUserAdded(userHandle, parentHandle);
- } else if (Intent.ACTION_USER_STARTING.equals(intent.getAction())) {
- final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
- mStorage.prefetchUser(userHandle);
- } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
- final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
- if (userHandle > 0) {
- removeUser(userHandle, /* unknownUser= */ false);
- }
- }
- }
- };
-
- @Override // binder interface
- public void systemReady() {
- migrateOldData();
- try {
- getGateKeeperService();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failure retrieving IGateKeeperService", e);
- }
- // TODO: maybe skip this for split system user mode.
- mStorage.prefetchUser(UserHandle.USER_SYSTEM);
- }
-
- private void migrateOldData() {
- try {
- // These Settings moved before multi-user was enabled, so we only have to do it for the
- // root user.
- if (getString("migrated", null, 0) == null) {
- final ContentResolver cr = mContext.getContentResolver();
- for (String validSetting : VALID_SETTINGS) {
- String value = Settings.Secure.getString(cr, validSetting);
- if (value != null) {
- setString(validSetting, value, 0);
- }
- }
- // No need to move the password / pattern files. They're already in the right place.
- setString("migrated", "true", 0);
- Slog.i(TAG, "Migrated lock settings to new location");
- }
-
- // These Settings changed after multi-user was enabled, hence need to be moved per user.
- if (getString("migrated_user_specific", null, 0) == null) {
- final ContentResolver cr = mContext.getContentResolver();
- List<UserInfo> users = mUserManager.getUsers();
- for (int user = 0; user < users.size(); user++) {
- // Migrate owner info
- final int userId = users.get(user).id;
- final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
- String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
- if (!TextUtils.isEmpty(ownerInfo)) {
- setString(OWNER_INFO, ownerInfo, userId);
- Settings.Secure.putStringForUser(cr, OWNER_INFO, "", userId);
- }
-
- // Migrate owner info enabled. Note there was a bug where older platforms only
- // stored this value if the checkbox was toggled at least once. The code detects
- // this case by handling the exception.
- final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
- boolean enabled;
- try {
- int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId);
- enabled = ivalue != 0;
- setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId);
- } catch (SettingNotFoundException e) {
- // Setting was never stored. Store it if the string is not empty.
- if (!TextUtils.isEmpty(ownerInfo)) {
- setLong(OWNER_INFO_ENABLED, 1, userId);
- }
- }
- Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
- }
- // No need to move the password / pattern files. They're already in the right place.
- setString("migrated_user_specific", "true", 0);
- Slog.i(TAG, "Migrated per-user lock settings to new location");
- }
-
- // Migrates biometric weak such that the fallback mechanism becomes the primary.
- if (getString("migrated_biometric_weak", null, 0) == null) {
- List<UserInfo> users = mUserManager.getUsers();
- for (int i = 0; i < users.size(); i++) {
- int userId = users.get(i).id;
- long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- userId);
- long alternateType = getLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- userId);
- if (type == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
- setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- alternateType,
- userId);
- }
- setLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- userId);
- }
- setString("migrated_biometric_weak", "true", 0);
- Slog.i(TAG, "Migrated biometric weak to use the fallback instead");
- }
-
- // Migrates lockscreen.disabled. Prior to M, the flag was ignored when more than one
- // user was present on the system, so if we're upgrading to M and there is more than one
- // user we disable the flag to remain consistent.
- if (getString("migrated_lockscreen_disabled", null, 0) == null) {
- final List<UserInfo> users = mUserManager.getUsers();
- final int userCount = users.size();
- int switchableUsers = 0;
- for (int i = 0; i < userCount; i++) {
- if (users.get(i).supportsSwitchTo()) {
- switchableUsers++;
- }
- }
-
- if (switchableUsers > 1) {
- for (int i = 0; i < userCount; i++) {
- int id = users.get(i).id;
-
- if (getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id)) {
- setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
- }
- }
- }
-
- setString("migrated_lockscreen_disabled", "true", 0);
- Slog.i(TAG, "Migrated lockscreen disabled flag");
- }
-
- final List<UserInfo> users = mUserManager.getUsers();
- for (int i = 0; i < users.size(); i++) {
- final UserInfo userInfo = users.get(i);
- if (userInfo.isManagedProfile() && mStorage.hasChildProfileLock(userInfo.id)) {
- // When managed profile has a unified lock, the password quality stored has 2
- // possibilities only.
- // 1). PASSWORD_QUALITY_UNSPECIFIED, which is upgraded from dp2, and we are
- // going to set it back to PASSWORD_QUALITY_ALPHANUMERIC.
- // 2). PASSWORD_QUALITY_ALPHANUMERIC, which is the actual password quality for
- // unified lock.
- final long quality = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);
- if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
- // Only possible when it's upgraded from nyc dp3
- Slog.i(TAG, "Migrated tied profile lock type");
- setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, userInfo.id);
- } else if (quality != DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC) {
- // It should not happen
- Slog.e(TAG, "Invalid tied profile lock type: " + quality);
- }
- }
- try {
- final String alias = LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userInfo.id;
- java.security.KeyStore keyStore =
- java.security.KeyStore.getInstance("AndroidKeyStore");
- keyStore.load(null);
- if (keyStore.containsAlias(alias)) {
- keyStore.deleteEntry(alias);
- }
- } catch (KeyStoreException | NoSuchAlgorithmException |
- CertificateException | IOException e) {
- Slog.e(TAG, "Unable to remove tied profile key", e);
- }
- }
-
- boolean isWatch = mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WATCH);
- // Wear used to set DISABLE_LOCKSCREEN to 'true', but because Wear now allows accounts
- // and device management the lockscreen must be re-enabled now for users that upgrade.
- if (isWatch && getString("migrated_wear_lockscreen_disabled", null, 0) == null) {
- final int userCount = users.size();
- for (int i = 0; i < userCount; i++) {
- int id = users.get(i).id;
- setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
- }
- setString("migrated_wear_lockscreen_disabled", "true", 0);
- Slog.i(TAG, "Migrated lockscreen_disabled for Wear devices");
- }
- } catch (RemoteException re) {
- Slog.e(TAG, "Unable to migrate old data", re);
- }
- }
-
- private final void checkWritePermission(int userId) {
- mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsWrite");
- }
-
- private final void checkPasswordReadPermission(int userId) {
- mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsRead");
- }
-
- private final void checkReadPermission(String requestedKey, int userId) {
- final int callingUid = Binder.getCallingUid();
-
- for (int i = 0; i < READ_CONTACTS_PROTECTED_SETTINGS.length; i++) {
- String key = READ_CONTACTS_PROTECTED_SETTINGS[i];
- if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(READ_CONTACTS)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("uid=" + callingUid
- + " needs permission " + READ_CONTACTS + " to read "
- + requestedKey + " for user " + userId);
- }
- }
-
- for (int i = 0; i < READ_PASSWORD_PROTECTED_SETTINGS.length; i++) {
- String key = READ_PASSWORD_PROTECTED_SETTINGS[i];
- if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(PERMISSION)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("uid=" + callingUid
- + " needs permission " + PERMISSION + " to read "
- + requestedKey + " for user " + userId);
- }
- }
- }
-
- @Override
- public boolean getSeparateProfileChallengeEnabled(int userId) throws RemoteException {
- checkReadPermission(SEPARATE_PROFILE_CHALLENGE_KEY, userId);
- synchronized (mSeparateChallengeLock) {
- return getBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, false, userId);
- }
- }
-
- @Override
- public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
- String managedUserPassword) throws RemoteException {
- checkWritePermission(userId);
- synchronized (mSeparateChallengeLock) {
- setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userId);
- if (enabled) {
- mStorage.removeChildProfileLock(userId);
- removeKeystoreProfileKey(userId);
- } else {
- tieManagedProfileLockIfNecessary(userId, managedUserPassword);
- }
- }
- }
-
- @Override
- public void setBoolean(String key, boolean value, int userId) throws RemoteException {
- checkWritePermission(userId);
- setStringUnchecked(key, userId, value ? "1" : "0");
- }
-
- @Override
- public void setLong(String key, long value, int userId) throws RemoteException {
- checkWritePermission(userId);
- setStringUnchecked(key, userId, Long.toString(value));
- }
-
- @Override
- public void setString(String key, String value, int userId) throws RemoteException {
- checkWritePermission(userId);
- setStringUnchecked(key, userId, value);
- }
-
- private void setStringUnchecked(String key, int userId, String value) {
- mStorage.writeKeyValue(key, value, userId);
- if (ArrayUtils.contains(SETTINGS_TO_BACKUP, key)) {
- BackupManager.dataChanged("com.android.providers.settings");
- }
- }
-
- @Override
- public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
- checkReadPermission(key, userId);
- String value = getStringUnchecked(key, null, userId);
- return TextUtils.isEmpty(value) ?
- defaultValue : (value.equals("1") || value.equals("true"));
- }
-
- @Override
- public long getLong(String key, long defaultValue, int userId) throws RemoteException {
- checkReadPermission(key, userId);
- String value = getStringUnchecked(key, null, userId);
- return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
- }
-
- @Override
- public String getString(String key, String defaultValue, int userId) throws RemoteException {
- checkReadPermission(key, userId);
- return getStringUnchecked(key, defaultValue, userId);
- }
-
- public String getStringUnchecked(String key, String defaultValue, int userId) {
- if (Settings.Secure.LOCK_PATTERN_ENABLED.equals(key)) {
- long ident = Binder.clearCallingIdentity();
- try {
- return mLockPatternUtils.isLockPatternEnabled(userId) ? "1" : "0";
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- if (LockPatternUtils.LEGACY_LOCK_PATTERN_ENABLED.equals(key)) {
- key = Settings.Secure.LOCK_PATTERN_ENABLED;
- }
-
- return mStorage.readKeyValue(key, defaultValue, userId);
- }
-
- @Override
- public boolean havePassword(int userId) throws RemoteException {
- synchronized (mSpManager) {
- if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- long handle = getSyntheticPasswordHandleLocked(userId);
- return mSpManager.getCredentialType(handle, userId) ==
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
- }
- }
- // Do we need a permissions check here?
- return mStorage.hasPassword(userId);
- }
-
- @Override
- public boolean havePattern(int userId) throws RemoteException {
- synchronized (mSpManager) {
- if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- long handle = getSyntheticPasswordHandleLocked(userId);
- return mSpManager.getCredentialType(handle, userId) ==
- LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
- }
- }
- // Do we need a permissions check here?
- return mStorage.hasPattern(userId);
- }
-
- private boolean isUserSecure(int userId) {
- synchronized (mSpManager) {
- try {
- if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- long handle = getSyntheticPasswordHandleLocked(userId);
- return mSpManager.getCredentialType(handle, userId) !=
- LockPatternUtils.CREDENTIAL_TYPE_NONE;
- }
- } catch (RemoteException e) {
- // fall through
- }
- }
- return mStorage.hasCredential(userId);
- }
-
- private void setKeystorePassword(String password, int userHandle) {
- final KeyStore ks = KeyStore.getInstance();
- ks.onUserPasswordChanged(userHandle, password);
- }
-
- private void unlockKeystore(String password, int userHandle) {
- if (DEBUG) Slog.v(TAG, "Unlock keystore for user: " + userHandle);
- final KeyStore ks = KeyStore.getInstance();
- ks.unlock(userHandle, password);
- }
-
- @VisibleForTesting
- protected String getDecryptedPasswordForTiedProfile(int userId)
- throws KeyStoreException, UnrecoverableKeyException,
- NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
- InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException,
- CertificateException, IOException {
- if (DEBUG) Slog.v(TAG, "Get child profile decrytped key");
- byte[] storedData = mStorage.readChildProfileLock(userId);
- if (storedData == null) {
- throw new FileNotFoundException("Child profile lock file not found");
- }
- byte[] iv = Arrays.copyOfRange(storedData, 0, PROFILE_KEY_IV_SIZE);
- byte[] encryptedPassword = Arrays.copyOfRange(storedData, PROFILE_KEY_IV_SIZE,
- storedData.length);
- byte[] decryptionResult;
- java.security.KeyStore keyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
- keyStore.load(null);
- SecretKey decryptionKey = (SecretKey) keyStore.getKey(
- LockPatternUtils.PROFILE_KEY_NAME_DECRYPT + userId, null);
-
- Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
- + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
-
- cipher.init(Cipher.DECRYPT_MODE, decryptionKey, new GCMParameterSpec(128, iv));
- decryptionResult = cipher.doFinal(encryptedPassword);
- return new String(decryptionResult, StandardCharsets.UTF_8);
- }
-
- private void unlockChildProfile(int profileHandle) throws RemoteException {
- try {
- doVerifyCredential(getDecryptedPasswordForTiedProfile(profileHandle),
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- false, 0 /* no challenge */, profileHandle, null /* progressCallback */);
- } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
- | NoSuchAlgorithmException | NoSuchPaddingException
- | InvalidAlgorithmParameterException | IllegalBlockSizeException
- | BadPaddingException | CertificateException | IOException e) {
- if (e instanceof FileNotFoundException) {
- Slog.i(TAG, "Child profile key not found");
- } else {
- Slog.e(TAG, "Failed to decrypt child profile key", e);
- }
- }
- }
-
- private void unlockUser(int userId, byte[] token, byte[] secret) {
- // TODO: make this method fully async so we can update UI with progress strings
- final CountDownLatch latch = new CountDownLatch(1);
- final IProgressListener listener = new IProgressListener.Stub() {
- @Override
- public void onStarted(int id, Bundle extras) throws RemoteException {
- Log.d(TAG, "unlockUser started");
- }
-
- @Override
- public void onProgress(int id, int progress, Bundle extras) throws RemoteException {
- Log.d(TAG, "unlockUser progress " + progress);
- }
-
- @Override
- public void onFinished(int id, Bundle extras) throws RemoteException {
- Log.d(TAG, "unlockUser finished");
- latch.countDown();
- }
- };
-
- try {
- mActivityManager.unlockUser(userId, token, secret, listener);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
-
- try {
- latch.await(15, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- try {
- if (!mUserManager.getUserInfo(userId).isManagedProfile()) {
- final List<UserInfo> profiles = mUserManager.getProfiles(userId);
- for (UserInfo pi : profiles) {
- // Unlock managed profile with unified lock
- if (pi.isManagedProfile()
- && !mLockPatternUtils.isSeparateProfileChallengeEnabled(pi.id)
- && mStorage.hasChildProfileLock(pi.id)
- && mUserManager.isUserRunning(pi.id)) {
- unlockChildProfile(pi.id);
- }
- }
- }
- } catch (RemoteException e) {
- Log.d(TAG, "Failed to unlock child profile", e);
- }
- }
-
- private Map<Integer, String> getDecryptedPasswordsForAllTiedProfiles(int userId) {
- if (mUserManager.getUserInfo(userId).isManagedProfile()) {
- return null;
- }
- Map<Integer, String> result = new ArrayMap<Integer, String>();
- final List<UserInfo> profiles = mUserManager.getProfiles(userId);
- final int size = profiles.size();
- for (int i = 0; i < size; i++) {
- final UserInfo profile = profiles.get(i);
- if (!profile.isManagedProfile()) {
- continue;
- }
- final int managedUserId = profile.id;
- if (mLockPatternUtils.isSeparateProfileChallengeEnabled(managedUserId)) {
- continue;
- }
- try {
- result.put(userId, getDecryptedPasswordForTiedProfile(userId));
- } catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException
- | NoSuchPaddingException | InvalidKeyException
- | InvalidAlgorithmParameterException | IllegalBlockSizeException
- | BadPaddingException | CertificateException | IOException e) {
- // ignore
- }
- }
- return result;
- }
-
- /**
- * Synchronize all profile's work challenge of the given user if it's unified: tie or clear them
- * depending on the parent user's secure state.
- *
- * When clearing tied work challenges, a pre-computed password table for profiles are required,
- * since changing password for profiles requires existing password, and existing passwords can
- * only be computed before the parent user's password is cleared.
- *
- * Strictly this is a recursive function, since setLockCredentialInternal ends up calling this
- * method again on profiles. However the recursion is guaranteed to terminate as this method
- * terminates when the user is a managed profile.
- */
- private void synchronizeUnifiedWorkChallengeForProfiles(int userId,
- Map<Integer, String> profilePasswordMap) throws RemoteException {
- if (mUserManager.getUserInfo(userId).isManagedProfile()) {
- return;
- }
- final boolean isSecure = isUserSecure(userId);
- final List<UserInfo> profiles = mUserManager.getProfiles(userId);
- final int size = profiles.size();
- for (int i = 0; i < size; i++) {
- final UserInfo profile = profiles.get(i);
- if (profile.isManagedProfile()) {
- final int managedUserId = profile.id;
- if (mLockPatternUtils.isSeparateProfileChallengeEnabled(managedUserId)) {
- continue;
- }
- if (isSecure) {
- tieManagedProfileLockIfNecessary(managedUserId, null);
- } else {
- // We use cached work profile password computed before clearing the parent's
- // credential, otherwise they get lost
- if (profilePasswordMap != null && profilePasswordMap.containsKey(managedUserId)) {
- setLockCredentialInternal(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
- profilePasswordMap.get(managedUserId), managedUserId);
- } else {
- Slog.wtf(TAG, "clear tied profile challenges, but no password supplied.");
- // Supplying null here would lead to untrusted credential change
- setLockCredentialInternal(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, null,
- managedUserId);
- }
- mStorage.removeChildProfileLock(managedUserId);
- removeKeystoreProfileKey(managedUserId);
- }
- }
- }
- }
-
- private boolean isManagedProfileWithUnifiedLock(int userId) {
- return mUserManager.getUserInfo(userId).isManagedProfile()
- && !mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
- }
-
- private boolean isManagedProfileWithSeparatedLock(int userId) {
- return mUserManager.getUserInfo(userId).isManagedProfile()
- && mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
- }
-
- // This method should be called by LockPatternUtil only, all internal methods in this class
- // should call setLockCredentialInternal.
- @Override
- public void setLockCredential(String credential, int type, String savedCredential, int userId)
- throws RemoteException {
- checkWritePermission(userId);
- synchronized (mSeparateChallengeLock) {
- setLockCredentialInternal(credential, type, savedCredential, userId);
- setSeparateProfileChallengeEnabled(userId, true, null);
- notifyPasswordChanged(userId);
- }
- }
-
- private void setLockCredentialInternal(String credential, int credentialType,
- String savedCredential, int userId) throws RemoteException {
- // Normalize savedCredential and credential such that empty string is always represented
- // as null.
- if (TextUtils.isEmpty(savedCredential)) {
- savedCredential = null;
- }
- if (TextUtils.isEmpty(credential)) {
- credential = null;
- }
- synchronized (mSpManager) {
- if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential,
- userId);
- return;
- }
- }
- if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
- if (credential != null) {
- Slog.wtf(TAG, "CredentialType is none, but credential is non-null.");
- }
- clearUserKeyProtection(userId);
- getGateKeeperService().clearSecureUserId(userId);
- mStorage.writeCredentialHash(CredentialHash.createEmptyHash(), userId);
- setKeystorePassword(null, userId);
- fixateNewestUserKeyAuth(userId);
- synchronizeUnifiedWorkChallengeForProfiles(userId, null);
- notifyActivePasswordMetricsAvailable(null, userId);
- return;
- }
- if (credential == null) {
- throw new RemoteException("Null credential with mismatched credential type");
- }
-
- CredentialHash currentHandle = mStorage.readCredentialHash(userId);
- if (isManagedProfileWithUnifiedLock(userId)) {
- // get credential from keystore when managed profile has unified lock
- if (savedCredential == null) {
- try {
- savedCredential = getDecryptedPasswordForTiedProfile(userId);
- } catch (FileNotFoundException e) {
- Slog.i(TAG, "Child profile key not found");
- } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
- | NoSuchAlgorithmException | NoSuchPaddingException
- | InvalidAlgorithmParameterException | IllegalBlockSizeException
- | BadPaddingException | CertificateException | IOException e) {
- Slog.e(TAG, "Failed to decrypt child profile key", e);
- }
- }
- } else {
- if (currentHandle.hash == null) {
- if (savedCredential != null) {
- Slog.w(TAG, "Saved credential provided, but none stored");
- }
- savedCredential = null;
- }
- }
- synchronized (mSpManager) {
- if (shouldMigrateToSyntheticPasswordLocked(userId)) {
- initializeSyntheticPasswordLocked(currentHandle.hash, savedCredential,
- currentHandle.type, userId);
- spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential,
- userId);
- return;
- }
- }
- if (DEBUG) Slog.d(TAG, "setLockCredentialInternal: user=" + userId);
- byte[] enrolledHandle = enrollCredential(currentHandle.hash, savedCredential, credential,
- userId);
- if (enrolledHandle != null) {
- CredentialHash willStore = CredentialHash.create(enrolledHandle, credentialType);
- mStorage.writeCredentialHash(willStore, userId);
- // push new secret and auth token to vold
- GateKeeperResponse gkResponse = getGateKeeperService()
- .verifyChallenge(userId, 0, willStore.hash, credential.getBytes());
- setUserKeyProtection(userId, credential, convertResponse(gkResponse));
- fixateNewestUserKeyAuth(userId);
- // Refresh the auth token
- doVerifyCredential(credential, credentialType, true, 0, userId, null /* progressCallback */);
- synchronizeUnifiedWorkChallengeForProfiles(userId, null);
- } else {
- throw new RemoteException("Failed to enroll " +
- (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ? "password"
- : "pattern"));
- }
- }
-
- private VerifyCredentialResponse convertResponse(GateKeeperResponse gateKeeperResponse) {
- VerifyCredentialResponse response;
- int responseCode = gateKeeperResponse.getResponseCode();
- if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
- response = new VerifyCredentialResponse(gateKeeperResponse.getTimeout());
- } else if (responseCode == GateKeeperResponse.RESPONSE_OK) {
- byte[] token = gateKeeperResponse.getPayload();
- if (token == null) {
- // something's wrong if there's no payload with a challenge
- Slog.e(TAG, "verifyChallenge response had no associated payload");
- response = VerifyCredentialResponse.ERROR;
- } else {
- response = new VerifyCredentialResponse(token);
- }
- } else {
- response = VerifyCredentialResponse.ERROR;
- }
- return response;
- }
-
- @VisibleForTesting
- protected void tieProfileLockToParent(int userId, String password) {
- if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
- byte[] randomLockSeed = password.getBytes(StandardCharsets.UTF_8);
- byte[] encryptionResult;
- byte[] iv;
- try {
- KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
- keyGenerator.init(new SecureRandom());
- SecretKey secretKey = keyGenerator.generateKey();
- java.security.KeyStore keyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
- keyStore.load(null);
- try {
- keyStore.setEntry(
- LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userId,
- new java.security.KeyStore.SecretKeyEntry(secretKey),
- new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
- .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
- .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
- .build());
- keyStore.setEntry(
- LockPatternUtils.PROFILE_KEY_NAME_DECRYPT + userId,
- new java.security.KeyStore.SecretKeyEntry(secretKey),
- new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
- .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
- .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
- .setUserAuthenticationRequired(true)
- .setUserAuthenticationValidityDurationSeconds(30)
- .build());
- // Key imported, obtain a reference to it.
- SecretKey keyStoreEncryptionKey = (SecretKey) keyStore.getKey(
- LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userId, null);
- Cipher cipher = Cipher.getInstance(
- KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
- + KeyProperties.ENCRYPTION_PADDING_NONE);
- cipher.init(Cipher.ENCRYPT_MODE, keyStoreEncryptionKey);
- encryptionResult = cipher.doFinal(randomLockSeed);
- iv = cipher.getIV();
- } finally {
- // The original key can now be discarded.
- keyStore.deleteEntry(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userId);
- }
- } catch (CertificateException | UnrecoverableKeyException
- | IOException | BadPaddingException | IllegalBlockSizeException | KeyStoreException
- | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
- throw new RuntimeException("Failed to encrypt key", e);
- }
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- try {
- if (iv.length != PROFILE_KEY_IV_SIZE) {
- throw new RuntimeException("Invalid iv length: " + iv.length);
- }
- outputStream.write(iv);
- outputStream.write(encryptionResult);
- } catch (IOException e) {
- throw new RuntimeException("Failed to concatenate byte arrays", e);
- }
- mStorage.writeChildProfileLock(userId, outputStream.toByteArray());
- }
-
- private byte[] enrollCredential(byte[] enrolledHandle,
- String enrolledCredential, String toEnroll, int userId)
- throws RemoteException {
- checkWritePermission(userId);
- byte[] enrolledCredentialBytes = enrolledCredential == null
- ? null
- : enrolledCredential.getBytes();
- byte[] toEnrollBytes = toEnroll == null
- ? null
- : toEnroll.getBytes();
- GateKeeperResponse response = getGateKeeperService().enroll(userId, enrolledHandle,
- enrolledCredentialBytes, toEnrollBytes);
-
- if (response == null) {
- return null;
- }
-
- byte[] hash = response.getPayload();
- if (hash != null) {
- setKeystorePassword(toEnroll, userId);
- } else {
- // Should not happen
- Slog.e(TAG, "Throttled while enrolling a password");
- }
- return hash;
- }
-
- private void setAuthlessUserKeyProtection(int userId, byte[] key) throws RemoteException {
- if (DEBUG) Slog.d(TAG, "setAuthlessUserKeyProtectiond: user=" + userId);
- addUserKeyAuth(userId, null, key);
- }
-
- private void setUserKeyProtection(int userId, String credential, VerifyCredentialResponse vcr)
- throws RemoteException {
- if (DEBUG) Slog.d(TAG, "setUserKeyProtection: user=" + userId);
- if (vcr == null) {
- throw new RemoteException("Null response verifying a credential we just set");
- }
- if (vcr.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
- throw new RemoteException("Non-OK response verifying a credential we just set: "
- + vcr.getResponseCode());
- }
- byte[] token = vcr.getPayload();
- if (token == null) {
- throw new RemoteException("Empty payload verifying a credential we just set");
- }
- addUserKeyAuth(userId, token, secretFromCredential(credential));
- }
-
- private void clearUserKeyProtection(int userId) throws RemoteException {
- if (DEBUG) Slog.d(TAG, "clearUserKeyProtection user=" + userId);
- addUserKeyAuth(userId, null, null);
- }
-
- private static byte[] secretFromCredential(String credential) throws RemoteException {
- try {
- MessageDigest digest = MessageDigest.getInstance("SHA-512");
- // Personalize the hash
- byte[] personalization = "Android FBE credential hash"
- .getBytes(StandardCharsets.UTF_8);
- // Pad it to the block size of the hash function
- personalization = Arrays.copyOf(personalization, 128);
- digest.update(personalization);
- digest.update(credential.getBytes(StandardCharsets.UTF_8));
- return digest.digest();
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("NoSuchAlgorithmException for SHA-512");
- }
- }
-
- private void addUserKeyAuth(int userId, byte[] token, byte[] secret)
- throws RemoteException {
- final UserInfo userInfo = mUserManager.getUserInfo(userId);
- final IStorageManager storageManager = mInjector.getStorageManager();
- final long callingId = Binder.clearCallingIdentity();
- try {
- storageManager.addUserKeyAuth(userId, userInfo.serialNumber, token, secret);
- } finally {
- Binder.restoreCallingIdentity(callingId);
- }
- }
-
- private void fixateNewestUserKeyAuth(int userId)
- throws RemoteException {
- if (DEBUG) Slog.d(TAG, "fixateNewestUserKeyAuth: user=" + userId);
- final IStorageManager storageManager = mInjector.getStorageManager();
- final long callingId = Binder.clearCallingIdentity();
- try {
- storageManager.fixateNewestUserKeyAuth(userId);
- } finally {
- Binder.restoreCallingIdentity(callingId);
- }
- }
-
- @Override
- public void resetKeyStore(int userId) throws RemoteException {
- checkWritePermission(userId);
- if (DEBUG) Slog.v(TAG, "Reset keystore for user: " + userId);
- int managedUserId = -1;
- String managedUserDecryptedPassword = null;
- final List<UserInfo> profiles = mUserManager.getProfiles(userId);
- for (UserInfo pi : profiles) {
- // Unlock managed profile with unified lock
- if (pi.isManagedProfile()
- && !mLockPatternUtils.isSeparateProfileChallengeEnabled(pi.id)
- && mStorage.hasChildProfileLock(pi.id)) {
- try {
- if (managedUserId == -1) {
- managedUserDecryptedPassword = getDecryptedPasswordForTiedProfile(pi.id);
- managedUserId = pi.id;
- } else {
- // Should not happen
- Slog.e(TAG, "More than one managed profile, uid1:" + managedUserId
- + ", uid2:" + pi.id);
- }
- } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
- | NoSuchAlgorithmException | NoSuchPaddingException
- | InvalidAlgorithmParameterException | IllegalBlockSizeException
- | BadPaddingException | CertificateException | IOException e) {
- Slog.e(TAG, "Failed to decrypt child profile key", e);
- }
- }
- }
- try {
- // Clear all the users credentials could have been installed in for this user.
- for (int profileId : mUserManager.getProfileIdsWithDisabled(userId)) {
- for (int uid : SYSTEM_CREDENTIAL_UIDS) {
- mKeyStore.clearUid(UserHandle.getUid(profileId, uid));
- }
- }
- } finally {
- if (managedUserId != -1 && managedUserDecryptedPassword != null) {
- if (DEBUG) Slog.v(TAG, "Restore tied profile lock");
- tieProfileLockToParent(managedUserId, managedUserDecryptedPassword);
- }
- }
- }
-
- @Override
- public VerifyCredentialResponse checkCredential(String credential, int type, int userId,
- ICheckCredentialProgressCallback progressCallback) throws RemoteException {
- checkPasswordReadPermission(userId);
- return doVerifyCredential(credential, type, false, 0, userId, progressCallback);
- }
-
- @Override
- public VerifyCredentialResponse verifyCredential(String credential, int type, long challenge,
- int userId) throws RemoteException {
- checkPasswordReadPermission(userId);
- return doVerifyCredential(credential, type, true, challenge, userId,
- null /* progressCallback */);
- }
-
- /**
- * Verify user credential and unlock the user. Fix pattern bug by deprecating the old base zero
- * format.
- */
- private VerifyCredentialResponse doVerifyCredential(String credential, int credentialType,
- boolean hasChallenge, long challenge, int userId,
- ICheckCredentialProgressCallback progressCallback) throws RemoteException {
- if (TextUtils.isEmpty(credential)) {
- throw new IllegalArgumentException("Credential can't be null or empty");
- }
- synchronized (mSpManager) {
- if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- VerifyCredentialResponse response = spBasedDoVerifyCredentialLocked(credential,
- credentialType, hasChallenge, challenge, userId, progressCallback);
- if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
- mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
- }
- return response;
- }
- }
- CredentialHash storedHash = mStorage.readCredentialHash(userId);
- if (storedHash.type != credentialType) {
- Slog.wtf(TAG, "doVerifyCredential type mismatch with stored credential??"
- + " stored: " + storedHash.type + " passed in: " + credentialType);
- return VerifyCredentialResponse.ERROR;
- }
-
- boolean shouldReEnrollBaseZero = storedHash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN
- && storedHash.isBaseZeroPattern;
-
- String credentialToVerify;
- if (shouldReEnrollBaseZero) {
- credentialToVerify = LockPatternUtils.patternStringToBaseZero(credential);
- } else {
- credentialToVerify = credential;
- }
-
- VerifyCredentialResponse response = verifyCredential(userId, storedHash, credentialToVerify,
- hasChallenge, challenge, progressCallback);
-
- if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
- mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
- if (shouldReEnrollBaseZero) {
- setLockCredentialInternal(credential, storedHash.type, credentialToVerify, userId);
- }
- }
-
- return response;
- }
-
- @Override
- public VerifyCredentialResponse verifyTiedProfileChallenge(String credential, int type,
- long challenge, int userId) throws RemoteException {
- checkPasswordReadPermission(userId);
- if (!isManagedProfileWithUnifiedLock(userId)) {
- throw new RemoteException("User id must be managed profile with unified lock");
- }
- final int parentProfileId = mUserManager.getProfileParent(userId).id;
- // Unlock parent by using parent's challenge
- final VerifyCredentialResponse parentResponse = doVerifyCredential(
- credential,
- type,
- true /* hasChallenge */,
- challenge,
- parentProfileId,
- null /* progressCallback */);
- if (parentResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
- // Failed, just return parent's response
- return parentResponse;
- }
-
- try {
- // Unlock work profile, and work profile with unified lock must use password only
- return doVerifyCredential(getDecryptedPasswordForTiedProfile(userId),
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- true,
- challenge,
- userId, null /* progressCallback */);
- } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
- | NoSuchAlgorithmException | NoSuchPaddingException
- | InvalidAlgorithmParameterException | IllegalBlockSizeException
- | BadPaddingException | CertificateException | IOException e) {
- Slog.e(TAG, "Failed to decrypt child profile key", e);
- throw new RemoteException("Unable to get tied profile token");
- }
- }
-
- /**
- * Lowest-level credential verification routine that talks to GateKeeper. If verification
- * passes, unlock the corresponding user and keystore. Also handles the migration from legacy
- * hash to GK.
- */
- private VerifyCredentialResponse verifyCredential(int userId, CredentialHash storedHash,
- String credential, boolean hasChallenge, long challenge,
- ICheckCredentialProgressCallback progressCallback) throws RemoteException {
- if ((storedHash == null || storedHash.hash.length == 0) && TextUtils.isEmpty(credential)) {
- // don't need to pass empty credentials to GateKeeper
- return VerifyCredentialResponse.OK;
- }
-
- if (storedHash == null || TextUtils.isEmpty(credential)) {
- return VerifyCredentialResponse.ERROR;
- }
-
- // We're potentially going to be doing a bunch of disk I/O below as part
- // of unlocking the user, so yell if calling from the main thread.
- StrictMode.noteDiskRead();
-
- if (storedHash.version == CredentialHash.VERSION_LEGACY) {
- final byte[] hash;
- if (storedHash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
- hash = LockPatternUtils.patternToHash(LockPatternUtils.stringToPattern(credential));
- } else {
- hash = mLockPatternUtils.passwordToHash(credential, userId);
- }
- if (Arrays.equals(hash, storedHash.hash)) {
- if (storedHash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
- unlockKeystore(LockPatternUtils.patternStringToBaseZero(credential), userId);
- } else {
- unlockKeystore(credential, userId);
- }
- // Users with legacy credentials don't have credential-backed
- // FBE keys, so just pass through a fake token/secret
- Slog.i(TAG, "Unlocking user with fake token: " + userId);
- final byte[] fakeToken = String.valueOf(userId).getBytes();
- unlockUser(userId, fakeToken, fakeToken);
-
- // migrate credential to GateKeeper
- setLockCredentialInternal(credential, storedHash.type, null, userId);
- if (!hasChallenge) {
- notifyActivePasswordMetricsAvailable(credential, userId);
- return VerifyCredentialResponse.OK;
- }
- // Fall through to get the auth token. Technically this should never happen,
- // as a user that had a legacy credential would have to unlock their device
- // before getting to a flow with a challenge, but supporting for consistency.
- } else {
- return VerifyCredentialResponse.ERROR;
- }
- }
- GateKeeperResponse gateKeeperResponse = getGateKeeperService()
- .verifyChallenge(userId, challenge, storedHash.hash, credential.getBytes());
- VerifyCredentialResponse response = convertResponse(gateKeeperResponse);
- boolean shouldReEnroll = gateKeeperResponse.getShouldReEnroll();
-
- if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-
- // credential has matched
-
- if (progressCallback != null) {
- progressCallback.onCredentialVerified();
- }
- notifyActivePasswordMetricsAvailable(credential, userId);
- unlockKeystore(credential, userId);
-
- Slog.i(TAG, "Unlocking user " + userId + " with token length "
- + response.getPayload().length);
- unlockUser(userId, response.getPayload(), secretFromCredential(credential));
-
- if (isManagedProfileWithSeparatedLock(userId)) {
- TrustManager trustManager =
- (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
- trustManager.setDeviceLockedForUser(userId, false);
- }
- if (shouldReEnroll) {
- setLockCredentialInternal(credential, storedHash.type, credential, userId);
- } else {
- // Now that we've cleared of all required GK migration, let's do the final
- // migration to synthetic password.
- synchronized (mSpManager) {
- if (shouldMigrateToSyntheticPasswordLocked(userId)) {
- AuthenticationToken auth = initializeSyntheticPasswordLocked(
- storedHash.hash, credential, storedHash.type, userId);
- activateEscrowTokens(auth, userId);
- }
- }
- }
- } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
- if (response.getTimeout() > 0) {
- requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
- }
- }
-
- return response;
- }
-
- private void notifyActivePasswordMetricsAvailable(String password, @UserIdInt int userId) {
- final PasswordMetrics metrics;
- if (password == null) {
- metrics = new PasswordMetrics();
- } else {
- metrics = PasswordMetrics.computeForPassword(password);
- metrics.quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(userId);
- }
-
- // Asynchronous to avoid dead lock
- mHandler.post(() -> {
- DevicePolicyManager dpm = (DevicePolicyManager)
- mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- dpm.setActivePasswordState(metrics, userId);
- });
- }
-
- /**
- * Call after {@link #notifyActivePasswordMetricsAvailable} so metrics are updated before
- * reporting the password changed.
- */
- private void notifyPasswordChanged(@UserIdInt int userId) {
- // Same handler as notifyActivePasswordMetricsAvailable to ensure correct ordering
- mHandler.post(() -> {
- DevicePolicyManager dpm = (DevicePolicyManager)
- mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- dpm.reportPasswordChanged(userId);
- });
- }
-
- @Override
- public boolean checkVoldPassword(int userId) throws RemoteException {
- if (!mFirstCallToVold) {
- return false;
- }
- mFirstCallToVold = false;
-
- checkPasswordReadPermission(userId);
-
- // There's no guarantee that this will safely connect, but if it fails
- // we will simply show the lock screen when we shouldn't, so relatively
- // benign. There is an outside chance something nasty would happen if
- // this service restarted before vold stales out the password in this
- // case. The nastiness is limited to not showing the lock screen when
- // we should, within the first minute of decrypting the phone if this
- // service can't connect to vold, it restarts, and then the new instance
- // does successfully connect.
- final IStorageManager service = mInjector.getStorageManager();
- String password;
- long identity = Binder.clearCallingIdentity();
- try {
- password = service.getPassword();
- service.clearPassword();
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- if (password == null) {
- return false;
- }
-
- try {
- if (mLockPatternUtils.isLockPatternEnabled(userId)) {
- if (checkCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, userId,
- null /* progressCallback */)
- .getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
- return true;
- }
- }
- } catch (Exception e) {
- }
-
- try {
- if (mLockPatternUtils.isLockPasswordEnabled(userId)) {
- if (checkCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, userId,
- null /* progressCallback */)
- .getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
- return true;
- }
- }
- } catch (Exception e) {
- }
-
- return false;
- }
-
- private void removeUser(int userId, boolean unknownUser) {
- mStorage.removeUser(userId);
- mStrongAuth.removeUser(userId);
-
- final KeyStore ks = KeyStore.getInstance();
- ks.onUserRemoved(userId);
-
- try {
- final IGateKeeperService gk = getGateKeeperService();
- if (gk != null) {
- gk.clearSecureUserId(userId);
- }
- } catch (RemoteException ex) {
- Slog.w(TAG, "unable to clear GK secure user id");
- }
- if (unknownUser || mUserManager.getUserInfo(userId).isManagedProfile()) {
- removeKeystoreProfileKey(userId);
- }
- }
-
- private void removeKeystoreProfileKey(int targetUserId) {
- if (DEBUG) Slog.v(TAG, "Remove keystore profile key for user: " + targetUserId);
- try {
- java.security.KeyStore keyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
- keyStore.load(null);
- keyStore.deleteEntry(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + targetUserId);
- keyStore.deleteEntry(LockPatternUtils.PROFILE_KEY_NAME_DECRYPT + targetUserId);
- } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
- | IOException e) {
- // We have tried our best to remove all keys
- Slog.e(TAG, "Unable to remove keystore profile key for user:" + targetUserId, e);
- }
- }
-
- @Override
- public void registerStrongAuthTracker(IStrongAuthTracker tracker) {
- checkPasswordReadPermission(UserHandle.USER_ALL);
- mStrongAuth.registerStrongAuthTracker(tracker);
- }
-
- @Override
- public void unregisterStrongAuthTracker(IStrongAuthTracker tracker) {
- checkPasswordReadPermission(UserHandle.USER_ALL);
- mStrongAuth.unregisterStrongAuthTracker(tracker);
- }
-
- @Override
- public void requireStrongAuth(int strongAuthReason, int userId) {
- checkWritePermission(userId);
- mStrongAuth.requireStrongAuth(strongAuthReason, userId);
- }
-
- @Override
- public void userPresent(int userId) {
- checkWritePermission(userId);
- mStrongAuth.reportUnlock(userId);
- }
-
- @Override
- public int getStrongAuthForUser(int userId) {
- checkPasswordReadPermission(userId);
- return mStrongAuthTracker.getStrongAuthForUser(userId);
- }
-
- private boolean isCallerShell() {
- final int callingUid = Binder.getCallingUid();
- return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
- }
-
- private void enforceShell() {
- if (!isCallerShell()) {
- throw new SecurityException("Caller must be shell");
- }
- }
-
- @Override
- public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ShellCallback callback, ResultReceiver resultReceiver)
- throws RemoteException {
- enforceShell();
- final long origId = Binder.clearCallingIdentity();
- try {
- (new LockSettingsShellCommand(mContext, new LockPatternUtils(mContext))).exec(
- this, in, out, err, args, callback, resultReceiver);
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
-
- private static final String[] VALID_SETTINGS = new String[] {
- LockPatternUtils.LOCKOUT_PERMANENT_KEY,
- LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
- LockPatternUtils.PATTERN_EVER_CHOSEN_KEY,
- LockPatternUtils.PASSWORD_TYPE_KEY,
- LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
- LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
- LockPatternUtils.DISABLE_LOCKSCREEN_KEY,
- LockPatternUtils.LOCKSCREEN_OPTIONS,
- LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
- LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY,
- LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS,
- LockPatternUtils.PASSWORD_HISTORY_KEY,
- Secure.LOCK_PATTERN_ENABLED,
- Secure.LOCK_BIOMETRIC_WEAK_FLAGS,
- Secure.LOCK_PATTERN_VISIBLE,
- Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED
- };
-
- // Reading these settings needs the contacts permission
- private static final String[] READ_CONTACTS_PROTECTED_SETTINGS = new String[] {
- Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
- Secure.LOCK_SCREEN_OWNER_INFO
- };
-
- // Reading these settings needs the same permission as checking the password
- private static final String[] READ_PASSWORD_PROTECTED_SETTINGS = new String[] {
- LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
- LockPatternUtils.PASSWORD_HISTORY_KEY,
- LockPatternUtils.PASSWORD_TYPE_KEY,
- SEPARATE_PROFILE_CHALLENGE_KEY
- };
-
- private static final String[] SETTINGS_TO_BACKUP = new String[] {
- Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
- Secure.LOCK_SCREEN_OWNER_INFO
- };
-
- private class GateKeeperDiedRecipient implements IBinder.DeathRecipient {
- @Override
- public void binderDied() {
- mGateKeeperService.asBinder().unlinkToDeath(this, 0);
- mGateKeeperService = null;
- }
- }
-
- protected synchronized IGateKeeperService getGateKeeperService()
- throws RemoteException {
- if (mGateKeeperService != null) {
- return mGateKeeperService;
- }
-
- final IBinder service = ServiceManager.getService(Context.GATEKEEPER_SERVICE);
- if (service != null) {
- service.linkToDeath(new GateKeeperDiedRecipient(), 0);
- mGateKeeperService = IGateKeeperService.Stub.asInterface(service);
- return mGateKeeperService;
- }
-
- Slog.e(TAG, "Unable to acquire GateKeeperService");
- return null;
- }
-
- /**
- * Precondition: vold and keystore unlocked.
- *
- * Create new synthetic password, set up synthetic password blob protected by the supplied
- * user credential, and make the newly-created SP blob active.
- *
- * The invariant under a synthetic password is:
- * 1. If user credential exists, then both vold and keystore and protected with keys derived
- * from the synthetic password.
- * 2. If user credential does not exist, vold and keystore protection are cleared. This is to
- * make it consistent with current behaviour. It also allows ActivityManager to call
- * unlockUser() with empty secret.
- * 3. Once a user is migrated to have synthetic password, its value will never change, no matter
- * whether the user changes his lockscreen PIN or clear/reset it. When the user clears its
- * lockscreen PIN, we still maintain the existing synthetic password in a password blob
- * protected by a default PIN. The only exception is when the DPC performs an untrusted
- * credential change, in which case we have no way to derive the existing synthetic password
- * and has to create a new one.
- * 4. The user SID is linked with synthetic password, but its cleared/re-created when the user
- * clears/re-creates his lockscreen PIN.
- *
- *
- * Different cases of calling this method:
- * 1. credentialHash != null
- * This implies credential != null, a new SP blob will be provisioned, and existing SID
- * migrated to associate with the new SP.
- * This happens during a normal migration case when the user currently has password.
- *
- * 2. credentialhash == null and credential == null
- * A new SP blob and a new SID will be created, while the user has no credentials.
- * This can happens when we are activating an escrow token on a unsecured device, during
- * which we want to create the SP structure with an empty user credential.
- *
- * 3. credentialhash == null and credential != null
- * This is the untrusted credential reset, OR the user sets a new lockscreen password
- * FOR THE FIRST TIME on a SP-enabled device. New credential and new SID will be created
- */
- private AuthenticationToken initializeSyntheticPasswordLocked(byte[] credentialHash,
- String credential, int credentialType, int userId) throws RemoteException {
- Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
- AuthenticationToken auth = mSpManager.newSyntheticPasswordAndSid(getGateKeeperService(),
- credentialHash, credential, userId);
- if (auth == null) {
- Slog.wtf(TAG, "initializeSyntheticPasswordLocked returns null auth token");
- return null;
- }
- long handle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
- credential, credentialType, auth, userId);
- if (credential != null) {
- if (credentialHash == null) {
- // Since when initializing SP, we didn't provide an existing password handle
- // for it to migrate SID, we need to create a new SID for the user.
- mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
- }
- mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId);
- setAuthlessUserKeyProtection(userId, auth.deriveDiskEncryptionKey());
- setKeystorePassword(auth.deriveKeyStorePassword(), userId);
- } else {
- clearUserKeyProtection(userId);
- setKeystorePassword(null, userId);
- getGateKeeperService().clearSecureUserId(userId);
- }
- fixateNewestUserKeyAuth(userId);
- setLong(SYNTHETIC_PASSWORD_HANDLE_KEY, handle, userId);
- return auth;
- }
-
- private long getSyntheticPasswordHandleLocked(int userId) {
- try {
- return getLong(SYNTHETIC_PASSWORD_HANDLE_KEY, 0, userId);
- } catch (RemoteException e) {
- return SyntheticPasswordManager.DEFAULT_HANDLE;
- }
- }
-
- private boolean isSyntheticPasswordBasedCredentialLocked(int userId) throws RemoteException {
- long handle = getSyntheticPasswordHandleLocked(userId);
- // This is a global setting
- long enabled = getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM);
- return enabled != 0 && handle != SyntheticPasswordManager.DEFAULT_HANDLE;
- }
-
- private boolean shouldMigrateToSyntheticPasswordLocked(int userId) throws RemoteException {
- long handle = getSyntheticPasswordHandleLocked(userId);
- // This is a global setting
- long enabled = getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM);
- return enabled != 0 && handle == SyntheticPasswordManager.DEFAULT_HANDLE;
- }
-
- private void enableSyntheticPasswordLocked() throws RemoteException {
- setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1, UserHandle.USER_SYSTEM);
- }
-
- private VerifyCredentialResponse spBasedDoVerifyCredentialLocked(String userCredential, int
- credentialType, boolean hasChallenge, long challenge, int userId,
- ICheckCredentialProgressCallback progressCallback) throws RemoteException {
- if (DEBUG) Slog.d(TAG, "spBasedDoVerifyCredentialLocked: user=" + userId);
- if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
- userCredential = null;
- }
- long handle = getSyntheticPasswordHandleLocked(userId);
- AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
- getGateKeeperService(), handle, userCredential, userId);
-
- VerifyCredentialResponse response = authResult.gkResponse;
- if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
- // credential has matched
- // perform verifyChallenge with synthetic password which generates the real auth
- // token for the current user
- response = mSpManager.verifyChallenge(getGateKeeperService(), authResult.authToken,
- challenge, userId);
- if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
- Slog.wtf(TAG, "verifyChallenge with SP failed.");
- return VerifyCredentialResponse.ERROR;
- }
- if (progressCallback != null) {
- progressCallback.onCredentialVerified();
- }
- notifyActivePasswordMetricsAvailable(userCredential, userId);
- unlockKeystore(authResult.authToken.deriveKeyStorePassword(), userId);
-
- final byte[] secret = authResult.authToken.deriveDiskEncryptionKey();
- Slog.i(TAG, "Unlocking user " + userId + " with secret only, length " + secret.length);
- unlockUser(userId, null, secret);
-
- if (isManagedProfileWithSeparatedLock(userId)) {
- TrustManager trustManager =
- (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
- trustManager.setDeviceLockedForUser(userId, false);
- }
- activateEscrowTokens(authResult.authToken, userId);
- } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
- if (response.getTimeout() > 0) {
- requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
- }
- }
-
- return response;
- }
-
- /**
- * Change the user's lockscreen password by creating a new SP blob and update the handle, based
- * on an existing authentication token. Even though a new SP blob is created, the underlying
- * synthetic password is never changed.
- *
- * When clearing credential, we keep the SP unchanged, but clear its password handle so its
- * SID is gone. We also clear password from (software-based) keystore and vold, which will be
- * added back when new password is set in future.
- */
- private long setLockCredentialWithAuthTokenLocked(String credential, int credentialType,
- AuthenticationToken auth, int userId) throws RemoteException {
- if (DEBUG) Slog.d(TAG, "setLockCredentialWithAuthTokenLocked: user=" + userId);
- long newHandle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
- credential, credentialType, auth, userId);
- final Map<Integer, String> profilePasswords;
- if (credential != null) {
- // // not needed by synchronizeUnifiedWorkChallengeForProfiles()
- profilePasswords = null;
-
- if (mSpManager.hasSidForUser(userId)) {
- // We are changing password of a secured device, nothing more needed as
- // createPasswordBasedSyntheticPassword has already taken care of maintaining
- // the password handle and SID unchanged.
-
- //refresh auth token
- mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId);
- } else {
- // A new password is set on a previously-unsecured device, we need to generate
- // a new SID, and re-add keys to vold and keystore.
- mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
- mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId);
- setAuthlessUserKeyProtection(userId, auth.deriveDiskEncryptionKey());
- fixateNewestUserKeyAuth(userId);
- setKeystorePassword(auth.deriveKeyStorePassword(), userId);
- }
- } else {
- // Cache all profile password if they use unified work challenge. This will later be
- // used to clear the profile's password in synchronizeUnifiedWorkChallengeForProfiles()
- profilePasswords = getDecryptedPasswordsForAllTiedProfiles(userId);
-
- // we are clearing password of a secured device, so need to nuke SID as well.
- mSpManager.clearSidForUser(userId);
- getGateKeeperService().clearSecureUserId(userId);
- // Clear key from vold so ActivityManager can just unlock the user with empty secret
- // during boot.
- clearUserKeyProtection(userId);
- fixateNewestUserKeyAuth(userId);
- setKeystorePassword(null, userId);
- }
- setLong(SYNTHETIC_PASSWORD_HANDLE_KEY, newHandle, userId);
- synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
- return newHandle;
- }
-
- private void spBasedSetLockCredentialInternalLocked(String credential, int credentialType,
- String savedCredential, int userId) throws RemoteException {
- if (DEBUG) Slog.d(TAG, "spBasedSetLockCredentialInternalLocked: user=" + userId);
- if (isManagedProfileWithUnifiedLock(userId)) {
- // get credential from keystore when managed profile has unified lock
- try {
- savedCredential = getDecryptedPasswordForTiedProfile(userId);
- } catch (FileNotFoundException e) {
- Slog.i(TAG, "Child profile key not found");
- } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
- | NoSuchAlgorithmException | NoSuchPaddingException
- | InvalidAlgorithmParameterException | IllegalBlockSizeException
- | BadPaddingException | CertificateException | IOException e) {
- Slog.e(TAG, "Failed to decrypt child profile key", e);
- }
- }
- long handle = getSyntheticPasswordHandleLocked(userId);
- AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
- getGateKeeperService(), handle, savedCredential, userId);
- VerifyCredentialResponse response = authResult.gkResponse;
- AuthenticationToken auth = authResult.authToken;
- if (auth != null) {
- // We are performing a trusted credential change i.e. a correct existing credential
- // is provided
- setLockCredentialWithAuthTokenLocked(credential, credentialType, auth, userId);
- mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
- } else if (response != null
- && response.getResponseCode() == VerifyCredentialResponse.RESPONSE_ERROR){
- // We are performing an untrusted credential change i.e. by DevicePolicyManager.
- // So provision a new SP and SID. This would invalidate existing escrow tokens.
- // Still support this for now but this flow will be removed in the next release.
-
- Slog.w(TAG, "Untrusted credential change invoked");
- initializeSyntheticPasswordLocked(null, credential, credentialType, userId);
- synchronizeUnifiedWorkChallengeForProfiles(userId, null);
- mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
- } else /* response == null || responseCode == VerifyCredentialResponse.RESPONSE_RETRY */ {
- Slog.w(TAG, "spBasedSetLockCredentialInternalLocked: " +
- (response != null ? "rate limit exceeded" : "failed"));
- return;
- }
- notifyActivePasswordMetricsAvailable(credential, userId);
-
- }
-
- @Override
- public long addEscrowToken(byte[] token, int userId) throws RemoteException {
- ensureCallerSystemUid();
- if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId);
- synchronized (mSpManager) {
- enableSyntheticPasswordLocked();
- // Migrate to synthetic password based credentials if the user has no password,
- // the token can then be activated immediately.
- AuthenticationToken auth = null;
- if (!isUserSecure(userId)) {
- if (shouldMigrateToSyntheticPasswordLocked(userId)) {
- auth = initializeSyntheticPasswordLocked(null, null,
- LockPatternUtils.CREDENTIAL_TYPE_NONE, userId);
- } else /* isSyntheticPasswordBasedCredentialLocked(userId) */ {
- long pwdHandle = getSyntheticPasswordHandleLocked(userId);
- auth = mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(),
- pwdHandle, null, userId).authToken;
- }
- }
- if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- disableEscrowTokenOnNonManagedDevicesIfNeeded(userId);
- if (!mSpManager.hasEscrowData(userId)) {
- throw new SecurityException("Escrow token is disabled on the current user");
- }
- }
- long handle = mSpManager.createTokenBasedSyntheticPassword(token, userId);
- if (auth != null) {
- mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId);
- }
- return handle;
- }
- }
-
- private void activateEscrowTokens(AuthenticationToken auth, int userId) throws RemoteException {
- if (DEBUG) Slog.d(TAG, "activateEscrowTokens: user=" + userId);
- disableEscrowTokenOnNonManagedDevicesIfNeeded(userId);
- synchronized (mSpManager) {
- for (long handle : mSpManager.getPendingTokensForUser(userId)) {
- Slog.i(TAG, String.format("activateEscrowTokens: %x %d ", handle, userId));
- mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId);
- }
- }
- }
-
- @Override
- public boolean isEscrowTokenActive(long handle, int userId) throws RemoteException {
- ensureCallerSystemUid();
- synchronized (mSpManager) {
- return mSpManager.existsHandle(handle, userId);
- }
- }
-
- @Override
- public boolean removeEscrowToken(long handle, int userId) throws RemoteException {
- ensureCallerSystemUid();
- synchronized (mSpManager) {
- if (handle == getSyntheticPasswordHandleLocked(userId)) {
- Slog.w(TAG, "Cannot remove password handle");
- return false;
- }
- if (mSpManager.removePendingToken(handle, userId)) {
- return true;
- }
- if (mSpManager.existsHandle(handle, userId)) {
- mSpManager.destroyTokenBasedSyntheticPassword(handle, userId);
- return true;
- } else {
- return false;
- }
- }
- }
-
- @Override
- public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle,
- byte[] token, int userId) throws RemoteException {
- ensureCallerSystemUid();
- boolean result;
- synchronized (mSpManager) {
- if (!mSpManager.hasEscrowData(userId)) {
- throw new SecurityException("Escrow token is disabled on the current user");
- }
- result = setLockCredentialWithTokenInternal(credential, type, tokenHandle, token,
- userId);
- }
- if (result) {
- synchronized (mSeparateChallengeLock) {
- setSeparateProfileChallengeEnabled(userId, true, null);
- }
- notifyPasswordChanged(userId);
- }
- return result;
- }
-
- private boolean setLockCredentialWithTokenInternal(String credential, int type,
- long tokenHandle, byte[] token, int userId) throws RemoteException {
- synchronized (mSpManager) {
- AuthenticationResult result = mSpManager.unwrapTokenBasedSyntheticPassword(
- getGateKeeperService(), tokenHandle, token, userId);
- if (result.authToken == null) {
- Slog.w(TAG, "Invalid escrow token supplied");
- return false;
- }
- long oldHandle = getSyntheticPasswordHandleLocked(userId);
- setLockCredentialWithAuthTokenLocked(credential, type, result.authToken, userId);
- mSpManager.destroyPasswordBasedSyntheticPassword(oldHandle, userId);
- return true;
- }
- }
-
- @Override
- public void unlockUserWithToken(long tokenHandle, byte[] token, int userId)
- throws RemoteException {
- ensureCallerSystemUid();
- AuthenticationResult authResult;
- synchronized (mSpManager) {
- if (!mSpManager.hasEscrowData(userId)) {
- throw new SecurityException("Escrow token is disabled on the current user");
- }
- authResult = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(),
- tokenHandle, token, userId);
- if (authResult.authToken == null) {
- Slog.w(TAG, "Invalid escrow token supplied");
- return;
- }
- }
- unlockUser(userId, null, authResult.authToken.deriveDiskEncryptionKey());
- }
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args){
- if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
-
- pw.println("Current lock settings service state:");
- pw.println(String.format("SP Enabled = %b",
- mLockPatternUtils.isSyntheticPasswordEnabled()));
-
- List<UserInfo> users = mUserManager.getUsers();
- for (int user = 0; user < users.size(); user++) {
- final int userId = users.get(user).id;
- pw.println(" User " + userId);
- synchronized (mSpManager) {
- pw.println(String.format(" SP Handle = %x",
- getSyntheticPasswordHandleLocked(userId)));
- }
- try {
- pw.println(String.format(" SID = %x",
- getGateKeeperService().getSecureUserId(userId)));
- } catch (RemoteException e) {
- // ignore.
- }
- }
- }
-
- private void disableEscrowTokenOnNonManagedDevicesIfNeeded(int userId) {
- long ident = Binder.clearCallingIdentity();
- try {
- // Managed profile should have escrow enabled
- if (mUserManager.getUserInfo(userId).isManagedProfile()) {
- Slog.i(TAG, "Managed profile can have escrow token");
- return;
- }
- DevicePolicyManager dpm = mInjector.getDevicePolicyManager();
- // Devices with Device Owner should have escrow enabled on all users.
- if (dpm.getDeviceOwnerComponentOnAnyUser() != null) {
- Slog.i(TAG, "Corp-owned device can have escrow token");
- return;
- }
- // We could also have a profile owner on the given (non-managed) user for unicorn cases
- if (dpm.getProfileOwnerAsUser(userId) != null) {
- Slog.i(TAG, "User with profile owner can have escrow token");
- return;
- }
- // If the device is yet to be provisioned (still in SUW), there is still
- // a chance that Device Owner will be set on the device later, so postpone
- // disabling escrow token for now.
- if (!dpm.isDeviceProvisioned()) {
- Slog.i(TAG, "Postpone disabling escrow tokens until device is provisioned");
- return;
- }
-
- // Escrow tokens are enabled on automotive builds.
- if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
- return;
- }
-
- // Disable escrow token permanently on all other device/user types.
- Slog.i(TAG, "Disabling escrow token on user " + userId);
- if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- mSpManager.destroyEscrowData(userId);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "disableEscrowTokenOnNonManagedDevices", e);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- private void ensureCallerSystemUid() throws SecurityException {
- final int callingUid = mInjector.binderGetCallingUid();
- if (callingUid != Process.SYSTEM_UID) {
- throw new SecurityException("Only system can call this API.");
- }
- }
-}
diff --git a/services/core/java/com/android/server/LockSettingsShellCommand.java b/services/core/java/com/android/server/LockSettingsShellCommand.java
deleted file mode 100644
index 9d671e39..0000000
--- a/services/core/java/com/android/server/LockSettingsShellCommand.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server;
-
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
-import static com.android.internal.widget.LockPatternUtils.stringToPattern;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.os.RemoteException;
-import android.os.ShellCommand;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
-
-class LockSettingsShellCommand extends ShellCommand {
-
- private static final String COMMAND_SET_PATTERN = "set-pattern";
- private static final String COMMAND_SET_PIN = "set-pin";
- private static final String COMMAND_SET_PASSWORD = "set-password";
- private static final String COMMAND_CLEAR = "clear";
- private static final String COMMAND_SP = "sp";
- private static final String COMMAND_SET_DISABLED = "set-disabled";
-
- private int mCurrentUserId;
- private final LockPatternUtils mLockPatternUtils;
- private final Context mContext;
- private String mOld = "";
- private String mNew = "";
-
- LockSettingsShellCommand(Context context, LockPatternUtils lockPatternUtils) {
- mContext = context;
- mLockPatternUtils = lockPatternUtils;
- }
-
- @Override
- public int onCommand(String cmd) {
- try {
- mCurrentUserId = ActivityManager.getService().getCurrentUser().id;
-
- parseArgs();
- if (!checkCredential()) {
- return -1;
- }
- switch (cmd) {
- case COMMAND_SET_PATTERN:
- runSetPattern();
- break;
- case COMMAND_SET_PASSWORD:
- runSetPassword();
- break;
- case COMMAND_SET_PIN:
- runSetPin();
- break;
- case COMMAND_CLEAR:
- runClear();
- break;
- case COMMAND_SP:
- runEnableSp();
- break;
- case COMMAND_SET_DISABLED:
- runSetDisabled();
- break;
- default:
- getErrPrintWriter().println("Unknown command: " + cmd);
- break;
- }
- return 0;
- } catch (Exception e) {
- getErrPrintWriter().println("Error while executing command: " + cmd);
- e.printStackTrace(getErrPrintWriter());
- return -1;
- }
- }
-
- @Override
- public void onHelp() {
- }
-
- private void parseArgs() {
- String opt;
- while ((opt = getNextOption()) != null) {
- if ("--old".equals(opt)) {
- mOld = getNextArgRequired();
- } else if ("--user".equals(opt)) {
- mCurrentUserId = Integer.parseInt(getNextArgRequired());
- } else {
- getErrPrintWriter().println("Unknown option: " + opt);
- throw new IllegalArgumentException();
- }
- }
- mNew = getNextArg();
- }
-
- private void runEnableSp() {
- if (mNew != null) {
- mLockPatternUtils.enableSyntheticPassword();
- getOutPrintWriter().println("Synthetic password enabled");
- }
- getOutPrintWriter().println(String.format("SP Enabled = %b",
- mLockPatternUtils.isSyntheticPasswordEnabled()));
- }
-
- private void runSetPattern() throws RemoteException {
- mLockPatternUtils.saveLockPattern(stringToPattern(mNew), mOld, mCurrentUserId);
- getOutPrintWriter().println("Pattern set to '" + mNew + "'");
- }
-
- private void runSetPassword() throws RemoteException {
- mLockPatternUtils.saveLockPassword(mNew, mOld, PASSWORD_QUALITY_ALPHABETIC, mCurrentUserId);
- getOutPrintWriter().println("Password set to '" + mNew + "'");
- }
-
- private void runSetPin() throws RemoteException {
- mLockPatternUtils.saveLockPassword(mNew, mOld, PASSWORD_QUALITY_NUMERIC, mCurrentUserId);
- getOutPrintWriter().println("Pin set to '" + mNew + "'");
- }
-
- private void runClear() throws RemoteException {
- mLockPatternUtils.clearLock(mOld, mCurrentUserId);
- getOutPrintWriter().println("Lock credential cleared");
- }
-
- private void runSetDisabled() throws RemoteException {
- final boolean disabled = Boolean.parseBoolean(mNew);
- mLockPatternUtils.setLockScreenDisabled(disabled, mCurrentUserId);
- getOutPrintWriter().println("Lock screen disabled set to " + disabled);
- }
-
- private boolean checkCredential() throws RemoteException, RequestThrottledException {
- final boolean havePassword = mLockPatternUtils.isLockPasswordEnabled(mCurrentUserId);
- final boolean havePattern = mLockPatternUtils.isLockPatternEnabled(mCurrentUserId);
- if (havePassword || havePattern) {
- boolean result;
- if (havePassword) {
- result = mLockPatternUtils.checkPassword(mOld, mCurrentUserId);
- } else {
- result = mLockPatternUtils.checkPattern(stringToPattern(mOld),
- mCurrentUserId);
- }
- if (result) {
- return true;
- } else {
- getOutPrintWriter().println("Old password '" + mOld + "' didn't match");
- return false;
- }
- } else {
- return true;
- }
- }
-}
diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java
deleted file mode 100644
index fe9bfd0..0000000
--- a/services/core/java/com/android/server/LockSettingsStorage.java
+++ /dev/null
@@ -1,717 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server;
-
-import static android.content.Context.USER_SERVICE;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.os.Environment;
-import android.os.UserManager;
-import android.os.storage.StorageManager;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.widget.LockPatternUtils;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-
-/**
- * Storage for the lock settings service.
- */
-class LockSettingsStorage {
-
- private static final String TAG = "LockSettingsStorage";
- private static final String TABLE = "locksettings";
- private static final boolean DEBUG = false;
-
- private static final String COLUMN_KEY = "name";
- private static final String COLUMN_USERID = "user";
- private static final String COLUMN_VALUE = "value";
-
- private static final String[] COLUMNS_FOR_QUERY = {
- COLUMN_VALUE
- };
- private static final String[] COLUMNS_FOR_PREFETCH = {
- COLUMN_KEY, COLUMN_VALUE
- };
-
- private static final String SYSTEM_DIRECTORY = "/system/";
- private static final String LOCK_PATTERN_FILE = "gatekeeper.pattern.key";
- private static final String BASE_ZERO_LOCK_PATTERN_FILE = "gatekeeper.gesture.key";
- private static final String LEGACY_LOCK_PATTERN_FILE = "gesture.key";
- private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
- private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key";
- private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
-
- private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
-
- private static final Object DEFAULT = new Object();
-
- private final DatabaseHelper mOpenHelper;
- private final Context mContext;
- private final Cache mCache = new Cache();
- private final Object mFileWriteLock = new Object();
-
- @VisibleForTesting
- public static class CredentialHash {
- static final int VERSION_LEGACY = 0;
- static final int VERSION_GATEKEEPER = 1;
-
- private CredentialHash(byte[] hash, int type, int version) {
- if (type != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
- if (hash == null) {
- throw new RuntimeException("Empty hash for CredentialHash");
- }
- } else /* type == LockPatternUtils.CREDENTIAL_TYPE_NONE */ {
- if (hash != null) {
- throw new RuntimeException("None type CredentialHash should not have hash");
- }
- }
- this.hash = hash;
- this.type = type;
- this.version = version;
- this.isBaseZeroPattern = false;
- }
-
- private CredentialHash(byte[] hash, boolean isBaseZeroPattern) {
- this.hash = hash;
- this.type = LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
- this.version = VERSION_GATEKEEPER;
- this.isBaseZeroPattern = isBaseZeroPattern;
- }
-
- static CredentialHash create(byte[] hash, int type) {
- if (type == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
- throw new RuntimeException("Bad type for CredentialHash");
- }
- return new CredentialHash(hash, type, VERSION_GATEKEEPER);
- }
-
- static CredentialHash createEmptyHash() {
- return new CredentialHash(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
- VERSION_GATEKEEPER);
- }
-
- byte[] hash;
- int type;
- int version;
- boolean isBaseZeroPattern;
- }
-
- public LockSettingsStorage(Context context) {
- mContext = context;
- mOpenHelper = new DatabaseHelper(context);
- }
-
- public void setDatabaseOnCreateCallback(Callback callback) {
- mOpenHelper.setCallback(callback);
- }
-
- public void writeKeyValue(String key, String value, int userId) {
- writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId);
- }
-
- public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) {
- ContentValues cv = new ContentValues();
- cv.put(COLUMN_KEY, key);
- cv.put(COLUMN_USERID, userId);
- cv.put(COLUMN_VALUE, value);
-
- db.beginTransaction();
- try {
- db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
- new String[] {key, Integer.toString(userId)});
- db.insert(TABLE, null, cv);
- db.setTransactionSuccessful();
- mCache.putKeyValue(key, value, userId);
- } finally {
- db.endTransaction();
- }
-
- }
-
- public String readKeyValue(String key, String defaultValue, int userId) {
- int version;
- synchronized (mCache) {
- if (mCache.hasKeyValue(key, userId)) {
- return mCache.peekKeyValue(key, defaultValue, userId);
- }
- version = mCache.getVersion();
- }
-
- Cursor cursor;
- Object result = DEFAULT;
- SQLiteDatabase db = mOpenHelper.getReadableDatabase();
- if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
- COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
- new String[] { Integer.toString(userId), key },
- null, null, null)) != null) {
- if (cursor.moveToFirst()) {
- result = cursor.getString(0);
- }
- cursor.close();
- }
- mCache.putKeyValueIfUnchanged(key, result, userId, version);
- return result == DEFAULT ? defaultValue : (String) result;
- }
-
- public void prefetchUser(int userId) {
- int version;
- synchronized (mCache) {
- if (mCache.isFetched(userId)) {
- return;
- }
- mCache.setFetched(userId);
- version = mCache.getVersion();
- }
-
- Cursor cursor;
- SQLiteDatabase db = mOpenHelper.getReadableDatabase();
- if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH,
- COLUMN_USERID + "=?",
- new String[] { Integer.toString(userId) },
- null, null, null)) != null) {
- while (cursor.moveToNext()) {
- String key = cursor.getString(0);
- String value = cursor.getString(1);
- mCache.putKeyValueIfUnchanged(key, value, userId, version);
- }
- cursor.close();
- }
-
- // Populate cache by reading the password and pattern files.
- readCredentialHash(userId);
- }
-
- private CredentialHash readPasswordHashIfExists(int userId) {
- byte[] stored = readFile(getLockPasswordFilename(userId));
- if (!ArrayUtils.isEmpty(stored)) {
- return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- CredentialHash.VERSION_GATEKEEPER);
- }
-
- stored = readFile(getLegacyLockPasswordFilename(userId));
- if (!ArrayUtils.isEmpty(stored)) {
- return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- CredentialHash.VERSION_LEGACY);
- }
- return null;
- }
-
- private CredentialHash readPatternHashIfExists(int userId) {
- byte[] stored = readFile(getLockPatternFilename(userId));
- if (!ArrayUtils.isEmpty(stored)) {
- return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
- CredentialHash.VERSION_GATEKEEPER);
- }
-
- stored = readFile(getBaseZeroLockPatternFilename(userId));
- if (!ArrayUtils.isEmpty(stored)) {
- return new CredentialHash(stored, true);
- }
-
- stored = readFile(getLegacyLockPatternFilename(userId));
- if (!ArrayUtils.isEmpty(stored)) {
- return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
- CredentialHash.VERSION_LEGACY);
- }
- return null;
- }
-
- public CredentialHash readCredentialHash(int userId) {
- CredentialHash passwordHash = readPasswordHashIfExists(userId);
- CredentialHash patternHash = readPatternHashIfExists(userId);
- if (passwordHash != null && patternHash != null) {
- if (passwordHash.version == CredentialHash.VERSION_GATEKEEPER) {
- return passwordHash;
- } else {
- return patternHash;
- }
- } else if (passwordHash != null) {
- return passwordHash;
- } else if (patternHash != null) {
- return patternHash;
- } else {
- return CredentialHash.createEmptyHash();
- }
- }
-
- public void removeChildProfileLock(int userId) {
- if (DEBUG)
- Slog.e(TAG, "Remove child profile lock for user: " + userId);
- try {
- deleteFile(getChildProfileLockFile(userId));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- public void writeChildProfileLock(int userId, byte[] lock) {
- writeFile(getChildProfileLockFile(userId), lock);
- }
-
- public byte[] readChildProfileLock(int userId) {
- return readFile(getChildProfileLockFile(userId));
- }
-
- public boolean hasChildProfileLock(int userId) {
- return hasFile(getChildProfileLockFile(userId));
- }
-
- public boolean hasPassword(int userId) {
- return hasFile(getLockPasswordFilename(userId)) ||
- hasFile(getLegacyLockPasswordFilename(userId));
- }
-
- public boolean hasPattern(int userId) {
- return hasFile(getLockPatternFilename(userId)) ||
- hasFile(getBaseZeroLockPatternFilename(userId)) ||
- hasFile(getLegacyLockPatternFilename(userId));
- }
-
- public boolean hasCredential(int userId) {
- return hasPassword(userId) || hasPattern(userId);
- }
-
- private boolean hasFile(String name) {
- byte[] contents = readFile(name);
- return contents != null && contents.length > 0;
- }
-
- private byte[] readFile(String name) {
- int version;
- synchronized (mCache) {
- if (mCache.hasFile(name)) {
- return mCache.peekFile(name);
- }
- version = mCache.getVersion();
- }
-
- RandomAccessFile raf = null;
- byte[] stored = null;
- try {
- raf = new RandomAccessFile(name, "r");
- stored = new byte[(int) raf.length()];
- raf.readFully(stored, 0, stored.length);
- raf.close();
- } catch (IOException e) {
- Slog.e(TAG, "Cannot read file " + e);
- } finally {
- if (raf != null) {
- try {
- raf.close();
- } catch (IOException e) {
- Slog.e(TAG, "Error closing file " + e);
- }
- }
- }
- mCache.putFileIfUnchanged(name, stored, version);
- return stored;
- }
-
- private void writeFile(String name, byte[] hash) {
- synchronized (mFileWriteLock) {
- RandomAccessFile raf = null;
- try {
- // Write the hash to file, requiring each write to be synchronized to the
- // underlying storage device immediately to avoid data loss in case of power loss.
- // This also ensures future secdiscard operation on the file succeeds since the
- // file would have been allocated on flash.
- raf = new RandomAccessFile(name, "rws");
- // Truncate the file if pattern is null, to clear the lock
- if (hash == null || hash.length == 0) {
- raf.setLength(0);
- } else {
- raf.write(hash, 0, hash.length);
- }
- raf.close();
- } catch (IOException e) {
- Slog.e(TAG, "Error writing to file " + e);
- } finally {
- if (raf != null) {
- try {
- raf.close();
- } catch (IOException e) {
- Slog.e(TAG, "Error closing file " + e);
- }
- }
- }
- mCache.putFile(name, hash);
- }
- }
-
- private void deleteFile(String name) {
- if (DEBUG) Slog.e(TAG, "Delete file " + name);
- synchronized (mFileWriteLock) {
- File file = new File(name);
- if (file.exists()) {
- file.delete();
- mCache.putFile(name, null);
- }
- }
- }
-
- public void writeCredentialHash(CredentialHash hash, int userId) {
- byte[] patternHash = null;
- byte[] passwordHash = null;
-
- if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
- passwordHash = hash.hash;
- } else if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
- patternHash = hash.hash;
- }
- writeFile(getLockPasswordFilename(userId), passwordHash);
- writeFile(getLockPatternFilename(userId), patternHash);
- }
-
- @VisibleForTesting
- String getLockPatternFilename(int userId) {
- return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE);
- }
-
- @VisibleForTesting
- String getLockPasswordFilename(int userId) {
- return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE);
- }
-
- @VisibleForTesting
- String getLegacyLockPatternFilename(int userId) {
- return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PATTERN_FILE);
- }
-
- @VisibleForTesting
- String getLegacyLockPasswordFilename(int userId) {
- return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PASSWORD_FILE);
- }
-
- private String getBaseZeroLockPatternFilename(int userId) {
- return getLockCredentialFilePathForUser(userId, BASE_ZERO_LOCK_PATTERN_FILE);
- }
-
- @VisibleForTesting
- String getChildProfileLockFile(int userId) {
- return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE);
- }
-
- private String getLockCredentialFilePathForUser(int userId, String basename) {
- String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() +
- SYSTEM_DIRECTORY;
- if (userId == 0) {
- // Leave it in the same place for user 0
- return dataSystemDirectory + basename;
- } else {
- return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath();
- }
- }
-
- public void writeSyntheticPasswordState(int userId, long handle, String name, byte[] data) {
- writeFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name), data);
- }
-
- public byte[] readSyntheticPasswordState(int userId, long handle, String name) {
- return readFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name));
- }
-
- public void deleteSyntheticPasswordState(int userId, long handle, String name) {
- String path = getSynthenticPasswordStateFilePathForUser(userId, handle, name);
- File file = new File(path);
- if (file.exists()) {
- try {
- mContext.getSystemService(StorageManager.class).secdiscard(file.getAbsolutePath());
- } catch (Exception e) {
- Slog.w(TAG, "Failed to secdiscard " + path, e);
- } finally {
- file.delete();
- }
- mCache.putFile(path, null);
- }
- }
-
- @VisibleForTesting
- protected File getSyntheticPasswordDirectoryForUser(int userId) {
- return new File(Environment.getDataSystemDeDirectory(userId) ,SYNTHETIC_PASSWORD_DIRECTORY);
- }
-
- @VisibleForTesting
- protected String getSynthenticPasswordStateFilePathForUser(int userId, long handle,
- String name) {
- File baseDir = getSyntheticPasswordDirectoryForUser(userId);
- String baseName = String.format("%016x.%s", handle, name);
- if (!baseDir.exists()) {
- baseDir.mkdir();
- }
- return new File(baseDir, baseName).getAbsolutePath();
- }
-
- public void removeUser(int userId) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
-
- final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
- final UserInfo parentInfo = um.getProfileParent(userId);
-
- if (parentInfo == null) {
- // This user owns its lock settings files - safe to delete them
- synchronized (mFileWriteLock) {
- String name = getLockPasswordFilename(userId);
- File file = new File(name);
- if (file.exists()) {
- file.delete();
- mCache.putFile(name, null);
- }
- name = getLockPatternFilename(userId);
- file = new File(name);
- if (file.exists()) {
- file.delete();
- mCache.putFile(name, null);
- }
- }
- } else {
- // Managed profile
- removeChildProfileLock(userId);
- }
-
- File spStateDir = getSyntheticPasswordDirectoryForUser(userId);
- try {
- db.beginTransaction();
- db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
- db.setTransactionSuccessful();
- mCache.removeUser(userId);
- // The directory itself will be deleted as part of user deletion operation by the
- // framework, so only need to purge cache here.
- //TODO: (b/34600579) invoke secdiscardable
- mCache.purgePath(spStateDir.getAbsolutePath());
- } finally {
- db.endTransaction();
- }
- }
-
- @VisibleForTesting
- void closeDatabase() {
- mOpenHelper.close();
- }
-
- @VisibleForTesting
- void clearCache() {
- mCache.clear();
- }
-
- public interface Callback {
- void initialize(SQLiteDatabase db);
- }
-
- class DatabaseHelper extends SQLiteOpenHelper {
- private static final String TAG = "LockSettingsDB";
- private static final String DATABASE_NAME = "locksettings.db";
-
- private static final int DATABASE_VERSION = 2;
-
- private Callback mCallback;
-
- public DatabaseHelper(Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
- setWriteAheadLoggingEnabled(true);
- }
-
- public void setCallback(Callback callback) {
- mCallback = callback;
- }
-
- private void createTable(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + TABLE + " (" +
- "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
- COLUMN_KEY + " TEXT," +
- COLUMN_USERID + " INTEGER," +
- COLUMN_VALUE + " TEXT" +
- ");");
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- createTable(db);
- if (mCallback != null) {
- mCallback.initialize(db);
- }
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
- int upgradeVersion = oldVersion;
- if (upgradeVersion == 1) {
- // Previously migrated lock screen widget settings. Now defunct.
- upgradeVersion = 2;
- }
-
- if (upgradeVersion != DATABASE_VERSION) {
- Log.w(TAG, "Failed to upgrade database!");
- }
- }
- }
-
- /**
- * Cache consistency model:
- * - Writes to storage write directly to the cache, but this MUST happen within the atomic
- * section either provided by the database transaction or mWriteLock, such that writes to the
- * cache and writes to the backing storage are guaranteed to occur in the same order
- *
- * - Reads can populate the cache, but because they are no strong ordering guarantees with
- * respect to writes this precaution is taken:
- * - The cache is assigned a version number that increases every time the cache is modified.
- * Reads from backing storage can only populate the cache if the backing storage
- * has not changed since the load operation has begun.
- * This guarantees that no read operation can shadow a write to the cache that happens
- * after it had begun.
- */
- private static class Cache {
- private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>();
- private final CacheKey mCacheKey = new CacheKey();
- private int mVersion = 0;
-
- String peekKeyValue(String key, String defaultValue, int userId) {
- Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId);
- return cached == DEFAULT ? defaultValue : (String) cached;
- }
-
- boolean hasKeyValue(String key, int userId) {
- return contains(CacheKey.TYPE_KEY_VALUE, key, userId);
- }
-
- void putKeyValue(String key, String value, int userId) {
- put(CacheKey.TYPE_KEY_VALUE, key, value, userId);
- }
-
- void putKeyValueIfUnchanged(String key, Object value, int userId, int version) {
- putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version);
- }
-
- byte[] peekFile(String fileName) {
- return (byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
- }
-
- boolean hasFile(String fileName) {
- return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
- }
-
- void putFile(String key, byte[] value) {
- put(CacheKey.TYPE_FILE, key, value, -1 /* userId */);
- }
-
- void putFileIfUnchanged(String key, byte[] value, int version) {
- putIfUnchanged(CacheKey.TYPE_FILE, key, value, -1 /* userId */, version);
- }
-
- void setFetched(int userId) {
- put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId);
- }
-
- boolean isFetched(int userId) {
- return contains(CacheKey.TYPE_FETCHED, "", userId);
- }
-
-
- private synchronized void put(int type, String key, Object value, int userId) {
- // Create a new CachKey here because it may be saved in the map if the key is absent.
- mCache.put(new CacheKey().set(type, key, userId), value);
- mVersion++;
- }
-
- private synchronized void putIfUnchanged(int type, String key, Object value, int userId,
- int version) {
- if (!contains(type, key, userId) && mVersion == version) {
- put(type, key, value, userId);
- }
- }
-
- private synchronized boolean contains(int type, String key, int userId) {
- return mCache.containsKey(mCacheKey.set(type, key, userId));
- }
-
- private synchronized Object peek(int type, String key, int userId) {
- return mCache.get(mCacheKey.set(type, key, userId));
- }
-
- private synchronized int getVersion() {
- return mVersion;
- }
-
- synchronized void removeUser(int userId) {
- for (int i = mCache.size() - 1; i >= 0; i--) {
- if (mCache.keyAt(i).userId == userId) {
- mCache.removeAt(i);
- }
- }
-
- // Make sure in-flight loads can't write to cache.
- mVersion++;
- }
-
- synchronized void purgePath(String path) {
- for (int i = mCache.size() - 1; i >= 0; i--) {
- CacheKey entry = mCache.keyAt(i);
- if (entry.type == CacheKey.TYPE_FILE && entry.key.startsWith(path)) {
- mCache.removeAt(i);
- }
- }
- mVersion++;
- }
-
- synchronized void clear() {
- mCache.clear();
- mVersion++;
- }
-
- private static final class CacheKey {
- static final int TYPE_KEY_VALUE = 0;
- static final int TYPE_FILE = 1;
- static final int TYPE_FETCHED = 2;
-
- String key;
- int userId;
- int type;
-
- public CacheKey set(int type, String key, int userId) {
- this.type = type;
- this.key = key;
- this.userId = userId;
- return this;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof CacheKey))
- return false;
- CacheKey o = (CacheKey) obj;
- return userId == o.userId && type == o.type && key.equals(o.key);
- }
-
- @Override
- public int hashCode() {
- return key.hashCode() ^ userId ^ type;
- }
- }
- }
-
-}
diff --git a/services/core/java/com/android/server/LockSettingsStrongAuth.java b/services/core/java/com/android/server/LockSettingsStrongAuth.java
deleted file mode 100644
index f5fe3db..0000000
--- a/services/core/java/com/android/server/LockSettingsStrongAuth.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server;
-
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
-
-import android.app.AlarmManager;
-import android.app.AlarmManager.OnAlarmListener;
-import android.app.admin.DevicePolicyManager;
-import android.app.trust.IStrongAuthTracker;
-import android.content.Context;
-import android.os.Binder;
-import android.os.DeadObjectException;
-import android.os.Handler;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Slog;
-import android.util.SparseIntArray;
-
-import java.util.ArrayList;
-
-/**
- * Keeps track of requests for strong authentication.
- */
-public class LockSettingsStrongAuth {
-
- private static final String TAG = "LockSettings";
-
- private static final int MSG_REQUIRE_STRONG_AUTH = 1;
- private static final int MSG_REGISTER_TRACKER = 2;
- private static final int MSG_UNREGISTER_TRACKER = 3;
- private static final int MSG_REMOVE_USER = 4;
- private static final int MSG_SCHEDULE_STRONG_AUTH_TIMEOUT = 5;
-
- private static final String STRONG_AUTH_TIMEOUT_ALARM_TAG =
- "LockSettingsStrongAuth.timeoutForUser";
-
- private final ArrayList<IStrongAuthTracker> mStrongAuthTrackers = new ArrayList<>();
- private final SparseIntArray mStrongAuthForUser = new SparseIntArray();
- private final ArrayMap<Integer, StrongAuthTimeoutAlarmListener>
- mStrongAuthTimeoutAlarmListenerForUser = new ArrayMap<>();
- private final int mDefaultStrongAuthFlags;
- private final Context mContext;
-
- private AlarmManager mAlarmManager;
-
- public LockSettingsStrongAuth(Context context) {
- mContext = context;
- mDefaultStrongAuthFlags = StrongAuthTracker.getDefaultFlags(context);
- mAlarmManager = context.getSystemService(AlarmManager.class);
- }
-
- private void handleAddStrongAuthTracker(IStrongAuthTracker tracker) {
- for (int i = 0; i < mStrongAuthTrackers.size(); i++) {
- if (mStrongAuthTrackers.get(i).asBinder() == tracker.asBinder()) {
- return;
- }
- }
- mStrongAuthTrackers.add(tracker);
-
- for (int i = 0; i < mStrongAuthForUser.size(); i++) {
- int key = mStrongAuthForUser.keyAt(i);
- int value = mStrongAuthForUser.valueAt(i);
- try {
- tracker.onStrongAuthRequiredChanged(value, key);
- } catch (RemoteException e) {
- Slog.e(TAG, "Exception while adding StrongAuthTracker.", e);
- }
- }
- }
-
- private void handleRemoveStrongAuthTracker(IStrongAuthTracker tracker) {
- for (int i = 0; i < mStrongAuthTrackers.size(); i++) {
- if (mStrongAuthTrackers.get(i).asBinder() == tracker.asBinder()) {
- mStrongAuthTrackers.remove(i);
- return;
- }
- }
- }
-
- private void handleRequireStrongAuth(int strongAuthReason, int userId) {
- if (userId == UserHandle.USER_ALL) {
- for (int i = 0; i < mStrongAuthForUser.size(); i++) {
- int key = mStrongAuthForUser.keyAt(i);
- handleRequireStrongAuthOneUser(strongAuthReason, key);
- }
- } else {
- handleRequireStrongAuthOneUser(strongAuthReason, userId);
- }
- }
-
- private void handleRequireStrongAuthOneUser(int strongAuthReason, int userId) {
- int oldValue = mStrongAuthForUser.get(userId, mDefaultStrongAuthFlags);
- int newValue = strongAuthReason == STRONG_AUTH_NOT_REQUIRED
- ? STRONG_AUTH_NOT_REQUIRED
- : (oldValue | strongAuthReason);
- if (oldValue != newValue) {
- mStrongAuthForUser.put(userId, newValue);
- notifyStrongAuthTrackers(newValue, userId);
- }
- }
-
- private void handleRemoveUser(int userId) {
- int index = mStrongAuthForUser.indexOfKey(userId);
- if (index >= 0) {
- mStrongAuthForUser.removeAt(index);
- notifyStrongAuthTrackers(mDefaultStrongAuthFlags, userId);
- }
- }
-
- private void handleScheduleStrongAuthTimeout(int userId) {
- final DevicePolicyManager dpm =
- (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- long when = SystemClock.elapsedRealtime() + dpm.getRequiredStrongAuthTimeout(null, userId);
- // cancel current alarm listener for the user (if there was one)
- StrongAuthTimeoutAlarmListener alarm = mStrongAuthTimeoutAlarmListenerForUser.get(userId);
- if (alarm != null) {
- mAlarmManager.cancel(alarm);
- } else {
- alarm = new StrongAuthTimeoutAlarmListener(userId);
- mStrongAuthTimeoutAlarmListenerForUser.put(userId, alarm);
- }
- // schedule a new alarm listener for the user
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when, STRONG_AUTH_TIMEOUT_ALARM_TAG,
- alarm, mHandler);
- }
-
- private void notifyStrongAuthTrackers(int strongAuthReason, int userId) {
- for (int i = 0; i < mStrongAuthTrackers.size(); i++) {
- try {
- mStrongAuthTrackers.get(i).onStrongAuthRequiredChanged(strongAuthReason, userId);
- } catch (DeadObjectException e) {
- Slog.d(TAG, "Removing dead StrongAuthTracker.");
- mStrongAuthTrackers.remove(i);
- i--;
- } catch (RemoteException e) {
- Slog.e(TAG, "Exception while notifying StrongAuthTracker.", e);
- }
- }
- }
-
- public void registerStrongAuthTracker(IStrongAuthTracker tracker) {
- mHandler.obtainMessage(MSG_REGISTER_TRACKER, tracker).sendToTarget();
- }
-
- public void unregisterStrongAuthTracker(IStrongAuthTracker tracker) {
- mHandler.obtainMessage(MSG_UNREGISTER_TRACKER, tracker).sendToTarget();
- }
-
- public void removeUser(int userId) {
- final int argNotUsed = 0;
- mHandler.obtainMessage(MSG_REMOVE_USER, userId, argNotUsed).sendToTarget();
- }
-
- public void requireStrongAuth(int strongAuthReason, int userId) {
- if (userId == UserHandle.USER_ALL || userId >= UserHandle.USER_SYSTEM) {
- mHandler.obtainMessage(MSG_REQUIRE_STRONG_AUTH, strongAuthReason,
- userId).sendToTarget();
- } else {
- throw new IllegalArgumentException(
- "userId must be an explicit user id or USER_ALL");
- }
- }
-
- public void reportUnlock(int userId) {
- requireStrongAuth(STRONG_AUTH_NOT_REQUIRED, userId);
- }
-
- public void reportSuccessfulStrongAuthUnlock(int userId) {
- final int argNotUsed = 0;
- mHandler.obtainMessage(MSG_SCHEDULE_STRONG_AUTH_TIMEOUT, userId, argNotUsed).sendToTarget();
- }
-
- private class StrongAuthTimeoutAlarmListener implements OnAlarmListener {
-
- private final int mUserId;
-
- public StrongAuthTimeoutAlarmListener(int userId) {
- mUserId = userId;
- }
-
- @Override
- public void onAlarm() {
- requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, mUserId);
- }
- }
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_REGISTER_TRACKER:
- handleAddStrongAuthTracker((IStrongAuthTracker) msg.obj);
- break;
- case MSG_UNREGISTER_TRACKER:
- handleRemoveStrongAuthTracker((IStrongAuthTracker) msg.obj);
- break;
- case MSG_REQUIRE_STRONG_AUTH:
- handleRequireStrongAuth(msg.arg1, msg.arg2);
- break;
- case MSG_REMOVE_USER:
- handleRemoveUser(msg.arg1);
- break;
- case MSG_SCHEDULE_STRONG_AUTH_TIMEOUT:
- handleScheduleStrongAuthTimeout(msg.arg1);
- break;
- }
- }
- };
-}
diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java
index 26e471e..7080c41 100644
--- a/services/core/java/com/android/server/MasterClearReceiver.java
+++ b/services/core/java/com/android/server/MasterClearReceiver.java
@@ -23,6 +23,7 @@
import android.os.AsyncTask;
import android.os.RecoverySystem;
import android.os.storage.StorageManager;
+import android.telephony.euicc.EuiccManager;
import android.util.Log;
import android.util.Slog;
import android.view.WindowManager;
@@ -33,6 +34,8 @@
public class MasterClearReceiver extends BroadcastReceiver {
private static final String TAG = "MasterClear";
+ private boolean mWipeExternalStorage;
+ private boolean mWipeEims;
@Override
public void onReceive(final Context context, final Intent intent) {
@@ -53,8 +56,8 @@
final boolean shutdown = intent.getBooleanExtra("shutdown", false);
final String reason = intent.getStringExtra(Intent.EXTRA_REASON);
- final boolean wipeExternalStorage = intent.getBooleanExtra(
- Intent.EXTRA_WIPE_EXTERNAL_STORAGE, false);
+ mWipeExternalStorage = intent.getBooleanExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, false);
+ mWipeEims = intent.getBooleanExtra(Intent.EXTRA_WIPE_ESIMS, false);
final boolean forceWipe = intent.getBooleanExtra(Intent.EXTRA_FORCE_MASTER_CLEAR, false)
|| intent.getBooleanExtra(Intent.EXTRA_FORCE_FACTORY_RESET, false);
@@ -74,20 +77,20 @@
}
};
- if (wipeExternalStorage) {
+ if (mWipeExternalStorage || mWipeEims) {
// thr will be started at the end of this task.
- new WipeAdoptableDisksTask(context, thr).execute();
+ new WipeDataTask(context, thr).execute();
} else {
thr.start();
}
}
- private class WipeAdoptableDisksTask extends AsyncTask<Void, Void, Void> {
+ private class WipeDataTask extends AsyncTask<Void, Void, Void> {
private final Thread mChainedTask;
private final Context mContext;
private final ProgressDialog mProgressDialog;
- public WipeAdoptableDisksTask(Context context, Thread chainedTask) {
+ public WipeDataTask(Context context, Thread chainedTask) {
mContext = context;
mChainedTask = chainedTask;
mProgressDialog = new ProgressDialog(context);
@@ -104,9 +107,16 @@
@Override
protected Void doInBackground(Void... params) {
Slog.w(TAG, "Wiping adoptable disks");
- StorageManager sm = (StorageManager) mContext.getSystemService(
- Context.STORAGE_SERVICE);
- sm.wipeAdoptableDisks();
+ if (mWipeExternalStorage) {
+ StorageManager sm = (StorageManager) mContext.getSystemService(
+ Context.STORAGE_SERVICE);
+ sm.wipeAdoptableDisks();
+ }
+ if (mWipeEims) {
+ EuiccManager euiccManager = (EuiccManager) mContext.getSystemService(
+ Context.EUICC_SERVICE);
+ // STOPSHIP: add EuiccManager API to factory reset eUICC
+ }
return null;
}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index ce4efd1..86c8909 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -100,7 +100,6 @@
import com.android.internal.util.Preconditions;
import com.android.server.NativeDaemonConnector.Command;
import com.android.server.NativeDaemonConnector.SensitiveArg;
-import com.android.server.net.LockdownVpnTracker;
import com.google.android.collect.Maps;
import java.io.BufferedReader;
@@ -155,7 +154,7 @@
*/
public static final String PERMISSION_SYSTEM = "SYSTEM";
- class NetdResponseCode {
+ static class NetdResponseCode {
/* Keep in sync with system/netd/server/ResponseCode.h */
public static final int InterfaceListResult = 110;
public static final int TetherInterfaceListResult = 111;
@@ -230,8 +229,8 @@
* If both locks need to be held, then they should be obtained in the order:
* first {@link #mQuotaLock} and then {@link #mRulesLock}.
*/
- private Object mQuotaLock = new Object();
- private Object mRulesLock = new Object();
+ private final Object mQuotaLock = new Object();
+ private final Object mRulesLock = new Object();
/** Set of interfaces with active quotas. */
@GuardedBy("mQuotaLock")
@@ -276,7 +275,7 @@
@GuardedBy("mQuotaLock")
private volatile boolean mDataSaverMode;
- private Object mIdleTimerLock = new Object();
+ private final Object mIdleTimerLock = new Object();
/** Set of interfaces with active idle timers. */
private static class IdleTimerParams {
public final int timeout;
@@ -660,7 +659,7 @@
}
}
- setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled());
+ setFirewallEnabled(mFirewallEnabled);
syncFirewallChainLocked(FIREWALL_CHAIN_NONE, "");
syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, "standby ");
@@ -1938,30 +1937,6 @@
}
}
- @Override
- public void setFirewallEgressSourceRule(String addr, boolean allow) {
- enforceSystemUid();
- Preconditions.checkState(mFirewallEnabled);
- final String rule = allow ? "allow" : "deny";
- try {
- mConnector.execute("firewall", "set_egress_source_rule", addr, rule);
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- }
-
- @Override
- public void setFirewallEgressDestRule(String addr, int port, boolean allow) {
- enforceSystemUid();
- Preconditions.checkState(mFirewallEnabled);
- final String rule = allow ? "allow" : "deny";
- try {
- mConnector.execute("firewall", "set_egress_dest_rule", addr, port, rule);
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- }
-
private void closeSocketsForFirewallChainLocked(int chain, String chainName) {
// UID ranges to close sockets on.
UidRange[] ranges;
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateService.java b/services/core/java/com/android/server/NetworkTimeUpdateService.java
index ebcda44..1ad1404 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateService.java
@@ -26,6 +26,8 @@
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
@@ -74,6 +76,7 @@
private long mNitzTimeSetTime = NOT_SET;
// TODO: Have a way to look up the timezone we are in
private long mNitzZoneSetTime = NOT_SET;
+ private Network mDefaultNetwork = null;
private Context mContext;
private TrustedTime mTime;
@@ -83,6 +86,8 @@
private AlarmManager mAlarmManager;
private PendingIntent mPendingPollIntent;
private SettingsObserver mSettingsObserver;
+ private ConnectivityManager mCM;
+ private NetworkTimeUpdateCallback mNetworkTimeUpdateCallback;
// The last time that we successfully fetched the NTP time.
private long mLastNtpFetchTime = NOT_SET;
private final PowerManager.WakeLock mWakeLock;
@@ -104,6 +109,7 @@
mContext = context;
mTime = NtpTrustedTime.getInstance(context);
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ mCM = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
Intent pollIntent = new Intent(ACTION_POLL, null);
mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
@@ -124,13 +130,12 @@
public void systemRunning() {
registerForTelephonyIntents();
registerForAlarms();
- registerForConnectivityIntents();
HandlerThread thread = new HandlerThread(TAG);
thread.start();
mHandler = new MyHandler(thread.getLooper());
- // Check the network time on the new thread
- mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
+ mNetworkTimeUpdateCallback = new NetworkTimeUpdateCallback();
+ mCM.registerDefaultNetworkCallback(mNetworkTimeUpdateCallback, mHandler);
mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
mSettingsObserver.observe(mContext);
@@ -153,15 +158,10 @@
}, new IntentFilter(ACTION_POLL));
}
- private void registerForConnectivityIntents() {
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- mContext.registerReceiver(mConnectivityReceiver, intentFilter);
- }
-
private void onPollNetworkTime(int event) {
- // If Automatic time is not set, don't bother.
- if (!isAutomaticTimeRequested()) return;
+ // If Automatic time is not set, don't bother. Similarly, if we don't
+ // have any default network, don't bother.
+ if (!isAutomaticTimeRequested() || mDefaultNetwork == null) return;
mWakeLock.acquire();
try {
onPollNetworkTimeUnderWakeLock(event);
@@ -263,22 +263,6 @@
}
};
- /** Receiver for ConnectivityManager events */
- private BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
- if (DBG) Log.d(TAG, "Received CONNECTIVITY_ACTION ");
- // Don't bother checking if we have connectivity, NtpTrustedTime does that for us.
- Message message = mHandler.obtainMessage(EVENT_NETWORK_CHANGED);
- // Send with a short delay to make sure the network is ready for use
- mHandler.sendMessageDelayed(message, NETWORK_CHANGE_EVENT_DELAY_MS);
- }
- }
- };
-
/** Handler to do the network accesses on */
private class MyHandler extends Handler {
@@ -298,6 +282,21 @@
}
}
+ private class NetworkTimeUpdateCallback extends NetworkCallback {
+ @Override
+ public void onAvailable(Network network) {
+ Log.d(TAG, String.format("New default network %s; checking time.", network));
+ mDefaultNetwork = network;
+ // Running on mHandler so invoke directly.
+ onPollNetworkTime(EVENT_NETWORK_CHANGED);
+ }
+
+ @Override
+ public void onLost(Network network) {
+ if (network.equals(mDefaultNetwork)) mDefaultNetwork = null;
+ }
+ }
+
/** Observer to watch for changes to the AUTO_TIME setting */
private static class SettingsObserver extends ContentObserver {
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index bb530fb..3efef01 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -21,11 +21,14 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
+import android.net.Uri;
import android.net.nsd.NsdServiceInfo;
import android.net.nsd.DnsSdTxtRecord;
import android.net.nsd.INsdManager;
import android.net.nsd.NsdManager;
import android.os.Binder;
+import android.os.HandlerThread;
+import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.os.UserHandle;
@@ -33,19 +36,21 @@
import android.util.Base64;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.net.InetAddress;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
-import com.android.server.NativeDaemonConnector.Command;
/**
* Network Service Discovery Service handles remote service discovery operation requests by
@@ -60,10 +65,10 @@
private static final boolean DBG = true;
private final Context mContext;
- private final ContentResolver mContentResolver;
+ private final NsdSettings mNsdSettings;
private final NsdStateMachine mNsdStateMachine;
- private final NativeDaemonConnector mNativeConnector;
- private final CountDownLatch mNativeDaemonConnected = new CountDownLatch(1);
+ private final DaemonConnection mDaemon;
+ private final NativeCallbackReceiver mDaemonCallback;
/**
* Clients receiving asynchronous messages
@@ -93,32 +98,24 @@
* Observes the NSD on/off setting, and takes action when changed.
*/
private void registerForNsdSetting() {
- ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
+ final ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
@Override
public void onChange(boolean selfChange) {
- if (isNsdEnabled()) {
- mNsdStateMachine.sendMessage(NsdManager.ENABLE);
- } else {
- mNsdStateMachine.sendMessage(NsdManager.DISABLE);
- }
+ notifyEnabled(isNsdEnabled());
}
};
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.NSD_ON),
- false, contentObserver);
+ final Uri uri = Settings.Global.getUriFor(Settings.Global.NSD_ON);
+ mNsdSettings.registerContentObserver(uri, contentObserver);
}
- NsdStateMachine(String name) {
- super(name);
+ NsdStateMachine(String name, Handler handler) {
+ super(name, handler);
addState(mDefaultState);
addState(mDisabledState, mDefaultState);
addState(mEnabledState, mDefaultState);
- if (isNsdEnabled()) {
- setInitialState(mEnabledState);
- } else {
- setInitialState(mDisabledState);
- }
+ State initialState = isNsdEnabled() ? mEnabledState : mDisabledState;
+ setInitialState(initialState);
setLogRecSize(25);
registerForNsdSetting();
}
@@ -158,7 +155,7 @@
}
//Last client
if (mClients.size() == 0) {
- stopMDnsDaemon();
+ mDaemon.stop();
}
break;
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
@@ -218,14 +215,14 @@
public void enter() {
sendNsdStateChangeBroadcast(true);
if (mClients.size() > 0) {
- startMDnsDaemon();
+ mDaemon.start();
}
}
@Override
public void exit() {
if (mClients.size() > 0) {
- stopMDnsDaemon();
+ mDaemon.stop();
}
}
@@ -244,8 +241,8 @@
}
private void removeRequestMap(int clientId, int globalId, ClientInfo clientInfo) {
- clientInfo.mClientIds.remove(clientId);
- clientInfo.mClientRequests.remove(clientId);
+ clientInfo.mClientIds.delete(clientId);
+ clientInfo.mClientRequests.delete(clientId);
mIdToClientInfoMap.remove(globalId);
}
@@ -259,7 +256,7 @@
//First client
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL &&
mClients.size() == 0) {
- startMDnsDaemon();
+ mDaemon.start();
}
return NOT_HANDLED;
case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
@@ -298,7 +295,7 @@
clientInfo = mClients.get(msg.replyTo);
try {
- id = clientInfo.mClientIds.get(msg.arg2).intValue();
+ id = clientInfo.mClientIds.get(msg.arg2);
} catch (NullPointerException e) {
replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
NsdManager.FAILURE_INTERNAL_ERROR);
@@ -336,7 +333,7 @@
if (DBG) Slog.d(TAG, "unregister service");
clientInfo = mClients.get(msg.replyTo);
try {
- id = clientInfo.mClientIds.get(msg.arg2).intValue();
+ id = clientInfo.mClientIds.get(msg.arg2);
} catch (NullPointerException e) {
replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
NsdManager.FAILURE_INTERNAL_ERROR);
@@ -542,57 +539,55 @@
return sb.toString();
}
- private NsdService(Context context) {
- mContext = context;
- mContentResolver = context.getContentResolver();
-
- mNativeConnector = new NativeDaemonConnector(new NativeCallbackReceiver(), "mdns", 10,
- MDNS_TAG, 25, null);
-
- mNsdStateMachine = new NsdStateMachine(TAG);
+ @VisibleForTesting
+ NsdService(Context ctx, NsdSettings settings, Handler handler, DaemonConnectionSupplier fn) {
+ mContext = ctx;
+ mNsdSettings = settings;
+ mNsdStateMachine = new NsdStateMachine(TAG, handler);
mNsdStateMachine.start();
-
- Thread th = new Thread(mNativeConnector, MDNS_TAG);
- th.start();
+ mDaemonCallback = new NativeCallbackReceiver();
+ mDaemon = fn.get(mDaemonCallback);
}
public static NsdService create(Context context) throws InterruptedException {
- NsdService service = new NsdService(context);
- service.mNativeDaemonConnected.await();
+ NsdSettings settings = NsdSettings.makeDefault(context);
+ HandlerThread thread = new HandlerThread(TAG);
+ thread.start();
+ Handler handler = new Handler(thread.getLooper());
+ NsdService service = new NsdService(context, settings, handler, DaemonConnection::new);
+ service.mDaemonCallback.awaitConnection();
return service;
}
public Messenger getMessenger() {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET,
- "NsdService");
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "NsdService");
return new Messenger(mNsdStateMachine.getHandler());
}
- public void setEnabled(boolean enable) {
+ public void setEnabled(boolean isEnabled) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
"NsdService");
- Settings.Global.putInt(mContentResolver, Settings.Global.NSD_ON, enable ? 1 : 0);
- if (enable) {
- mNsdStateMachine.sendMessage(NsdManager.ENABLE);
- } else {
- mNsdStateMachine.sendMessage(NsdManager.DISABLE);
- }
+ mNsdSettings.putEnabledStatus(isEnabled);
+ notifyEnabled(isEnabled);
}
- private void sendNsdStateChangeBroadcast(boolean enabled) {
+ private void notifyEnabled(boolean isEnabled) {
+ mNsdStateMachine.sendMessage(isEnabled ? NsdManager.ENABLE : NsdManager.DISABLE);
+ }
+
+ private void sendNsdStateChangeBroadcast(boolean isEnabled) {
final Intent intent = new Intent(NsdManager.ACTION_NSD_STATE_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- if (enabled) {
- intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_ENABLED);
- } else {
- intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_DISABLED);
- }
+ int nsdState = isEnabled ? NsdManager.NSD_STATE_ENABLED : NsdManager.NSD_STATE_DISABLED;
+ intent.putExtra(NsdManager.EXTRA_NSD_STATE, nsdState);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
private boolean isNsdEnabled() {
- boolean ret = Settings.Global.getInt(mContentResolver, Settings.Global.NSD_ON, 1) == 1;
- if (DBG) Slog.d(TAG, "Network service discovery enabled " + ret);
+ boolean ret = mNsdSettings.isEnabled();
+ if (DBG) {
+ Slog.d(TAG, "Network service discovery is " + (ret ? "enabled" : "disabled"));
+ }
return ret;
}
@@ -656,14 +651,23 @@
}
class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks {
- public void onDaemonConnected() {
- mNativeDaemonConnected.countDown();
+ private final CountDownLatch connected = new CountDownLatch(1);
+
+ public void awaitConnection() throws InterruptedException {
+ connected.await();
}
+ @Override
+ public void onDaemonConnected() {
+ connected.countDown();
+ }
+
+ @Override
public boolean onCheckHoldWakeLock(int code) {
return false;
}
+ @Override
public boolean onEvent(int code, String raw, String[] cooked) {
// TODO: NDC translates a message to a callback, we could enhance NDC to
// directly interact with a state machine through messages
@@ -673,132 +677,88 @@
}
}
- private boolean startMDnsDaemon() {
- if (DBG) Slog.d(TAG, "startMDnsDaemon");
- try {
- mNativeConnector.execute("mdnssd", "start-service");
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to start daemon" + e);
- return false;
- }
- return true;
+ interface DaemonConnectionSupplier {
+ DaemonConnection get(NativeCallbackReceiver callback);
}
- private boolean stopMDnsDaemon() {
- if (DBG) Slog.d(TAG, "stopMDnsDaemon");
- try {
- mNativeConnector.execute("mdnssd", "stop-service");
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to start daemon" + e);
- return false;
+ @VisibleForTesting
+ public static class DaemonConnection {
+ final NativeDaemonConnector mNativeConnector;
+
+ DaemonConnection(NativeCallbackReceiver callback) {
+ mNativeConnector = new NativeDaemonConnector(callback, "mdns", 10, MDNS_TAG, 25, null);
+ new Thread(mNativeConnector, MDNS_TAG).start();
}
- return true;
+
+ public boolean execute(Object... args) {
+ if (DBG) {
+ Slog.d(TAG, "mdnssd " + Arrays.toString(args));
+ }
+ try {
+ mNativeConnector.execute("mdnssd", args);
+ } catch (NativeDaemonConnectorException e) {
+ Slog.e(TAG, "Failed to execute mdnssd " + Arrays.toString(args), e);
+ return false;
+ }
+ return true;
+ }
+
+ public void start() {
+ execute("start-service");
+ }
+
+ public void stop() {
+ execute("stop-service");
+ }
}
private boolean registerService(int regId, NsdServiceInfo service) {
- if (DBG) Slog.d(TAG, "registerService: " + regId + " " + service);
- try {
- Command cmd = new Command("mdnssd", "register", regId, service.getServiceName(),
- service.getServiceType(), service.getPort(),
- Base64.encodeToString(service.getTxtRecord(), Base64.DEFAULT)
- .replace("\n", ""));
-
- mNativeConnector.execute(cmd);
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to execute registerService " + e);
- return false;
+ if (DBG) {
+ Slog.d(TAG, "registerService: " + regId + " " + service);
}
- return true;
+ String name = service.getServiceName();
+ String type = service.getServiceType();
+ int port = service.getPort();
+ byte[] textRecord = service.getTxtRecord();
+ String record = Base64.encodeToString(textRecord, Base64.DEFAULT).replace("\n", "");
+ return mDaemon.execute("register", regId, name, type, port, record);
}
private boolean unregisterService(int regId) {
- if (DBG) Slog.d(TAG, "unregisterService: " + regId);
- try {
- mNativeConnector.execute("mdnssd", "stop-register", regId);
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to execute unregisterService " + e);
- return false;
- }
- return true;
+ return mDaemon.execute("stop-register", regId);
}
private boolean updateService(int regId, DnsSdTxtRecord t) {
- if (DBG) Slog.d(TAG, "updateService: " + regId + " " + t);
- try {
- if (t == null) return false;
- mNativeConnector.execute("mdnssd", "update", regId, t.size(), t.getRawData());
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to updateServices " + e);
+ if (t == null) {
return false;
}
- return true;
+ return mDaemon.execute("update", regId, t.size(), t.getRawData());
}
private boolean discoverServices(int discoveryId, String serviceType) {
- if (DBG) Slog.d(TAG, "discoverServices: " + discoveryId + " " + serviceType);
- try {
- mNativeConnector.execute("mdnssd", "discover", discoveryId, serviceType);
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to discoverServices " + e);
- return false;
- }
- return true;
+ return mDaemon.execute("discover", discoveryId, serviceType);
}
private boolean stopServiceDiscovery(int discoveryId) {
- if (DBG) Slog.d(TAG, "stopServiceDiscovery: " + discoveryId);
- try {
- mNativeConnector.execute("mdnssd", "stop-discover", discoveryId);
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to stopServiceDiscovery " + e);
- return false;
- }
- return true;
+ return mDaemon.execute("stop-discover", discoveryId);
}
private boolean resolveService(int resolveId, NsdServiceInfo service) {
- if (DBG) Slog.d(TAG, "resolveService: " + resolveId + " " + service);
- try {
- mNativeConnector.execute("mdnssd", "resolve", resolveId, service.getServiceName(),
- service.getServiceType(), "local.");
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to resolveService " + e);
- return false;
- }
- return true;
+ String name = service.getServiceName();
+ String type = service.getServiceType();
+ return mDaemon.execute("resolve", resolveId, name, type, "local.");
}
private boolean stopResolveService(int resolveId) {
- if (DBG) Slog.d(TAG, "stopResolveService: " + resolveId);
- try {
- mNativeConnector.execute("mdnssd", "stop-resolve", resolveId);
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to stop resolve " + e);
- return false;
- }
- return true;
+ return mDaemon.execute("stop-resolve", resolveId);
}
private boolean getAddrInfo(int resolveId, String hostname) {
- if (DBG) Slog.d(TAG, "getAdddrInfo: " + resolveId);
- try {
- mNativeConnector.execute("mdnssd", "getaddrinfo", resolveId, hostname);
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to getAddrInfo " + e);
- return false;
- }
- return true;
+ return mDaemon.execute("getaddrinfo", resolveId, hostname);
}
private boolean stopGetAddrInfo(int resolveId) {
- if (DBG) Slog.d(TAG, "stopGetAdddrInfo: " + resolveId);
- try {
- mNativeConnector.execute("mdnssd", "stop-getaddrinfo", resolveId);
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to stopGetAddrInfo " + e);
- return false;
- }
- return true;
+ return mDaemon.execute("stop-getaddrinfo", resolveId);
}
@Override
@@ -854,10 +814,10 @@
private NsdServiceInfo mResolvedService;
/* A map from client id to unique id sent to mDns */
- private final SparseArray<Integer> mClientIds = new SparseArray<>();
+ private final SparseIntArray mClientIds = new SparseIntArray();
/* A map from client id to the type of the request we had received */
- private final SparseArray<Integer> mClientRequests = new SparseArray<>();
+ private final SparseIntArray mClientRequests = new SparseIntArray();
private ClientInfo(AsyncChannel c, Messenger m) {
mChannel = c;
@@ -884,6 +844,7 @@
// and send cancellations to the daemon.
private void expungeAllRequests() {
int globalId, clientId, i;
+ // TODO: to keep handler responsive, do not clean all requests for that client at once.
for (i = 0; i < mClientIds.size(); i++) {
clientId = mClientIds.keyAt(i);
globalId = mClientIds.valueAt(i);
@@ -911,15 +872,42 @@
// mClientIds is a sparse array of listener id -> mDnsClient id. For a given mDnsClient id,
// return the corresponding listener id. mDnsClient id is also called a global id.
private int getClientId(final int globalId) {
- // This doesn't use mClientIds.indexOfValue because indexOfValue uses == (not .equals)
- // while also coercing the int primitives to Integer objects.
- for (int i = 0, nSize = mClientIds.size(); i < nSize; i++) {
- int mDnsId = mClientIds.valueAt(i);
- if (globalId == mDnsId) {
- return mClientIds.keyAt(i);
- }
+ int idx = mClientIds.indexOfValue(globalId);
+ if (idx < 0) {
+ return idx;
}
- return -1;
+ return mClientIds.keyAt(idx);
+ }
+ }
+
+ /**
+ * Interface which encapsulates dependencies of NsdService that are hard to mock, hard to
+ * override, or have side effects on global state in unit tests.
+ */
+ @VisibleForTesting
+ public interface NsdSettings {
+ boolean isEnabled();
+ void putEnabledStatus(boolean isEnabled);
+ void registerContentObserver(Uri uri, ContentObserver observer);
+
+ static NsdSettings makeDefault(Context context) {
+ final ContentResolver resolver = context.getContentResolver();
+ return new NsdSettings() {
+ @Override
+ public boolean isEnabled() {
+ return Settings.Global.getInt(resolver, Settings.Global.NSD_ON, 1) == 1;
+ }
+
+ @Override
+ public void putEnabledStatus(boolean isEnabled) {
+ Settings.Global.putInt(resolver, Settings.Global.NSD_ON, isEnabled ? 1 : 0);
+ }
+
+ @Override
+ public void registerContentObserver(Uri uri, ContentObserver observer) {
+ resolver.registerContentObserver(uri, false, observer);
+ }
+ };
}
}
}
diff --git a/services/core/java/com/android/server/OemLockService.java b/services/core/java/com/android/server/OemLockService.java
deleted file mode 100644
index 03f82a8..0000000
--- a/services/core/java/com/android/server/OemLockService.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.Manifest;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.service.oemlock.IOemLockService;
-import android.service.persistentdata.PersistentDataBlockManager;
-
-/**
- * Service for managing the OEM lock state of the device.
- *
- * The current implementation is a wrapper around the previous implementation of OEM lock.
- * - the DISALLOW_OEM_UNLOCK user restriction was set if the carrier disallowed unlock
- * - the user allows unlock in settings which calls PDBM.setOemUnlockEnabled()
- */
-public class OemLockService extends SystemService {
- private Context mContext;
-
- public OemLockService(Context context) {
- super(context);
- mContext = context;
- }
-
- @Override
- public void onStart() {
- publishBinderService(Context.OEM_LOCK_SERVICE, mService);
- }
-
- private boolean doIsOemUnlockAllowedByCarrier() {
- return !UserManager.get(mContext).hasUserRestriction(UserManager.DISALLOW_OEM_UNLOCK);
- }
-
- private boolean doIsOemUnlockAllowedByUser() {
- final PersistentDataBlockManager pdbm = (PersistentDataBlockManager)
- mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
-
- final long token = Binder.clearCallingIdentity();
- try {
- return pdbm.getOemUnlockEnabled();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- /**
- * Implements the binder interface for the service.
- */
- private final IBinder mService = new IOemLockService.Stub() {
- @Override
- public void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) {
- enforceManageCarrierOemUnlockPermission();
- enforceUserIsAdmin();
-
- // Note: this implementation does not require a signature
-
- // Continue using user restriction for backwards compatibility
- final UserHandle userHandle = UserHandle.of(UserHandle.getCallingUserId());
- final long token = Binder.clearCallingIdentity();
- try {
- UserManager.get(mContext)
- .setUserRestriction(UserManager.DISALLOW_OEM_UNLOCK, !allowed, userHandle);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public boolean isOemUnlockAllowedByCarrier() {
- enforceManageCarrierOemUnlockPermission();
- return doIsOemUnlockAllowedByCarrier();
- }
-
- @Override
- public void setOemUnlockAllowedByUser(boolean allowedByUser) {
- if (ActivityManager.isUserAMonkey()) {
- // Prevent a monkey from changing this
- return;
- }
-
- enforceManageUserOemUnlockPermission();
- enforceUserIsAdmin();
-
- final PersistentDataBlockManager pdbm = (PersistentDataBlockManager)
- mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
-
- final long token = Binder.clearCallingIdentity();
- try {
- // The method name is misleading as it really just means whether or not the device
- // can be unlocked but doesn't actually do any unlocking.
- pdbm.setOemUnlockEnabled(allowedByUser);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public boolean isOemUnlockAllowedByUser() {
- enforceManageUserOemUnlockPermission();
- return doIsOemUnlockAllowedByUser();
- }
- };
-
- private void enforceManageCarrierOemUnlockPermission() {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.MANAGE_CARRIER_OEM_UNLOCK_STATE,
- "Can't manage OEM unlock allowed by carrier");
- }
-
- private void enforceManageUserOemUnlockPermission() {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.MANAGE_USER_OEM_UNLOCK_STATE,
- "Can't manage OEM unlock allowed by user");
- }
-
- private void enforceUserIsAdmin() {
- final int userId = UserHandle.getCallingUserId();
- final long token = Binder.clearCallingIdentity();
- try {
- if (!UserManager.get(mContext).isUserAdmin(userId)) {
- throw new SecurityException("Must be an admin user");
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-}
diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
new file mode 100644
index 0000000..80f8e51
--- /dev/null
+++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+/**
+ * Internal interface for storing and retrieving persistent data.
+ */
+public interface PersistentDataBlockManagerInternal {
+
+ /** Stores the handle to a lockscreen credential to be used for Factory Reset Protection. */
+ void setFrpCredentialHandle(byte[] handle);
+
+ /** Retrieves handle to a lockscreen credential to be used for Factory Reset Protection. */
+ byte[] getFrpCredentialHandle();
+}
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index e3cd87c..1d4c3db 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -32,6 +32,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
import libcore.io.IoUtils;
@@ -71,8 +72,13 @@
private static final int HEADER_SIZE = 8;
// Magic number to mark block device as adhering to the format consumed by this service
private static final int PARTITION_TYPE_MARKER = 0x19901873;
+ /** Size of the block reserved for FPR credential, including 4 bytes for the size header. */
+ private static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000;
+ /** Maximum size of the FRP credential handle that can be stored. */
+ private static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4;
// Limit to 100k as blocks larger than this might cause strain on Binder.
private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
+
public static final int DIGEST_SIZE_BYTES = 32;
private static final String OEM_UNLOCK_PROP = "sys.oem_unlock_allowed";
private static final String FLASH_LOCK_PROP = "ro.boot.flash.locked";
@@ -136,6 +142,7 @@
Thread.currentThread().interrupt();
throw new IllegalStateException("Service " + TAG + " init interrupted", e);
}
+ LocalServices.addService(PersistentDataBlockManagerInternal.class, mInternalService);
}
super.onBootPhase(phase);
}
@@ -382,7 +389,7 @@
enforceUid(Binder.getCallingUid());
// Need to ensure we don't write over the last byte
- long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
+ long maxBlockSize = getMaximumDataBlockSize();
if (data.length > maxBlockSize) {
// partition is ~500k so shouldn't be a problem to downcast
return (int) -maxBlockSize;
@@ -562,8 +569,99 @@
@Override
public long getMaximumDataBlockSize() {
- long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
+ long actualSize = getBlockDeviceSize() - HEADER_SIZE - DIGEST_SIZE_BYTES
+ - FRP_CREDENTIAL_RESERVED_SIZE - 1;
return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
}
+
+ @Override
+ public boolean hasFrpCredentialHandle() {
+ enforcePersistentDataBlockAccess();
+ return mInternalService.getFrpCredentialHandle() != null;
+ }
+ };
+
+ private PersistentDataBlockManagerInternal mInternalService =
+ new PersistentDataBlockManagerInternal() {
+
+ @Override
+ public void setFrpCredentialHandle(byte[] handle) {
+ Preconditions.checkArgument(handle == null || handle.length > 0,
+ "handle must be null or non-empty");
+ Preconditions.checkArgument(handle == null
+ || handle.length <= MAX_FRP_CREDENTIAL_HANDLE_SIZE,
+ "handle must not be longer than " + MAX_FRP_CREDENTIAL_HANDLE_SIZE);
+
+ FileOutputStream outputStream;
+ try {
+ outputStream = new FileOutputStream(new File(mDataBlockFile));
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "partition not available", e);
+ return;
+ }
+
+ ByteBuffer data = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE);
+ data.putInt(handle == null ? 0 : handle.length);
+ if (handle != null) {
+ data.put(handle);
+ }
+ data.flip();
+
+ synchronized (mLock) {
+ if (!mIsWritable) {
+ IoUtils.closeQuietly(outputStream);
+ return;
+ }
+
+ try {
+ FileChannel channel = outputStream.getChannel();
+
+ channel.position(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE);
+ channel.write(data);
+ outputStream.flush();
+ } catch (IOException e) {
+ Slog.e(TAG, "unable to access persistent partition", e);
+ return;
+ } finally {
+ IoUtils.closeQuietly(outputStream);
+ }
+
+ computeAndWriteDigestLocked();
+ }
+ }
+
+ @Override
+ public byte[] getFrpCredentialHandle() {
+ if (!enforceChecksumValidity()) {
+ return null;
+ }
+
+ DataInputStream inputStream;
+ try {
+ inputStream = new DataInputStream(
+ new FileInputStream(new File(mDataBlockFile)));
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "partition not available");
+ return null;
+ }
+
+ try {
+ synchronized (mLock) {
+ inputStream.skip(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE);
+ int length = inputStream.readInt();
+ if (length <= 0 || length > MAX_FRP_CREDENTIAL_HANDLE_SIZE) {
+ return null;
+ }
+ byte[] bytes = new byte[length];
+ inputStream.readFully(bytes);
+ return bytes;
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "unable to access persistent partition", e);
+ return null;
+ } finally {
+ IoUtils.closeQuietly(inputStream);
+ }
+ }
};
}
diff --git a/services/core/java/com/android/server/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/SyntheticPasswordCrypto.java
deleted file mode 100644
index 71ab2a5..0000000
--- a/services/core/java/com/android/server/SyntheticPasswordCrypto.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.CertificateException;
-import java.util.Arrays;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.KeyGenerator;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.GCMParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-public class SyntheticPasswordCrypto {
- private static final int PROFILE_KEY_IV_SIZE = 12;
- private static final int AES_KEY_LENGTH = 32; // 256-bit AES key
- private static final byte[] APPLICATION_ID_PERSONALIZATION = "application-id".getBytes();
- // Time between the user credential is verified with GK and the decryption of synthetic password
- // under the auth-bound key. This should always happen one after the other, but give it 15
- // seconds just to be sure.
- private static final int USER_AUTHENTICATION_VALIDITY = 15;
-
- private static byte[] decrypt(SecretKey key, byte[] blob)
- throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
- InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
- if (blob == null) {
- return null;
- }
- byte[] iv = Arrays.copyOfRange(blob, 0, PROFILE_KEY_IV_SIZE);
- byte[] ciphertext = Arrays.copyOfRange(blob, PROFILE_KEY_IV_SIZE, blob.length);
- Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
- + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
- cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
- return cipher.doFinal(ciphertext);
- }
-
- private static byte[] encrypt(SecretKey key, byte[] blob)
- throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
- InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
- if (blob == null) {
- return null;
- }
- Cipher cipher = Cipher.getInstance(
- KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
- + KeyProperties.ENCRYPTION_PADDING_NONE);
- cipher.init(Cipher.ENCRYPT_MODE, key);
- byte[] ciphertext = cipher.doFinal(blob);
- byte[] iv = cipher.getIV();
- if (iv.length != PROFILE_KEY_IV_SIZE) {
- throw new RuntimeException("Invalid iv length: " + iv.length);
- }
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- outputStream.write(iv);
- outputStream.write(ciphertext);
- return outputStream.toByteArray();
- }
-
- public static byte[] encrypt(byte[] keyBytes, byte[] personalisation, byte[] message) {
- byte[] keyHash = personalisedHash(personalisation, keyBytes);
- SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
- KeyProperties.KEY_ALGORITHM_AES);
- try {
- return encrypt(key, message);
- } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
- | IllegalBlockSizeException | BadPaddingException | IOException e) {
- e.printStackTrace();
- return null;
- }
- }
-
- public static byte[] decrypt(byte[] keyBytes, byte[] personalisation, byte[] ciphertext) {
- byte[] keyHash = personalisedHash(personalisation, keyBytes);
- SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
- KeyProperties.KEY_ALGORITHM_AES);
- try {
- return decrypt(key, ciphertext);
- } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
- | IllegalBlockSizeException | BadPaddingException
- | InvalidAlgorithmParameterException e) {
- e.printStackTrace();
- return null;
- }
- }
-
- public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
- try {
- KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
- keyStore.load(null);
-
- SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
- byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob);
- return decrypt(decryptionKey, intermediate);
- } catch (CertificateException | IOException | BadPaddingException
- | IllegalBlockSizeException
- | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
- | InvalidKeyException | UnrecoverableKeyException
- | InvalidAlgorithmParameterException e) {
- e.printStackTrace();
- throw new RuntimeException("Failed to decrypt blob", e);
- }
- }
-
- public static byte[] createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid) {
- try {
- KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
- keyGenerator.init(new SecureRandom());
- SecretKey secretKey = keyGenerator.generateKey();
- KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
- keyStore.load(null);
- KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
- .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
- .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
- .setCriticalToDeviceEncryption(true);
- if (sid != 0) {
- builder.setUserAuthenticationRequired(true)
- .setBoundToSpecificSecureUserId(sid)
- .setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_VALIDITY);
- }
-
- keyStore.setEntry(keyAlias,
- new KeyStore.SecretKeyEntry(secretKey),
- builder.build());
- byte[] intermediate = encrypt(secretKey, data);
- return encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
-
- } catch (CertificateException | IOException | BadPaddingException
- | IllegalBlockSizeException
- | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
- | InvalidKeyException e) {
- e.printStackTrace();
- throw new RuntimeException("Failed to encrypt blob", e);
- }
- }
-
- public static void destroyBlobKey(String keyAlias) {
- KeyStore keyStore;
- try {
- keyStore = KeyStore.getInstance("AndroidKeyStore");
- keyStore.load(null);
- keyStore.deleteEntry(keyAlias);
- } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
- | IOException e) {
- e.printStackTrace();
- }
- }
-
- protected static byte[] personalisedHash(byte[] personalisation, byte[]... message) {
- try {
- final int PADDING_LENGTH = 128;
- MessageDigest digest = MessageDigest.getInstance("SHA-512");
- if (personalisation.length > PADDING_LENGTH) {
- throw new RuntimeException("Personalisation too long");
- }
- // Personalize the hash
- // Pad it to the block size of the hash function
- personalisation = Arrays.copyOf(personalisation, PADDING_LENGTH);
- digest.update(personalisation);
- for (byte[] data : message) {
- digest.update(data);
- }
- return digest.digest();
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("NoSuchAlgorithmException for SHA-512", e);
- }
- }
-}
diff --git a/services/core/java/com/android/server/SyntheticPasswordManager.java b/services/core/java/com/android/server/SyntheticPasswordManager.java
deleted file mode 100644
index 1d17ff7..0000000
--- a/services/core/java/com/android/server/SyntheticPasswordManager.java
+++ /dev/null
@@ -1,707 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.RemoteException;
-import android.service.gatekeeper.GateKeeperResponse;
-import android.service.gatekeeper.IGateKeeperService;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.VerifyCredentialResponse;
-
-import libcore.util.HexEncoding;
-
-import java.nio.ByteBuffer;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Set;
-
-
-/**
- * A class that maintains the wrapping of synthetic password by user credentials or escrow tokens.
- * It's (mostly) a pure storage for synthetic passwords, providing APIs to creating and destroying
- * synthetic password blobs which are wrapped by user credentials or escrow tokens.
- *
- * Here is the assumptions it makes:
- * Each user has one single synthetic password at any time.
- * The SP has an associated password handle, which binds to the SID for that user. The password
- * handle is persisted by SyntheticPasswordManager internally.
- * If the user credential is null, it's treated as if the credential is DEFAULT_PASSWORD
- *
- * Information persisted on disk:
- * for each user (stored under DEFAULT_HANDLE):
- * SP_HANDLE_NAME: GateKeeper password handle of synthetic password. Only available if user
- * credential exists, cleared when user clears their credential.
- * SP_E0_NAME, SP_P1_NAME: Secret to derive synthetic password when combined with escrow
- * tokens. Destroyed when escrow support is turned off for the given user.
- *
- * for each SP blob under the user (stored under the corresponding handle):
- * SP_BLOB_NAME: The encrypted synthetic password. Always exists.
- * PASSWORD_DATA_NAME: Metadata about user credential. Only exists for password based SP.
- * SECDISCARDABLE_NAME: Part of the necessary ingredient to decrypt SP_BLOB_NAME for the
- * purpose of secure deletion.
- *
- */
-public class SyntheticPasswordManager {
- private static final String SP_BLOB_NAME = "spblob";
- private static final String SP_E0_NAME = "e0";
- private static final String SP_P1_NAME = "p1";
- private static final String SP_HANDLE_NAME = "handle";
- private static final String SECDISCARDABLE_NAME = "secdis";
- private static final int SECDISCARDABLE_LENGTH = 16 * 1024;
- private static final String PASSWORD_DATA_NAME = "pwd";
-
- public static final long DEFAULT_HANDLE = 0;
- private static final String DEFAULT_PASSWORD = "default-password";
-
- private static final byte SYNTHETIC_PASSWORD_VERSION = 1;
- private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0;
- private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1;
-
- // 256-bit synthetic password
- private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8;
-
- private static final int PASSWORD_SCRYPT_N = 11;
- private static final int PASSWORD_SCRYPT_R = 3;
- private static final int PASSWORD_SCRYPT_P = 1;
- private static final int PASSWORD_SALT_LENGTH = 16;
- private static final int PASSWORD_TOKEN_LENGTH = 32;
- private static final String TAG = "SyntheticPasswordManager";
-
- private static final byte[] PERSONALISATION_SECDISCARDABLE = "secdiscardable-transform".getBytes();
- private static final byte[] PERSONALIZATION_KEY_STORE_PASSWORD = "keystore-password".getBytes();
- private static final byte[] PERSONALIZATION_USER_GK_AUTH = "user-gk-authentication".getBytes();
- private static final byte[] PERSONALIZATION_SP_GK_AUTH = "sp-gk-authentication".getBytes();
- private static final byte[] PERSONALIZATION_FBE_KEY = "fbe-key".getBytes();
- private static final byte[] PERSONALIZATION_SP_SPLIT = "sp-split".getBytes();
- private static final byte[] PERSONALIZATION_E0 = "e0-encryption".getBytes();
-
- static class AuthenticationResult {
- public AuthenticationToken authToken;
- public VerifyCredentialResponse gkResponse;
- }
-
- static class AuthenticationToken {
- /*
- * Here is the relationship between all three fields:
- * P0 and P1 are two randomly-generated blocks. P1 is stored on disk but P0 is not.
- * syntheticPassword = hash(P0 || P1)
- * E0 = P0 encrypted under syntheticPassword, stored on disk.
- */
- private @Nullable byte[] E0;
- private @Nullable byte[] P1;
- private @NonNull String syntheticPassword;
-
- public String deriveKeyStorePassword() {
- return bytesToHex(SyntheticPasswordCrypto.personalisedHash(
- PERSONALIZATION_KEY_STORE_PASSWORD, syntheticPassword.getBytes()));
- }
-
- public byte[] deriveGkPassword() {
- return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_SP_GK_AUTH,
- syntheticPassword.getBytes());
- }
-
- public byte[] deriveDiskEncryptionKey() {
- return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_FBE_KEY,
- syntheticPassword.getBytes());
- }
-
- private void initialize(byte[] P0, byte[] P1) {
- this.P1 = P1;
- this.syntheticPassword = String.valueOf(HexEncoding.encode(
- SyntheticPasswordCrypto.personalisedHash(
- PERSONALIZATION_SP_SPLIT, P0, P1)));
- this.E0 = SyntheticPasswordCrypto.encrypt(this.syntheticPassword.getBytes(),
- PERSONALIZATION_E0, P0);
- }
-
- public void recreate(byte[] secret) {
- initialize(secret, this.P1);
- }
-
- protected static AuthenticationToken create() {
- AuthenticationToken result = new AuthenticationToken();
- result.initialize(secureRandom(SYNTHETIC_PASSWORD_LENGTH),
- secureRandom(SYNTHETIC_PASSWORD_LENGTH));
- return result;
- }
-
- public byte[] computeP0() {
- if (E0 == null) {
- return null;
- }
- return SyntheticPasswordCrypto.decrypt(syntheticPassword.getBytes(), PERSONALIZATION_E0,
- E0);
- }
- }
-
- static class PasswordData {
- byte scryptN;
- byte scryptR;
- byte scryptP;
- public int passwordType;
- byte[] salt;
- public byte[] passwordHandle;
-
- public static PasswordData create(int passwordType) {
- PasswordData result = new PasswordData();
- result.scryptN = PASSWORD_SCRYPT_N;
- result.scryptR = PASSWORD_SCRYPT_R;
- result.scryptP = PASSWORD_SCRYPT_P;
- result.passwordType = passwordType;
- result.salt = secureRandom(PASSWORD_SALT_LENGTH);
- return result;
- }
-
- public static PasswordData fromBytes(byte[] data) {
- PasswordData result = new PasswordData();
- ByteBuffer buffer = ByteBuffer.allocate(data.length);
- buffer.put(data, 0, data.length);
- buffer.flip();
- result.passwordType = buffer.getInt();
- result.scryptN = buffer.get();
- result.scryptR = buffer.get();
- result.scryptP = buffer.get();
- int saltLen = buffer.getInt();
- result.salt = new byte[saltLen];
- buffer.get(result.salt);
- int handleLen = buffer.getInt();
- result.passwordHandle = new byte[handleLen];
- buffer.get(result.passwordHandle);
- return result;
- }
-
- public byte[] toBytes() {
- ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + 3 * Byte.BYTES
- + Integer.BYTES + salt.length + Integer.BYTES + passwordHandle.length);
- buffer.putInt(passwordType);
- buffer.put(scryptN);
- buffer.put(scryptR);
- buffer.put(scryptP);
- buffer.putInt(salt.length);
- buffer.put(salt);
- buffer.putInt(passwordHandle.length);
- buffer.put(passwordHandle);
- return buffer.array();
- }
- }
-
- private LockSettingsStorage mStorage;
-
- public SyntheticPasswordManager(LockSettingsStorage storage) {
- mStorage = storage;
- }
-
-
- public int getCredentialType(long handle, int userId) {
- byte[] passwordData = loadState(PASSWORD_DATA_NAME, handle, userId);
- if (passwordData == null) {
- Log.w(TAG, "getCredentialType: encountered empty password data for user " + userId);
- return LockPatternUtils.CREDENTIAL_TYPE_NONE;
- }
- return PasswordData.fromBytes(passwordData).passwordType;
- }
-
- /**
- * Initializing a new Authentication token, possibly from an existing credential and hash.
- *
- * The authentication token would bear a randomly-generated synthetic password.
- *
- * This method has the side effect of rebinding the SID of the given user to the
- * newly-generated SP.
- *
- * If the existing credential hash is non-null, the existing SID mill be migrated so
- * the synthetic password in the authentication token will produce the same SID
- * (the corresponding synthetic password handle is persisted by SyntheticPasswordManager
- * in a per-user data storage.)
- *
- * If the existing credential hash is null, it means the given user should have no SID so
- * SyntheticPasswordManager will nuke any SP handle previously persisted. In this case,
- * the supplied credential parameter is also ignored.
- *
- * Also saves the escrow information necessary to re-generate the synthetic password under
- * an escrow scheme. This information can be removed with {@link #destroyEscrowData} if
- * password escrow should be disabled completely on the given user.
- *
- */
- public AuthenticationToken newSyntheticPasswordAndSid(IGateKeeperService gatekeeper,
- byte[] hash, String credential, int userId) throws RemoteException {
- AuthenticationToken result = AuthenticationToken.create();
- GateKeeperResponse response;
- if (hash != null) {
- response = gatekeeper.enroll(userId, hash, credential.getBytes(),
- result.deriveGkPassword());
- if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
- Log.w(TAG, "Fail to migrate SID, assuming no SID, user " + userId);
- clearSidForUser(userId);
- } else {
- saveSyntheticPasswordHandle(response.getPayload(), userId);
- }
- } else {
- clearSidForUser(userId);
- }
- saveEscrowData(result, userId);
- return result;
- }
-
- /**
- * Enroll a new password handle and SID for the given synthetic password and persist it on disk.
- * Used when adding password to previously-unsecured devices.
- */
- public void newSidForUser(IGateKeeperService gatekeeper, AuthenticationToken authToken,
- int userId) throws RemoteException {
- GateKeeperResponse response = gatekeeper.enroll(userId, null, null,
- authToken.deriveGkPassword());
- if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
- Log.e(TAG, "Fail to create new SID for user " + userId);
- return;
- }
- saveSyntheticPasswordHandle(response.getPayload(), userId);
- }
-
- // Nuke the SP handle (and as a result, its SID) for the given user.
- public void clearSidForUser(int userId) {
- destroyState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId);
- }
-
- public boolean hasSidForUser(int userId) {
- return hasState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId);
- }
-
- // if null, it means there is no SID associated with the user
- // This can happen if the user is migrated to SP but currently
- // do not have a lockscreen password.
- private byte[] loadSyntheticPasswordHandle(int userId) {
- return loadState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId);
- }
-
- private void saveSyntheticPasswordHandle(byte[] spHandle, int userId) {
- saveState(SP_HANDLE_NAME, spHandle, DEFAULT_HANDLE, userId);
- }
-
- private boolean loadEscrowData(AuthenticationToken authToken, int userId) {
- authToken.E0 = loadState(SP_E0_NAME, DEFAULT_HANDLE, userId);
- authToken.P1 = loadState(SP_P1_NAME, DEFAULT_HANDLE, userId);
- return authToken.E0 != null && authToken.P1 != null;
- }
-
- private void saveEscrowData(AuthenticationToken authToken, int userId) {
- saveState(SP_E0_NAME, authToken.E0, DEFAULT_HANDLE, userId);
- saveState(SP_P1_NAME, authToken.P1, DEFAULT_HANDLE, userId);
- }
-
- public boolean hasEscrowData(int userId) {
- return hasState(SP_E0_NAME, DEFAULT_HANDLE, userId)
- && hasState(SP_P1_NAME, DEFAULT_HANDLE, userId);
- }
-
- public void destroyEscrowData(int userId) {
- destroyState(SP_E0_NAME, DEFAULT_HANDLE, userId);
- destroyState(SP_P1_NAME, DEFAULT_HANDLE, userId);
- }
-
- /**
- * Create a new password based SP blob based on the supplied authentication token, such that
- * a future successful authentication with unwrapPasswordBasedSyntheticPassword() would result
- * in the same authentication token.
- *
- * This method only creates SP blob wrapping around the given synthetic password and does not
- * handle logic around SID or SP handle. The caller should separately ensure that the user's SID
- * is consistent with the device state by calling other APIs in this class.
- *
- * @see #newSidForUser
- * @see #clearSidForUser
- */
- public long createPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
- String credential, int credentialType, AuthenticationToken authToken, int userId)
- throws RemoteException {
- if (credential == null || credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
- credentialType = LockPatternUtils.CREDENTIAL_TYPE_NONE;
- credential = DEFAULT_PASSWORD;
- }
-
- long handle = generateHandle();
- PasswordData pwd = PasswordData.create(credentialType);
- byte[] pwdToken = computePasswordToken(credential, pwd);
-
- // In case GK enrollment leaves persistent state around (in RPMB), this will nuke them
- // to prevent them from accumulating and causing problems.
- gatekeeper.clearSecureUserId(fakeUid(userId));
- GateKeeperResponse response = gatekeeper.enroll(fakeUid(userId), null, null,
- passwordTokenToGkInput(pwdToken));
- if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
- Log.e(TAG, "Fail to enroll user password when creating SP for user " + userId);
- return DEFAULT_HANDLE;
- }
- pwd.passwordHandle = response.getPayload();
- long sid = sidFromPasswordHandle(pwd.passwordHandle);
- saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
-
- byte[] applicationId = transformUnderSecdiscardable(pwdToken,
- createSecdiscardable(handle, userId));
- createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED, authToken,
- applicationId, sid, userId);
- return handle;
- }
-
- private ArrayMap<Integer, ArrayMap<Long, byte[]>> tokenMap = new ArrayMap<>();
-
- public long createTokenBasedSyntheticPassword(byte[] token, int userId) {
- long handle = generateHandle();
- byte[] applicationId = transformUnderSecdiscardable(token,
- createSecdiscardable(handle, userId));
- if (!tokenMap.containsKey(userId)) {
- tokenMap.put(userId, new ArrayMap<>());
- }
- tokenMap.get(userId).put(handle, applicationId);
- return handle;
- }
-
- public Set<Long> getPendingTokensForUser(int userId) {
- if (!tokenMap.containsKey(userId)) {
- return Collections.emptySet();
- }
- return tokenMap.get(userId).keySet();
- }
-
- public boolean removePendingToken(long handle, int userId) {
- if (!tokenMap.containsKey(userId)) {
- return false;
- }
- return tokenMap.get(userId).remove(handle) != null;
- }
-
- public boolean activateTokenBasedSyntheticPassword(long handle, AuthenticationToken authToken,
- int userId) {
- if (!tokenMap.containsKey(userId)) {
- return false;
- }
- byte[] applicationId = tokenMap.get(userId).get(handle);
- if (applicationId == null) {
- return false;
- }
- if (!loadEscrowData(authToken, userId)) {
- Log.w(TAG, "User is not escrowable");
- return false;
- }
- createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, authToken,
- applicationId, 0L, userId);
- tokenMap.get(userId).remove(handle);
- return true;
- }
-
- private void createSyntheticPasswordBlob(long handle, byte type, AuthenticationToken authToken,
- byte[] applicationId, long sid, int userId) {
- final byte[] secret;
- if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
- secret = authToken.computeP0();
- } else {
- secret = authToken.syntheticPassword.getBytes();
- }
- byte[] content = createSPBlob(getHandleName(handle), secret, applicationId, sid);
- byte[] blob = new byte[content.length + 1 + 1];
- blob[0] = SYNTHETIC_PASSWORD_VERSION;
- blob[1] = type;
- System.arraycopy(content, 0, blob, 2, content.length);
- saveState(SP_BLOB_NAME, blob, handle, userId);
- }
-
- /**
- * Decrypt a synthetic password by supplying the user credential and corresponding password
- * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
- * verification to referesh the SID & Auth token maintained by the system.
- */
- public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
- long handle, String credential, int userId) throws RemoteException {
- if (credential == null) {
- credential = DEFAULT_PASSWORD;
- }
- AuthenticationResult result = new AuthenticationResult();
- PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle, userId));
- byte[] pwdToken = computePasswordToken(credential, pwd);
- byte[] gkPwdToken = passwordTokenToGkInput(pwdToken);
-
- GateKeeperResponse response = gatekeeper.verifyChallenge(fakeUid(userId), 0L,
- pwd.passwordHandle, gkPwdToken);
- int responseCode = response.getResponseCode();
- if (responseCode == GateKeeperResponse.RESPONSE_OK) {
- result.gkResponse = VerifyCredentialResponse.OK;
- if (response.getShouldReEnroll()) {
- GateKeeperResponse reenrollResponse = gatekeeper.enroll(fakeUid(userId),
- pwd.passwordHandle, gkPwdToken, gkPwdToken);
- if (reenrollResponse.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
- pwd.passwordHandle = reenrollResponse.getPayload();
- saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
- } else {
- Log.w(TAG, "Fail to re-enroll user password for user " + userId);
- // continue the flow anyway
- }
- }
- } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
- result.gkResponse = new VerifyCredentialResponse(response.getTimeout());
- return result;
- } else {
- result.gkResponse = VerifyCredentialResponse.ERROR;
- return result;
- }
-
-
- byte[] applicationId = transformUnderSecdiscardable(pwdToken,
- loadSecdiscardable(handle, userId));
- result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED,
- applicationId, userId);
-
- // Perform verifyChallenge to refresh auth tokens for GK if user password exists.
- result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
- return result;
- }
-
- /**
- * Decrypt a synthetic password by supplying an escrow token and corresponding token
- * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
- * verification to referesh the SID & Auth token maintained by the system.
- */
- public @NonNull AuthenticationResult unwrapTokenBasedSyntheticPassword(
- IGateKeeperService gatekeeper, long handle, byte[] token, int userId)
- throws RemoteException {
- AuthenticationResult result = new AuthenticationResult();
- byte[] applicationId = transformUnderSecdiscardable(token,
- loadSecdiscardable(handle, userId));
- result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED,
- applicationId, userId);
- if (result.authToken != null) {
- result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
- if (result.gkResponse == null) {
- // The user currently has no password. return OK with null payload so null
- // is propagated to unlockUser()
- result.gkResponse = VerifyCredentialResponse.OK;
- }
- } else {
- result.gkResponse = VerifyCredentialResponse.ERROR;
- }
- return result;
- }
-
- private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type,
- byte[] applicationId, int userId) {
- byte[] blob = loadState(SP_BLOB_NAME, handle, userId);
- if (blob == null) {
- return null;
- }
- if (blob[0] != SYNTHETIC_PASSWORD_VERSION) {
- throw new RuntimeException("Unknown blob version");
- }
- if (blob[1] != type) {
- throw new RuntimeException("Invalid blob type");
- }
- byte[] secret = decryptSPBlob(getHandleName(handle),
- Arrays.copyOfRange(blob, 2, blob.length), applicationId);
- if (secret == null) {
- Log.e(TAG, "Fail to decrypt SP for user " + userId);
- return null;
- }
- AuthenticationToken result = new AuthenticationToken();
- if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
- if (!loadEscrowData(result, userId)) {
- Log.e(TAG, "User is not escrowable: " + userId);
- return null;
- }
- result.recreate(secret);
- } else {
- result.syntheticPassword = new String(secret);
- }
- return result;
- }
-
- /**
- * performs GK verifyChallenge and returns auth token, re-enrolling SP password handle
- * if required.
- *
- * Normally performing verifyChallenge with an AuthenticationToken should always return
- * RESPONSE_OK, since user authentication failures are detected earlier when trying to
- * decrypt SP.
- */
- public @Nullable VerifyCredentialResponse verifyChallenge(IGateKeeperService gatekeeper,
- @NonNull AuthenticationToken auth, long challenge, int userId) throws RemoteException {
- byte[] spHandle = loadSyntheticPasswordHandle(userId);
- if (spHandle == null) {
- // There is no password handle associated with the given user, i.e. the user is not
- // secured by lockscreen and has no SID, so just return here;
- return null;
- }
- VerifyCredentialResponse result;
- GateKeeperResponse response = gatekeeper.verifyChallenge(userId, challenge,
- spHandle, auth.deriveGkPassword());
- int responseCode = response.getResponseCode();
- if (responseCode == GateKeeperResponse.RESPONSE_OK) {
- result = new VerifyCredentialResponse(response.getPayload());
- if (response.getShouldReEnroll()) {
- response = gatekeeper.enroll(userId, spHandle,
- spHandle, auth.deriveGkPassword());
- if (response.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
- spHandle = response.getPayload();
- saveSyntheticPasswordHandle(spHandle, userId);
- // Call self again to re-verify with updated handle
- return verifyChallenge(gatekeeper, auth, challenge, userId);
- } else {
- Log.w(TAG, "Fail to re-enroll SP handle for user " + userId);
- // Fall through, return existing handle
- }
- }
- } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
- result = new VerifyCredentialResponse(response.getTimeout());
- } else {
- result = VerifyCredentialResponse.ERROR;
- }
- return result;
- }
-
- public boolean existsHandle(long handle, int userId) {
- return hasState(SP_BLOB_NAME, handle, userId);
- }
-
- public void destroyTokenBasedSyntheticPassword(long handle, int userId) {
- destroySyntheticPassword(handle, userId);
- destroyState(SECDISCARDABLE_NAME, handle, userId);
- }
-
- public void destroyPasswordBasedSyntheticPassword(long handle, int userId) {
- destroySyntheticPassword(handle, userId);
- destroyState(SECDISCARDABLE_NAME, handle, userId);
- destroyState(PASSWORD_DATA_NAME, handle, userId);
- }
-
- private void destroySyntheticPassword(long handle, int userId) {
- destroyState(SP_BLOB_NAME, handle, userId);
- destroySPBlobKey(getHandleName(handle));
- }
-
- private byte[] transformUnderSecdiscardable(byte[] data, byte[] rawSecdiscardable) {
- byte[] secdiscardable = SyntheticPasswordCrypto.personalisedHash(
- PERSONALISATION_SECDISCARDABLE, rawSecdiscardable);
- byte[] result = new byte[data.length + secdiscardable.length];
- System.arraycopy(data, 0, result, 0, data.length);
- System.arraycopy(secdiscardable, 0, result, data.length, secdiscardable.length);
- return result;
- }
-
- private byte[] createSecdiscardable(long handle, int userId) {
- byte[] data = secureRandom(SECDISCARDABLE_LENGTH);
- saveState(SECDISCARDABLE_NAME, data, handle, userId);
- return data;
- }
-
- private byte[] loadSecdiscardable(long handle, int userId) {
- return loadState(SECDISCARDABLE_NAME, handle, userId);
- }
-
- private boolean hasState(String stateName, long handle, int userId) {
- return !ArrayUtils.isEmpty(loadState(stateName, handle, userId));
- }
-
- private byte[] loadState(String stateName, long handle, int userId) {
- return mStorage.readSyntheticPasswordState(userId, handle, stateName);
- }
-
- private void saveState(String stateName, byte[] data, long handle, int userId) {
- mStorage.writeSyntheticPasswordState(userId, handle, stateName, data);
- }
-
- private void destroyState(String stateName, long handle, int userId) {
- mStorage.deleteSyntheticPasswordState(userId, handle, stateName);
- }
-
- protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] applicationId) {
- return SyntheticPasswordCrypto.decryptBlob(blobKeyName, blob, applicationId);
- }
-
- protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] applicationId, long sid) {
- return SyntheticPasswordCrypto.createBlob(blobKeyName, data, applicationId, sid);
- }
-
- protected void destroySPBlobKey(String keyAlias) {
- SyntheticPasswordCrypto.destroyBlobKey(keyAlias);
- }
-
- public static long generateHandle() {
- SecureRandom rng = new SecureRandom();
- long result;
- do {
- result = rng.nextLong();
- } while (result == DEFAULT_HANDLE);
- return result;
- }
-
- private int fakeUid(int uid) {
- return 100000 + uid;
- }
-
- protected static byte[] secureRandom(int length) {
- try {
- return SecureRandom.getInstance("SHA1PRNG").generateSeed(length);
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- return null;
- }
- }
-
- private String getHandleName(long handle) {
- return String.format("%s%x", LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX, handle);
- }
-
- private byte[] computePasswordToken(String password, PasswordData data) {
- return scrypt(password, data.salt, 1 << data.scryptN, 1 << data.scryptR, 1 << data.scryptP,
- PASSWORD_TOKEN_LENGTH);
- }
-
- private byte[] passwordTokenToGkInput(byte[] token) {
- return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_USER_GK_AUTH, token);
- }
-
- protected long sidFromPasswordHandle(byte[] handle) {
- return nativeSidFromPasswordHandle(handle);
- }
-
- protected byte[] scrypt(String password, byte[] salt, int N, int r, int p, int outLen) {
- return nativeScrypt(password.getBytes(), salt, N, r, p, outLen);
- }
-
- native long nativeSidFromPasswordHandle(byte[] handle);
- native byte[] nativeScrypt(byte[] password, byte[] salt, int N, int r, int p, int outLen);
-
- final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
- public static String bytesToHex(byte[] bytes) {
- if (bytes == null) {
- return "null";
- }
- char[] hexChars = new char[bytes.length * 2];
- for ( int j = 0; j < bytes.length; j++ ) {
- int v = bytes[j] & 0xFF;
- hexChars[j * 2] = hexArray[v >>> 4];
- hexChars[j * 2 + 1] = hexArray[v & 0x0F];
- }
- return new String(hexChars);
- }
-}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 4ce76f4..e609bd7 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -157,10 +157,6 @@
private int[] mDataConnectionState;
- private boolean[] mDataConnectionPossible;
-
- private String[] mDataConnectionReason;
-
private ArrayList<String>[] mConnectedApns;
private LinkProperties[] mDataConnectionLinkProperties;
@@ -310,8 +306,6 @@
mDataActivationState = new int[numPhones];
mSignalStrength = new SignalStrength[numPhones];
mMessageWaiting = new boolean[numPhones];
- mDataConnectionPossible = new boolean[numPhones];
- mDataConnectionReason = new String[numPhones];
mCallForwarding = new boolean[numPhones];
mCellLocation = new Bundle[numPhones];
mDataConnectionLinkProperties = new LinkProperties[numPhones];
@@ -328,8 +322,6 @@
mSignalStrength[i] = new SignalStrength();
mMessageWaiting[i] = false;
mCallForwarding[i] = false;
- mDataConnectionPossible[i] = false;
- mDataConnectionReason[i] = "";
mCellLocation[i] = new Bundle();
mCellInfo.add(i, null);
mConnectedApns[i] = new ArrayList<String>();
@@ -1080,16 +1072,16 @@
}
}
- public void notifyDataConnection(int state, boolean isDataConnectivityPossible,
+ public void notifyDataConnection(int state, boolean isDataAllowed,
String reason, String apn, String apnType, LinkProperties linkProperties,
NetworkCapabilities networkCapabilities, int networkType, boolean roaming) {
notifyDataConnectionForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, state,
- isDataConnectivityPossible,reason, apn, apnType, linkProperties,
+ isDataAllowed,reason, apn, apnType, linkProperties,
networkCapabilities, networkType, roaming);
}
public void notifyDataConnectionForSubscriber(int subId, int state,
- boolean isDataConnectivityPossible, String reason, String apn, String apnType,
+ boolean isDataAllowed, String reason, String apn, String apnType,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
int networkType, boolean roaming) {
if (!checkNotifyPermission("notifyDataConnection()" )) {
@@ -1097,7 +1089,7 @@
}
if (VDBG) {
log("notifyDataConnectionForSubscriber: subId=" + subId
- + " state=" + state + " isDataConnectivityPossible=" + isDataConnectivityPossible
+ + " state=" + state + " isDataAllowed=" + isDataAllowed
+ " reason='" + reason
+ "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType
+ " mRecords.size()=" + mRecords.size());
@@ -1125,8 +1117,6 @@
}
}
}
- mDataConnectionPossible[phoneId] = isDataConnectivityPossible;
- mDataConnectionReason[phoneId] = reason;
mDataConnectionLinkProperties[phoneId] = linkProperties;
mDataConnectionNetworkCapabilities[phoneId] = networkCapabilities;
if (mDataConnectionNetworkType[phoneId] != networkType) {
@@ -1173,7 +1163,7 @@
}
handleRemoveListLocked();
}
- broadcastDataConnectionStateChanged(state, isDataConnectivityPossible, reason, apn,
+ broadcastDataConnectionStateChanged(state, isDataAllowed, reason, apn,
apnType, linkProperties, networkCapabilities, roaming, subId);
broadcastPreciseDataConnectionStateChanged(state, networkType, apnType, apn, reason,
linkProperties, "");
@@ -1414,8 +1404,6 @@
pw.println("mCallForwarding=" + mCallForwarding[i]);
pw.println("mDataActivity=" + mDataActivity[i]);
pw.println("mDataConnectionState=" + mDataConnectionState[i]);
- pw.println("mDataConnectionPossible=" + mDataConnectionPossible[i]);
- pw.println("mDataConnectionReason=" + mDataConnectionReason[i]);
pw.println("mDataConnectionLinkProperties=" + mDataConnectionLinkProperties[i]);
pw.println("mDataConnectionNetworkCapabilities=" +
mDataConnectionNetworkCapabilities[i]);
@@ -1544,7 +1532,7 @@
}
private void broadcastDataConnectionStateChanged(int state,
- boolean isDataConnectivityPossible,
+ boolean isDataAllowed,
String reason, String apn, String apnType, LinkProperties linkProperties,
NetworkCapabilities networkCapabilities, boolean roaming, int subId) {
// Note: not reporting to the battery stats service here, because the
@@ -1553,7 +1541,7 @@
Intent intent = new Intent(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
intent.putExtra(PhoneConstants.STATE_KEY,
PhoneConstantConversions.convertDataState(state).toString());
- if (!isDataConnectivityPossible) {
+ if (!isDataAllowed) {
intent.putExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY, true);
}
if (reason != null) {
diff --git a/services/core/java/com/android/server/ThreadPriorityBooster.java b/services/core/java/com/android/server/ThreadPriorityBooster.java
index 31726ad..0dfecc0 100644
--- a/services/core/java/com/android/server/ThreadPriorityBooster.java
+++ b/services/core/java/com/android/server/ThreadPriorityBooster.java
@@ -16,14 +16,16 @@
package com.android.server;
-import android.os.Process;
+import static android.os.Process.getThreadPriority;
+import static android.os.Process.myTid;
+import static android.os.Process.setThreadPriority;
/**
* Utility class to boost threads in sections where important locks are held.
*/
public class ThreadPriorityBooster {
- private final int mBoostToPriority;
+ private volatile int mBoostToPriority;
private final int mLockGuardIndex;
private final ThreadLocal<PriorityState> mThreadState = new ThreadLocal<PriorityState>() {
@@ -38,12 +40,12 @@
}
public void boost() {
- final int tid = Process.myTid();
- final int prevPriority = Process.getThreadPriority(tid);
- PriorityState state = mThreadState.get();
+ final int tid = myTid();
+ final int prevPriority = getThreadPriority(tid);
+ final PriorityState state = mThreadState.get();
state.prevPriority = prevPriority;
if (state.regionCounter == 0 && prevPriority > mBoostToPriority) {
- Process.setThreadPriority(tid, mBoostToPriority);
+ setThreadPriority(tid, mBoostToPriority);
}
state.regionCounter++;
if (LockGuard.ENABLED) {
@@ -52,10 +54,28 @@
}
public void reset() {
- PriorityState state = mThreadState.get();
+ final PriorityState state = mThreadState.get();
state.regionCounter--;
- if (state.regionCounter == 0 && state.prevPriority > mBoostToPriority) {
- Process.setThreadPriority(Process.myTid(), state.prevPriority);
+ final int currentPriority = getThreadPriority(myTid());
+ if (state.regionCounter == 0 && state.prevPriority != currentPriority) {
+ setThreadPriority(myTid(), state.prevPriority);
+ }
+ }
+
+ /**
+ * Updates the priority we boost the threads to, and updates the current thread's priority if
+ * necessary.
+ */
+ protected void setBoostToPriority(int priority) {
+
+ // We don't care about the other threads here, as long as they see the update of this
+ // variable immediately.
+ mBoostToPriority = priority;
+ final PriorityState state = mThreadState.get();
+ final int tid = myTid();
+ final int prevPriority = getThreadPriority(tid);
+ if (state.regionCounter != 0 && prevPriority != priority) {
+ setThreadPriority(tid, priority);
}
}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 03e9dd2..c68e5d6 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -73,6 +73,7 @@
private final LinkedList<VibrationInfo> mPreviousVibrations;
private final int mPreviousVibrationsLimit;
+ private final boolean mAllowPriorityVibrationsInLowPowerMode;
private final boolean mSupportsAmplitudeControl;
private final int mDefaultVibrationAmplitude;
private final VibrationEffect[] mFallbackEffects;
@@ -213,6 +214,9 @@
mDefaultVibrationAmplitude = mContext.getResources().getInteger(
com.android.internal.R.integer.config_defaultVibrationAmplitude);
+ mAllowPriorityVibrationsInLowPowerMode = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_allowPriorityVibrationsInLowPowerMode);
+
mPreviousVibrations = new LinkedList<>();
IntentFilter filter = new IntentFilter();
@@ -221,19 +225,24 @@
long[] clickEffectTimings = getLongIntArray(context.getResources(),
com.android.internal.R.array.config_virtualKeyVibePattern);
- VibrationEffect clickEffect;
- if (clickEffectTimings.length == 0) {
- clickEffect = null;
- } else if (clickEffectTimings.length == 1) {
- clickEffect = VibrationEffect.createOneShot(
- clickEffectTimings[0], VibrationEffect.DEFAULT_AMPLITUDE);
- } else {
- clickEffect = VibrationEffect.createWaveform(clickEffectTimings, -1);
- }
+ VibrationEffect clickEffect = createEffect(clickEffectTimings);
VibrationEffect doubleClickEffect = VibrationEffect.createWaveform(
new long[] {0, 30, 100, 30} /*timings*/, -1);
+ long[] tickEffectTimings = getLongIntArray(context.getResources(),
+ com.android.internal.R.array.config_clockTickVibePattern);
+ VibrationEffect tickEffect = createEffect(tickEffectTimings);
- mFallbackEffects = new VibrationEffect[] { clickEffect, doubleClickEffect };
+ mFallbackEffects = new VibrationEffect[] { clickEffect, doubleClickEffect, tickEffect };
+ }
+
+ private static VibrationEffect createEffect(long[] timings) {
+ if (timings == null || timings.length == 0) {
+ return null;
+ } else if (timings.length == 1) {
+ return VibrationEffect.createOneShot(timings[0], VibrationEffect.DEFAULT_AMPLITUDE);
+ } else {
+ return VibrationEffect.createWaveform(timings, -1);
+ }
}
public void systemReady() {
@@ -456,7 +465,7 @@
}
private void startVibrationLocked(final Vibration vib) {
- if (mLowPowerMode && vib.mUsageHint != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
+ if (!isAllowedToVibrate(vib)) {
if (DEBUG) {
Slog.e(TAG, "Vibrate ignored, low power mode");
}
@@ -505,6 +514,26 @@
}
}
+ private boolean isAllowedToVibrate(Vibration vib) {
+ if (!mLowPowerMode) {
+ return true;
+ }
+ if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
+ return true;
+ }
+ if (!mAllowPriorityVibrationsInLowPowerMode) {
+ return false;
+ }
+ if (vib.mUsageHint == AudioAttributes.USAGE_ALARM ||
+ vib.mUsageHint == AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY ||
+ vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST) {
+
+ return true;
+ }
+
+ return false;
+ }
+
private boolean shouldVibrateForRingtone() {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
int ringerMode = audioManager.getRingerModeInternal();
diff --git a/services/core/java/com/android/server/accounts/AccountAuthenticatorCache.java b/services/core/java/com/android/server/accounts/AccountAuthenticatorCache.java
index 7552368..b9453db 100644
--- a/services/core/java/com/android/server/accounts/AccountAuthenticatorCache.java
+++ b/services/core/java/com/android/server/accounts/AccountAuthenticatorCache.java
@@ -53,6 +53,7 @@
AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME, sSerializer);
}
+ @Override
public AuthenticatorDescription parseServiceAttributes(Resources res,
String packageName, AttributeSet attrs) {
TypedArray sa = res.obtainAttributes(attrs,
@@ -81,11 +82,13 @@
}
private static class MySerializer implements XmlSerializerAndParser<AuthenticatorDescription> {
+ @Override
public void writeAsXml(AuthenticatorDescription item, XmlSerializer out)
throws IOException {
out.attribute(null, "type", item.type);
}
+ @Override
public AuthenticatorDescription createFromXml(XmlPullParser parser)
throws IOException, XmlPullParserException {
return AuthenticatorDescription.newKey(parser.getAttributeValue(null, "type"));
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index ec4dc64..1604729 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -751,6 +751,11 @@
}
}
+ private boolean isVisible(int visibility) {
+ return visibility == AccountManager.VISIBILITY_VISIBLE ||
+ visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE;
+ }
+
/**
* Updates visibility for given account name and package.
*
@@ -803,8 +808,10 @@
if (notify) {
for (Entry<String, Integer> packageToVisibility : packagesToVisibility
.entrySet()) {
- if (shouldNotifyOnVisibilityChange(packageToVisibility.getValue(),
- resolveAccountVisibility(account, packageName, accounts))) {
+ int oldVisibility = packageToVisibility.getValue();
+ int currentVisibility =
+ resolveAccountVisibility(account, packageName, accounts);
+ if (isVisible(oldVisibility) != isVisible(currentVisibility)) {
notifyPackage(packageToVisibility.getKey(), accounts);
}
}
@@ -1181,8 +1188,7 @@
for (Entry<String, Integer> packageToVisibility :
packagesToVisibility.entrySet()) {
- if (shouldNotifyOnVisibilityChange(packageToVisibility.getValue(),
- AccountManager.VISIBILITY_NOT_VISIBLE)) {
+ if (isVisible(packageToVisibility.getValue())) {
notifyPackage(packageToVisibility.getKey(), accounts);
}
}
@@ -1218,14 +1224,6 @@
}
}
- private boolean shouldNotifyOnVisibilityChange(int oldVisibility, int newVisibility) {
- boolean oldVisible = (oldVisibility == AccountManager.VISIBILITY_VISIBLE) ||
- (oldVisibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
- boolean newVisible = (newVisibility == AccountManager.VISIBILITY_VISIBLE) ||
- (newVisibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
- return oldVisible == newVisible;
- }
-
private SparseBooleanArray getUidsOfInstalledOrUpdatedPackagesAsUser(int userId) {
// Get the UIDs of all apps that might have data on the device. We want
// to preserve user data if the app might otherwise be storing data.
@@ -1374,6 +1372,7 @@
if (accounts != null) {
synchronized (accounts.dbLock) {
synchronized (accounts.cacheLock) {
+ accounts.statementForLogging.close();
accounts.accountsDb.close();
}
}
@@ -5062,6 +5061,7 @@
this.userDebugDbInsertionPoint = userDebugDbInsertionPoint;
}
+ @Override
public void run() {
SQLiteStatement logStatement = userAccount.statementForLogging;
logStatement.bindLong(1, accountId);
@@ -6178,7 +6178,7 @@
}
}
- private class NotificationId {
+ private static class NotificationId {
final String mTag;
private final int mId;
diff --git a/services/core/java/com/android/server/accounts/AccountsDb.java b/services/core/java/com/android/server/accounts/AccountsDb.java
index 8ca7ea1..0c3d268 100644
--- a/services/core/java/com/android/server/accounts/AccountsDb.java
+++ b/services/core/java/com/android/server/accounts/AccountsDb.java
@@ -1321,6 +1321,7 @@
}
}
+ @Override
public void close() {
mDeDatabase.close();
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index bad7091..c417484 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -813,7 +813,8 @@
String title;
String msg;
String[] pkgs;
- long oldestStartTime = System.currentTimeMillis(); // now
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ long oldestStartTime = nowElapsed;
if (active.size() == 1) {
intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", active.get(0).mPackageName, null));
@@ -846,8 +847,8 @@
.addExtras(notificationBundle)
.setSmallIcon(R.drawable.stat_sys_vitals)
.setOngoing(true)
- .setShowWhen(oldestStartTime > 0)
- .setWhen(oldestStartTime)
+ .setShowWhen(oldestStartTime < nowElapsed)
+ .setWhen(System.currentTimeMillis() - (nowElapsed - oldestStartTime))
.setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color))
.setContentTitle(title)
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f3ecfeb..f80dd62 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -453,6 +453,11 @@
public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
+ /**
+ * Priority we boost main thread and RT of top app to.
+ */
+ public static final int TOP_APP_PRIORITY_BOOST = -10;
+
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM;
private static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
@@ -599,6 +604,8 @@
// the notification will not be legible to the user.
private static final int MAX_BUGREPORT_TITLE_SIZE = 50;
+ private static final int NATIVE_DUMP_TIMEOUT_MS = 2000; // 2 seconds;
+
/** All system services */
SystemServiceManager mSystemServiceManager;
AssistUtils mAssistUtils;
@@ -1449,12 +1456,6 @@
= new ProcessMap<ArrayList<ProcessRecord>>();
/**
- * This is set if we had to do a delayed dexopt of an app before launching
- * it, to increase the ANR timeouts in that case.
- */
- boolean mDidDexOpt;
-
- /**
* Set if the systemServer made a call to enterSafeMode.
*/
boolean mSafeMode;
@@ -1950,13 +1951,6 @@
}
} break;
case SERVICE_TIMEOUT_MSG: {
- if (mDidDexOpt) {
- mDidDexOpt = false;
- Message nmsg = mHandler.obtainMessage(SERVICE_TIMEOUT_MSG);
- nmsg.obj = msg.obj;
- mHandler.sendMessageDelayed(nmsg, ActiveServices.SERVICE_TIMEOUT);
- return;
- }
mServices.serviceTimeout((ProcessRecord)msg.obj);
} break;
case SERVICE_FOREGROUND_TIMEOUT_MSG: {
@@ -2032,13 +2026,6 @@
}
} break;
case PROC_START_TIMEOUT_MSG: {
- if (mDidDexOpt) {
- mDidDexOpt = false;
- Message nmsg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
- nmsg.obj = msg.obj;
- mHandler.sendMessageDelayed(nmsg, PROC_START_TIMEOUT);
- return;
- }
ProcessRecord app = (ProcessRecord)msg.obj;
synchronized (ActivityManagerService.this) {
processStartTimedOutLocked(app);
@@ -5431,34 +5418,103 @@
* @param firstPids of dalvik VM processes to dump stack traces for first
* @param lastPids of dalvik VM processes to dump stack traces for last
* @param nativePids optional list of native pids to dump stack crawls
- * @return file containing stack traces, or null if no dump file is configured
*/
public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids,
ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
ArrayList<Integer> nativePids) {
- String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
- if (tracesPath == null || tracesPath.length() == 0) {
- return null;
+ ArrayList<Integer> extraPids = null;
+
+ // Measure CPU usage as soon as we're called in order to get a realistic sampling
+ // of the top users at the time of the request.
+ if (processCpuTracker != null) {
+ processCpuTracker.init();
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException ignored) {
+ }
+
+ processCpuTracker.update();
+
+ // We'll take the stack crawls of just the top apps using CPU.
+ final int N = processCpuTracker.countWorkingStats();
+ extraPids = new ArrayList<>();
+ for (int i = 0; i < N && extraPids.size() < 5; i++) {
+ ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
+ if (lastPids.indexOfKey(stats.pid) >= 0) {
+ if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
+
+ extraPids.add(stats.pid);
+ } else if (DEBUG_ANR) {
+ Slog.d(TAG, "Skipping next CPU consuming process, not a java proc: "
+ + stats.pid);
+ }
+ }
}
- File tracesFile = new File(tracesPath);
- try {
- if (clearTraces && tracesFile.exists()) tracesFile.delete();
- tracesFile.createNewFile();
- FileUtils.setPermissions(tracesFile.getPath(), 0666, -1, -1); // -rw-rw-rw-
- } catch (IOException e) {
- Slog.w(TAG, "Unable to prepare ANR traces file: " + tracesPath, e);
- return null;
+ boolean useTombstonedForJavaTraces = false;
+ File tracesFile;
+
+ final String tracesDir = SystemProperties.get("dalvik.vm.stack-trace-dir", "");
+ if (tracesDir.isEmpty()) {
+ // When dalvik.vm.stack-trace-dir is not set, we are using the "old" trace
+ // dumping scheme. All traces are written to a global trace file (usually
+ // "/data/anr/traces.txt") so the code below must take care to unlink and recreate
+ // the file if requested.
+ //
+ // This mode of operation will be removed in the near future.
+
+
+ String globalTracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
+ if (globalTracesPath.isEmpty()) {
+ Slog.w(TAG, "dumpStackTraces: no trace path configured");
+ return null;
+ }
+
+ tracesFile = new File(globalTracesPath);
+ try {
+ if (clearTraces && tracesFile.exists()) {
+ tracesFile.delete();
+ }
+
+ tracesFile.createNewFile();
+ FileUtils.setPermissions(globalTracesPath, 0666, -1, -1); // -rw-rw-rw-
+ } catch (IOException e) {
+ Slog.w(TAG, "Unable to prepare ANR traces file: " + tracesFile, e);
+ return null;
+ }
+ } else {
+ // When dalvik.vm.stack-trace-dir is set, we use the "new" trace dumping scheme.
+ // Each set of ANR traces is written to a separate file and dumpstate will process
+ // all such files and add them to a captured bug report if they're recent enough.
+ //
+ // NOTE: We should consider creating the file in native code atomically once we've
+ // gotten rid of the old scheme of dumping and lot of the code that deals with paths
+ // can be removed.
+ try {
+ tracesFile = File.createTempFile("anr_", "", new File(tracesDir));
+ FileUtils.setPermissions(tracesFile.getAbsolutePath(), 0600, -1, -1); // -rw-------
+ } catch (IOException ioe) {
+ Slog.w(TAG, "Unable to create ANR traces file: ", ioe);
+ return null;
+ }
+
+ useTombstonedForJavaTraces = true;
}
- dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativePids);
+ dumpStackTraces(tracesFile.getAbsolutePath(), firstPids, nativePids, extraPids,
+ useTombstonedForJavaTraces);
return tracesFile;
}
+ /**
+ * Legacy code, do not use. Existing users will be deleted.
+ *
+ * @deprecated
+ */
+ @Deprecated
public static class DumpStackFileObserver extends FileObserver {
// Keep in sync with frameworks/native/cmds/dumpstate/utils.cpp
private static final int TRACE_DUMP_TIMEOUT_MS = 10000; // 10 seconds
- static final int NATIVE_DUMP_TIMEOUT_MS = 2000; // 2 seconds;
private final String mTracesPath;
private boolean mClosed;
@@ -5512,17 +5568,45 @@
}
}
- private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids,
- ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
- ArrayList<Integer> nativePids) {
- // Use a FileObserver to detect when traces finish writing.
- // The order of traces is considered important to maintain for legibility.
- DumpStackFileObserver observer = new DumpStackFileObserver(tracesPath);
+ /**
+ * Dump java traces for process {@code pid} to the specified file. If java trace dumping
+ * fails, a native backtrace is attempted. Note that the timeout {@code timeoutMs} only applies
+ * to the java section of the trace, a further {@code NATIVE_DUMP_TIMEOUT_MS} might be spent
+ * attempting to obtain native traces in the case of a failure. Returns the total time spent
+ * capturing traces.
+ */
+ private static long dumpJavaTracesTombstoned(int pid, String fileName, long timeoutMs) {
+ final long timeStart = SystemClock.elapsedRealtime();
+ if (!Debug.dumpJavaBacktraceToFileTimeout(pid, fileName, (int) (timeoutMs / 1000))) {
+ Debug.dumpNativeBacktraceToFileTimeout(pid, fileName,
+ (NATIVE_DUMP_TIMEOUT_MS / 1000));
+ }
+
+ return SystemClock.elapsedRealtime() - timeStart;
+ }
+
+ private static void dumpStackTraces(String tracesFile, ArrayList<Integer> firstPids,
+ ArrayList<Integer> nativePids, ArrayList<Integer> extraPids,
+ boolean useTombstonedForJavaTraces) {
+
+ // We don't need any sort of inotify based monitoring when we're dumping traces via
+ // tombstoned. Data is piped to an "intercept" FD installed in tombstoned so we're in full
+ // control of all writes to the file in question.
+ final DumpStackFileObserver observer;
+ if (useTombstonedForJavaTraces) {
+ observer = null;
+ } else {
+ // Use a FileObserver to detect when traces finish writing.
+ // The order of traces is considered important to maintain for legibility.
+ observer = new DumpStackFileObserver(tracesFile);
+ }
// We must complete all stack dumps within 20 seconds.
long remainingTime = 20 * 1000;
try {
- observer.startWatching();
+ if (observer != null) {
+ observer.startWatching();
+ }
// First collect all of the stacks of the most important pids.
if (firstPids != null) {
@@ -5530,7 +5614,12 @@
for (int i = 0; i < num; i++) {
if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for pid "
+ firstPids.get(i));
- final long timeTaken = observer.dumpWithTimeout(firstPids.get(i), remainingTime);
+ final long timeTaken;
+ if (useTombstonedForJavaTraces) {
+ timeTaken = dumpJavaTracesTombstoned(firstPids.get(i), tracesFile, remainingTime);
+ } else {
+ timeTaken = observer.dumpWithTimeout(firstPids.get(i), remainingTime);
+ }
remainingTime -= timeTaken;
if (remainingTime <= 0) {
@@ -5549,12 +5638,11 @@
if (nativePids != null) {
for (int pid : nativePids) {
if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid);
- final long nativeDumpTimeoutMs = Math.min(
- DumpStackFileObserver.NATIVE_DUMP_TIMEOUT_MS, remainingTime);
+ final long nativeDumpTimeoutMs = Math.min(NATIVE_DUMP_TIMEOUT_MS, remainingTime);
final long start = SystemClock.elapsedRealtime();
Debug.dumpNativeBacktraceToFileTimeout(
- pid, tracesPath, (int) (nativeDumpTimeoutMs / 1000));
+ pid, tracesFile, (int) (nativeDumpTimeoutMs / 1000));
final long timeTaken = SystemClock.elapsedRealtime() - start;
remainingTime -= timeTaken;
@@ -5570,48 +5658,34 @@
}
}
- // Lastly, measure CPU usage.
- if (processCpuTracker != null) {
- processCpuTracker.init();
- System.gc();
- processCpuTracker.update();
- try {
- synchronized (processCpuTracker) {
- processCpuTracker.wait(500); // measure over 1/2 second.
+ // Lastly, dump stacks for all extra PIDs from the CPU tracker.
+ if (extraPids != null) {
+ for (int pid : extraPids) {
+ if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid " + pid);
+
+ final long timeTaken;
+ if (useTombstonedForJavaTraces) {
+ timeTaken = dumpJavaTracesTombstoned(pid, tracesFile, remainingTime);
+ } else {
+ timeTaken = observer.dumpWithTimeout(pid, remainingTime);
}
- } catch (InterruptedException e) {
- }
- processCpuTracker.update();
- // We'll take the stack crawls of just the top apps using CPU.
- final int N = processCpuTracker.countWorkingStats();
- int numProcs = 0;
- for (int i=0; i<N && numProcs<5; i++) {
- ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
- if (lastPids.indexOfKey(stats.pid) >= 0) {
- numProcs++;
-
- if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
-
- final long timeTaken = observer.dumpWithTimeout(stats.pid, remainingTime);
- remainingTime -= timeTaken;
- if (remainingTime <= 0) {
- Slog.e(TAG, "Aborting stack trace dump (current extra pid=" + stats.pid +
+ remainingTime -= timeTaken;
+ if (remainingTime <= 0) {
+ Slog.e(TAG, "Aborting stack trace dump (current extra pid=" + pid +
"); deadline exceeded.");
- return;
- }
+ return;
+ }
- if (DEBUG_ANR) {
- Slog.d(TAG, "Done with extra pid " + stats.pid + " in " + timeTaken + "ms");
- }
- } else if (DEBUG_ANR) {
- Slog.d(TAG, "Skipping next CPU consuming process, not a java proc: "
- + stats.pid);
+ if (DEBUG_ANR) {
+ Slog.d(TAG, "Done with extra pid " + pid + " in " + timeTaken + "ms");
}
}
}
} finally {
- observer.stopWatching();
+ if (observer != null) {
+ observer.stopWatching();
+ }
}
}
@@ -5658,7 +5732,7 @@
if (app != null) {
ArrayList<Integer> firstPids = new ArrayList<Integer>();
firstPids.add(app.pid);
- dumpStackTraces(tracesPath, firstPids, null, null, null);
+ dumpStackTraces(tracesPath, firstPids, null, null, true /* useTombstoned */);
}
File lastTracesFile = null;
@@ -12829,7 +12903,7 @@
timeout = getInputDispatchingTimeoutLocked(proc);
}
- if (!inputDispatchingTimedOut(proc, null, null, aboveSystem, reason)) {
+ if (inputDispatchingTimedOut(proc, null, null, aboveSystem, reason)) {
return -1;
}
@@ -12862,12 +12936,6 @@
return false;
}
- if (mDidDexOpt) {
- // Give more time since we were dexopting.
- mDidDexOpt = false;
- return false;
- }
-
if (proc.instr != null) {
Bundle info = new Bundle();
info.putString("shortMsg", "keyDispatchingTimedOut");
@@ -13426,7 +13494,7 @@
setThreadScheduler(proc.renderThreadTid,
SCHED_FIFO | SCHED_RESET_ON_FORK, 1);
} else {
- setThreadPriority(proc.renderThreadTid, -10);
+ setThreadPriority(proc.renderThreadTid, TOP_APP_PRIORITY_BOOST);
}
}
} else {
@@ -19158,10 +19226,11 @@
final Uri data = intent.getData();
final String ssp;
if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
- final ApplicationInfo aInfo =
- getPackageManagerInternalLocked().getApplicationInfo(
- ssp,
- userId);
+ ApplicationInfo aInfo = null;
+ try {
+ aInfo = AppGlobals.getPackageManager()
+ .getApplicationInfo(ssp, 0 /*flags*/, userId);
+ } catch (RemoteException ignore) {}
if (aInfo == null) {
Slog.w(TAG, "Dropping ACTION_PACKAGE_REPLACED for non-existent pkg:"
+ " ssp=" + ssp + " data=" + data);
@@ -21885,10 +21954,11 @@
}
} else {
// Boost priority for top app UI and render threads
- setThreadPriority(app.pid, -10);
+ setThreadPriority(app.pid, TOP_APP_PRIORITY_BOOST);
if (app.renderThreadTid != 0) {
try {
- setThreadPriority(app.renderThreadTid, -10);
+ setThreadPriority(app.renderThreadTid,
+ TOP_APP_PRIORITY_BOOST);
} catch (IllegalArgumentException e) {
// thread died, ignore
}
@@ -24281,7 +24351,6 @@
}
void updateApplicationInfoLocked(@NonNull List<String> packagesToUpdate, int userId) {
- final PackageManagerInternal packageManager = getPackageManagerInternalLocked();
final boolean updateFrameworkRes = packagesToUpdate.contains("android");
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
final ProcessRecord app = mLruProcesses.get(i);
@@ -24298,8 +24367,8 @@
final String packageName = app.pkgList.keyAt(j);
if (updateFrameworkRes || packagesToUpdate.contains(packageName)) {
try {
- final ApplicationInfo ai = packageManager.getApplicationInfo(
- packageName, app.userId);
+ final ApplicationInfo ai = AppGlobals.getPackageManager()
+ .getApplicationInfo(packageName, 0 /*flags*/, app.userId);
if (ai != null) {
app.thread.scheduleApplicationInfoChanged(ai);
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 9db957c..a45becd 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -3650,7 +3650,7 @@
finishActivityResultsLocked(r, resultCode, resultData);
- final boolean endTask = index <= 0;
+ final boolean endTask = index <= 0 && !task.isClearingToReuseTask();
final int transit = endTask ? TRANSIT_TASK_CLOSE : TRANSIT_ACTIVITY_CLOSE;
if (mResumedActivity == r) {
if (DEBUG_VISIBILITY || DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index b55bae9..25dc354 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1461,6 +1461,7 @@
mService.getGlobalConfiguration(), r.getMergedOverrideConfiguration());
r.setLastReportedConfiguration(mergedConfiguration);
+ logIfTransactionTooLarge(r.intent, r.icicle);
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info,
// TODO: Have this take the merged configuration instead of separate global and
@@ -1546,6 +1547,21 @@
return true;
}
+ private void logIfTransactionTooLarge(Intent intent, Bundle icicle) {
+ int extrasSize = 0;
+ if (intent != null) {
+ final Bundle extras = intent.getExtras();
+ if (extras != null) {
+ extrasSize = extras.getSize();
+ }
+ }
+ int icicleSize = (icicle == null ? 0 : icicle.getSize());
+ if (extrasSize + icicleSize > 200000) {
+ Slog.e(TAG, "Transaction too large, intent: " + intent + ", extras size: " + extrasSize
+ + ", icicle size: " + icicleSize);
+ }
+ }
+
void startSpecificActivityLocked(ActivityRecord r,
boolean andResume, boolean checkConfig) {
// Is this activity's application already running?
@@ -2364,7 +2380,7 @@
}
void notifyAppTransitionDone() {
- continueUpdateBounds(HOME_STACK_ID);
+ continueUpdateBounds(RECENTS_STACK_ID);
for (int i = mResizingTasksDuringAnimation.size() - 1; i >= 0; i--) {
final int taskId = mResizingTasksDuringAnimation.valueAt(i);
final TaskRecord task =
@@ -4415,13 +4431,6 @@
case IDLE_TIMEOUT_MSG: {
if (DEBUG_IDLE) Slog.d(TAG_IDLE,
"handleMessage: IDLE_TIMEOUT_MSG: r=" + msg.obj);
- if (mService.mDidDexOpt) {
- mService.mDidDexOpt = false;
- Message nmsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG);
- nmsg.obj = msg.obj;
- mHandler.sendMessageDelayed(nmsg, IDLE_TIMEOUT);
- return;
- }
// We don't at this point know if the activity is fullscreen,
// so we need to be conservative and assume it isn't.
activityIdleInternal((ActivityRecord) msg.obj,
@@ -4447,11 +4456,6 @@
}
} break;
case LAUNCH_TIMEOUT_MSG: {
- if (mService.mDidDexOpt) {
- mService.mDidDexOpt = false;
- mHandler.sendEmptyMessageDelayed(LAUNCH_TIMEOUT_MSG, LAUNCH_TIMEOUT);
- return;
- }
synchronized (mService) {
if (mLaunchingActivity.isHeld()) {
Slog.w(TAG, "Launch timeout has expired, giving up wake lock!");
@@ -5090,73 +5094,79 @@
+ taskId + " can't be launch in the home/recents stack.");
}
- if (launchStackId == DOCKED_STACK_ID) {
- mWindowManager.setDockedStackCreateState(
- activityOptions.getDockCreateMode(), null /* initialBounds */);
+ mWindowManager.deferSurfaceLayout();
+ try {
+ if (launchStackId == DOCKED_STACK_ID) {
+ mWindowManager.setDockedStackCreateState(
+ activityOptions.getDockCreateMode(), null /* initialBounds */);
- // Defer updating the stack in which recents is until the app transition is done, to
- // not run into issues where we still need to draw the task in recents but the
- // docked stack is already created.
- deferUpdateBounds(HOME_STACK_ID);
- mWindowManager.prepareAppTransition(TRANSIT_DOCK_TASK_FROM_RECENTS, false);
- }
-
- task = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE,
- launchStackId);
- if (task == null) {
- continueUpdateBounds(HOME_STACK_ID);
- mWindowManager.executeAppTransition();
- throw new IllegalArgumentException(
- "startActivityFromRecentsInner: Task " + taskId + " not found.");
- }
-
- // Since we don't have an actual source record here, we assume that the currently focused
- // activity was the source.
- final ActivityStack focusedStack = getFocusedStack();
- final ActivityRecord sourceRecord =
- focusedStack != null ? focusedStack.topActivity() : null;
-
- if (launchStackId != INVALID_STACK_ID) {
- if (task.getStackId() != launchStackId) {
- task.reparent(launchStackId, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, ANIMATE,
- DEFER_RESUME, "startActivityFromRecents");
+ // Defer updating the stack in which recents is until the app transition is done, to
+ // not run into issues where we still need to draw the task in recents but the
+ // docked stack is already created.
+ deferUpdateBounds(RECENTS_STACK_ID);
+ mWindowManager.prepareAppTransition(TRANSIT_DOCK_TASK_FROM_RECENTS, false);
}
- }
- // If the user must confirm credentials (e.g. when first launching a work app and the
- // Work Challenge is present) let startActivityInPackage handle the intercepting.
- if (!mService.mUserController.shouldConfirmCredentials(task.userId)
- && task.getRootActivity() != null) {
- mService.mActivityStarter.sendPowerHintForLaunchStartIfNeeded(true /* forceSend */);
- mActivityMetricsLogger.notifyActivityLaunching();
- mService.moveTaskToFrontLocked(task.taskId, 0, bOptions);
- mActivityMetricsLogger.notifyActivityLaunched(ActivityManager.START_TASK_TO_FRONT,
- task.getTopActivity());
+ task = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE,
+ launchStackId);
+ if (task == null) {
+ continueUpdateBounds(RECENTS_STACK_ID);
+ mWindowManager.executeAppTransition();
+ throw new IllegalArgumentException(
+ "startActivityFromRecentsInner: Task " + taskId + " not found.");
+ }
- // If we are launching the task in the docked stack, put it into resizing mode so
- // the window renders full-screen with the background filling the void. Also only
- // call this at the end to make sure that tasks exists on the window manager side.
+ // Since we don't have an actual source record here, we assume that the currently
+ // focused activity was the source.
+ final ActivityStack focusedStack = getFocusedStack();
+ final ActivityRecord sourceRecord =
+ focusedStack != null ? focusedStack.topActivity() : null;
+
+ if (launchStackId != INVALID_STACK_ID) {
+ if (task.getStackId() != launchStackId) {
+ task.reparent(launchStackId, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, ANIMATE,
+ DEFER_RESUME, "startActivityFromRecents");
+ }
+ }
+
+ // If the user must confirm credentials (e.g. when first launching a work app and the
+ // Work Challenge is present) let startActivityInPackage handle the intercepting.
+ if (!mService.mUserController.shouldConfirmCredentials(task.userId)
+ && task.getRootActivity() != null) {
+ mService.mActivityStarter.sendPowerHintForLaunchStartIfNeeded(true /* forceSend */);
+ mActivityMetricsLogger.notifyActivityLaunching();
+ mService.moveTaskToFrontLocked(task.taskId, 0, bOptions);
+ mActivityMetricsLogger.notifyActivityLaunched(ActivityManager.START_TASK_TO_FRONT,
+ task.getTopActivity());
+
+ // If we are launching the task in the docked stack, put it into resizing mode so
+ // the window renders full-screen with the background filling the void. Also only
+ // call this at the end to make sure that tasks exists on the window manager side.
+ if (launchStackId == DOCKED_STACK_ID) {
+ setResizingDuringAnimation(task);
+ }
+
+ mService.mActivityStarter.postStartActivityProcessing(task.getTopActivity(),
+ ActivityManager.START_TASK_TO_FRONT,
+ sourceRecord != null
+ ? sourceRecord.getTask().getStackId() : INVALID_STACK_ID,
+ sourceRecord, task.getStack());
+ return ActivityManager.START_TASK_TO_FRONT;
+ }
+ callingUid = task.mCallingUid;
+ callingPackage = task.mCallingPackage;
+ intent = task.intent;
+ intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
+ userId = task.userId;
+ int result = mService.startActivityInPackage(callingUid, callingPackage, intent, null,
+ null, null, 0, 0, bOptions, userId, null, task);
if (launchStackId == DOCKED_STACK_ID) {
setResizingDuringAnimation(task);
}
-
- mService.mActivityStarter.postStartActivityProcessing(task.getTopActivity(),
- ActivityManager.START_TASK_TO_FRONT,
- sourceRecord != null ? sourceRecord.getTask().getStackId() : INVALID_STACK_ID,
- sourceRecord, task.getStack());
- return ActivityManager.START_TASK_TO_FRONT;
+ return result;
+ } finally {
+ mWindowManager.continueSurfaceLayout();
}
- callingUid = task.mCallingUid;
- callingPackage = task.mCallingPackage;
- intent = task.intent;
- intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
- userId = task.userId;
- int result = mService.startActivityInPackage(callingUid, callingPackage, intent, null,
- null, null, 0, 0, bOptions, userId, null, task);
- if (launchStackId == DOCKED_STACK_ID) {
- setResizingDuringAnimation(task);
- }
- return result;
}
/**
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 07caf9e..1ed2ac1 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -2083,6 +2083,10 @@
private ActivityStack getLaunchStack(ActivityRecord r, int launchFlags, TaskRecord task,
ActivityOptions aOptions) {
+ // We are reusing a task, keep the stack!
+ if (mReuseTask != null) {
+ return mReuseTask.getStack();
+ }
// If the activity is of a specific type, return the associated stack, creating it if
// necessary
@@ -2096,11 +2100,6 @@
return mSupervisor.getStack(ASSISTANT_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
}
- // We are reusing a task, keep the stack!
- if (mReuseTask != null) {
- return mReuseTask.getStack();
- }
-
final int launchDisplayId =
(aOptions != null) ? aOptions.getLaunchDisplayId() : INVALID_DISPLAY;
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index cfb5478..a83f989 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -55,6 +55,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Set;
import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
@@ -908,10 +909,11 @@
// For background ANRs, don't pass the ProcessCpuTracker to
// avoid spending 1/2 second collecting stats to rank lastPids.
- File tracesFile = mService.dumpStackTraces(true, firstPids,
- (isSilentANR) ? null : processCpuTracker,
- (isSilentANR) ? null : lastPids,
- nativePids);
+ File tracesFile = ActivityManagerService.dumpStackTraces(
+ true, firstPids,
+ (isSilentANR) ? null : processCpuTracker,
+ (isSilentANR) ? null : lastPids,
+ nativePids);
String cpuInfo = null;
if (ActivityManagerService.MONITOR_CPU_USAGE) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c20221b..06ab75a 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -188,6 +188,7 @@
}
private native int getPlatformLowPowerStats(ByteBuffer outBuffer);
+ private native int getSubsystemLowPowerStats(ByteBuffer outBuffer);
private CharsetDecoder mDecoderStat = StandardCharsets.UTF_8
.newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
@@ -219,6 +220,28 @@
}
}
+ @Override
+ public String getSubsystemLowPowerStats() {
+ Slog.d(TAG, "begin getSubsystemLowPowerStats");
+ try {
+ mUtf8BufferStat.clear();
+ mUtf16BufferStat.clear();
+ mDecoderStat.reset();
+ int bytesWritten = getSubsystemLowPowerStats(mUtf8BufferStat);
+ if (bytesWritten < 0) {
+ return null;
+ } else if (bytesWritten == 0) {
+ return "Empty";
+ }
+ mUtf8BufferStat.limit(bytesWritten);
+ mDecoderStat.decode(mUtf8BufferStat, mUtf16BufferStat, true);
+ mUtf16BufferStat.flip();
+ return mUtf16BufferStat.toString();
+ } finally {
+ Slog.d(TAG, "end getSubsystemLowPowerStats");
+ }
+ }
+
BatteryStatsService(File systemDir, Handler handler) {
// Our handler here will be accessing the disk, use a different thread than
// what the ActivityManagerService gave us (no I/O on that one!).
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 639b7a9..1705b58 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1403,13 +1403,6 @@
long now = SystemClock.uptimeMillis();
BroadcastRecord r = mOrderedBroadcasts.get(0);
if (fromMsg) {
- if (mService.mDidDexOpt) {
- // Delay timeouts until dexopt finishes.
- mService.mDidDexOpt = false;
- long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod;
- setBroadcastTimeoutLocked(timeoutTime);
- return;
- }
if (!mService.mProcessesReady) {
// Only process broadcast timeouts if the system is ready. That way
// PRE_BOOT_COMPLETED broadcasts can't timeout as they are intended
diff --git a/services/core/java/com/android/server/am/LockTaskNotify.java b/services/core/java/com/android/server/am/LockTaskNotify.java
index 8c18c46..0412db5 100644
--- a/services/core/java/com/android/server/am/LockTaskNotify.java
+++ b/services/core/java/com/android/server/am/LockTaskNotify.java
@@ -20,6 +20,8 @@
import android.content.Context;
import android.os.Handler;
import android.os.Message;
+import android.os.SystemClock;
+import android.util.Slog;
import android.view.WindowManager;
import android.widget.Toast;
@@ -31,10 +33,12 @@
*/
public class LockTaskNotify {
private static final String TAG = "LockTaskNotify";
+ private static final long SHOW_TOAST_MINIMUM_INTERVAL = 1000;
private final Context mContext;
private final H mHandler;
private Toast mLastToast;
+ private long mLastShowToastTime;
public LockTaskNotify(Context context) {
mContext = context;
@@ -55,10 +59,16 @@
if (text == null) {
return;
}
+ long showToastTime = SystemClock.elapsedRealtime();
+ if ((showToastTime - mLastShowToastTime) < SHOW_TOAST_MINIMUM_INTERVAL) {
+ Slog.i(TAG, "Ignore toast since it is requested in very short interval.");
+ return;
+ }
if (mLastToast != null) {
mLastToast.cancel();
}
mLastToast = makeAllUserToastAndShow(text);
+ mLastShowToastTime = showToastTime;
}
public void show(boolean starting) {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index e81e6d7..4f211e3 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1592,6 +1592,13 @@
}
/**
+ * @return {@code true} if the task is being cleared for the purposes of being reused.
+ */
+ boolean isClearingToReuseTask() {
+ return mReuseTask;
+ }
+
+ /**
* Find the activity in the history stack within the given task. Returns
* the index within the history at which it's found, or < 0 if not found.
*/
@@ -2329,7 +2336,7 @@
pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));
pw.print(" mSupportsPictureInPicture=" + mSupportsPictureInPicture);
pw.print(" isResizeable=" + isResizeable());
- pw.print(" firstActiveTime=" + lastActiveTime);
+ pw.print(" firstActiveTime=" + firstActiveTime);
pw.print(" lastActiveTime=" + lastActiveTime);
pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)");
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 6a310f2..ba8fa31 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -123,12 +123,13 @@
private final Injector mInjector;
private final Handler mHandler;
- // Holds the current foreground user's id
+ // Holds the current foreground user's id. Use mLock when updating
@GuardedBy("mLock")
- private int mCurrentUserId = UserHandle.USER_SYSTEM;
- // Holds the target user's id during a user switch
+ private volatile int mCurrentUserId = UserHandle.USER_SYSTEM;
+ // Holds the target user's id during a user switch. The value of mCurrentUserId will be updated
+ // once target user goes into the foreground. Use mLock when updating
@GuardedBy("mLock")
- private int mTargetUserId = UserHandle.USER_NULL;
+ private volatile int mTargetUserId = UserHandle.USER_NULL;
/**
* Which users have been started, so are allowed to run code.
@@ -1505,6 +1506,11 @@
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
+
+ // Optimization - if there is no pending user switch, return current id
+ if (mTargetUserId == UserHandle.USER_NULL) {
+ return getUserInfo(mCurrentUserId);
+ }
synchronized (mLock) {
return getCurrentUserLocked();
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index e5c3106..5a7f6e3 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -352,7 +352,7 @@
AudioSystem.STREAM_MUSIC, // STREAM_TTS
AudioSystem.STREAM_MUSIC // STREAM_ACCESSIBILITY
};
- private int[] mStreamVolumeAlias;
+ protected static int[] mStreamVolumeAlias;
/**
* Map AudioSystem.STREAM_* constants to app ops. This should be used
@@ -1817,7 +1817,7 @@
}
// UI update and Broadcast Intent
- private void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) {
+ protected void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) {
streamType = mStreamVolumeAlias[streamType];
if (streamType == AudioSystem.STREAM_MUSIC) {
diff --git a/services/core/java/com/android/server/car/CarServiceHelperService.java b/services/core/java/com/android/server/car/CarServiceHelperService.java
new file mode 100644
index 0000000..9392a6a
--- /dev/null
+++ b/services/core/java/com/android/server/car/CarServiceHelperService.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.car;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.car.ICarServiceHelper;
+import com.android.server.SystemService;
+
+/**
+ * System service side companion service for CarService.
+ * Starts car service and provide necessary API for CarService. Only for car product.
+ */
+public class CarServiceHelperService extends SystemService {
+ private static final String TAG = "CarServiceHelper";
+ private static final String CAR_SERVICE_INTERFACE = "android.car.ICar";
+ private final ICarServiceHelperImpl mHelper = new ICarServiceHelperImpl();
+ private IBinder mCarService;
+ private final ServiceConnection mCarServiceConnection = new ServiceConnection() {
+
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+ Slog.i(TAG, "**CarService connected**");
+ mCarService = iBinder;
+ // Cannot depend on ICar which is defined in CarService, so handle binder call directly
+ // instead.
+ // void setCarServiceHelper(in IBinder helper)
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(CAR_SERVICE_INTERFACE);
+ data.writeStrongBinder(mHelper.asBinder());
+ try {
+ mCarService.transact(IBinder.FIRST_CALL_TRANSACTION, // setCarServiceHelper
+ data, null, Binder.FLAG_ONEWAY);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException from car service", e);
+ handleCarServiceCrash();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+ handleCarServiceCrash();
+ }
+ };
+
+ public CarServiceHelperService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ Intent intent = new Intent();
+ intent.setPackage("com.android.car");
+ intent.setAction(CAR_SERVICE_INTERFACE);
+ if (!getContext().bindServiceAsUser(intent, mCarServiceConnection, Context.BIND_AUTO_CREATE,
+ UserHandle.SYSTEM)) {
+ Slog.wtf(TAG, "cannot start car service");
+ }
+ }
+
+ private void handleCarServiceCrash() {
+ //TODO define recovery bahavior
+ }
+
+ private class ICarServiceHelperImpl extends ICarServiceHelper.Stub {
+ //TODO
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 214cfce..4094083 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -135,6 +135,10 @@
}
}
+ @Override
+ public synchronized void onWakeupEvent(String prefix, int uid, int gid, long timestampNs) {
+ }
+
public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
flushProtos(events, mConnectEvents, IpConnectivityEventBuilder::toProto);
flushProtos(events, mDnsEvents, IpConnectivityEventBuilder::toProto);
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 11296ab..0ee2a41 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -480,7 +480,11 @@
*/
@VisibleForTesting
public static final class CaptivePortalProbeResult {
- static final CaptivePortalProbeResult FAILED = new CaptivePortalProbeResult(599);
+ static final int SUCCESS_CODE = 204;
+ static final int FAILED_CODE = 599;
+
+ static final CaptivePortalProbeResult FAILED = new CaptivePortalProbeResult(FAILED_CODE);
+ static final CaptivePortalProbeResult SUCCESS = new CaptivePortalProbeResult(SUCCESS_CODE);
private final int mHttpResponseCode; // HTTP response code returned from Internet probe.
final String redirectUrl; // Redirect destination returned from Internet probe.
@@ -498,9 +502,16 @@
this(httpResponseCode, null, null);
}
- boolean isSuccessful() { return mHttpResponseCode == 204; }
+ boolean isSuccessful() {
+ return mHttpResponseCode == SUCCESS_CODE;
+ }
+
boolean isPortal() {
- return !isSuccessful() && mHttpResponseCode >= 200 && mHttpResponseCode <= 399;
+ return !isSuccessful() && (mHttpResponseCode >= 200) && (mHttpResponseCode <= 399);
+ }
+
+ boolean isFailed() {
+ return !isSuccessful() && !isPortal();
}
}
@@ -714,7 +725,7 @@
protected CaptivePortalProbeResult isCaptivePortal() {
if (!mIsCaptivePortalCheckEnabled) {
validationLog("Validation disabled.");
- return new CaptivePortalProbeResult(204);
+ return CaptivePortalProbeResult.SUCCESS;
}
URL pacUrl = null;
@@ -818,7 +829,7 @@
@VisibleForTesting
protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType) {
HttpURLConnection urlConnection = null;
- int httpResponseCode = 599;
+ int httpResponseCode = CaptivePortalProbeResult.FAILED_CODE;
String redirectUrl = null;
final Stopwatch probeTimer = new Stopwatch().start();
final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE);
@@ -857,7 +868,7 @@
if (probeType == ValidationProbeEvent.PROBE_PAC) {
validationLog(
probeType, url, "PAC fetch 200 response interpreted as 204 response.");
- httpResponseCode = 204;
+ httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE;
} else if (urlConnection.getContentLengthLong() == 0) {
// Consider 200 response with "Content-length=0" to not be a captive portal.
// There's no point in considering this a captive portal as the user cannot
@@ -865,20 +876,20 @@
// See http://b/9972012.
validationLog(probeType, url,
"200 response with Content-length=0 interpreted as 204 response.");
- httpResponseCode = 204;
+ httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE;
} else if (urlConnection.getContentLengthLong() == -1) {
// When no Content-length (default value == -1), attempt to read a byte from the
// response. Do not use available() as it is unreliable. See http://b/33498325.
if (urlConnection.getInputStream().read() == -1) {
validationLog(
probeType, url, "Empty 200 response interpreted as 204 response.");
- httpResponseCode = 204;
+ httpResponseCode = CaptivePortalProbeResult.SUCCESS_CODE;
}
}
}
} catch (IOException e) {
- validationLog(probeType, url, "Probably not a portal: exception " + e);
- if (httpResponseCode == 599) {
+ validationLog(probeType, url, "Probe failed with exception " + e);
+ if (httpResponseCode == CaptivePortalProbeResult.FAILED_CODE) {
// TODO: Ping gateway and DNS server and log results.
}
} finally {
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 4b71cc1..5f74d5e 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -18,6 +18,7 @@
import static android.hardware.usb.UsbManager.USB_CONNECTED;
import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
+import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
@@ -58,6 +59,7 @@
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Handler;
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.Message;
@@ -88,8 +90,10 @@
import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
import com.android.server.connectivity.tethering.IPv6TetheringInterfaceServices;
import com.android.server.connectivity.tethering.OffloadController;
+import com.android.server.connectivity.tethering.SimChangeListener;
import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
import com.android.server.connectivity.tethering.TetheringConfiguration;
+import com.android.server.connectivity.tethering.TetheringDependencies;
import com.android.server.connectivity.tethering.UpstreamNetworkMonitor;
import com.android.server.net.BaseNetworkObserver;
@@ -170,6 +174,7 @@
private final OffloadController mOffloadController;
private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
private final HashSet<TetherInterfaceStateMachine> mForwardedDownstreams;
+ private final SimChangeListener mSimChange;
private volatile TetheringConfiguration mConfig;
private String mCurrentUpstreamIface;
@@ -184,7 +189,8 @@
public Tethering(Context context, INetworkManagementService nmService,
INetworkStatsService statsService, INetworkPolicyManager policyManager,
- Looper looper, MockableSystemProperties systemProperties) {
+ Looper looper, MockableSystemProperties systemProperties,
+ TetheringDependencies deps) {
mLog.mark("constructed");
mContext = context;
mNMService = nmService;
@@ -200,10 +206,16 @@
mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper);
mTetherMasterSM.start();
- mOffloadController = new OffloadController(mTetherMasterSM.getHandler(), mLog);
+ final Handler smHandler = mTetherMasterSM.getHandler();
+ mOffloadController = new OffloadController(smHandler,
+ deps.getOffloadHardwareInterface(smHandler, mLog),
+ mContext.getContentResolver(),
+ mLog);
mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
mContext, mTetherMasterSM, TetherMasterSM.EVENT_UPSTREAM_CALLBACK, mLog);
mForwardedDownstreams = new HashSet<>();
+ mSimChange = new SimChangeListener(
+ mContext, mTetherMasterSM.getHandler(), () -> reevaluateSimCardProvisioning());
mStateReceiver = new StateReceiver();
IntentFilter filter = new IntentFilter();
@@ -233,8 +245,10 @@
return (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
}
+ // NOTE: This is always invoked on the mLooper thread.
private void updateConfiguration() {
- mConfig = new TetheringConfiguration(mContext);
+ mConfig = new TetheringConfiguration(mContext, mLog);
+ mUpstreamNetworkMonitor.updateMobileRequiresDun(mConfig.isDunRequired);
}
@Override
@@ -243,21 +257,11 @@
// See NetlinkHandler.cpp:71.
if (VDBG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up);
synchronized (mPublicSync) {
- int interfaceType = ifaceNameToType(iface);
- if (interfaceType == ConnectivityManager.TETHERING_INVALID) {
- return;
- }
-
- TetherState tetherState = mTetherStates.get(iface);
if (up) {
- if (tetherState == null) {
- trackNewTetherableInterface(iface, interfaceType);
- }
+ maybeTrackNewInterfaceLocked(iface);
} else {
- if (interfaceType == ConnectivityManager.TETHERING_BLUETOOTH) {
- tetherState.stateMachine.sendMessage(
- TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
- mTetherStates.remove(iface);
+ if (ifaceNameToType(iface) == ConnectivityManager.TETHERING_BLUETOOTH) {
+ stopTrackingInterfaceLocked(iface);
} else {
// Ignore usb0 down after enabling RNDIS.
// We will handle disconnect in interfaceRemoved.
@@ -291,18 +295,7 @@
public void interfaceAdded(String iface) {
if (VDBG) Log.d(TAG, "interfaceAdded " + iface);
synchronized (mPublicSync) {
- int interfaceType = ifaceNameToType(iface);
- if (interfaceType == ConnectivityManager.TETHERING_INVALID) {
- if (VDBG) Log.d(TAG, iface + " is not a tetherable iface, ignoring");
- return;
- }
-
- TetherState tetherState = mTetherStates.get(iface);
- if (tetherState == null) {
- trackNewTetherableInterface(iface, interfaceType);
- } else {
- if (VDBG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring");
- }
+ maybeTrackNewInterfaceLocked(iface);
}
}
@@ -310,15 +303,7 @@
public void interfaceRemoved(String iface) {
if (VDBG) Log.d(TAG, "interfaceRemoved " + iface);
synchronized (mPublicSync) {
- TetherState tetherState = mTetherStates.get(iface);
- if (tetherState == null) {
- if (VDBG) {
- Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring");
- }
- return;
- }
- tetherState.stateMachine.sendMessage(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
- mTetherStates.remove(iface);
+ stopTrackingInterfaceLocked(iface);
}
}
@@ -370,6 +355,20 @@
return (provisionApp.length == 2);
}
+ // Used by the SIM card change observation code.
+ // TODO: De-duplicate above code.
+ private boolean hasMobileHotspotProvisionApp() {
+ try {
+ if (!mContext.getResources().getString(com.android.internal.R.string.
+ config_mobile_hotspot_provision_app_no_ui).isEmpty()) {
+ Log.d(TAG, "re-evaluate provisioning");
+ return true;
+ }
+ } catch (Resources.NotFoundException e) {}
+ Log.d(TAG, "no prov-check needed for new SIM");
+ return false;
+ }
+
/**
* Enables or disables tethering for the given type. This should only be called once
* provisioning has succeeded or is not necessary. It will also schedule provisioning rechecks
@@ -550,6 +549,16 @@
}
}
+ // Used by the SIM card change observation code.
+ // TODO: De-duplicate with above code, where possible.
+ private void startProvisionIntent(int tetherType) {
+ final Intent startProvIntent = new Intent();
+ startProvIntent.putExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE, tetherType);
+ startProvIntent.putExtra(ConnectivityManager.EXTRA_RUN_PROVISION, true);
+ startProvIntent.setComponent(TETHER_SERVICE);
+ mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT);
+ }
+
public int tether(String iface) {
return tether(iface, IControlsTethering.STATE_TETHERED);
}
@@ -747,7 +756,7 @@
mLastNotificationId = id;
notificationManager.notifyAsUser(null, mLastNotificationId,
- mTetheredNotificationBuilder.build(), UserHandle.ALL);
+ mTetheredNotificationBuilder.buildInto(new Notification()), UserHandle.ALL);
}
private void clearTetheredNotification() {
@@ -859,23 +868,16 @@
ipServingMode = IControlsTethering.STATE_LOCAL_ONLY;
break;
default:
- // Resort to legacy "guessing" behaviour.
- //
- // When the AP comes up and we've been requested to tether it,
- // do so. Otherwise, assume it's a local-only hotspot request.
- //
- // TODO: Once all AP broadcasts are known to include ifname and
- // mode information delete this code path and log an error.
- ipServingMode = mWifiTetherRequested
- ? IControlsTethering.STATE_TETHERED
- : IControlsTethering.STATE_LOCAL_ONLY;
- break;
+ mLog.e("Cannot enable IP serving in unknown WiFi mode: " + wifiIpMode);
+ return;
}
if (!TextUtils.isEmpty(ifname)) {
changeInterfaceState(ifname, ipServingMode);
} else {
- tetherMatchingInterfaces(ipServingMode, ConnectivityManager.TETHERING_WIFI);
+ mLog.e(String.format(
+ "Cannot enable IP serving in mode %s on missing interface name",
+ ipServingMode));
}
}
@@ -1072,6 +1074,29 @@
return false;
}
+ private void reevaluateSimCardProvisioning() {
+ if (!hasMobileHotspotProvisionApp()) return;
+
+ ArrayList<Integer> tethered = new ArrayList<>();
+ synchronized (mPublicSync) {
+ for (int i = 0; i < mTetherStates.size(); i++) {
+ TetherState tetherState = mTetherStates.valueAt(i);
+ if (tetherState.lastState != IControlsTethering.STATE_TETHERED) {
+ continue; // Skip interfaces that aren't tethered.
+ }
+ String iface = mTetherStates.keyAt(i);
+ int interfaceType = ifaceNameToType(iface);
+ if (interfaceType != ConnectivityManager.TETHERING_INVALID) {
+ tethered.add(interfaceType);
+ }
+ }
+ }
+
+ for (int tetherType : tethered) {
+ startProvisionIntent(tetherType);
+ }
+ }
+
class TetherMasterSM extends StateMachine {
private static final int BASE_MASTER = Protocol.BASE_TETHERING;
// an interface SM has requested Tethering/Local Hotspot
@@ -1117,7 +1142,6 @@
TetherMasterSM(String name, Looper looper) {
super(name, looper);
- //Add states
mInitialState = new InitialState();
mTetherModeAliveState = new TetherModeAliveState();
mSetIpForwardingEnabledErrorState = new SetIpForwardingEnabledErrorState();
@@ -1168,15 +1192,6 @@
return false;
}
- protected void requestUpstreamMobileConnection() {
- mUpstreamNetworkMonitor.updateMobileRequiresDun(mConfig.isDunRequired);
- mUpstreamNetworkMonitor.registerMobileNetworkRequest();
- }
-
- protected void unrequestUpstreamMobileConnection() {
- mUpstreamNetworkMonitor.releaseMobileNetworkRequest();
- }
-
protected boolean turnOnMasterTetherSettings() {
final TetheringConfiguration cfg = mConfig;
try {
@@ -1225,17 +1240,26 @@
}
protected void chooseUpstreamType(boolean tryCell) {
- final int upstreamType = findPreferredUpstreamType(tryCell);
+ updateConfiguration(); // TODO - remove?
+
+ final int upstreamType = findPreferredUpstreamType(
+ getConnectivityManager(), mConfig);
+ if (upstreamType == ConnectivityManager.TYPE_NONE) {
+ if (tryCell) {
+ mUpstreamNetworkMonitor.registerMobileNetworkRequest();
+ // We think mobile should be coming up; don't set a retry.
+ } else {
+ sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
+ }
+ }
setUpstreamByType(upstreamType);
}
- protected int findPreferredUpstreamType(boolean tryCell) {
- final ConnectivityManager cm = getConnectivityManager();
+ // TODO: Move this function into UpstreamNetworkMonitor.
+ protected int findPreferredUpstreamType(ConnectivityManager cm,
+ TetheringConfiguration cfg) {
int upType = ConnectivityManager.TYPE_NONE;
- updateConfiguration(); // TODO - remove?
-
- final TetheringConfiguration cfg = mConfig;
if (VDBG) {
Log.d(TAG, "chooseUpstreamType has upstream iface types:");
for (Integer netType : cfg.preferredUpstreamIfaceTypes) {
@@ -1256,27 +1280,18 @@
final int preferredUpstreamMobileApn = cfg.isDunRequired
? ConnectivityManager.TYPE_MOBILE_DUN
: ConnectivityManager.TYPE_MOBILE_HIPRI;
- if (DBG) {
- Log.d(TAG, "chooseUpstreamType(" + tryCell + "),"
- + " preferredApn="
- + ConnectivityManager.getNetworkTypeName(preferredUpstreamMobileApn)
- + ", got type="
- + ConnectivityManager.getNetworkTypeName(upType));
- }
+ mLog.log(String.format(
+ "findPreferredUpstreamType(), preferredApn=%s, got type=%s",
+ getNetworkTypeName(preferredUpstreamMobileApn),
+ getNetworkTypeName(upType)));
switch (upType) {
case ConnectivityManager.TYPE_MOBILE_DUN:
case ConnectivityManager.TYPE_MOBILE_HIPRI:
// If we're on DUN, put our own grab on it.
- requestUpstreamMobileConnection();
+ mUpstreamNetworkMonitor.registerMobileNetworkRequest();
break;
case ConnectivityManager.TYPE_NONE:
- if (tryCell) {
- requestUpstreamMobileConnection();
- // We think mobile should be coming up; don't set a retry.
- } else {
- sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
- }
break;
default:
/* If we've found an active upstream connection that's not DUN/HIPRI
@@ -1285,7 +1300,7 @@
* If we found NONE we don't want to do this as we want any previous
* requests to keep trying to bring up something we can use.
*/
- unrequestUpstreamMobileConnection();
+ mUpstreamNetworkMonitor.releaseMobileNetworkRequest();
break;
}
@@ -1373,127 +1388,6 @@
}
}
- private class SimChangeListener {
- private final Context mContext;
- private final AtomicInteger mSimBcastGenerationNumber;
- private BroadcastReceiver mBroadcastReceiver;
-
- SimChangeListener(Context ctx) {
- mContext = ctx;
- mSimBcastGenerationNumber = new AtomicInteger(0);
- }
-
- public int generationNumber() {
- return mSimBcastGenerationNumber.get();
- }
-
- public void startListening() {
- if (DBG) Log.d(TAG, "startListening for SIM changes");
-
- if (mBroadcastReceiver != null) return;
-
- mBroadcastReceiver = new SimChangeBroadcastReceiver(
- mSimBcastGenerationNumber.incrementAndGet());
- final IntentFilter filter = new IntentFilter();
- filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
-
- mContext.registerReceiver(mBroadcastReceiver, filter, null,
- mTetherMasterSM.getHandler());
- }
-
- public void stopListening() {
- if (DBG) Log.d(TAG, "stopListening for SIM changes");
-
- if (mBroadcastReceiver == null) return;
-
- mSimBcastGenerationNumber.incrementAndGet();
- mContext.unregisterReceiver(mBroadcastReceiver);
- mBroadcastReceiver = null;
- }
-
- public boolean hasMobileHotspotProvisionApp() {
- try {
- if (!mContext.getResources().getString(com.android.internal.R.string.
- config_mobile_hotspot_provision_app_no_ui).isEmpty()) {
- Log.d(TAG, "re-evaluate provisioning");
- return true;
- }
- } catch (Resources.NotFoundException e) {}
- Log.d(TAG, "no prov-check needed for new SIM");
- return false;
- }
-
- private boolean isSimCardLoaded(String state) {
- return IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(state);
- }
-
- private void startProvisionIntent(int tetherType) {
- final Intent startProvIntent = new Intent();
- startProvIntent.putExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE, tetherType);
- startProvIntent.putExtra(ConnectivityManager.EXTRA_RUN_PROVISION, true);
- startProvIntent.setComponent(TETHER_SERVICE);
- mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT);
- }
-
- private class SimChangeBroadcastReceiver extends BroadcastReceiver {
- // used to verify this receiver is still current
- final private int mGenerationNumber;
-
- // used to check the sim state transition from non-loaded to loaded
- private boolean mSimNotLoadedSeen = false;
-
- public SimChangeBroadcastReceiver(int generationNumber) {
- mGenerationNumber = generationNumber;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final int currentGenerationNumber = mSimBcastGenerationNumber.get();
-
- if (DBG) {
- Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber +
- ", current generationNumber=" + currentGenerationNumber);
- }
- if (mGenerationNumber != currentGenerationNumber) return;
-
- final String state = intent.getStringExtra(
- IccCardConstants.INTENT_KEY_ICC_STATE);
- Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" +
- mSimNotLoadedSeen);
-
- if (!isSimCardLoaded(state)) {
- if (!mSimNotLoadedSeen) mSimNotLoadedSeen = true;
- return;
- }
-
- if (isSimCardLoaded(state) && mSimNotLoadedSeen) {
- mSimNotLoadedSeen = false;
-
- if (!hasMobileHotspotProvisionApp()) return;
-
- ArrayList<Integer> tethered = new ArrayList<Integer>();
- synchronized (mPublicSync) {
- for (int i = 0; i < mTetherStates.size(); i++) {
- TetherState tetherState = mTetherStates.valueAt(i);
- if (tetherState.lastState != IControlsTethering.STATE_TETHERED) {
- continue; // Skip interfaces that aren't tethered.
- }
- String iface = mTetherStates.keyAt(i);
- int interfaceType = ifaceNameToType(iface);
- if (interfaceType != ConnectivityManager.TETHERING_INVALID) {
- tethered.add(new Integer(interfaceType));
- }
- }
- }
-
- for (int tetherType : tethered) {
- startProvisionIntent(tetherType);
- }
- }
- }
- }
- }
-
private void handleInterfaceServingStateActive(int mode, TetherInterfaceStateMachine who) {
if (mNotifyList.indexOf(who) < 0) {
mNotifyList.add(who);
@@ -1539,7 +1433,6 @@
}
class TetherModeAliveState extends TetherMasterUtilState {
- final SimChangeListener simChange = new SimChangeListener(mContext);
boolean mUpstreamWanted = false;
boolean mTryCell = true;
@@ -1551,12 +1444,13 @@
return;
}
- simChange.startListening();
+ mSimChange.startListening();
mUpstreamNetworkMonitor.start();
- mOffloadController.start();
+ // TODO: De-duplicate with updateUpstreamWanted() below.
if (upstreamWanted()) {
mUpstreamWanted = true;
+ mOffloadController.start();
chooseUpstreamType(true);
mTryCell = false;
}
@@ -1565,9 +1459,8 @@
@Override
public void exit() {
mOffloadController.stop();
- unrequestUpstreamMobileConnection();
mUpstreamNetworkMonitor.stop();
- simChange.stopListening();
+ mSimChange.stopListening();
notifyTetheredOfNewUpstreamIface(null);
handleNewUpstreamNetworkState(null);
}
@@ -1575,6 +1468,13 @@
private boolean updateUpstreamWanted() {
final boolean previousUpstreamWanted = mUpstreamWanted;
mUpstreamWanted = upstreamWanted();
+ if (mUpstreamWanted != previousUpstreamWanted) {
+ if (mUpstreamWanted) {
+ mOffloadController.start();
+ } else {
+ mOffloadController.stop();
+ }
+ }
return previousUpstreamWanted;
}
@@ -1603,7 +1503,8 @@
handleInterfaceServingStateInactive(who);
if (mNotifyList.isEmpty()) {
- // transitions appropriately
+ // This transitions us out of TetherModeAliveState,
+ // either to InitialState or an error state.
turnOffMasterTetherSettings();
break;
}
@@ -1824,6 +1725,7 @@
pw.println(" - lastError = " + tetherState.lastError);
}
pw.println("Upstream wanted: " + upstreamWanted());
+ pw.println("Current upstream interface: " + mCurrentUpstreamIface);
pw.decreaseIndent();
}
@@ -1841,7 +1743,7 @@
private static boolean argsContain(String[] args, String target) {
for (String arg : args) {
- if (arg.equals(target)) return true;
+ if (target.equals(arg)) return true;
}
return false;
}
@@ -1894,15 +1796,40 @@
sendTetherStateChangedBroadcast();
}
- private void trackNewTetherableInterface(String iface, int interfaceType) {
- TetherState tetherState;
- tetherState = new TetherState(new TetherInterfaceStateMachine(iface, mLooper,
- interfaceType, mLog, mNMService, mStatsService, this,
- new IPv6TetheringInterfaceServices(iface, mNMService, mLog)));
+ private void maybeTrackNewInterfaceLocked(final String iface) {
+ // If we don't care about this type of interface, ignore.
+ final int interfaceType = ifaceNameToType(iface);
+ if (interfaceType == ConnectivityManager.TETHERING_INVALID) {
+ mLog.log(iface + " is not a tetherable iface, ignoring");
+ return;
+ }
+
+ // If we have already started a TISM for this interface, skip.
+ if (mTetherStates.containsKey(iface)) {
+ mLog.log("active iface (" + iface + ") reported as added, ignoring");
+ return;
+ }
+
+ mLog.log("adding TetheringInterfaceStateMachine for: " + iface);
+ final TetherState tetherState = new TetherState(
+ new TetherInterfaceStateMachine(
+ iface, mLooper, interfaceType, mLog, mNMService, mStatsService, this,
+ new IPv6TetheringInterfaceServices(iface, mNMService, mLog)));
mTetherStates.put(iface, tetherState);
tetherState.stateMachine.start();
}
+ private void stopTrackingInterfaceLocked(final String iface) {
+ final TetherState tetherState = mTetherStates.get(iface);
+ if (tetherState == null) {
+ mLog.log("attempting to remove unknown iface (" + iface + "), ignoring");
+ return;
+ }
+ tetherState.stateMachine.sendMessage(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
+ mLog.log("removing TetheringInterfaceStateMachine for: " + iface);
+ mTetherStates.remove(iface);
+ }
+
private static String[] copy(String[] strarray) {
return Arrays.copyOf(strarray, strarray.length);
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index d8de0bd..27968a9 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -32,7 +32,6 @@
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -140,6 +139,7 @@
private NetworkAgent mNetworkAgent;
private final Looper mLooper;
private final NetworkCapabilities mNetworkCapabilities;
+ private final SystemServices mSystemServices;
/**
* Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
@@ -207,7 +207,7 @@
final boolean isPackageRemoved = !intent.getBooleanExtra(
Intent.EXTRA_REPLACING, false);
if (isPackageRemoved) {
- setAndSaveAlwaysOnPackage(null, false);
+ setAlwaysOnPackage(null, false);
}
break;
}
@@ -218,11 +218,18 @@
private boolean mIsPackageIntentReceiverRegistered = false;
public Vpn(Looper looper, Context context, INetworkManagementService netService,
- int userHandle) {
+ @UserIdInt int userHandle) {
+ this(looper, context, netService, userHandle, new SystemServices(context));
+ }
+
+ @VisibleForTesting
+ protected Vpn(Looper looper, Context context, INetworkManagementService netService,
+ int userHandle, SystemServices systemServices) {
mContext = context;
mNetd = netService;
mUserHandle = userHandle;
mLooper = looper;
+ mSystemServices = systemServices;
mPackage = VpnConfig.LEGACY_VPN;
mOwnerUID = getAppUid(mPackage, mUserHandle);
@@ -238,6 +245,8 @@
mNetworkCapabilities = new NetworkCapabilities();
mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
+
+ loadAlwaysOnPackage();
}
/**
@@ -263,6 +272,30 @@
}
/**
+ * Chooses whether to force all connections to go though VPN.
+ *
+ * Used to enable/disable legacy VPN lockdown.
+ *
+ * This uses the same ip rule mechanism as {@link #setAlwaysOnPackage(String, boolean)};
+ * previous settings from calling that function will be replaced and saved with the
+ * always-on state.
+ *
+ * @param lockdown whether to prevent all traffic outside of a VPN.
+ */
+ public synchronized void setLockdown(boolean lockdown) {
+ enforceControlPermissionOrInternalCaller();
+
+ setVpnForcedLocked(lockdown);
+ mLockdown = lockdown;
+
+ // Update app lockdown setting if it changed. Legacy VPN lockdown status is controlled by
+ // LockdownVpnTracker.isEnabled() which keeps track of its own state.
+ if (mAlwaysOn) {
+ saveAlwaysOnPackage();
+ }
+ }
+
+ /**
* Configures an always-on VPN connection through a specific application.
* This connection is automatically granted and persisted after a reboot.
*
@@ -276,6 +309,26 @@
*/
public synchronized boolean setAlwaysOnPackage(String packageName, boolean lockdown) {
enforceControlPermissionOrInternalCaller();
+
+ if (setAlwaysOnPackageInternal(packageName, lockdown)) {
+ saveAlwaysOnPackage();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Configures an always-on VPN connection through a specific application, the same as
+ * {@link #setAlwaysOnPackage}.
+ *
+ * Does not perform permission checks. Does not persist any of the changes to storage.
+ *
+ * @param packageName the package to designate as always-on VPN supplier.
+ * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
+ * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
+ */
+ @GuardedBy("this")
+ private boolean setAlwaysOnPackageInternal(String packageName, boolean lockdown) {
if (VpnConfig.LEGACY_VPN.equals(packageName)) {
Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on.");
return false;
@@ -348,32 +401,33 @@
/**
* Save the always-on package and lockdown config into Settings.Secure
*/
- public synchronized void saveAlwaysOnPackage() {
+ @GuardedBy("this")
+ private void saveAlwaysOnPackage() {
final long token = Binder.clearCallingIdentity();
try {
- final ContentResolver cr = mContext.getContentResolver();
- Settings.Secure.putStringForUser(cr, Settings.Secure.ALWAYS_ON_VPN_APP,
+ mSystemServices.settingsSecurePutStringForUser(Settings.Secure.ALWAYS_ON_VPN_APP,
getAlwaysOnPackage(), mUserHandle);
- Settings.Secure.putIntForUser(cr, Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
- (mLockdown ? 1 : 0), mUserHandle);
+ mSystemServices.settingsSecurePutIntForUser(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
+ (mAlwaysOn && mLockdown ? 1 : 0), mUserHandle);
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
- * Set and save always-on package and lockdown config
- * @see Vpn#setAlwaysOnPackage(String, boolean)
- * @see Vpn#saveAlwaysOnPackage()
- *
- * @return result of Vpn#setAndSaveAlwaysOnPackage(String, boolean)
+ * Load the always-on package and lockdown config from Settings.Secure
*/
- private synchronized boolean setAndSaveAlwaysOnPackage(String packageName, boolean lockdown) {
- if (setAlwaysOnPackage(packageName, lockdown)) {
- saveAlwaysOnPackage();
- return true;
- } else {
- return false;
+ @GuardedBy("this")
+ private void loadAlwaysOnPackage() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final String alwaysOnPackage = mSystemServices.settingsSecureGetStringForUser(
+ Settings.Secure.ALWAYS_ON_VPN_APP, mUserHandle);
+ final boolean alwaysOnLockdown = mSystemServices.settingsSecureGetIntForUser(
+ Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, 0 /*default*/, mUserHandle) != 0;
+ setAlwaysOnPackageInternal(alwaysOnPackage, alwaysOnLockdown);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
@@ -547,6 +601,7 @@
mConfig = null;
updateState(DetailedState.IDLE, "prepare");
+ setVpnForcedLocked(mLockdown);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -993,9 +1048,7 @@
Log.wtf(TAG, "Failed to add restricted user to owner", e);
}
}
- if (mAlwaysOn) {
- setVpnForcedLocked(mLockdown);
- }
+ setVpnForcedLocked(mLockdown);
}
}
}
@@ -1012,9 +1065,7 @@
Log.wtf(TAG, "Failed to remove restricted user to owner", e);
}
}
- if (mAlwaysOn) {
- setVpnForcedLocked(mLockdown);
- }
+ setVpnForcedLocked(mLockdown);
}
}
}
@@ -1024,7 +1075,7 @@
*/
public synchronized void onUserStopped() {
// Switch off networking lockdown (if it was enabled)
- setVpnForcedLocked(false);
+ setLockdown(false);
mAlwaysOn = false;
unregisterPackageChangeReceiverLocked();
@@ -1051,20 +1102,31 @@
*/
@GuardedBy("this")
private void setVpnForcedLocked(boolean enforce) {
+ final List<String> exemptedPackages =
+ isNullOrLegacyVpn(mPackage) ? null : Collections.singletonList(mPackage);
+ setVpnForcedWithExemptionsLocked(enforce, exemptedPackages);
+ }
+
+ /**
+ * @see #setVpnForcedLocked
+ */
+ @GuardedBy("this")
+ private void setVpnForcedWithExemptionsLocked(boolean enforce,
+ @Nullable List<String> exemptedPackages) {
final Set<UidRange> removedRanges = new ArraySet<>(mBlockedUsers);
+
+ Set<UidRange> addedRanges = Collections.emptySet();
if (enforce) {
- final Set<UidRange> addedRanges = createUserAndRestrictedProfilesRanges(mUserHandle,
+ addedRanges = createUserAndRestrictedProfilesRanges(mUserHandle,
/* allowedApplications */ null,
- /* disallowedApplications */ Collections.singletonList(mPackage));
+ /* disallowedApplications */ exemptedPackages);
removedRanges.removeAll(addedRanges);
addedRanges.removeAll(mBlockedUsers);
-
- setAllowOnlyVpnForUids(false, removedRanges);
- setAllowOnlyVpnForUids(true, addedRanges);
- } else {
- setAllowOnlyVpnForUids(false, removedRanges);
}
+
+ setAllowOnlyVpnForUids(false, removedRanges);
+ setAllowOnlyVpnForUids(true, addedRanges);
}
/**
@@ -1277,11 +1339,7 @@
private void updateAlwaysOnNotification(DetailedState networkState) {
final boolean visible = (mAlwaysOn && networkState != DetailedState.CONNECTED);
- updateAlwaysOnNotificationInternal(visible);
- }
- @VisibleForTesting
- protected void updateAlwaysOnNotificationInternal(boolean visible) {
final UserHandle user = UserHandle.of(mUserHandle);
final long token = Binder.clearCallingIdentity();
try {
@@ -1291,10 +1349,8 @@
return;
}
final Intent intent = new Intent(Settings.ACTION_VPN_SETTINGS);
- final PendingIntent configIntent = PendingIntent.getActivityAsUser(
- mContext, /* request */ 0, intent,
- PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
- null, user);
+ final PendingIntent configIntent = mSystemServices.pendingIntentGetActivityAsUser(
+ intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT, user);
final Notification.Builder builder =
new Notification.Builder(mContext, SystemNotificationChannels.VPN)
.setSmallIcon(R.drawable.vpn_connected)
@@ -1312,6 +1368,58 @@
}
}
+ /**
+ * Facade for system service calls that change, or depend on, state outside of
+ * {@link ConnectivityService} and have hard-to-mock interfaces.
+ *
+ * @see com.android.server.connectivity.VpnTest
+ */
+ @VisibleForTesting
+ public static class SystemServices {
+ private final Context mContext;
+
+ public SystemServices(@NonNull Context context) {
+ mContext = context;
+ }
+
+ /**
+ * @see PendingIntent#getActivityAsUser()
+ */
+ public PendingIntent pendingIntentGetActivityAsUser(
+ Intent intent, int flags, UserHandle user) {
+ return PendingIntent.getActivityAsUser(mContext, 0 /*request*/, intent, flags,
+ null /*options*/, user);
+ }
+
+ /**
+ * @see Settings.Secure#putStringForUser
+ */
+ public void settingsSecurePutStringForUser(String key, String value, int userId) {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(), key, value, userId);
+ }
+
+ /**
+ * @see Settings.Secure#putIntForUser
+ */
+ public void settingsSecurePutIntForUser(String key, int value, int userId) {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(), key, value, userId);
+ }
+
+ /**
+ * @see Settings.Secure#getStringForUser
+ */
+ public String settingsSecureGetStringForUser(String key, int userId) {
+ return Settings.Secure.getStringForUser(mContext.getContentResolver(), key, userId);
+ }
+
+ /**
+ * @see Settings.Secure#getIntForUser
+ */
+ public int settingsSecureGetIntForUser(String key, int def, int userId) {
+ return Settings.Secure.getIntForUser(mContext.getContentResolver(), key, def, userId);
+ }
+ }
+
private native int jniCreate(int mtu);
private native String jniGetName(int tun);
private native int jniSetAddresses(String interfaze, String addresses);
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
index 8f21d99..cb50e9f 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -16,12 +16,22 @@
package com.android.server.connectivity.tethering;
+import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+
+import android.content.ContentResolver;
import android.net.LinkProperties;
-import android.os.Handler;
+import android.net.RouteInfo;
import android.net.util.SharedLog;
+import android.os.Handler;
+import android.provider.Settings;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
/**
- * A wrapper around hardware offload interface.
+ * A class to encapsulate the business logic of programming the tethering
+ * hardware offload interface.
*
* @hide
*/
@@ -29,27 +39,116 @@
private static final String TAG = OffloadController.class.getSimpleName();
private final Handler mHandler;
+ private final OffloadHardwareInterface mHwInterface;
+ private final ContentResolver mContentResolver;
private final SharedLog mLog;
+ private boolean mConfigInitialized;
+ private boolean mControlInitialized;
private LinkProperties mUpstreamLinkProperties;
- public OffloadController(Handler h, SharedLog log) {
+ public OffloadController(Handler h, OffloadHardwareInterface hwi,
+ ContentResolver contentResolver, SharedLog log) {
mHandler = h;
+ mHwInterface = hwi;
+ mContentResolver = contentResolver;
mLog = log.forSubComponent(TAG);
}
public void start() {
- // TODO: initOffload() and configure callbacks to be handled on our
- // preferred Handler.
- mLog.i("tethering offload not supported");
+ if (isOffloadDisabled() || started()) return;
+
+ if (!mConfigInitialized) {
+ mConfigInitialized = mHwInterface.initOffloadConfig();
+ if (!mConfigInitialized) {
+ mLog.i("tethering offload config not supported");
+ stop();
+ return;
+ }
+ }
+
+ mControlInitialized = mHwInterface.initOffloadControl(
+ new OffloadHardwareInterface.ControlCallback() {
+ @Override
+ public void onOffloadEvent(int event) {
+ mLog.log("got offload event: " + event);
+ }
+
+ @Override
+ public void onNatTimeoutUpdate(int proto,
+ String srcAddr, int srcPort,
+ String dstAddr, int dstPort) {
+ mLog.log(String.format("NAT timeout update: %s (%s,%s) -> (%s,%s)",
+ proto, srcAddr, srcPort, dstAddr, dstPort));
+ }
+ });
+ if (!mControlInitialized) {
+ mLog.i("tethering offload control not supported");
+ stop();
+ }
}
public void stop() {
- // TODO: stopOffload().
mUpstreamLinkProperties = null;
+ mHwInterface.stopOffloadControl();
+ mControlInitialized = false;
+ mConfigInitialized = false;
}
public void setUpstreamLinkProperties(LinkProperties lp) {
- // TODO: setUpstreamParameters().
- mUpstreamLinkProperties = lp;
+ if (!started()) return;
+
+ mUpstreamLinkProperties = (lp != null) ? new LinkProperties(lp) : null;
+ // TODO: examine return code and decide what to do if programming
+ // upstream parameters fails (probably just wait for a subsequent
+ // onOffloadEvent() callback to tell us offload is available again and
+ // then reapply all state).
+ pushUpstreamParameters();
+ }
+
+ // TODO: public void addDownStream(...)
+
+ private boolean isOffloadDisabled() {
+ // Defaults to |false| if not present.
+ return (Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0) != 0);
+ }
+
+ private boolean started() {
+ return mConfigInitialized && mControlInitialized;
+ }
+
+ private boolean pushUpstreamParameters() {
+ if (mUpstreamLinkProperties == null) {
+ return mHwInterface.setUpstreamParameters(null, null, null, null);
+ }
+
+ // A stacked interface cannot be an upstream for hardware offload.
+ // Consequently, we examine only the primary interface name, look at
+ // getAddresses() rather than getAllAddresses(), and check getRoutes()
+ // rather than getAllRoutes().
+ final String iface = mUpstreamLinkProperties.getInterfaceName();
+ final ArrayList<String> v6gateways = new ArrayList<>();
+ String v4addr = null;
+ String v4gateway = null;
+
+ for (InetAddress ip : mUpstreamLinkProperties.getAddresses()) {
+ if (ip instanceof Inet4Address) {
+ v4addr = ip.getHostAddress();
+ break;
+ }
+ }
+
+ // Find the gateway addresses of all default routes of either address family.
+ for (RouteInfo ri : mUpstreamLinkProperties.getRoutes()) {
+ if (!ri.hasGateway()) continue;
+
+ final String gateway = ri.getGateway().getHostAddress();
+ if (ri.isIPv4Default()) {
+ v4gateway = gateway;
+ } else if (ri.isIPv6Default()) {
+ v6gateways.add(gateway);
+ }
+ }
+
+ return mHwInterface.setUpstreamParameters(iface, v4addr, v4gateway, v6gateways);
}
}
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
new file mode 100644
index 0000000..3ecf0d1
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
+import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
+import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.net.util.SharedLog;
+
+import java.util.ArrayList;
+
+
+/**
+ * Capture tethering dependencies, for injection.
+ *
+ * @hide
+ */
+public class OffloadHardwareInterface {
+ private static final String TAG = OffloadHardwareInterface.class.getSimpleName();
+
+ private static native boolean configOffload();
+
+ private final Handler mHandler;
+ private final SharedLog mLog;
+ private IOffloadControl mOffloadControl;
+ private TetheringOffloadCallback mTetheringOffloadCallback;
+ private ControlCallback mControlCallback;
+
+ public static class ControlCallback {
+ public void onOffloadEvent(int event) {}
+
+ public void onNatTimeoutUpdate(int proto,
+ String srcAddr, int srcPort,
+ String dstAddr, int dstPort) {}
+ }
+
+ public OffloadHardwareInterface(Handler h, SharedLog log) {
+ mHandler = h;
+ mLog = log.forSubComponent(TAG);
+ }
+
+ public boolean initOffloadConfig() {
+ return configOffload();
+ }
+
+ public boolean initOffloadControl(ControlCallback controlCb) {
+ mControlCallback = controlCb;
+
+ if (mOffloadControl == null) {
+ try {
+ mOffloadControl = IOffloadControl.getService();
+ } catch (RemoteException e) {
+ mLog.e("tethering offload control not supported: " + e);
+ return false;
+ }
+ }
+
+ mTetheringOffloadCallback = new TetheringOffloadCallback(mHandler, mControlCallback);
+ final CbResults results = new CbResults();
+ try {
+ mOffloadControl.initOffload(
+ mTetheringOffloadCallback,
+ (boolean success, String errMsg) -> {
+ results.success = success;
+ results.errMsg = errMsg;
+ });
+ } catch (RemoteException e) {
+ mLog.e("failed to initOffload: " + e);
+ return false;
+ }
+
+ if (!results.success) mLog.e("initOffload failed: " + results.errMsg);
+ return results.success;
+ }
+
+ public void stopOffloadControl() {
+ if (mOffloadControl != null) {
+ try {
+ mOffloadControl.stopOffload(
+ (boolean success, String errMsg) -> {
+ if (!success) mLog.e("stopOffload failed: " + errMsg);
+ });
+ } catch (RemoteException e) {
+ mLog.e("failed to stopOffload: " + e);
+ }
+ }
+ mOffloadControl = null;
+ mTetheringOffloadCallback = null;
+ mControlCallback = null;
+ }
+
+ public boolean setUpstreamParameters(
+ String iface, String v4addr, String v4gateway, ArrayList<String> v6gws) {
+ final CbResults results = new CbResults();
+ try {
+ mOffloadControl.setUpstreamParameters(
+ iface, v4addr, v4gateway, v6gws,
+ (boolean success, String errMsg) -> {
+ results.success = success;
+ results.errMsg = errMsg;
+ });
+ } catch (RemoteException e) {
+ mLog.e("failed to setUpstreamParameters: " + e);
+ return false;
+ }
+
+ if (!results.success) mLog.e("setUpstreamParameters failed: " + results.errMsg);
+ return results.success;
+ }
+
+ private static class TetheringOffloadCallback extends ITetheringOffloadCallback.Stub {
+ public final Handler handler;
+ public final ControlCallback controlCb;
+
+ public TetheringOffloadCallback(Handler h, ControlCallback cb) {
+ handler = h;
+ controlCb = cb;
+ }
+
+ @Override
+ public void onEvent(int event) {
+ handler.post(() -> { controlCb.onOffloadEvent(event); });
+ }
+
+ @Override
+ public void updateTimeout(NatTimeoutUpdate params) {
+ handler.post(() -> {
+ controlCb.onNatTimeoutUpdate(
+ params.proto,
+ params.src.addr, params.src.port,
+ params.dst.addr, params.dst.port);
+ });
+ }
+ }
+
+ private static class CbResults {
+ boolean success;
+ String errMsg;
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java b/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java
new file mode 100644
index 0000000..3e60f9f
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import static com.android.internal.telephony.IccCardConstants.INTENT_VALUE_ICC_LOADED;
+import static com.android.internal.telephony.IccCardConstants.INTENT_KEY_ICC_STATE;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.internal.telephony.TelephonyIntents;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ * A utility class that runs the provided callback on the provided handler when
+ * observing a new SIM card having been loaded.
+ *
+ * @hide
+ */
+public class SimChangeListener {
+ private static final String TAG = SimChangeListener.class.getSimpleName();
+ private static final boolean DBG = false;
+
+ private final Context mContext;
+ private final Handler mTarget;
+ private final AtomicInteger mSimBcastGenerationNumber;
+ private final Runnable mCallback;
+ private BroadcastReceiver mBroadcastReceiver;
+
+ public SimChangeListener(Context ctx, Handler handler, Runnable onSimCardLoadedCallback) {
+ mContext = ctx;
+ mTarget = handler;
+ mCallback = onSimCardLoadedCallback;
+ mSimBcastGenerationNumber = new AtomicInteger(0);
+ }
+
+ public int generationNumber() {
+ return mSimBcastGenerationNumber.get();
+ }
+
+ public void startListening() {
+ if (DBG) Log.d(TAG, "startListening for SIM changes");
+
+ if (mBroadcastReceiver != null) return;
+
+ mBroadcastReceiver = new SimChangeBroadcastReceiver(
+ mSimBcastGenerationNumber.incrementAndGet());
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+
+ mContext.registerReceiver(mBroadcastReceiver, filter, null, mTarget);
+ }
+
+ public void stopListening() {
+ if (DBG) Log.d(TAG, "stopListening for SIM changes");
+
+ if (mBroadcastReceiver == null) return;
+
+ mSimBcastGenerationNumber.incrementAndGet();
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ mBroadcastReceiver = null;
+ }
+
+ private boolean isSimCardLoaded(String state) {
+ return INTENT_VALUE_ICC_LOADED.equals(state);
+ }
+
+ private class SimChangeBroadcastReceiver extends BroadcastReceiver {
+ // used to verify this receiver is still current
+ final private int mGenerationNumber;
+
+ // used to check the sim state transition from non-loaded to loaded
+ private boolean mSimNotLoadedSeen = false;
+
+ public SimChangeBroadcastReceiver(int generationNumber) {
+ mGenerationNumber = generationNumber;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int currentGenerationNumber = mSimBcastGenerationNumber.get();
+
+ if (DBG) {
+ Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber +
+ ", current generationNumber=" + currentGenerationNumber);
+ }
+ if (mGenerationNumber != currentGenerationNumber) return;
+
+ final String state = intent.getStringExtra(INTENT_KEY_ICC_STATE);
+ Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" +
+ mSimNotLoadedSeen);
+
+ if (!isSimCardLoaded(state)) {
+ mSimNotLoadedSeen = true;
+ return;
+ }
+
+ if (mSimNotLoadedSeen) {
+ mSimNotLoadedSeen = false;
+ mCallback.run();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 6941193..b7fbfb7 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -25,7 +25,7 @@
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.telephony.TelephonyManager;
-import android.util.Log;
+import android.net.util.SharedLog;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -74,20 +74,29 @@
public final String[] dhcpRanges;
public final String[] defaultIPv4DNS;
- public TetheringConfiguration(Context ctx) {
+ public TetheringConfiguration(Context ctx, SharedLog log) {
+ final SharedLog configLog = log.forSubComponent("config");
+
tetherableUsbRegexs = ctx.getResources().getStringArray(
com.android.internal.R.array.config_tether_usb_regexs);
+ // TODO: Evaluate deleting this altogether now that Wi-Fi always passes
+ // us an interface name. Careful consideration needs to be given to
+ // implications for Settings and for provisioning checks.
tetherableWifiRegexs = ctx.getResources().getStringArray(
com.android.internal.R.array.config_tether_wifi_regexs);
tetherableBluetoothRegexs = ctx.getResources().getStringArray(
com.android.internal.R.array.config_tether_bluetooth_regexs);
final int dunCheck = checkDunRequired(ctx);
+ configLog.log("DUN check returned: " + dunCheckString(dunCheck));
+
preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(ctx, dunCheck);
isDunRequired = preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN);
dhcpRanges = getDhcpRanges(ctx);
defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
+
+ configLog.log(toString());
}
public boolean isUsb(String iface) {
@@ -110,21 +119,25 @@
pw.print("isDunRequired: ");
pw.println(isDunRequired);
- String[] upstreamTypes = null;
- if (preferredUpstreamIfaceTypes != null) {
- upstreamTypes = new String[preferredUpstreamIfaceTypes.size()];
- int i = 0;
- for (Integer netType : preferredUpstreamIfaceTypes) {
- upstreamTypes[i] = ConnectivityManager.getNetworkTypeName(netType);
- i++;
- }
- }
- dumpStringArray(pw, "preferredUpstreamIfaceTypes", upstreamTypes);
+ dumpStringArray(pw, "preferredUpstreamIfaceTypes",
+ preferredUpstreamNames(preferredUpstreamIfaceTypes));
dumpStringArray(pw, "dhcpRanges", dhcpRanges);
dumpStringArray(pw, "defaultIPv4DNS", defaultIPv4DNS);
}
+ public String toString() {
+ final StringJoiner sj = new StringJoiner(" ");
+ sj.add(String.format("tetherableUsbRegexs:%s", makeString(tetherableUsbRegexs)));
+ sj.add(String.format("tetherableWifiRegexs:%s", makeString(tetherableWifiRegexs)));
+ sj.add(String.format("tetherableBluetoothRegexs:%s",
+ makeString(tetherableBluetoothRegexs)));
+ sj.add(String.format("isDunRequired:%s", isDunRequired));
+ sj.add(String.format("preferredUpstreamIfaceTypes:%s",
+ makeString(preferredUpstreamNames(preferredUpstreamIfaceTypes))));
+ return String.format("TetheringConfiguration{%s}", sj.toString());
+ }
+
private static void dumpStringArray(PrintWriter pw, String label, String[] values) {
pw.print(label);
pw.print(": ");
@@ -140,11 +153,42 @@
pw.println();
}
+ private static String makeString(String[] strings) {
+ final StringJoiner sj = new StringJoiner(",", "[", "]");
+ for (String s : strings) sj.add(s);
+ return sj.toString();
+ }
+
+ private static String[] preferredUpstreamNames(Collection<Integer> upstreamTypes) {
+ String[] upstreamNames = null;
+
+ if (upstreamTypes != null) {
+ upstreamNames = new String[upstreamTypes.size()];
+ int i = 0;
+ for (Integer netType : upstreamTypes) {
+ upstreamNames[i] = ConnectivityManager.getNetworkTypeName(netType);
+ i++;
+ }
+ }
+
+ return upstreamNames;
+ }
+
private static int checkDunRequired(Context ctx) {
final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE);
return (tm != null) ? tm.getTetherApnRequired() : DUN_UNSPECIFIED;
}
+ private static String dunCheckString(int dunCheck) {
+ switch (dunCheck) {
+ case DUN_NOT_REQUIRED: return "DUN_NOT_REQUIRED";
+ case DUN_REQUIRED: return "DUN_REQUIRED";
+ case DUN_UNSPECIFIED: return "DUN_UNSPECIFIED";
+ default:
+ return String.format("UNKNOWN (%s)", dunCheck);
+ }
+ }
+
private static Collection<Integer> getUpstreamIfaceTypes(Context ctx, int dunCheck) {
final int ifaceTypes[] = ctx.getResources().getIntArray(
com.android.internal.R.array.config_tether_upstream_types);
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
new file mode 100644
index 0000000..b8174b6
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import android.os.Handler;
+import android.net.util.SharedLog;
+
+
+/**
+ * Capture tethering dependencies, for injection.
+ *
+ * @hide
+ */
+public class TetheringDependencies {
+ public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) {
+ return new OffloadHardwareInterface(h, log);
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index be71490..cd6038f 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -332,19 +332,16 @@
@Override
public void onAvailable(Network network) {
- checkExpectedThread();
handleAvailable(mCallbackType, network);
}
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) {
- checkExpectedThread();
handleNetCap(network, newNc);
}
@Override
public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
- checkExpectedThread();
handleLinkProp(network, newLp);
}
@@ -353,15 +350,8 @@
@Override
public void onLost(Network network) {
- checkExpectedThread();
handleLost(mCallbackType, network);
}
-
- private void checkExpectedThread() {
- if (Looper.myLooper() != mHandler.getLooper()) {
- Log.wtf(TAG, "Handling callback in unexpected thread.");
- }
- }
}
private void releaseCallback(NetworkCallback cb) {
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 1504538..3ca65cd 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1858,7 +1858,7 @@
account.account.name, account.userId, account.account.type);
pw.println("=======================================================================");
- final PrintTable table = new PrintTable(12);
+ final PrintTable table = new PrintTable(13);
table.set(0, 0,
"Authority", // 0
"Syncable", // 1
@@ -1871,7 +1871,8 @@
"User", // 8
"Tot", // 9
"Time", // 10
- "Last Sync" // 11
+ "Last Sync", // 11
+ "Etc" // 12
);
final List<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> sorted =
@@ -1921,6 +1922,7 @@
}
}
+ row1 = row;
if (status.lastSuccessTime != 0) {
table.set(row1++, 11, SyncStorageEngine.SOURCES[status.lastSuccessSource]
+ " " + "SUCCESS");
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 168744b..3d24fed 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -32,8 +32,8 @@
import android.text.format.DateUtils;
import android.util.EventLog;
import android.util.MathUtils;
-import android.util.Spline;
import android.util.Slog;
+import android.util.Spline;
import android.util.TimeUtils;
import java.io.PrintWriter;
@@ -229,13 +229,13 @@
// debugging purposes.
mDozing = dozing;
boolean changed = setLightSensorEnabled(enable && !dozing);
+ if (enable && !dozing && userInitiatedChange) {
+ prepareBrightnessAdjustmentSample();
+ }
changed |= setScreenAutoBrightnessAdjustment(adjustment);
if (changed) {
updateAutoBrightness(false /*sendUpdate*/);
}
- if (enable && !dozing && userInitiatedChange) {
- prepareBrightnessAdjustmentSample();
- }
}
public void dump(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 9862516..e2fd0ac 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -369,8 +369,8 @@
mPendingBacklight = backlight;
boolean changeInProgress = mStateChangeInProgress || mBacklightChangeInProgress;
- mStateChangeInProgress = stateChanged;
- mBacklightChangeInProgress = backlightChanged;
+ mStateChangeInProgress = stateChanged || mStateChangeInProgress;
+ mBacklightChangeInProgress = backlightChanged || mBacklightChangeInProgress;
if (!changeInProgress) {
mLock.notifyAll();
diff --git a/services/core/java/com/android/server/display/DisplayTransformManager.java b/services/core/java/com/android/server/display/DisplayTransformManager.java
index 6902b1a..dbbb318 100644
--- a/services/core/java/com/android/server/display/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/DisplayTransformManager.java
@@ -23,7 +23,6 @@
import android.os.ServiceManager;
import android.util.Slog;
import android.util.SparseArray;
-
import com.android.internal.annotations.GuardedBy;
import java.util.Arrays;
@@ -48,6 +47,9 @@
*/
public static final int LEVEL_COLOR_MATRIX_INVERT_COLOR = 300;
+ private static final int SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX = 1015;
+ private static final int SURFACE_FLINGER_TRANSACTION_DALTONIZER = 1014;
+
/**
* Map of level -> color transformation matrix.
*/
@@ -172,7 +174,7 @@
data.writeInt(0);
}
try {
- flinger.transact(1015, data, null, 0);
+ flinger.transact(SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX, data, null, 0);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to set color transform", ex);
} finally {
@@ -191,7 +193,7 @@
data.writeInterfaceToken("android.ui.ISurfaceComposer");
data.writeInt(mode);
try {
- flinger.transact(1014, data, null, 0);
+ flinger.transact(SURFACE_FLINGER_TRANSACTION_DALTONIZER, data, null, 0);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to set Daltonizer mode", ex);
} finally {
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 841a951..cdc973b 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -668,7 +668,7 @@
private final class HotplugDisplayEventReceiver extends DisplayEventReceiver {
public HotplugDisplayEventReceiver(Looper looper) {
- super(looper);
+ super(looper, VSYNC_SOURCE_APP);
}
@Override
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 907b5c1..717efbf 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -232,6 +232,9 @@
private static native void nativeReloadDeviceAliases(long ptr);
private static native String nativeDump(long ptr);
private static native void nativeMonitor(long ptr);
+ private static native boolean nativeIsInputDeviceEnabled(long ptr, int deviceId);
+ private static native void nativeEnableInputDevice(long ptr, int deviceId);
+ private static native void nativeDisableInputDevice(long ptr, int deviceId);
private static native void nativeSetPointerIconType(long ptr, int iconId);
private static native void nativeReloadPointerIcons(long ptr);
private static native void nativeSetCustomPointerIcon(long ptr, PointerIcon icon);
@@ -650,6 +653,32 @@
return null;
}
+ // Binder call
+ @Override
+ public boolean isInputDeviceEnabled(int deviceId) {
+ return nativeIsInputDeviceEnabled(mPtr, deviceId);
+ }
+
+ // Binder call
+ @Override
+ public void enableInputDevice(int deviceId) {
+ if (!checkCallingPermission(android.Manifest.permission.DISABLE_INPUT_DEVICE,
+ "enableInputDevice()")) {
+ throw new SecurityException("Requires DISABLE_INPUT_DEVICE permission");
+ }
+ nativeEnableInputDevice(mPtr, deviceId);
+ }
+
+ // Binder call
+ @Override
+ public void disableInputDevice(int deviceId) {
+ if (!checkCallingPermission(android.Manifest.permission.DISABLE_INPUT_DEVICE,
+ "disableInputDevice()")) {
+ throw new SecurityException("Requires DISABLE_INPUT_DEVICE permission");
+ }
+ nativeDisableInputDevice(mPtr, deviceId);
+ }
+
/**
* Gets the ids of all input devices in the system.
* @return The input device ids.
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 3fd91dc..4a45c07 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -77,10 +77,12 @@
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.location.gnssmetrics.GnssMetrics;
import com.android.internal.location.GpsNetInitiatedHandler;
import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
+
import com.android.server.power.BatterySaverPolicy;
import com.android.server.power.BatterySaverPolicy.ServiceType;
@@ -417,6 +419,9 @@
private static final float ITAR_SPEED_LIMIT_METERS_PER_SECOND = 400.0F;
private boolean mItarSpeedLimitExceeded = false;
+ // GNSS Metrics
+ private GnssMetrics mGnssMetrics;
+
private final IGnssStatusProvider mGnssStatusProvider = new IGnssStatusProvider.Stub() {
@Override
public void registerGnssStatusCallback(IGnssStatusListener callback) {
@@ -778,6 +783,7 @@
return isEnabled();
}
};
+ mGnssMetrics = new GnssMetrics();
}
/**
@@ -1178,21 +1184,29 @@
@Override
public int getStatus(Bundle extras) {
- if (extras != null) {
- extras.putInt("satellites", mSvCount);
- }
+ setLocationExtras(extras);
return mStatus;
}
- private void updateStatus(int status, int svCount) {
- if (status != mStatus || svCount != mSvCount) {
+ private void updateStatus(int status, int svCount, int meanCn0, int maxCn0) {
+ if (status != mStatus || svCount != mSvCount || meanCn0 != mMeanCn0 || maxCn0 != mMaxCn0 ) {
mStatus = status;
mSvCount = svCount;
- mLocationExtras.putInt("satellites", svCount);
+ mMeanCn0 = meanCn0;
+ mMaxCn0 = maxCn0;
+ setLocationExtras(mLocationExtras);
mStatusUpdateTime = SystemClock.elapsedRealtime();
}
}
+ private void setLocationExtras(Bundle extras) {
+ if (extras != null) {
+ extras.putInt("satellites", mSvCount);
+ extras.putInt("meanCn0", mMeanCn0);
+ extras.putInt("maxCn0", mMaxCn0);
+ }
+ }
+
@Override
public long getStatusUpdateTime() {
return mStatusUpdateTime;
@@ -1443,9 +1457,8 @@
}
// reset SV count to zero
- updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0);
+ updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0, 0, 0);
mFixRequestTime = SystemClock.elapsedRealtime();
-
if (!hasCapability(GPS_CAPABILITY_SCHEDULING)) {
// set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT
// and our fix interval is not short
@@ -1467,7 +1480,7 @@
mLastFixTime = 0;
// reset SV count to zero
- updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0);
+ updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0, 0, 0);
}
}
@@ -1496,6 +1509,7 @@
if (mItarSpeedLimitExceeded) {
Log.i(TAG, "Hal reported a speed in excess of ITAR limit." +
" GPS/GNSS Navigation output blocked.");
+ mGnssMetrics.logReceivedLocationStatus(false);
return; // No output of location allowed
}
@@ -1515,11 +1529,23 @@
}
}
+ mGnssMetrics.logReceivedLocationStatus(hasLatLong);
+ if (hasLatLong) {
+ if (location.hasAccuracy()) {
+ mGnssMetrics.logPositionAccuracyMeters(location.getAccuracy());
+ }
+ if (mTimeToFirstFix > 0) {
+ int timeBetweenFixes = (int) (SystemClock.elapsedRealtime() - mLastFixTime);
+ mGnssMetrics.logMissedReports(mFixInterval, timeBetweenFixes);
+ }
+ }
+
mLastFixTime = SystemClock.elapsedRealtime();
// report time to first fix
if (mTimeToFirstFix == 0 && hasLatLong) {
mTimeToFirstFix = (int)(mLastFixTime - mFixRequestTime);
if (DEBUG) Log.d(TAG, "TTFF: " + mTimeToFirstFix);
+ mGnssMetrics.logTimeToFirstFixMilliSecs(mTimeToFirstFix);
// notify status listeners
mListenerHelper.onFirstFix(mTimeToFirstFix);
@@ -1540,7 +1566,7 @@
Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION);
intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, true);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- updateStatus(LocationProvider.AVAILABLE, mSvCount);
+ updateStatus(LocationProvider.AVAILABLE, mSvCount, mMeanCn0, mMaxCn0);
}
if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mStarted &&
@@ -1604,15 +1630,21 @@
if (VERBOSE) {
Log.v(TAG, "SV count: " + svCount);
}
- // Calculate number of sets used in fix.
+ // Calculate number of satellites used in fix.
int usedInFixCount = 0;
+ int maxCn0 = 0;
+ int meanCn0 = 0;
for (int i = 0; i < svCount; i++) {
if ((mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX) != 0) {
++usedInFixCount;
+ if (mCn0s[i] > maxCn0) {
+ maxCn0 = (int)mCn0s[i];
+ }
+ meanCn0 += mCn0s[i];
}
if (VERBOSE) {
Log.v(TAG, "svid: " + (mSvidWithFlags[i] >> GnssStatus.SVID_SHIFT_WIDTH) +
- " cn0: " + mCn0s[i]/10 +
+ " cn0: " + mCn0s[i] +
" elev: " + mSvElevations[i] +
" azimuth: " + mSvAzimuths[i] +
" carrier frequency: " + mSvCarrierFreqs[i] +
@@ -1626,8 +1658,11 @@
? "" : "F"));
}
}
- // return number of sets used in fix instead of total
- updateStatus(mStatus, usedInFixCount);
+ if (usedInFixCount > 0) {
+ meanCn0 /= usedInFixCount;
+ }
+ // return number of sats used in fix instead of total reported
+ updateStatus(mStatus, usedInFixCount, meanCn0, maxCn0);
if (mNavigating && mStatus == LocationProvider.AVAILABLE && mLastFixTime > 0 &&
SystemClock.elapsedRealtime() - mLastFixTime > RECENT_FIX_TIMEOUT) {
@@ -1635,7 +1670,7 @@
Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION);
intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, false);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, mSvCount);
+ updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, mSvCount, mMeanCn0, mMaxCn0);
}
}
@@ -1801,6 +1836,25 @@
};
}
+ public interface GnssMetricsProvider {
+ /**
+ * Returns GNSS metrics as proto string
+ */
+ String getGnssMetricsAsProtoString();
+ }
+
+ /**
+ * @hide
+ */
+ public GnssMetricsProvider getGnssMetricsProvider() {
+ return new GnssMetricsProvider() {
+ @Override
+ public String getGnssMetricsAsProtoString() {
+ return mGnssMetrics.dumpGnssMetricsAsProtoString();
+ }
+ };
+ }
+
/**
* Initialize Batching if enabled
*/
@@ -2430,6 +2484,8 @@
}
}
+
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
StringBuilder s = new StringBuilder();
@@ -2446,10 +2502,9 @@
if (hasCapability(GPS_CAPABILITY_MEASUREMENTS)) s.append("MEASUREMENTS ");
if (hasCapability(GPS_CAPABILITY_NAV_MESSAGES)) s.append("NAV_MESSAGES ");
s.append(")\n");
-
- s.append(" internal state: ").append(native_get_internal_state());
+ s.append(mGnssMetrics.dumpGnssMetricsAsText());
+ s.append(" native internal state: ").append(native_get_internal_state());
s.append("\n");
-
pw.append(s);
}
@@ -2493,6 +2548,8 @@
private float mSvAzimuths[] = new float[MAX_SVS];
private float mSvCarrierFreqs[] = new float[MAX_SVS];
private int mSvCount;
+ private int mMeanCn0;
+ private int mMaxCn0;
// preallocated to avoid memory allocation in reportNmea()
private byte[] mNmeaBuffer = new byte[120];
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
new file mode 100644
index 0000000..fe2c5bd
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -0,0 +1,2421 @@
+/*
+ * Copyright (C) 2012 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.locksettings;
+
+import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
+import static android.Manifest.permission.READ_CONTACTS;
+import static android.content.Context.KEYGUARD_SERVICE;
+
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
+import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
+import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
+
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.app.KeyguardManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.PasswordMetrics;
+import android.app.backup.BackupManager;
+import android.app.trust.IStrongAuthTracker;
+import android.app.trust.TrustManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IProgressListener;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.ShellCallback;
+import android.os.StrictMode;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.IStorageManager;
+import android.os.storage.StorageManager;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.provider.Settings.SettingNotFoundException;
+import android.security.KeyStore;
+import android.security.keystore.AndroidKeyStoreProvider;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.KeyProtection;
+import android.service.gatekeeper.GateKeeperResponse;
+import android.service.gatekeeper.IGateKeeperService;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
+import com.android.internal.widget.ICheckCredentialProgressCallback;
+import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.SystemService;
+import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
+import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
+import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+
+import libcore.util.HexEncoding;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyStoreException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+
+/**
+ * Keeps the lock pattern/password data and related settings for each user. Used by
+ * LockPatternUtils. Needs to be a service because Settings app also needs to be able to save
+ * lockscreen information for secondary users.
+ *
+ * @hide
+ */
+public class LockSettingsService extends ILockSettings.Stub {
+ private static final String TAG = "LockSettingsService";
+ private static final String PERMISSION = ACCESS_KEYGUARD_SECURE_STORAGE;
+ private static final boolean DEBUG = false;
+
+ private static final int PROFILE_KEY_IV_SIZE = 12;
+ private static final String SEPARATE_PROFILE_CHALLENGE_KEY = "lockscreen.profilechallenge";
+ private static final int SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT = 1;
+
+ // Order of holding lock: mSeparateChallengeLock -> mSpManager -> this
+ private final Object mSeparateChallengeLock = new Object();
+
+ private final DeviceProvisionedObserver mDeviceProvisionedObserver =
+ new DeviceProvisionedObserver();
+
+ private final Injector mInjector;
+ private final Context mContext;
+ private final Handler mHandler;
+ @VisibleForTesting
+ protected final LockSettingsStorage mStorage;
+ private final LockSettingsStrongAuth mStrongAuth;
+ private final SynchronizedStrongAuthTracker mStrongAuthTracker;
+
+ private final LockPatternUtils mLockPatternUtils;
+ private final NotificationManager mNotificationManager;
+ private final UserManager mUserManager;
+ private final IActivityManager mActivityManager;
+ private final SyntheticPasswordManager mSpManager;
+
+ private final KeyStore mKeyStore;
+
+ private boolean mFirstCallToVold;
+ protected IGateKeeperService mGateKeeperService;
+
+ /**
+ * The UIDs that are used for system credential storage in keystore.
+ */
+ private static final int[] SYSTEM_CREDENTIAL_UIDS = {
+ Process.WIFI_UID, Process.VPN_UID,
+ Process.ROOT_UID, Process.SYSTEM_UID };
+
+ // This class manages life cycle events for encrypted users on File Based Encryption (FBE)
+ // devices. The most basic of these is to show/hide notifications about missing features until
+ // the user unlocks the account and credential-encrypted storage is available.
+ public static final class Lifecycle extends SystemService {
+ private LockSettingsService mLockSettingsService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ AndroidKeyStoreProvider.install();
+ mLockSettingsService = new LockSettingsService(getContext());
+ publishBinderService("lock_settings", mLockSettingsService);
+ }
+
+ @Override
+ public void onStartUser(int userHandle) {
+ mLockSettingsService.onStartUser(userHandle);
+ }
+
+ @Override
+ public void onUnlockUser(int userHandle) {
+ mLockSettingsService.onUnlockUser(userHandle);
+ }
+
+ @Override
+ public void onCleanupUser(int userHandle) {
+ mLockSettingsService.onCleanupUser(userHandle);
+ }
+ }
+
+ @VisibleForTesting
+ protected static class SynchronizedStrongAuthTracker
+ extends LockPatternUtils.StrongAuthTracker {
+ public SynchronizedStrongAuthTracker(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void handleStrongAuthRequiredChanged(int strongAuthFlags, int userId) {
+ synchronized (this) {
+ super.handleStrongAuthRequiredChanged(strongAuthFlags, userId);
+ }
+ }
+
+ @Override
+ public int getStrongAuthForUser(int userId) {
+ synchronized (this) {
+ return super.getStrongAuthForUser(userId);
+ }
+ }
+
+ void register(LockSettingsStrongAuth strongAuth) {
+ strongAuth.registerStrongAuthTracker(this.mStub);
+ }
+ }
+
+ /**
+ * Tie managed profile to primary profile if it is in unified mode and not tied before.
+ *
+ * @param managedUserId Managed profile user Id
+ * @param managedUserPassword Managed profile original password (when it has separated lock).
+ * NULL when it does not have a separated lock before.
+ */
+ public void tieManagedProfileLockIfNecessary(int managedUserId, String managedUserPassword) {
+ if (DEBUG) Slog.v(TAG, "Check child profile lock for user: " + managedUserId);
+ // Only for managed profile
+ if (!mUserManager.getUserInfo(managedUserId).isManagedProfile()) {
+ return;
+ }
+ // Do not tie managed profile when work challenge is enabled
+ if (mLockPatternUtils.isSeparateProfileChallengeEnabled(managedUserId)) {
+ return;
+ }
+ // Do not tie managed profile to parent when it's done already
+ if (mStorage.hasChildProfileLock(managedUserId)) {
+ return;
+ }
+ // Do not tie it to parent when parent does not have a screen lock
+ final int parentId = mUserManager.getProfileParent(managedUserId).id;
+ if (!isUserSecure(parentId)) {
+ if (DEBUG) Slog.v(TAG, "Parent does not have a screen lock");
+ return;
+ }
+ // Do not tie when the parent has no SID (but does have a screen lock).
+ // This can only happen during an upgrade path where SID is yet to be
+ // generated when the user unlocks for the first time.
+ try {
+ if (getGateKeeperService().getSecureUserId(parentId) == 0) {
+ return;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to talk to GateKeeper service", e);
+ return;
+ }
+ if (DEBUG) Slog.v(TAG, "Tie managed profile to parent now!");
+ byte[] randomLockSeed = new byte[] {};
+ try {
+ randomLockSeed = SecureRandom.getInstance("SHA1PRNG").generateSeed(40);
+ String newPassword = String.valueOf(HexEncoding.encode(randomLockSeed));
+ final int quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+ setLockCredentialInternal(newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ managedUserPassword, quality, managedUserId);
+ // We store a private credential for the managed user that's unlocked by the primary
+ // account holder's credential. As such, the user will never be prompted to enter this
+ // password directly, so we always store a password.
+ setLong(LockPatternUtils.PASSWORD_TYPE_KEY, quality, managedUserId);
+ tieProfileLockToParent(managedUserId, newPassword);
+ } catch (NoSuchAlgorithmException | RemoteException e) {
+ Slog.e(TAG, "Fail to tie managed profile", e);
+ // Nothing client can do to fix this issue, so we do not throw exception out
+ }
+ }
+
+ static class Injector {
+
+ protected Context mContext;
+
+ public Injector(Context context) {
+ mContext = context;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public Handler getHandler() {
+ return new Handler();
+ }
+
+ public LockSettingsStorage getStorage() {
+ final LockSettingsStorage storage = new LockSettingsStorage(mContext);
+ storage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() {
+ @Override
+ public void initialize(SQLiteDatabase db) {
+ // Get the lockscreen default from a system property, if available
+ boolean lockScreenDisable = SystemProperties.getBoolean(
+ "ro.lockscreen.disable.default", false);
+ if (lockScreenDisable) {
+ storage.writeKeyValue(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0);
+ }
+ }
+ });
+ return storage;
+ }
+
+ public LockSettingsStrongAuth getStrongAuth() {
+ return new LockSettingsStrongAuth(mContext);
+ }
+
+ public SynchronizedStrongAuthTracker getStrongAuthTracker() {
+ return new SynchronizedStrongAuthTracker(mContext);
+ }
+
+ public IActivityManager getActivityManager() {
+ return ActivityManager.getService();
+ }
+
+ public LockPatternUtils getLockPatternUtils() {
+ return new LockPatternUtils(mContext);
+ }
+
+ public NotificationManager getNotificationManager() {
+ return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ public UserManager getUserManager() {
+ return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ }
+
+ public DevicePolicyManager getDevicePolicyManager() {
+ return (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ }
+
+ public KeyStore getKeyStore() {
+ return KeyStore.getInstance();
+ }
+
+ public IStorageManager getStorageManager() {
+ final IBinder service = ServiceManager.getService("mount");
+ if (service != null) {
+ return IStorageManager.Stub.asInterface(service);
+ }
+ return null;
+ }
+
+ public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) {
+ return new SyntheticPasswordManager(storage, getUserManager());
+ }
+
+ public int binderGetCallingUid() {
+ return Binder.getCallingUid();
+ }
+ }
+
+ public LockSettingsService(Context context) {
+ this(new Injector(context));
+ }
+
+ @VisibleForTesting
+ protected LockSettingsService(Injector injector) {
+ mInjector = injector;
+ mContext = injector.getContext();
+ mKeyStore = injector.getKeyStore();
+ mHandler = injector.getHandler();
+ mStrongAuth = injector.getStrongAuth();
+ mActivityManager = injector.getActivityManager();
+
+ mLockPatternUtils = injector.getLockPatternUtils();
+ mFirstCallToVold = true;
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_ADDED);
+ filter.addAction(Intent.ACTION_USER_STARTING);
+ filter.addAction(Intent.ACTION_USER_REMOVED);
+ injector.getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter,
+ null, null);
+
+ mStorage = injector.getStorage();
+ mNotificationManager = injector.getNotificationManager();
+ mUserManager = injector.getUserManager();
+ mStrongAuthTracker = injector.getStrongAuthTracker();
+ mStrongAuthTracker.register(mStrongAuth);
+
+ mSpManager = injector.getSyntheticPasswordManager(mStorage);
+ }
+
+ /**
+ * If the account is credential-encrypted, show notification requesting the user to unlock the
+ * device.
+ */
+ private void maybeShowEncryptionNotificationForUser(@UserIdInt int userId) {
+ final UserInfo user = mUserManager.getUserInfo(userId);
+ if (!user.isManagedProfile()) {
+ // When the user is locked, we communicate it loud-and-clear
+ // on the lockscreen; we only show a notification below for
+ // locked managed profiles.
+ return;
+ }
+
+ final UserHandle userHandle = user.getUserHandle();
+ final boolean isSecure = isUserSecure(userId);
+ if (isSecure && !mUserManager.isUserUnlockingOrUnlocked(userHandle)) {
+ UserInfo parent = mUserManager.getProfileParent(userId);
+ if (parent != null &&
+ mUserManager.isUserUnlockingOrUnlocked(parent.getUserHandle()) &&
+ !mUserManager.isQuietModeEnabled(userHandle)) {
+ // Only show notifications for managed profiles once their parent
+ // user is unlocked.
+ showEncryptionNotificationForProfile(userHandle);
+ }
+ }
+ }
+
+ private void showEncryptionNotificationForProfile(UserHandle user) {
+ Resources r = mContext.getResources();
+ CharSequence title = r.getText(
+ com.android.internal.R.string.user_encrypted_title);
+ CharSequence message = r.getText(
+ com.android.internal.R.string.profile_encrypted_message);
+ CharSequence detail = r.getText(
+ com.android.internal.R.string.profile_encrypted_detail);
+
+ final KeyguardManager km = (KeyguardManager) mContext.getSystemService(KEYGUARD_SERVICE);
+ final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null,
+ user.getIdentifier());
+ if (unlockIntent == null) {
+ return;
+ }
+ unlockIntent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ PendingIntent intent = PendingIntent.getActivity(mContext, 0, unlockIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ showEncryptionNotification(user, title, message, detail, intent);
+ }
+
+ private void showEncryptionNotification(UserHandle user, CharSequence title,
+ CharSequence message, CharSequence detail, PendingIntent intent) {
+ if (DEBUG) Slog.v(TAG, "showing encryption notification, user: " + user.getIdentifier());
+
+ // Suppress all notifications on non-FBE devices for now
+ if (!StorageManager.isFileEncryptedNativeOrEmulated()) return;
+
+ Notification notification =
+ new Notification.Builder(mContext, SystemNotificationChannels.SECURITY)
+ .setSmallIcon(com.android.internal.R.drawable.ic_user_secure)
+ .setWhen(0)
+ .setOngoing(true)
+ .setTicker(title)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setContentTitle(title)
+ .setContentText(message)
+ .setSubText(detail)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setContentIntent(intent)
+ .build();
+ mNotificationManager.notifyAsUser(null, SystemMessage.NOTE_FBE_ENCRYPTED_NOTIFICATION,
+ notification, user);
+ }
+
+ private void hideEncryptionNotification(UserHandle userHandle) {
+ if (DEBUG) Slog.v(TAG, "hide encryption notification, user: " + userHandle.getIdentifier());
+ mNotificationManager.cancelAsUser(null, SystemMessage.NOTE_FBE_ENCRYPTED_NOTIFICATION,
+ userHandle);
+ }
+
+ public void onCleanupUser(int userId) {
+ hideEncryptionNotification(new UserHandle(userId));
+ }
+
+ public void onStartUser(final int userId) {
+ maybeShowEncryptionNotificationForUser(userId);
+ }
+
+ public void onUnlockUser(final int userId) {
+ // Perform tasks which require locks in LSS on a handler, as we are callbacks from
+ // ActivityManager.unlockUser()
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // Hide notification first, as tie managed profile lock takes time
+ hideEncryptionNotification(new UserHandle(userId));
+
+ // Now we have unlocked the parent user we should show notifications
+ // about any profiles that exist.
+ List<UserInfo> profiles = mUserManager.getProfiles(userId);
+ for (int i = 0; i < profiles.size(); i++) {
+ UserInfo profile = profiles.get(i);
+ final boolean isSecure = isUserSecure(profile.id);
+ if (isSecure && profile.isManagedProfile()) {
+ UserHandle userHandle = profile.getUserHandle();
+ if (!mUserManager.isUserUnlockingOrUnlocked(userHandle) &&
+ !mUserManager.isQuietModeEnabled(userHandle)) {
+ showEncryptionNotificationForProfile(userHandle);
+ }
+ }
+ }
+
+ if (mUserManager.getUserInfo(userId).isManagedProfile()) {
+ tieManagedProfileLockIfNecessary(userId, null);
+ }
+ }
+ });
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_USER_ADDED.equals(intent.getAction())) {
+ // Notify keystore that a new user was added.
+ final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+ if (userHandle > UserHandle.USER_SYSTEM) {
+ removeUser(userHandle, /* unknownUser= */ true);
+ }
+ final KeyStore ks = KeyStore.getInstance();
+ final UserInfo parentInfo = mUserManager.getProfileParent(userHandle);
+ final int parentHandle = parentInfo != null ? parentInfo.id : -1;
+ ks.onUserAdded(userHandle, parentHandle);
+ } else if (Intent.ACTION_USER_STARTING.equals(intent.getAction())) {
+ final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+ mStorage.prefetchUser(userHandle);
+ } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
+ final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+ if (userHandle > 0) {
+ removeUser(userHandle, /* unknownUser= */ false);
+ }
+ }
+ }
+ };
+
+ @Override // binder interface
+ public void systemReady() {
+ migrateOldData();
+ try {
+ getGateKeeperService();
+ mSpManager.initWeaverService();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failure retrieving IGateKeeperService", e);
+ }
+ mDeviceProvisionedObserver.onSystemReady();
+ // TODO: maybe skip this for split system user mode.
+ mStorage.prefetchUser(UserHandle.USER_SYSTEM);
+ }
+
+ private void migrateOldData() {
+ try {
+ // These Settings moved before multi-user was enabled, so we only have to do it for the
+ // root user.
+ if (getString("migrated", null, 0) == null) {
+ final ContentResolver cr = mContext.getContentResolver();
+ for (String validSetting : VALID_SETTINGS) {
+ String value = Settings.Secure.getString(cr, validSetting);
+ if (value != null) {
+ setString(validSetting, value, 0);
+ }
+ }
+ // No need to move the password / pattern files. They're already in the right place.
+ setString("migrated", "true", 0);
+ Slog.i(TAG, "Migrated lock settings to new location");
+ }
+
+ // These Settings changed after multi-user was enabled, hence need to be moved per user.
+ if (getString("migrated_user_specific", null, 0) == null) {
+ final ContentResolver cr = mContext.getContentResolver();
+ List<UserInfo> users = mUserManager.getUsers();
+ for (int user = 0; user < users.size(); user++) {
+ // Migrate owner info
+ final int userId = users.get(user).id;
+ final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
+ String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
+ if (!TextUtils.isEmpty(ownerInfo)) {
+ setString(OWNER_INFO, ownerInfo, userId);
+ Settings.Secure.putStringForUser(cr, OWNER_INFO, "", userId);
+ }
+
+ // Migrate owner info enabled. Note there was a bug where older platforms only
+ // stored this value if the checkbox was toggled at least once. The code detects
+ // this case by handling the exception.
+ final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
+ boolean enabled;
+ try {
+ int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId);
+ enabled = ivalue != 0;
+ setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId);
+ } catch (SettingNotFoundException e) {
+ // Setting was never stored. Store it if the string is not empty.
+ if (!TextUtils.isEmpty(ownerInfo)) {
+ setLong(OWNER_INFO_ENABLED, 1, userId);
+ }
+ }
+ Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
+ }
+ // No need to move the password / pattern files. They're already in the right place.
+ setString("migrated_user_specific", "true", 0);
+ Slog.i(TAG, "Migrated per-user lock settings to new location");
+ }
+
+ // Migrates biometric weak such that the fallback mechanism becomes the primary.
+ if (getString("migrated_biometric_weak", null, 0) == null) {
+ List<UserInfo> users = mUserManager.getUsers();
+ for (int i = 0; i < users.size(); i++) {
+ int userId = users.get(i).id;
+ long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ userId);
+ long alternateType = getLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ userId);
+ if (type == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
+ setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+ alternateType,
+ userId);
+ }
+ setLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ userId);
+ }
+ setString("migrated_biometric_weak", "true", 0);
+ Slog.i(TAG, "Migrated biometric weak to use the fallback instead");
+ }
+
+ // Migrates lockscreen.disabled. Prior to M, the flag was ignored when more than one
+ // user was present on the system, so if we're upgrading to M and there is more than one
+ // user we disable the flag to remain consistent.
+ if (getString("migrated_lockscreen_disabled", null, 0) == null) {
+ final List<UserInfo> users = mUserManager.getUsers();
+ final int userCount = users.size();
+ int switchableUsers = 0;
+ for (int i = 0; i < userCount; i++) {
+ if (users.get(i).supportsSwitchTo()) {
+ switchableUsers++;
+ }
+ }
+
+ if (switchableUsers > 1) {
+ for (int i = 0; i < userCount; i++) {
+ int id = users.get(i).id;
+
+ if (getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id)) {
+ setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
+ }
+ }
+ }
+
+ setString("migrated_lockscreen_disabled", "true", 0);
+ Slog.i(TAG, "Migrated lockscreen disabled flag");
+ }
+
+ final List<UserInfo> users = mUserManager.getUsers();
+ for (int i = 0; i < users.size(); i++) {
+ final UserInfo userInfo = users.get(i);
+ if (userInfo.isManagedProfile() && mStorage.hasChildProfileLock(userInfo.id)) {
+ // When managed profile has a unified lock, the password quality stored has 2
+ // possibilities only.
+ // 1). PASSWORD_QUALITY_UNSPECIFIED, which is upgraded from dp2, and we are
+ // going to set it back to PASSWORD_QUALITY_ALPHANUMERIC.
+ // 2). PASSWORD_QUALITY_ALPHANUMERIC, which is the actual password quality for
+ // unified lock.
+ final long quality = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);
+ if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+ // Only possible when it's upgraded from nyc dp3
+ Slog.i(TAG, "Migrated tied profile lock type");
+ setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, userInfo.id);
+ } else if (quality != DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC) {
+ // It should not happen
+ Slog.e(TAG, "Invalid tied profile lock type: " + quality);
+ }
+ }
+ try {
+ final String alias = LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userInfo.id;
+ java.security.KeyStore keyStore =
+ java.security.KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ if (keyStore.containsAlias(alias)) {
+ keyStore.deleteEntry(alias);
+ }
+ } catch (KeyStoreException | NoSuchAlgorithmException |
+ CertificateException | IOException e) {
+ Slog.e(TAG, "Unable to remove tied profile key", e);
+ }
+ }
+
+ boolean isWatch = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WATCH);
+ // Wear used to set DISABLE_LOCKSCREEN to 'true', but because Wear now allows accounts
+ // and device management the lockscreen must be re-enabled now for users that upgrade.
+ if (isWatch && getString("migrated_wear_lockscreen_disabled", null, 0) == null) {
+ final int userCount = users.size();
+ for (int i = 0; i < userCount; i++) {
+ int id = users.get(i).id;
+ setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
+ }
+ setString("migrated_wear_lockscreen_disabled", "true", 0);
+ Slog.i(TAG, "Migrated lockscreen_disabled for Wear devices");
+ }
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to migrate old data", re);
+ }
+ }
+
+ private final void checkWritePermission(int userId) {
+ mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsWrite");
+ }
+
+ private final void checkPasswordReadPermission(int userId) {
+ mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsRead");
+ }
+
+ private final void checkReadPermission(String requestedKey, int userId) {
+ final int callingUid = Binder.getCallingUid();
+
+ for (int i = 0; i < READ_CONTACTS_PROTECTED_SETTINGS.length; i++) {
+ String key = READ_CONTACTS_PROTECTED_SETTINGS[i];
+ if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(READ_CONTACTS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("uid=" + callingUid
+ + " needs permission " + READ_CONTACTS + " to read "
+ + requestedKey + " for user " + userId);
+ }
+ }
+
+ for (int i = 0; i < READ_PASSWORD_PROTECTED_SETTINGS.length; i++) {
+ String key = READ_PASSWORD_PROTECTED_SETTINGS[i];
+ if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(PERMISSION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("uid=" + callingUid
+ + " needs permission " + PERMISSION + " to read "
+ + requestedKey + " for user " + userId);
+ }
+ }
+ }
+
+ @Override
+ public boolean getSeparateProfileChallengeEnabled(int userId) throws RemoteException {
+ checkReadPermission(SEPARATE_PROFILE_CHALLENGE_KEY, userId);
+ synchronized (mSeparateChallengeLock) {
+ return getBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, false, userId);
+ }
+ }
+
+ @Override
+ public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
+ String managedUserPassword) throws RemoteException {
+ checkWritePermission(userId);
+ synchronized (mSeparateChallengeLock) {
+ setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userId);
+ if (enabled) {
+ mStorage.removeChildProfileLock(userId);
+ removeKeystoreProfileKey(userId);
+ } else {
+ tieManagedProfileLockIfNecessary(userId, managedUserPassword);
+ }
+ }
+ }
+
+ @Override
+ public void setBoolean(String key, boolean value, int userId) throws RemoteException {
+ checkWritePermission(userId);
+ setStringUnchecked(key, userId, value ? "1" : "0");
+ }
+
+ @Override
+ public void setLong(String key, long value, int userId) throws RemoteException {
+ checkWritePermission(userId);
+ setStringUnchecked(key, userId, Long.toString(value));
+ }
+
+ @Override
+ public void setString(String key, String value, int userId) throws RemoteException {
+ checkWritePermission(userId);
+ setStringUnchecked(key, userId, value);
+ }
+
+ private void setStringUnchecked(String key, int userId, String value) {
+ Preconditions.checkArgument(userId != USER_FRP, "cannot store lock settings for FRP user");
+
+ mStorage.writeKeyValue(key, value, userId);
+ if (ArrayUtils.contains(SETTINGS_TO_BACKUP, key)) {
+ BackupManager.dataChanged("com.android.providers.settings");
+ }
+ }
+
+ @Override
+ public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
+ checkReadPermission(key, userId);
+ String value = getStringUnchecked(key, null, userId);
+ return TextUtils.isEmpty(value) ?
+ defaultValue : (value.equals("1") || value.equals("true"));
+ }
+
+ @Override
+ public long getLong(String key, long defaultValue, int userId) throws RemoteException {
+ checkReadPermission(key, userId);
+ String value = getStringUnchecked(key, null, userId);
+ return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
+ }
+
+ @Override
+ public String getString(String key, String defaultValue, int userId) throws RemoteException {
+ checkReadPermission(key, userId);
+ return getStringUnchecked(key, defaultValue, userId);
+ }
+
+ public String getStringUnchecked(String key, String defaultValue, int userId) {
+ if (Settings.Secure.LOCK_PATTERN_ENABLED.equals(key)) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ return mLockPatternUtils.isLockPatternEnabled(userId) ? "1" : "0";
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ if (userId == USER_FRP) {
+ return getFrpStringUnchecked(key);
+ }
+
+ if (LockPatternUtils.LEGACY_LOCK_PATTERN_ENABLED.equals(key)) {
+ key = Settings.Secure.LOCK_PATTERN_ENABLED;
+ }
+
+ return mStorage.readKeyValue(key, defaultValue, userId);
+ }
+
+ private String getFrpStringUnchecked(String key) {
+ if (LockPatternUtils.PASSWORD_TYPE_KEY.equals(key)) {
+ return String.valueOf(readFrpPasswordQuality());
+ }
+ return null;
+ }
+
+ private int readFrpPasswordQuality() {
+ return mStorage.readPersistentDataBlock().qualityForUi;
+ }
+
+ @Override
+ public boolean havePassword(int userId) throws RemoteException {
+ synchronized (mSpManager) {
+ if (isSyntheticPasswordBasedCredentialLocked(userId)) {
+ long handle = getSyntheticPasswordHandleLocked(userId);
+ return mSpManager.getCredentialType(handle, userId) ==
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+ }
+ }
+ // Do we need a permissions check here?
+ return mStorage.hasPassword(userId);
+ }
+
+ @Override
+ public boolean havePattern(int userId) throws RemoteException {
+ synchronized (mSpManager) {
+ if (isSyntheticPasswordBasedCredentialLocked(userId)) {
+ long handle = getSyntheticPasswordHandleLocked(userId);
+ return mSpManager.getCredentialType(handle, userId) ==
+ LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+ }
+ }
+ // Do we need a permissions check here?
+ return mStorage.hasPattern(userId);
+ }
+
+ private boolean isUserSecure(int userId) {
+ synchronized (mSpManager) {
+ try {
+ if (isSyntheticPasswordBasedCredentialLocked(userId)) {
+ long handle = getSyntheticPasswordHandleLocked(userId);
+ return mSpManager.getCredentialType(handle, userId) !=
+ LockPatternUtils.CREDENTIAL_TYPE_NONE;
+ }
+ } catch (RemoteException e) {
+ // fall through
+ }
+ }
+ return mStorage.hasCredential(userId);
+ }
+
+ private void setKeystorePassword(String password, int userHandle) {
+ final KeyStore ks = KeyStore.getInstance();
+ ks.onUserPasswordChanged(userHandle, password);
+ }
+
+ private void unlockKeystore(String password, int userHandle) {
+ if (DEBUG) Slog.v(TAG, "Unlock keystore for user: " + userHandle);
+ final KeyStore ks = KeyStore.getInstance();
+ ks.unlock(userHandle, password);
+ }
+
+ @VisibleForTesting
+ protected String getDecryptedPasswordForTiedProfile(int userId)
+ throws KeyStoreException, UnrecoverableKeyException,
+ NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
+ InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException,
+ CertificateException, IOException {
+ if (DEBUG) Slog.v(TAG, "Get child profile decrytped key");
+ byte[] storedData = mStorage.readChildProfileLock(userId);
+ if (storedData == null) {
+ throw new FileNotFoundException("Child profile lock file not found");
+ }
+ byte[] iv = Arrays.copyOfRange(storedData, 0, PROFILE_KEY_IV_SIZE);
+ byte[] encryptedPassword = Arrays.copyOfRange(storedData, PROFILE_KEY_IV_SIZE,
+ storedData.length);
+ byte[] decryptionResult;
+ java.security.KeyStore keyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ SecretKey decryptionKey = (SecretKey) keyStore.getKey(
+ LockPatternUtils.PROFILE_KEY_NAME_DECRYPT + userId, null);
+
+ Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
+
+ cipher.init(Cipher.DECRYPT_MODE, decryptionKey, new GCMParameterSpec(128, iv));
+ decryptionResult = cipher.doFinal(encryptedPassword);
+ return new String(decryptionResult, StandardCharsets.UTF_8);
+ }
+
+ private void unlockChildProfile(int profileHandle) throws RemoteException {
+ try {
+ doVerifyCredential(getDecryptedPasswordForTiedProfile(profileHandle),
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ false, 0 /* no challenge */, profileHandle, null /* progressCallback */);
+ } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
+ | NoSuchAlgorithmException | NoSuchPaddingException
+ | InvalidAlgorithmParameterException | IllegalBlockSizeException
+ | BadPaddingException | CertificateException | IOException e) {
+ if (e instanceof FileNotFoundException) {
+ Slog.i(TAG, "Child profile key not found");
+ } else {
+ Slog.e(TAG, "Failed to decrypt child profile key", e);
+ }
+ }
+ }
+
+ private void unlockUser(int userId, byte[] token, byte[] secret) {
+ // TODO: make this method fully async so we can update UI with progress strings
+ final CountDownLatch latch = new CountDownLatch(1);
+ final IProgressListener listener = new IProgressListener.Stub() {
+ @Override
+ public void onStarted(int id, Bundle extras) throws RemoteException {
+ Log.d(TAG, "unlockUser started");
+ }
+
+ @Override
+ public void onProgress(int id, int progress, Bundle extras) throws RemoteException {
+ Log.d(TAG, "unlockUser progress " + progress);
+ }
+
+ @Override
+ public void onFinished(int id, Bundle extras) throws RemoteException {
+ Log.d(TAG, "unlockUser finished");
+ latch.countDown();
+ }
+ };
+
+ try {
+ mActivityManager.unlockUser(userId, token, secret, listener);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+
+ try {
+ latch.await(15, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ try {
+ if (!mUserManager.getUserInfo(userId).isManagedProfile()) {
+ final List<UserInfo> profiles = mUserManager.getProfiles(userId);
+ for (UserInfo pi : profiles) {
+ // Unlock managed profile with unified lock
+ if (pi.isManagedProfile()
+ && !mLockPatternUtils.isSeparateProfileChallengeEnabled(pi.id)
+ && mStorage.hasChildProfileLock(pi.id)
+ && mUserManager.isUserRunning(pi.id)) {
+ unlockChildProfile(pi.id);
+ }
+ }
+ }
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to unlock child profile", e);
+ }
+ }
+
+ private Map<Integer, String> getDecryptedPasswordsForAllTiedProfiles(int userId) {
+ if (mUserManager.getUserInfo(userId).isManagedProfile()) {
+ return null;
+ }
+ Map<Integer, String> result = new ArrayMap<Integer, String>();
+ final List<UserInfo> profiles = mUserManager.getProfiles(userId);
+ final int size = profiles.size();
+ for (int i = 0; i < size; i++) {
+ final UserInfo profile = profiles.get(i);
+ if (!profile.isManagedProfile()) {
+ continue;
+ }
+ final int managedUserId = profile.id;
+ if (mLockPatternUtils.isSeparateProfileChallengeEnabled(managedUserId)) {
+ continue;
+ }
+ try {
+ result.put(userId, getDecryptedPasswordForTiedProfile(userId));
+ } catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException
+ | NoSuchPaddingException | InvalidKeyException
+ | InvalidAlgorithmParameterException | IllegalBlockSizeException
+ | BadPaddingException | CertificateException | IOException e) {
+ // ignore
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Synchronize all profile's work challenge of the given user if it's unified: tie or clear them
+ * depending on the parent user's secure state.
+ *
+ * When clearing tied work challenges, a pre-computed password table for profiles are required,
+ * since changing password for profiles requires existing password, and existing passwords can
+ * only be computed before the parent user's password is cleared.
+ *
+ * Strictly this is a recursive function, since setLockCredentialInternal ends up calling this
+ * method again on profiles. However the recursion is guaranteed to terminate as this method
+ * terminates when the user is a managed profile.
+ */
+ private void synchronizeUnifiedWorkChallengeForProfiles(int userId,
+ Map<Integer, String> profilePasswordMap) throws RemoteException {
+ if (mUserManager.getUserInfo(userId).isManagedProfile()) {
+ return;
+ }
+ final boolean isSecure = isUserSecure(userId);
+ final List<UserInfo> profiles = mUserManager.getProfiles(userId);
+ final int size = profiles.size();
+ for (int i = 0; i < size; i++) {
+ final UserInfo profile = profiles.get(i);
+ if (profile.isManagedProfile()) {
+ final int managedUserId = profile.id;
+ if (mLockPatternUtils.isSeparateProfileChallengeEnabled(managedUserId)) {
+ continue;
+ }
+ if (isSecure) {
+ tieManagedProfileLockIfNecessary(managedUserId, null);
+ } else {
+ // We use cached work profile password computed before clearing the parent's
+ // credential, otherwise they get lost
+ if (profilePasswordMap != null && profilePasswordMap.containsKey(managedUserId)) {
+ setLockCredentialInternal(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
+ profilePasswordMap.get(managedUserId),
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, managedUserId);
+ } else {
+ Slog.wtf(TAG, "clear tied profile challenges, but no password supplied.");
+ // Supplying null here would lead to untrusted credential change
+ setLockCredentialInternal(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, null,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, managedUserId);
+ }
+ mStorage.removeChildProfileLock(managedUserId);
+ removeKeystoreProfileKey(managedUserId);
+ }
+ }
+ }
+ }
+
+ private boolean isManagedProfileWithUnifiedLock(int userId) {
+ return mUserManager.getUserInfo(userId).isManagedProfile()
+ && !mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
+ }
+
+ private boolean isManagedProfileWithSeparatedLock(int userId) {
+ return mUserManager.getUserInfo(userId).isManagedProfile()
+ && mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
+ }
+
+ // This method should be called by LockPatternUtil only, all internal methods in this class
+ // should call setLockCredentialInternal.
+ @Override
+ public void setLockCredential(String credential, int type, String savedCredential,
+ int requestedQuality, int userId)
+ throws RemoteException {
+ checkWritePermission(userId);
+ synchronized (mSeparateChallengeLock) {
+ setLockCredentialInternal(credential, type, savedCredential, requestedQuality, userId);
+ setSeparateProfileChallengeEnabled(userId, true, null);
+ notifyPasswordChanged(userId);
+ }
+ }
+
+ private void setLockCredentialInternal(String credential, int credentialType,
+ String savedCredential, int requestedQuality, int userId) throws RemoteException {
+ // Normalize savedCredential and credential such that empty string is always represented
+ // as null.
+ if (TextUtils.isEmpty(savedCredential)) {
+ savedCredential = null;
+ }
+ if (TextUtils.isEmpty(credential)) {
+ credential = null;
+ }
+ synchronized (mSpManager) {
+ if (isSyntheticPasswordBasedCredentialLocked(userId)) {
+ spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential,
+ requestedQuality, userId);
+ return;
+ }
+ }
+
+ if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ if (credential != null) {
+ Slog.wtf(TAG, "CredentialType is none, but credential is non-null.");
+ }
+ clearUserKeyProtection(userId);
+ getGateKeeperService().clearSecureUserId(userId);
+ mStorage.writeCredentialHash(CredentialHash.createEmptyHash(), userId);
+ setKeystorePassword(null, userId);
+ fixateNewestUserKeyAuth(userId);
+ synchronizeUnifiedWorkChallengeForProfiles(userId, null);
+ notifyActivePasswordMetricsAvailable(null, userId);
+
+ if (mStorage.getPersistentDataBlock() != null
+ && LockPatternUtils.userOwnsFrpCredential(mUserManager.getUserInfo(userId))) {
+ // If owner, write to persistent storage for FRP
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, userId, 0, null);
+ }
+ return;
+ }
+ if (credential == null) {
+ throw new RemoteException("Null credential with mismatched credential type");
+ }
+
+ CredentialHash currentHandle = mStorage.readCredentialHash(userId);
+ if (isManagedProfileWithUnifiedLock(userId)) {
+ // get credential from keystore when managed profile has unified lock
+ if (savedCredential == null) {
+ try {
+ savedCredential = getDecryptedPasswordForTiedProfile(userId);
+ } catch (FileNotFoundException e) {
+ Slog.i(TAG, "Child profile key not found");
+ } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
+ | NoSuchAlgorithmException | NoSuchPaddingException
+ | InvalidAlgorithmParameterException | IllegalBlockSizeException
+ | BadPaddingException | CertificateException | IOException e) {
+ Slog.e(TAG, "Failed to decrypt child profile key", e);
+ }
+ }
+ } else {
+ if (currentHandle.hash == null) {
+ if (savedCredential != null) {
+ Slog.w(TAG, "Saved credential provided, but none stored");
+ }
+ savedCredential = null;
+ }
+ }
+ synchronized (mSpManager) {
+ if (shouldMigrateToSyntheticPasswordLocked(userId)) {
+ initializeSyntheticPasswordLocked(currentHandle.hash, savedCredential,
+ currentHandle.type, requestedQuality, userId);
+ spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential,
+ requestedQuality, userId);
+ return;
+ }
+ }
+ if (DEBUG) Slog.d(TAG, "setLockCredentialInternal: user=" + userId);
+ byte[] enrolledHandle = enrollCredential(currentHandle.hash, savedCredential, credential,
+ userId);
+ if (enrolledHandle != null) {
+ CredentialHash willStore = CredentialHash.create(enrolledHandle, credentialType);
+ mStorage.writeCredentialHash(willStore, userId);
+ // push new secret and auth token to vold
+ GateKeeperResponse gkResponse = getGateKeeperService()
+ .verifyChallenge(userId, 0, willStore.hash, credential.getBytes());
+ setUserKeyProtection(userId, credential, convertResponse(gkResponse));
+ fixateNewestUserKeyAuth(userId);
+ // Refresh the auth token
+ doVerifyCredential(credential, credentialType, true, 0, userId, null /* progressCallback */);
+ synchronizeUnifiedWorkChallengeForProfiles(userId, null);
+ if (mStorage.getPersistentDataBlock() != null
+ && LockPatternUtils.userOwnsFrpCredential(mUserManager.getUserInfo(userId))) {
+ // If owner, write to persistent storage for FRP
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_GATEKEEPER, userId,
+ requestedQuality, willStore.toBytes());
+ }
+ } else {
+ throw new RemoteException("Failed to enroll " +
+ (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ? "password"
+ : "pattern"));
+ }
+ }
+
+ private VerifyCredentialResponse convertResponse(GateKeeperResponse gateKeeperResponse) {
+ return VerifyCredentialResponse.fromGateKeeperResponse(gateKeeperResponse);
+ }
+
+ @VisibleForTesting
+ protected void tieProfileLockToParent(int userId, String password) {
+ if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
+ byte[] randomLockSeed = password.getBytes(StandardCharsets.UTF_8);
+ byte[] encryptionResult;
+ byte[] iv;
+ try {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
+ keyGenerator.init(new SecureRandom());
+ SecretKey secretKey = keyGenerator.generateKey();
+ java.security.KeyStore keyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ try {
+ keyStore.setEntry(
+ LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userId,
+ new java.security.KeyStore.SecretKeyEntry(secretKey),
+ new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .build());
+ keyStore.setEntry(
+ LockPatternUtils.PROFILE_KEY_NAME_DECRYPT + userId,
+ new java.security.KeyStore.SecretKeyEntry(secretKey),
+ new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .setUserAuthenticationRequired(true)
+ .setUserAuthenticationValidityDurationSeconds(30)
+ .build());
+ // Key imported, obtain a reference to it.
+ SecretKey keyStoreEncryptionKey = (SecretKey) keyStore.getKey(
+ LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userId, null);
+ Cipher cipher = Cipher.getInstance(
+ KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
+ + KeyProperties.ENCRYPTION_PADDING_NONE);
+ cipher.init(Cipher.ENCRYPT_MODE, keyStoreEncryptionKey);
+ encryptionResult = cipher.doFinal(randomLockSeed);
+ iv = cipher.getIV();
+ } finally {
+ // The original key can now be discarded.
+ keyStore.deleteEntry(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userId);
+ }
+ } catch (CertificateException | UnrecoverableKeyException
+ | IOException | BadPaddingException | IllegalBlockSizeException | KeyStoreException
+ | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
+ throw new RuntimeException("Failed to encrypt key", e);
+ }
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ try {
+ if (iv.length != PROFILE_KEY_IV_SIZE) {
+ throw new RuntimeException("Invalid iv length: " + iv.length);
+ }
+ outputStream.write(iv);
+ outputStream.write(encryptionResult);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to concatenate byte arrays", e);
+ }
+ mStorage.writeChildProfileLock(userId, outputStream.toByteArray());
+ }
+
+ private byte[] enrollCredential(byte[] enrolledHandle,
+ String enrolledCredential, String toEnroll, int userId)
+ throws RemoteException {
+ checkWritePermission(userId);
+ byte[] enrolledCredentialBytes = enrolledCredential == null
+ ? null
+ : enrolledCredential.getBytes();
+ byte[] toEnrollBytes = toEnroll == null
+ ? null
+ : toEnroll.getBytes();
+ GateKeeperResponse response = getGateKeeperService().enroll(userId, enrolledHandle,
+ enrolledCredentialBytes, toEnrollBytes);
+
+ if (response == null) {
+ return null;
+ }
+
+ byte[] hash = response.getPayload();
+ if (hash != null) {
+ setKeystorePassword(toEnroll, userId);
+ } else {
+ // Should not happen
+ Slog.e(TAG, "Throttled while enrolling a password");
+ }
+ return hash;
+ }
+
+ private void setAuthlessUserKeyProtection(int userId, byte[] key) throws RemoteException {
+ if (DEBUG) Slog.d(TAG, "setAuthlessUserKeyProtectiond: user=" + userId);
+ addUserKeyAuth(userId, null, key);
+ }
+
+ private void setUserKeyProtection(int userId, String credential, VerifyCredentialResponse vcr)
+ throws RemoteException {
+ if (DEBUG) Slog.d(TAG, "setUserKeyProtection: user=" + userId);
+ if (vcr == null) {
+ throw new RemoteException("Null response verifying a credential we just set");
+ }
+ if (vcr.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
+ throw new RemoteException("Non-OK response verifying a credential we just set: "
+ + vcr.getResponseCode());
+ }
+ byte[] token = vcr.getPayload();
+ if (token == null) {
+ throw new RemoteException("Empty payload verifying a credential we just set");
+ }
+ addUserKeyAuth(userId, token, secretFromCredential(credential));
+ }
+
+ private void clearUserKeyProtection(int userId) throws RemoteException {
+ if (DEBUG) Slog.d(TAG, "clearUserKeyProtection user=" + userId);
+ addUserKeyAuth(userId, null, null);
+ }
+
+ private static byte[] secretFromCredential(String credential) throws RemoteException {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-512");
+ // Personalize the hash
+ byte[] personalization = "Android FBE credential hash"
+ .getBytes(StandardCharsets.UTF_8);
+ // Pad it to the block size of the hash function
+ personalization = Arrays.copyOf(personalization, 128);
+ digest.update(personalization);
+ digest.update(credential.getBytes(StandardCharsets.UTF_8));
+ return digest.digest();
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("NoSuchAlgorithmException for SHA-512");
+ }
+ }
+
+ private void addUserKeyAuth(int userId, byte[] token, byte[] secret)
+ throws RemoteException {
+ final UserInfo userInfo = mUserManager.getUserInfo(userId);
+ final IStorageManager storageManager = mInjector.getStorageManager();
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ storageManager.addUserKeyAuth(userId, userInfo.serialNumber, token, secret);
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ private void fixateNewestUserKeyAuth(int userId)
+ throws RemoteException {
+ if (DEBUG) Slog.d(TAG, "fixateNewestUserKeyAuth: user=" + userId);
+ final IStorageManager storageManager = mInjector.getStorageManager();
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ storageManager.fixateNewestUserKeyAuth(userId);
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ @Override
+ public void resetKeyStore(int userId) throws RemoteException {
+ checkWritePermission(userId);
+ if (DEBUG) Slog.v(TAG, "Reset keystore for user: " + userId);
+ int managedUserId = -1;
+ String managedUserDecryptedPassword = null;
+ final List<UserInfo> profiles = mUserManager.getProfiles(userId);
+ for (UserInfo pi : profiles) {
+ // Unlock managed profile with unified lock
+ if (pi.isManagedProfile()
+ && !mLockPatternUtils.isSeparateProfileChallengeEnabled(pi.id)
+ && mStorage.hasChildProfileLock(pi.id)) {
+ try {
+ if (managedUserId == -1) {
+ managedUserDecryptedPassword = getDecryptedPasswordForTiedProfile(pi.id);
+ managedUserId = pi.id;
+ } else {
+ // Should not happen
+ Slog.e(TAG, "More than one managed profile, uid1:" + managedUserId
+ + ", uid2:" + pi.id);
+ }
+ } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
+ | NoSuchAlgorithmException | NoSuchPaddingException
+ | InvalidAlgorithmParameterException | IllegalBlockSizeException
+ | BadPaddingException | CertificateException | IOException e) {
+ Slog.e(TAG, "Failed to decrypt child profile key", e);
+ }
+ }
+ }
+ try {
+ // Clear all the users credentials could have been installed in for this user.
+ for (int profileId : mUserManager.getProfileIdsWithDisabled(userId)) {
+ for (int uid : SYSTEM_CREDENTIAL_UIDS) {
+ mKeyStore.clearUid(UserHandle.getUid(profileId, uid));
+ }
+ }
+ } finally {
+ if (managedUserId != -1 && managedUserDecryptedPassword != null) {
+ if (DEBUG) Slog.v(TAG, "Restore tied profile lock");
+ tieProfileLockToParent(managedUserId, managedUserDecryptedPassword);
+ }
+ }
+ }
+
+ @Override
+ public VerifyCredentialResponse checkCredential(String credential, int type, int userId,
+ ICheckCredentialProgressCallback progressCallback) throws RemoteException {
+ checkPasswordReadPermission(userId);
+ return doVerifyCredential(credential, type, false, 0, userId, progressCallback);
+ }
+
+ @Override
+ public VerifyCredentialResponse verifyCredential(String credential, int type, long challenge,
+ int userId) throws RemoteException {
+ checkPasswordReadPermission(userId);
+ return doVerifyCredential(credential, type, true, challenge, userId,
+ null /* progressCallback */);
+ }
+
+ /**
+ * Verify user credential and unlock the user. Fix pattern bug by deprecating the old base zero
+ * format.
+ */
+ private VerifyCredentialResponse doVerifyCredential(String credential, int credentialType,
+ boolean hasChallenge, long challenge, int userId,
+ ICheckCredentialProgressCallback progressCallback) throws RemoteException {
+ if (TextUtils.isEmpty(credential)) {
+ throw new IllegalArgumentException("Credential can't be null or empty");
+ }
+ if (userId == USER_FRP && Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
+ Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
+ return VerifyCredentialResponse.ERROR;
+ }
+ synchronized (mSpManager) {
+ if (isSyntheticPasswordBasedCredentialLocked(userId)) {
+ VerifyCredentialResponse response = spBasedDoVerifyCredentialLocked(credential,
+ credentialType, hasChallenge, challenge, userId, progressCallback);
+ if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+ mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
+ }
+ return response;
+ }
+ }
+ final CredentialHash storedHash;
+ if (userId == USER_FRP) {
+ PersistentData data = mStorage.readPersistentDataBlock();
+ if (data.type != PersistentData.TYPE_GATEKEEPER) {
+ Slog.wtf(TAG, "Expected PersistentData.TYPE_GATEKEEPER, but was: " + data.type);
+ return VerifyCredentialResponse.ERROR;
+ }
+ return verifyFrpCredential(credential, credentialType, data, progressCallback);
+ } else {
+ storedHash = mStorage.readCredentialHash(userId);
+ }
+
+ if (storedHash.type != credentialType) {
+ Slog.wtf(TAG, "doVerifyCredential type mismatch with stored credential??"
+ + " stored: " + storedHash.type + " passed in: " + credentialType);
+ return VerifyCredentialResponse.ERROR;
+ }
+
+ boolean shouldReEnrollBaseZero = storedHash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN
+ && storedHash.isBaseZeroPattern;
+
+ String credentialToVerify;
+ if (shouldReEnrollBaseZero) {
+ credentialToVerify = LockPatternUtils.patternStringToBaseZero(credential);
+ } else {
+ credentialToVerify = credential;
+ }
+
+ VerifyCredentialResponse response = verifyCredential(userId, storedHash, credentialToVerify,
+ hasChallenge, challenge, progressCallback);
+
+ if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+ mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
+ if (shouldReEnrollBaseZero) {
+ setLockCredentialInternal(credential, storedHash.type, credentialToVerify,
+ DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
+ }
+ }
+
+ return response;
+ }
+
+ private VerifyCredentialResponse verifyFrpCredential(String credential, int credentialType,
+ PersistentData data, ICheckCredentialProgressCallback progressCallback)
+ throws RemoteException {
+ CredentialHash storedHash = CredentialHash.fromBytes(data.payload);
+ if (storedHash.type != credentialType) {
+ Slog.wtf(TAG, "doVerifyCredential type mismatch with stored credential??"
+ + " stored: " + storedHash.type + " passed in: " + credentialType);
+ return VerifyCredentialResponse.ERROR;
+ }
+ if (ArrayUtils.isEmpty(storedHash.hash) || TextUtils.isEmpty(credential)) {
+ Slog.e(TAG, "Stored hash or credential is empty");
+ return VerifyCredentialResponse.ERROR;
+ }
+ VerifyCredentialResponse response = VerifyCredentialResponse.fromGateKeeperResponse(
+ getGateKeeperService().verifyChallenge(data.userId, 0 /* challenge */,
+ storedHash.hash, credential.getBytes()));
+ if (progressCallback != null
+ && response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+ progressCallback.onCredentialVerified();
+ }
+ return response;
+ }
+
+ @Override
+ public VerifyCredentialResponse verifyTiedProfileChallenge(String credential, int type,
+ long challenge, int userId) throws RemoteException {
+ checkPasswordReadPermission(userId);
+ if (!isManagedProfileWithUnifiedLock(userId)) {
+ throw new RemoteException("User id must be managed profile with unified lock");
+ }
+ final int parentProfileId = mUserManager.getProfileParent(userId).id;
+ // Unlock parent by using parent's challenge
+ final VerifyCredentialResponse parentResponse = doVerifyCredential(
+ credential,
+ type,
+ true /* hasChallenge */,
+ challenge,
+ parentProfileId,
+ null /* progressCallback */);
+ if (parentResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
+ // Failed, just return parent's response
+ return parentResponse;
+ }
+
+ try {
+ // Unlock work profile, and work profile with unified lock must use password only
+ return doVerifyCredential(getDecryptedPasswordForTiedProfile(userId),
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ true,
+ challenge,
+ userId, null /* progressCallback */);
+ } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
+ | NoSuchAlgorithmException | NoSuchPaddingException
+ | InvalidAlgorithmParameterException | IllegalBlockSizeException
+ | BadPaddingException | CertificateException | IOException e) {
+ Slog.e(TAG, "Failed to decrypt child profile key", e);
+ throw new RemoteException("Unable to get tied profile token");
+ }
+ }
+
+ /**
+ * Lowest-level credential verification routine that talks to GateKeeper. If verification
+ * passes, unlock the corresponding user and keystore. Also handles the migration from legacy
+ * hash to GK.
+ */
+ private VerifyCredentialResponse verifyCredential(int userId, CredentialHash storedHash,
+ String credential, boolean hasChallenge, long challenge,
+ ICheckCredentialProgressCallback progressCallback) throws RemoteException {
+ if ((storedHash == null || storedHash.hash.length == 0) && TextUtils.isEmpty(credential)) {
+ // don't need to pass empty credentials to GateKeeper
+ return VerifyCredentialResponse.OK;
+ }
+
+ if (storedHash == null || TextUtils.isEmpty(credential)) {
+ return VerifyCredentialResponse.ERROR;
+ }
+
+ // We're potentially going to be doing a bunch of disk I/O below as part
+ // of unlocking the user, so yell if calling from the main thread.
+ StrictMode.noteDiskRead();
+
+ if (storedHash.version == CredentialHash.VERSION_LEGACY) {
+ final byte[] hash;
+ if (storedHash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
+ hash = LockPatternUtils.patternToHash(LockPatternUtils.stringToPattern(credential));
+ } else {
+ hash = mLockPatternUtils.passwordToHash(credential, userId);
+ }
+ if (Arrays.equals(hash, storedHash.hash)) {
+ if (storedHash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
+ unlockKeystore(LockPatternUtils.patternStringToBaseZero(credential), userId);
+ } else {
+ unlockKeystore(credential, userId);
+ }
+ // Users with legacy credentials don't have credential-backed
+ // FBE keys, so just pass through a fake token/secret
+ Slog.i(TAG, "Unlocking user with fake token: " + userId);
+ final byte[] fakeToken = String.valueOf(userId).getBytes();
+ unlockUser(userId, fakeToken, fakeToken);
+
+ // migrate credential to GateKeeper
+ setLockCredentialInternal(credential, storedHash.type, null,
+ storedHash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN
+ ? DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ : DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
+ /* TODO(roosa): keep the same password quality */, userId);
+ if (!hasChallenge) {
+ notifyActivePasswordMetricsAvailable(credential, userId);
+ return VerifyCredentialResponse.OK;
+ }
+ // Fall through to get the auth token. Technically this should never happen,
+ // as a user that had a legacy credential would have to unlock their device
+ // before getting to a flow with a challenge, but supporting for consistency.
+ } else {
+ return VerifyCredentialResponse.ERROR;
+ }
+ }
+ GateKeeperResponse gateKeeperResponse = getGateKeeperService()
+ .verifyChallenge(userId, challenge, storedHash.hash, credential.getBytes());
+ VerifyCredentialResponse response = convertResponse(gateKeeperResponse);
+ boolean shouldReEnroll = gateKeeperResponse.getShouldReEnroll();
+
+ if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+
+ // credential has matched
+
+ if (progressCallback != null) {
+ progressCallback.onCredentialVerified();
+ }
+ notifyActivePasswordMetricsAvailable(credential, userId);
+ unlockKeystore(credential, userId);
+
+ Slog.i(TAG, "Unlocking user " + userId + " with token length "
+ + response.getPayload().length);
+ unlockUser(userId, response.getPayload(), secretFromCredential(credential));
+
+ if (isManagedProfileWithSeparatedLock(userId)) {
+ TrustManager trustManager =
+ (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
+ trustManager.setDeviceLockedForUser(userId, false);
+ }
+ int reEnrollQuality = storedHash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN
+ ? DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ : DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
+ /* TODO(roosa): keep the same password quality */;
+ if (shouldReEnroll) {
+ setLockCredentialInternal(credential, storedHash.type, credential,
+ reEnrollQuality, userId);
+ } else {
+ // Now that we've cleared of all required GK migration, let's do the final
+ // migration to synthetic password.
+ synchronized (mSpManager) {
+ if (shouldMigrateToSyntheticPasswordLocked(userId)) {
+ AuthenticationToken auth = initializeSyntheticPasswordLocked(
+ storedHash.hash, credential, storedHash.type, reEnrollQuality,
+ userId);
+ activateEscrowTokens(auth, userId);
+ }
+ }
+ }
+ } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
+ if (response.getTimeout() > 0) {
+ requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
+ }
+ }
+
+ return response;
+ }
+
+ private void notifyActivePasswordMetricsAvailable(String password, @UserIdInt int userId) {
+ final PasswordMetrics metrics;
+ if (password == null) {
+ metrics = new PasswordMetrics();
+ } else {
+ metrics = PasswordMetrics.computeForPassword(password);
+ metrics.quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(userId);
+ }
+
+ // Asynchronous to avoid dead lock
+ mHandler.post(() -> {
+ DevicePolicyManager dpm = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ dpm.setActivePasswordState(metrics, userId);
+ });
+ }
+
+ /**
+ * Call after {@link #notifyActivePasswordMetricsAvailable} so metrics are updated before
+ * reporting the password changed.
+ */
+ private void notifyPasswordChanged(@UserIdInt int userId) {
+ // Same handler as notifyActivePasswordMetricsAvailable to ensure correct ordering
+ mHandler.post(() -> {
+ DevicePolicyManager dpm = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ dpm.reportPasswordChanged(userId);
+ });
+ }
+
+ @Override
+ public boolean checkVoldPassword(int userId) throws RemoteException {
+ if (!mFirstCallToVold) {
+ return false;
+ }
+ mFirstCallToVold = false;
+
+ checkPasswordReadPermission(userId);
+
+ // There's no guarantee that this will safely connect, but if it fails
+ // we will simply show the lock screen when we shouldn't, so relatively
+ // benign. There is an outside chance something nasty would happen if
+ // this service restarted before vold stales out the password in this
+ // case. The nastiness is limited to not showing the lock screen when
+ // we should, within the first minute of decrypting the phone if this
+ // service can't connect to vold, it restarts, and then the new instance
+ // does successfully connect.
+ final IStorageManager service = mInjector.getStorageManager();
+ String password;
+ long identity = Binder.clearCallingIdentity();
+ try {
+ password = service.getPassword();
+ service.clearPassword();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ if (password == null) {
+ return false;
+ }
+
+ try {
+ if (mLockPatternUtils.isLockPatternEnabled(userId)) {
+ if (checkCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, userId,
+ null /* progressCallback */)
+ .getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
+ return true;
+ }
+ }
+ } catch (Exception e) {
+ }
+
+ try {
+ if (mLockPatternUtils.isLockPasswordEnabled(userId)) {
+ if (checkCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, userId,
+ null /* progressCallback */)
+ .getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
+ return true;
+ }
+ }
+ } catch (Exception e) {
+ }
+
+ return false;
+ }
+
+ private void removeUser(int userId, boolean unknownUser) {
+ mSpManager.removeUser(userId);
+ mStorage.removeUser(userId);
+ mStrongAuth.removeUser(userId);
+
+ final KeyStore ks = KeyStore.getInstance();
+ ks.onUserRemoved(userId);
+
+ try {
+ final IGateKeeperService gk = getGateKeeperService();
+ if (gk != null) {
+ gk.clearSecureUserId(userId);
+ }
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "unable to clear GK secure user id");
+ }
+ if (unknownUser || mUserManager.getUserInfo(userId).isManagedProfile()) {
+ removeKeystoreProfileKey(userId);
+ }
+ }
+
+ private void removeKeystoreProfileKey(int targetUserId) {
+ if (DEBUG) Slog.v(TAG, "Remove keystore profile key for user: " + targetUserId);
+ try {
+ java.security.KeyStore keyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ keyStore.deleteEntry(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + targetUserId);
+ keyStore.deleteEntry(LockPatternUtils.PROFILE_KEY_NAME_DECRYPT + targetUserId);
+ } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
+ | IOException e) {
+ // We have tried our best to remove all keys
+ Slog.e(TAG, "Unable to remove keystore profile key for user:" + targetUserId, e);
+ }
+ }
+
+ @Override
+ public void registerStrongAuthTracker(IStrongAuthTracker tracker) {
+ checkPasswordReadPermission(UserHandle.USER_ALL);
+ mStrongAuth.registerStrongAuthTracker(tracker);
+ }
+
+ @Override
+ public void unregisterStrongAuthTracker(IStrongAuthTracker tracker) {
+ checkPasswordReadPermission(UserHandle.USER_ALL);
+ mStrongAuth.unregisterStrongAuthTracker(tracker);
+ }
+
+ @Override
+ public void requireStrongAuth(int strongAuthReason, int userId) {
+ checkWritePermission(userId);
+ mStrongAuth.requireStrongAuth(strongAuthReason, userId);
+ }
+
+ @Override
+ public void userPresent(int userId) {
+ checkWritePermission(userId);
+ mStrongAuth.reportUnlock(userId);
+ }
+
+ @Override
+ public int getStrongAuthForUser(int userId) {
+ checkPasswordReadPermission(userId);
+ return mStrongAuthTracker.getStrongAuthForUser(userId);
+ }
+
+ private boolean isCallerShell() {
+ final int callingUid = Binder.getCallingUid();
+ return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
+ }
+
+ private void enforceShell() {
+ if (!isCallerShell()) {
+ throw new SecurityException("Caller must be shell");
+ }
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver)
+ throws RemoteException {
+ enforceShell();
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ (new LockSettingsShellCommand(mContext, new LockPatternUtils(mContext))).exec(
+ this, in, out, err, args, callback, resultReceiver);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ private static final String[] VALID_SETTINGS = new String[] {
+ LockPatternUtils.LOCKOUT_PERMANENT_KEY,
+ LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
+ LockPatternUtils.PATTERN_EVER_CHOSEN_KEY,
+ LockPatternUtils.PASSWORD_TYPE_KEY,
+ LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
+ LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
+ LockPatternUtils.DISABLE_LOCKSCREEN_KEY,
+ LockPatternUtils.LOCKSCREEN_OPTIONS,
+ LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
+ LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY,
+ LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS,
+ LockPatternUtils.PASSWORD_HISTORY_KEY,
+ Secure.LOCK_PATTERN_ENABLED,
+ Secure.LOCK_BIOMETRIC_WEAK_FLAGS,
+ Secure.LOCK_PATTERN_VISIBLE,
+ Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED
+ };
+
+ // Reading these settings needs the contacts permission
+ private static final String[] READ_CONTACTS_PROTECTED_SETTINGS = new String[] {
+ Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
+ Secure.LOCK_SCREEN_OWNER_INFO
+ };
+
+ // Reading these settings needs the same permission as checking the password
+ private static final String[] READ_PASSWORD_PROTECTED_SETTINGS = new String[] {
+ LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
+ LockPatternUtils.PASSWORD_HISTORY_KEY,
+ LockPatternUtils.PASSWORD_TYPE_KEY,
+ SEPARATE_PROFILE_CHALLENGE_KEY
+ };
+
+ private static final String[] SETTINGS_TO_BACKUP = new String[] {
+ Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
+ Secure.LOCK_SCREEN_OWNER_INFO,
+ Secure.LOCK_PATTERN_VISIBLE,
+ LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS
+ };
+
+ private class GateKeeperDiedRecipient implements IBinder.DeathRecipient {
+ @Override
+ public void binderDied() {
+ mGateKeeperService.asBinder().unlinkToDeath(this, 0);
+ mGateKeeperService = null;
+ }
+ }
+
+ protected synchronized IGateKeeperService getGateKeeperService()
+ throws RemoteException {
+ if (mGateKeeperService != null) {
+ return mGateKeeperService;
+ }
+
+ final IBinder service = ServiceManager.getService(Context.GATEKEEPER_SERVICE);
+ if (service != null) {
+ service.linkToDeath(new GateKeeperDiedRecipient(), 0);
+ mGateKeeperService = IGateKeeperService.Stub.asInterface(service);
+ return mGateKeeperService;
+ }
+
+ Slog.e(TAG, "Unable to acquire GateKeeperService");
+ return null;
+ }
+
+ /**
+ * Precondition: vold and keystore unlocked.
+ *
+ * Create new synthetic password, set up synthetic password blob protected by the supplied
+ * user credential, and make the newly-created SP blob active.
+ *
+ * The invariant under a synthetic password is:
+ * 1. If user credential exists, then both vold and keystore and protected with keys derived
+ * from the synthetic password.
+ * 2. If user credential does not exist, vold and keystore protection are cleared. This is to
+ * make it consistent with current behaviour. It also allows ActivityManager to call
+ * unlockUser() with empty secret.
+ * 3. Once a user is migrated to have synthetic password, its value will never change, no matter
+ * whether the user changes his lockscreen PIN or clear/reset it. When the user clears its
+ * lockscreen PIN, we still maintain the existing synthetic password in a password blob
+ * protected by a default PIN. The only exception is when the DPC performs an untrusted
+ * credential change, in which case we have no way to derive the existing synthetic password
+ * and has to create a new one.
+ * 4. The user SID is linked with synthetic password, but its cleared/re-created when the user
+ * clears/re-creates his lockscreen PIN.
+ *
+ *
+ * Different cases of calling this method:
+ * 1. credentialHash != null
+ * This implies credential != null, a new SP blob will be provisioned, and existing SID
+ * migrated to associate with the new SP.
+ * This happens during a normal migration case when the user currently has password.
+ *
+ * 2. credentialhash == null and credential == null
+ * A new SP blob and a new SID will be created, while the user has no credentials.
+ * This can happens when we are activating an escrow token on a unsecured device, during
+ * which we want to create the SP structure with an empty user credential.
+ *
+ * 3. credentialhash == null and credential != null
+ * This is the untrusted credential reset, OR the user sets a new lockscreen password
+ * FOR THE FIRST TIME on a SP-enabled device. New credential and new SID will be created
+ */
+ private AuthenticationToken initializeSyntheticPasswordLocked(byte[] credentialHash,
+ String credential, int credentialType, int requestedQuality,
+ int userId) throws RemoteException {
+ Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
+ AuthenticationToken auth = mSpManager.newSyntheticPasswordAndSid(getGateKeeperService(),
+ credentialHash, credential, userId);
+ if (auth == null) {
+ Slog.wtf(TAG, "initializeSyntheticPasswordLocked returns null auth token");
+ return null;
+ }
+ long handle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
+ credential, credentialType, auth, requestedQuality, userId);
+ if (credential != null) {
+ if (credentialHash == null) {
+ // Since when initializing SP, we didn't provide an existing password handle
+ // for it to migrate SID, we need to create a new SID for the user.
+ mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
+ }
+ mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId);
+ setAuthlessUserKeyProtection(userId, auth.deriveDiskEncryptionKey());
+ setKeystorePassword(auth.deriveKeyStorePassword(), userId);
+ } else {
+ clearUserKeyProtection(userId);
+ setKeystorePassword(null, userId);
+ getGateKeeperService().clearSecureUserId(userId);
+ }
+ fixateNewestUserKeyAuth(userId);
+ setLong(SYNTHETIC_PASSWORD_HANDLE_KEY, handle, userId);
+ return auth;
+ }
+
+ private long getSyntheticPasswordHandleLocked(int userId) {
+ try {
+ return getLong(SYNTHETIC_PASSWORD_HANDLE_KEY, 0, userId);
+ } catch (RemoteException e) {
+ return SyntheticPasswordManager.DEFAULT_HANDLE;
+ }
+ }
+
+ private boolean isSyntheticPasswordBasedCredentialLocked(int userId) throws RemoteException {
+ if (userId == USER_FRP) {
+ final int type = mStorage.readPersistentDataBlock().type;
+ return type == PersistentData.TYPE_SP || type == PersistentData.TYPE_SP_WEAVER;
+ }
+ long handle = getSyntheticPasswordHandleLocked(userId);
+ // This is a global setting
+ long enabled = getLong(SYNTHETIC_PASSWORD_ENABLED_KEY,
+ SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT, UserHandle.USER_SYSTEM);
+ return enabled != 0 && handle != SyntheticPasswordManager.DEFAULT_HANDLE;
+ }
+
+ private boolean shouldMigrateToSyntheticPasswordLocked(int userId) throws RemoteException {
+ long handle = getSyntheticPasswordHandleLocked(userId);
+ // This is a global setting
+ long enabled = getLong(SYNTHETIC_PASSWORD_ENABLED_KEY,
+ SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT, UserHandle.USER_SYSTEM);
+ return enabled != 0 && handle == SyntheticPasswordManager.DEFAULT_HANDLE;
+ }
+
+ private void enableSyntheticPasswordLocked() throws RemoteException {
+ setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1, UserHandle.USER_SYSTEM);
+ }
+
+ private VerifyCredentialResponse spBasedDoVerifyCredentialLocked(String userCredential, int
+ credentialType, boolean hasChallenge, long challenge, int userId,
+ ICheckCredentialProgressCallback progressCallback) throws RemoteException {
+ if (DEBUG) Slog.d(TAG, "spBasedDoVerifyCredentialLocked: user=" + userId);
+ if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ userCredential = null;
+ }
+ if (userId == USER_FRP) {
+ return mSpManager.verifyFrpCredential(getGateKeeperService(),
+ userCredential, credentialType, progressCallback);
+ }
+
+ long handle = getSyntheticPasswordHandleLocked(userId);
+ AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
+ getGateKeeperService(), handle, userCredential, userId);
+
+ VerifyCredentialResponse response = authResult.gkResponse;
+ if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+ // credential has matched
+ // perform verifyChallenge with synthetic password which generates the real auth
+ // token for the current user
+ response = mSpManager.verifyChallenge(getGateKeeperService(), authResult.authToken,
+ challenge, userId);
+ if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
+ Slog.wtf(TAG, "verifyChallenge with SP failed.");
+ return VerifyCredentialResponse.ERROR;
+ }
+ if (progressCallback != null) {
+ progressCallback.onCredentialVerified();
+ }
+ notifyActivePasswordMetricsAvailable(userCredential, userId);
+ unlockKeystore(authResult.authToken.deriveKeyStorePassword(), userId);
+
+ final byte[] secret = authResult.authToken.deriveDiskEncryptionKey();
+ Slog.i(TAG, "Unlocking user " + userId + " with secret only, length " + secret.length);
+ unlockUser(userId, null, secret);
+
+ if (isManagedProfileWithSeparatedLock(userId)) {
+ TrustManager trustManager =
+ (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
+ trustManager.setDeviceLockedForUser(userId, false);
+ }
+ activateEscrowTokens(authResult.authToken, userId);
+ } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
+ if (response.getTimeout() > 0) {
+ requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
+ }
+ }
+
+ return response;
+ }
+
+ /**
+ * Change the user's lockscreen password by creating a new SP blob and update the handle, based
+ * on an existing authentication token. Even though a new SP blob is created, the underlying
+ * synthetic password is never changed.
+ *
+ * When clearing credential, we keep the SP unchanged, but clear its password handle so its
+ * SID is gone. We also clear password from (software-based) keystore and vold, which will be
+ * added back when new password is set in future.
+ */
+ private long setLockCredentialWithAuthTokenLocked(String credential, int credentialType,
+ AuthenticationToken auth, int requestedQuality, int userId) throws RemoteException {
+ if (DEBUG) Slog.d(TAG, "setLockCredentialWithAuthTokenLocked: user=" + userId);
+ long newHandle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
+ credential, credentialType, auth, requestedQuality, userId);
+ final Map<Integer, String> profilePasswords;
+ if (credential != null) {
+ // // not needed by synchronizeUnifiedWorkChallengeForProfiles()
+ profilePasswords = null;
+
+ if (mSpManager.hasSidForUser(userId)) {
+ // We are changing password of a secured device, nothing more needed as
+ // createPasswordBasedSyntheticPassword has already taken care of maintaining
+ // the password handle and SID unchanged.
+
+ //refresh auth token
+ mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId);
+ } else {
+ // A new password is set on a previously-unsecured device, we need to generate
+ // a new SID, and re-add keys to vold and keystore.
+ mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
+ mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId);
+ setAuthlessUserKeyProtection(userId, auth.deriveDiskEncryptionKey());
+ fixateNewestUserKeyAuth(userId);
+ setKeystorePassword(auth.deriveKeyStorePassword(), userId);
+ }
+ } else {
+ // Cache all profile password if they use unified work challenge. This will later be
+ // used to clear the profile's password in synchronizeUnifiedWorkChallengeForProfiles()
+ profilePasswords = getDecryptedPasswordsForAllTiedProfiles(userId);
+
+ // we are clearing password of a secured device, so need to nuke SID as well.
+ mSpManager.clearSidForUser(userId);
+ getGateKeeperService().clearSecureUserId(userId);
+ // Clear key from vold so ActivityManager can just unlock the user with empty secret
+ // during boot.
+ clearUserKeyProtection(userId);
+ fixateNewestUserKeyAuth(userId);
+ setKeystorePassword(null, userId);
+ }
+ setLong(SYNTHETIC_PASSWORD_HANDLE_KEY, newHandle, userId);
+ synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
+ return newHandle;
+ }
+
+ private void spBasedSetLockCredentialInternalLocked(String credential, int credentialType,
+ String savedCredential, int requestedQuality, int userId) throws RemoteException {
+ if (DEBUG) Slog.d(TAG, "spBasedSetLockCredentialInternalLocked: user=" + userId);
+ if (isManagedProfileWithUnifiedLock(userId)) {
+ // get credential from keystore when managed profile has unified lock
+ try {
+ savedCredential = getDecryptedPasswordForTiedProfile(userId);
+ } catch (FileNotFoundException e) {
+ Slog.i(TAG, "Child profile key not found");
+ } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
+ | NoSuchAlgorithmException | NoSuchPaddingException
+ | InvalidAlgorithmParameterException | IllegalBlockSizeException
+ | BadPaddingException | CertificateException | IOException e) {
+ Slog.e(TAG, "Failed to decrypt child profile key", e);
+ }
+ }
+ long handle = getSyntheticPasswordHandleLocked(userId);
+ AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
+ getGateKeeperService(), handle, savedCredential, userId);
+ VerifyCredentialResponse response = authResult.gkResponse;
+ AuthenticationToken auth = authResult.authToken;
+ if (auth != null) {
+ // We are performing a trusted credential change i.e. a correct existing credential
+ // is provided
+ setLockCredentialWithAuthTokenLocked(credential, credentialType, auth, requestedQuality,
+ userId);
+ mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
+ } else if (response != null
+ && response.getResponseCode() == VerifyCredentialResponse.RESPONSE_ERROR){
+ // We are performing an untrusted credential change i.e. by DevicePolicyManager.
+ // So provision a new SP and SID. This would invalidate existing escrow tokens.
+ // Still support this for now but this flow will be removed in the next release.
+
+ Slog.w(TAG, "Untrusted credential change invoked");
+ initializeSyntheticPasswordLocked(null, credential, credentialType, requestedQuality,
+ userId);
+ synchronizeUnifiedWorkChallengeForProfiles(userId, null);
+ mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
+ } else /* response == null || responseCode == VerifyCredentialResponse.RESPONSE_RETRY */ {
+ Slog.w(TAG, "spBasedSetLockCredentialInternalLocked: " +
+ (response != null ? "rate limit exceeded" : "failed"));
+ return;
+ }
+ notifyActivePasswordMetricsAvailable(credential, userId);
+
+ }
+
+ @Override
+ public long addEscrowToken(byte[] token, int userId) throws RemoteException {
+ ensureCallerSystemUid();
+ if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId);
+ synchronized (mSpManager) {
+ enableSyntheticPasswordLocked();
+ // Migrate to synthetic password based credentials if the user has no password,
+ // the token can then be activated immediately.
+ AuthenticationToken auth = null;
+ if (!isUserSecure(userId)) {
+ if (shouldMigrateToSyntheticPasswordLocked(userId)) {
+ auth = initializeSyntheticPasswordLocked(null, null,
+ LockPatternUtils.CREDENTIAL_TYPE_NONE,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId);
+ } else /* isSyntheticPasswordBasedCredentialLocked(userId) */ {
+ long pwdHandle = getSyntheticPasswordHandleLocked(userId);
+ auth = mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(),
+ pwdHandle, null, userId).authToken;
+ }
+ }
+ if (isSyntheticPasswordBasedCredentialLocked(userId)) {
+ disableEscrowTokenOnNonManagedDevicesIfNeeded(userId);
+ if (!mSpManager.hasEscrowData(userId)) {
+ throw new SecurityException("Escrow token is disabled on the current user");
+ }
+ }
+ long handle = mSpManager.createTokenBasedSyntheticPassword(token, userId);
+ if (auth != null) {
+ mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId);
+ }
+ return handle;
+ }
+ }
+
+ private void activateEscrowTokens(AuthenticationToken auth, int userId) throws RemoteException {
+ if (DEBUG) Slog.d(TAG, "activateEscrowTokens: user=" + userId);
+ disableEscrowTokenOnNonManagedDevicesIfNeeded(userId);
+ synchronized (mSpManager) {
+ for (long handle : mSpManager.getPendingTokensForUser(userId)) {
+ Slog.i(TAG, String.format("activateEscrowTokens: %x %d ", handle, userId));
+ mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId);
+ }
+ }
+ }
+
+ @Override
+ public boolean isEscrowTokenActive(long handle, int userId) throws RemoteException {
+ ensureCallerSystemUid();
+ synchronized (mSpManager) {
+ return mSpManager.existsHandle(handle, userId);
+ }
+ }
+
+ @Override
+ public boolean removeEscrowToken(long handle, int userId) throws RemoteException {
+ ensureCallerSystemUid();
+ synchronized (mSpManager) {
+ if (handle == getSyntheticPasswordHandleLocked(userId)) {
+ Slog.w(TAG, "Cannot remove password handle");
+ return false;
+ }
+ if (mSpManager.removePendingToken(handle, userId)) {
+ return true;
+ }
+ if (mSpManager.existsHandle(handle, userId)) {
+ mSpManager.destroyTokenBasedSyntheticPassword(handle, userId);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ @Override
+ public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle,
+ byte[] token, int requestedQuality, int userId) throws RemoteException {
+ ensureCallerSystemUid();
+ boolean result;
+ synchronized (mSpManager) {
+ if (!mSpManager.hasEscrowData(userId)) {
+ throw new SecurityException("Escrow token is disabled on the current user");
+ }
+ result = setLockCredentialWithTokenInternal(credential, type, tokenHandle, token,
+ requestedQuality, userId);
+ }
+ if (result) {
+ synchronized (mSeparateChallengeLock) {
+ setSeparateProfileChallengeEnabled(userId, true, null);
+ }
+ notifyPasswordChanged(userId);
+ }
+ return result;
+ }
+
+ private boolean setLockCredentialWithTokenInternal(String credential, int type,
+ long tokenHandle, byte[] token, int requestedQuality, int userId) throws RemoteException {
+ synchronized (mSpManager) {
+ AuthenticationResult result = mSpManager.unwrapTokenBasedSyntheticPassword(
+ getGateKeeperService(), tokenHandle, token, userId);
+ if (result.authToken == null) {
+ Slog.w(TAG, "Invalid escrow token supplied");
+ return false;
+ }
+ long oldHandle = getSyntheticPasswordHandleLocked(userId);
+ setLockCredentialWithAuthTokenLocked(credential, type, result.authToken,
+ requestedQuality, userId);
+ mSpManager.destroyPasswordBasedSyntheticPassword(oldHandle, userId);
+ return true;
+ }
+ }
+
+ @Override
+ public void unlockUserWithToken(long tokenHandle, byte[] token, int userId)
+ throws RemoteException {
+ ensureCallerSystemUid();
+ AuthenticationResult authResult;
+ synchronized (mSpManager) {
+ if (!mSpManager.hasEscrowData(userId)) {
+ throw new SecurityException("Escrow token is disabled on the current user");
+ }
+ authResult = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(),
+ tokenHandle, token, userId);
+ if (authResult.authToken == null) {
+ Slog.w(TAG, "Invalid escrow token supplied");
+ return;
+ }
+ }
+ unlockUser(userId, null, authResult.authToken.deriveDiskEncryptionKey());
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args){
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+ pw.println("Current lock settings service state:");
+ pw.println(String.format("SP Enabled = %b",
+ mLockPatternUtils.isSyntheticPasswordEnabled()));
+
+ List<UserInfo> users = mUserManager.getUsers();
+ for (int user = 0; user < users.size(); user++) {
+ final int userId = users.get(user).id;
+ pw.println(" User " + userId);
+ synchronized (mSpManager) {
+ pw.println(String.format(" SP Handle = %x",
+ getSyntheticPasswordHandleLocked(userId)));
+ }
+ try {
+ pw.println(String.format(" SID = %x",
+ getGateKeeperService().getSecureUserId(userId)));
+ } catch (RemoteException e) {
+ // ignore.
+ }
+ }
+ }
+
+ private void disableEscrowTokenOnNonManagedDevicesIfNeeded(int userId) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ // Managed profile should have escrow enabled
+ if (mUserManager.getUserInfo(userId).isManagedProfile()) {
+ Slog.i(TAG, "Managed profile can have escrow token");
+ return;
+ }
+ DevicePolicyManager dpm = mInjector.getDevicePolicyManager();
+ // Devices with Device Owner should have escrow enabled on all users.
+ if (dpm.getDeviceOwnerComponentOnAnyUser() != null) {
+ Slog.i(TAG, "Corp-owned device can have escrow token");
+ return;
+ }
+ // We could also have a profile owner on the given (non-managed) user for unicorn cases
+ if (dpm.getProfileOwnerAsUser(userId) != null) {
+ Slog.i(TAG, "User with profile owner can have escrow token");
+ return;
+ }
+ // If the device is yet to be provisioned (still in SUW), there is still
+ // a chance that Device Owner will be set on the device later, so postpone
+ // disabling escrow token for now.
+ if (!dpm.isDeviceProvisioned()) {
+ Slog.i(TAG, "Postpone disabling escrow tokens until device is provisioned");
+ return;
+ }
+
+ // Escrow tokens are enabled on automotive builds.
+ if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ return;
+ }
+
+ // Disable escrow token permanently on all other device/user types.
+ Slog.i(TAG, "Disabling escrow token on user " + userId);
+ if (isSyntheticPasswordBasedCredentialLocked(userId)) {
+ mSpManager.destroyEscrowData(userId);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "disableEscrowTokenOnNonManagedDevices", e);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private void ensureCallerSystemUid() throws SecurityException {
+ final int callingUid = mInjector.binderGetCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException("Only system can call this API.");
+ }
+ }
+
+ private class DeviceProvisionedObserver extends ContentObserver {
+ private final Uri mDeviceProvisionedUri = Settings.Global.getUriFor(
+ Settings.Global.DEVICE_PROVISIONED);
+
+ private boolean mRegistered;
+
+ public DeviceProvisionedObserver() {
+ super(null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (mDeviceProvisionedUri.equals(uri)) {
+ updateRegistration();
+
+ if (isProvisioned()) {
+ Slog.i(TAG, "Reporting device setup complete to IGateKeeperService");
+ reportDeviceSetupComplete();
+ }
+ }
+ }
+
+ public void onSystemReady() {
+ if (frpCredentialEnabled()) {
+ updateRegistration();
+ } else {
+ // If we don't intend to use frpCredentials and we're not provisioned yet, send
+ // deviceSetupComplete immediately, so gatekeeper can discard any lingering
+ // credentials immediately.
+ if (!isProvisioned()) {
+ Slog.i(TAG, "FRP credential disabled, reporting device setup complete "
+ + "to Gatekeeper immediately");
+ reportDeviceSetupComplete();
+ }
+ }
+ }
+
+ private void reportDeviceSetupComplete() {
+ try {
+ getGateKeeperService().reportDeviceSetupComplete();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failure reporting to IGateKeeperService", e);
+ }
+ }
+
+ private void updateRegistration() {
+ boolean register = !isProvisioned();
+ if (register == mRegistered) {
+ return;
+ }
+ if (register) {
+ mContext.getContentResolver().registerContentObserver(mDeviceProvisionedUri,
+ false, this);
+ } else {
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+ mRegistered = register;
+ }
+
+ private boolean isProvisioned() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
new file mode 100644
index 0000000..d730c56
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.locksettings;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static com.android.internal.widget.LockPatternUtils.stringToPattern;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
+
+class LockSettingsShellCommand extends ShellCommand {
+
+ private static final String COMMAND_SET_PATTERN = "set-pattern";
+ private static final String COMMAND_SET_PIN = "set-pin";
+ private static final String COMMAND_SET_PASSWORD = "set-password";
+ private static final String COMMAND_CLEAR = "clear";
+ private static final String COMMAND_SP = "sp";
+ private static final String COMMAND_SET_DISABLED = "set-disabled";
+
+ private int mCurrentUserId;
+ private final LockPatternUtils mLockPatternUtils;
+ private final Context mContext;
+ private String mOld = "";
+ private String mNew = "";
+
+ LockSettingsShellCommand(Context context, LockPatternUtils lockPatternUtils) {
+ mContext = context;
+ mLockPatternUtils = lockPatternUtils;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ try {
+ mCurrentUserId = ActivityManager.getService().getCurrentUser().id;
+
+ parseArgs();
+ if (!checkCredential()) {
+ return -1;
+ }
+ switch (cmd) {
+ case COMMAND_SET_PATTERN:
+ runSetPattern();
+ break;
+ case COMMAND_SET_PASSWORD:
+ runSetPassword();
+ break;
+ case COMMAND_SET_PIN:
+ runSetPin();
+ break;
+ case COMMAND_CLEAR:
+ runClear();
+ break;
+ case COMMAND_SP:
+ runChangeSp();
+ break;
+ case COMMAND_SET_DISABLED:
+ runSetDisabled();
+ break;
+ default:
+ getErrPrintWriter().println("Unknown command: " + cmd);
+ break;
+ }
+ return 0;
+ } catch (Exception e) {
+ getErrPrintWriter().println("Error while executing command: " + cmd);
+ e.printStackTrace(getErrPrintWriter());
+ return -1;
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ }
+
+ private void parseArgs() {
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if ("--old".equals(opt)) {
+ mOld = getNextArgRequired();
+ } else if ("--user".equals(opt)) {
+ mCurrentUserId = Integer.parseInt(getNextArgRequired());
+ } else {
+ getErrPrintWriter().println("Unknown option: " + opt);
+ throw new IllegalArgumentException();
+ }
+ }
+ mNew = getNextArg();
+ }
+
+ private void runChangeSp() {
+ if (mNew != null ) {
+ if ("1".equals(mNew)) {
+ mLockPatternUtils.enableSyntheticPassword();
+ getOutPrintWriter().println("Synthetic password enabled");
+ } else if ("0".equals(mNew)) {
+ mLockPatternUtils.disableSyntheticPassword();
+ getOutPrintWriter().println("Synthetic password disabled");
+ }
+ }
+ getOutPrintWriter().println(String.format("SP Enabled = %b",
+ mLockPatternUtils.isSyntheticPasswordEnabled()));
+ }
+
+ private void runSetPattern() throws RemoteException {
+ mLockPatternUtils.saveLockPattern(stringToPattern(mNew), mOld, mCurrentUserId);
+ getOutPrintWriter().println("Pattern set to '" + mNew + "'");
+ }
+
+ private void runSetPassword() throws RemoteException {
+ mLockPatternUtils.saveLockPassword(mNew, mOld, PASSWORD_QUALITY_ALPHABETIC, mCurrentUserId);
+ getOutPrintWriter().println("Password set to '" + mNew + "'");
+ }
+
+ private void runSetPin() throws RemoteException {
+ mLockPatternUtils.saveLockPassword(mNew, mOld, PASSWORD_QUALITY_NUMERIC, mCurrentUserId);
+ getOutPrintWriter().println("Pin set to '" + mNew + "'");
+ }
+
+ private void runClear() throws RemoteException {
+ mLockPatternUtils.clearLock(mOld, mCurrentUserId);
+ getOutPrintWriter().println("Lock credential cleared");
+ }
+
+ private void runSetDisabled() throws RemoteException {
+ final boolean disabled = Boolean.parseBoolean(mNew);
+ mLockPatternUtils.setLockScreenDisabled(disabled, mCurrentUserId);
+ getOutPrintWriter().println("Lock screen disabled set to " + disabled);
+ }
+
+ private boolean checkCredential() throws RemoteException, RequestThrottledException {
+ final boolean havePassword = mLockPatternUtils.isLockPasswordEnabled(mCurrentUserId);
+ final boolean havePattern = mLockPatternUtils.isLockPatternEnabled(mCurrentUserId);
+ if (havePassword || havePattern) {
+ boolean result;
+ if (havePassword) {
+ result = mLockPatternUtils.checkPassword(mOld, mCurrentUserId);
+ } else {
+ result = mLockPatternUtils.checkPattern(stringToPattern(mOld),
+ mCurrentUserId);
+ }
+ if (result) {
+ return true;
+ } else {
+ getOutPrintWriter().println("Old password '" + mOld + "' didn't match");
+ return false;
+ }
+ } else {
+ return true;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
new file mode 100644
index 0000000..79372e48
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -0,0 +1,903 @@
+/*
+ * Copyright (C) 2014 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.locksettings;
+
+import static android.content.Context.USER_SERVICE;
+
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Environment;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.server.LocalServices;
+import com.android.server.PersistentDataBlockManagerInternal;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Storage for the lock settings service.
+ */
+class LockSettingsStorage {
+
+ private static final String TAG = "LockSettingsStorage";
+ private static final String TABLE = "locksettings";
+ private static final boolean DEBUG = false;
+
+ private static final String COLUMN_KEY = "name";
+ private static final String COLUMN_USERID = "user";
+ private static final String COLUMN_VALUE = "value";
+
+ private static final String[] COLUMNS_FOR_QUERY = {
+ COLUMN_VALUE
+ };
+ private static final String[] COLUMNS_FOR_PREFETCH = {
+ COLUMN_KEY, COLUMN_VALUE
+ };
+
+ private static final String SYSTEM_DIRECTORY = "/system/";
+ private static final String LOCK_PATTERN_FILE = "gatekeeper.pattern.key";
+ private static final String BASE_ZERO_LOCK_PATTERN_FILE = "gatekeeper.gesture.key";
+ private static final String LEGACY_LOCK_PATTERN_FILE = "gesture.key";
+ private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
+ private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key";
+ private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
+
+ private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
+
+ private static final Object DEFAULT = new Object();
+
+ private final DatabaseHelper mOpenHelper;
+ private final Context mContext;
+ private final Cache mCache = new Cache();
+ private final Object mFileWriteLock = new Object();
+
+ private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal;
+
+ @VisibleForTesting
+ public static class CredentialHash {
+ static final int VERSION_LEGACY = 0;
+ static final int VERSION_GATEKEEPER = 1;
+
+ private CredentialHash(byte[] hash, int type, int version) {
+ this(hash, type, version, false /* isBaseZeroPattern */);
+ }
+
+ private CredentialHash(byte[] hash, int type, int version, boolean isBaseZeroPattern) {
+ if (type != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ if (hash == null) {
+ throw new RuntimeException("Empty hash for CredentialHash");
+ }
+ } else /* type == LockPatternUtils.CREDENTIAL_TYPE_NONE */ {
+ if (hash != null) {
+ throw new RuntimeException("None type CredentialHash should not have hash");
+ }
+ }
+ this.hash = hash;
+ this.type = type;
+ this.version = version;
+ this.isBaseZeroPattern = isBaseZeroPattern;
+ }
+
+ private static CredentialHash createBaseZeroPattern(byte[] hash) {
+ return new CredentialHash(hash, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+ VERSION_GATEKEEPER, true /* isBaseZeroPattern */);
+ }
+
+ static CredentialHash create(byte[] hash, int type) {
+ if (type == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ throw new RuntimeException("Bad type for CredentialHash");
+ }
+ return new CredentialHash(hash, type, VERSION_GATEKEEPER);
+ }
+
+ static CredentialHash createEmptyHash() {
+ return new CredentialHash(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
+ VERSION_GATEKEEPER);
+ }
+
+ byte[] hash;
+ int type;
+ int version;
+ boolean isBaseZeroPattern;
+
+ public byte[] toBytes() {
+ Preconditions.checkState(!isBaseZeroPattern, "base zero patterns are not serializable");
+
+ try {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(os);
+ dos.write(version);
+ dos.write(type);
+ if (hash != null && hash.length > 0) {
+ dos.writeInt(hash.length);
+ dos.write(hash);
+ } else {
+ dos.writeInt(0);
+ }
+ dos.close();
+ return os.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static CredentialHash fromBytes(byte[] bytes) {
+ try {
+ DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes));
+ int version = is.read();
+ int type = is.read();
+ int hashSize = is.readInt();
+ byte[] hash = null;
+ if (hashSize > 0) {
+ hash = new byte[hashSize];
+ is.readFully(hash);
+ }
+ return new CredentialHash(hash, type, version);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ public LockSettingsStorage(Context context) {
+ mContext = context;
+ mOpenHelper = new DatabaseHelper(context);
+ }
+
+ public void setDatabaseOnCreateCallback(Callback callback) {
+ mOpenHelper.setCallback(callback);
+ }
+
+ public void writeKeyValue(String key, String value, int userId) {
+ writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId);
+ }
+
+ public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) {
+ ContentValues cv = new ContentValues();
+ cv.put(COLUMN_KEY, key);
+ cv.put(COLUMN_USERID, userId);
+ cv.put(COLUMN_VALUE, value);
+
+ db.beginTransaction();
+ try {
+ db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
+ new String[] {key, Integer.toString(userId)});
+ db.insert(TABLE, null, cv);
+ db.setTransactionSuccessful();
+ mCache.putKeyValue(key, value, userId);
+ } finally {
+ db.endTransaction();
+ }
+
+ }
+
+ public String readKeyValue(String key, String defaultValue, int userId) {
+ int version;
+ synchronized (mCache) {
+ if (mCache.hasKeyValue(key, userId)) {
+ return mCache.peekKeyValue(key, defaultValue, userId);
+ }
+ version = mCache.getVersion();
+ }
+
+ Cursor cursor;
+ Object result = DEFAULT;
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
+ COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
+ new String[] { Integer.toString(userId), key },
+ null, null, null)) != null) {
+ if (cursor.moveToFirst()) {
+ result = cursor.getString(0);
+ }
+ cursor.close();
+ }
+ mCache.putKeyValueIfUnchanged(key, result, userId, version);
+ return result == DEFAULT ? defaultValue : (String) result;
+ }
+
+ public void prefetchUser(int userId) {
+ int version;
+ synchronized (mCache) {
+ if (mCache.isFetched(userId)) {
+ return;
+ }
+ mCache.setFetched(userId);
+ version = mCache.getVersion();
+ }
+
+ Cursor cursor;
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH,
+ COLUMN_USERID + "=?",
+ new String[] { Integer.toString(userId) },
+ null, null, null)) != null) {
+ while (cursor.moveToNext()) {
+ String key = cursor.getString(0);
+ String value = cursor.getString(1);
+ mCache.putKeyValueIfUnchanged(key, value, userId, version);
+ }
+ cursor.close();
+ }
+
+ // Populate cache by reading the password and pattern files.
+ readCredentialHash(userId);
+ }
+
+ private CredentialHash readPasswordHashIfExists(int userId) {
+ byte[] stored = readFile(getLockPasswordFilename(userId));
+ if (!ArrayUtils.isEmpty(stored)) {
+ return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ CredentialHash.VERSION_GATEKEEPER);
+ }
+
+ stored = readFile(getLegacyLockPasswordFilename(userId));
+ if (!ArrayUtils.isEmpty(stored)) {
+ return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ CredentialHash.VERSION_LEGACY);
+ }
+ return null;
+ }
+
+ private CredentialHash readPatternHashIfExists(int userId) {
+ byte[] stored = readFile(getLockPatternFilename(userId));
+ if (!ArrayUtils.isEmpty(stored)) {
+ return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+ CredentialHash.VERSION_GATEKEEPER);
+ }
+
+ stored = readFile(getBaseZeroLockPatternFilename(userId));
+ if (!ArrayUtils.isEmpty(stored)) {
+ return CredentialHash.createBaseZeroPattern(stored);
+ }
+
+ stored = readFile(getLegacyLockPatternFilename(userId));
+ if (!ArrayUtils.isEmpty(stored)) {
+ return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+ CredentialHash.VERSION_LEGACY);
+ }
+ return null;
+ }
+
+ public CredentialHash readCredentialHash(int userId) {
+ CredentialHash passwordHash = readPasswordHashIfExists(userId);
+ CredentialHash patternHash = readPatternHashIfExists(userId);
+ if (passwordHash != null && patternHash != null) {
+ if (passwordHash.version == CredentialHash.VERSION_GATEKEEPER) {
+ return passwordHash;
+ } else {
+ return patternHash;
+ }
+ } else if (passwordHash != null) {
+ return passwordHash;
+ } else if (patternHash != null) {
+ return patternHash;
+ } else {
+ return CredentialHash.createEmptyHash();
+ }
+ }
+
+ public void removeChildProfileLock(int userId) {
+ if (DEBUG)
+ Slog.e(TAG, "Remove child profile lock for user: " + userId);
+ try {
+ deleteFile(getChildProfileLockFile(userId));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void writeChildProfileLock(int userId, byte[] lock) {
+ writeFile(getChildProfileLockFile(userId), lock);
+ }
+
+ public byte[] readChildProfileLock(int userId) {
+ return readFile(getChildProfileLockFile(userId));
+ }
+
+ public boolean hasChildProfileLock(int userId) {
+ return hasFile(getChildProfileLockFile(userId));
+ }
+
+ public boolean hasPassword(int userId) {
+ return hasFile(getLockPasswordFilename(userId)) ||
+ hasFile(getLegacyLockPasswordFilename(userId));
+ }
+
+ public boolean hasPattern(int userId) {
+ return hasFile(getLockPatternFilename(userId)) ||
+ hasFile(getBaseZeroLockPatternFilename(userId)) ||
+ hasFile(getLegacyLockPatternFilename(userId));
+ }
+
+ public boolean hasCredential(int userId) {
+ return hasPassword(userId) || hasPattern(userId);
+ }
+
+ private boolean hasFile(String name) {
+ byte[] contents = readFile(name);
+ return contents != null && contents.length > 0;
+ }
+
+ private byte[] readFile(String name) {
+ int version;
+ synchronized (mCache) {
+ if (mCache.hasFile(name)) {
+ return mCache.peekFile(name);
+ }
+ version = mCache.getVersion();
+ }
+
+ RandomAccessFile raf = null;
+ byte[] stored = null;
+ try {
+ raf = new RandomAccessFile(name, "r");
+ stored = new byte[(int) raf.length()];
+ raf.readFully(stored, 0, stored.length);
+ raf.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot read file " + e);
+ } finally {
+ if (raf != null) {
+ try {
+ raf.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Error closing file " + e);
+ }
+ }
+ }
+ mCache.putFileIfUnchanged(name, stored, version);
+ return stored;
+ }
+
+ private void writeFile(String name, byte[] hash) {
+ synchronized (mFileWriteLock) {
+ RandomAccessFile raf = null;
+ try {
+ // Write the hash to file, requiring each write to be synchronized to the
+ // underlying storage device immediately to avoid data loss in case of power loss.
+ // This also ensures future secdiscard operation on the file succeeds since the
+ // file would have been allocated on flash.
+ raf = new RandomAccessFile(name, "rws");
+ // Truncate the file if pattern is null, to clear the lock
+ if (hash == null || hash.length == 0) {
+ raf.setLength(0);
+ } else {
+ raf.write(hash, 0, hash.length);
+ }
+ raf.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Error writing to file " + e);
+ } finally {
+ if (raf != null) {
+ try {
+ raf.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Error closing file " + e);
+ }
+ }
+ }
+ mCache.putFile(name, hash);
+ }
+ }
+
+ private void deleteFile(String name) {
+ if (DEBUG) Slog.e(TAG, "Delete file " + name);
+ synchronized (mFileWriteLock) {
+ File file = new File(name);
+ if (file.exists()) {
+ file.delete();
+ mCache.putFile(name, null);
+ }
+ }
+ }
+
+ public void writeCredentialHash(CredentialHash hash, int userId) {
+ byte[] patternHash = null;
+ byte[] passwordHash = null;
+
+ if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
+ passwordHash = hash.hash;
+ } else if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
+ patternHash = hash.hash;
+ }
+ writeFile(getLockPasswordFilename(userId), passwordHash);
+ writeFile(getLockPatternFilename(userId), patternHash);
+ }
+
+ @VisibleForTesting
+ String getLockPatternFilename(int userId) {
+ return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE);
+ }
+
+ @VisibleForTesting
+ String getLockPasswordFilename(int userId) {
+ return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE);
+ }
+
+ @VisibleForTesting
+ String getLegacyLockPatternFilename(int userId) {
+ return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PATTERN_FILE);
+ }
+
+ @VisibleForTesting
+ String getLegacyLockPasswordFilename(int userId) {
+ return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PASSWORD_FILE);
+ }
+
+ private String getBaseZeroLockPatternFilename(int userId) {
+ return getLockCredentialFilePathForUser(userId, BASE_ZERO_LOCK_PATTERN_FILE);
+ }
+
+ @VisibleForTesting
+ String getChildProfileLockFile(int userId) {
+ return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE);
+ }
+
+ private String getLockCredentialFilePathForUser(int userId, String basename) {
+ String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() +
+ SYSTEM_DIRECTORY;
+ if (userId == 0) {
+ // Leave it in the same place for user 0
+ return dataSystemDirectory + basename;
+ } else {
+ return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath();
+ }
+ }
+
+ public void writeSyntheticPasswordState(int userId, long handle, String name, byte[] data) {
+ writeFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name), data);
+ }
+
+ public byte[] readSyntheticPasswordState(int userId, long handle, String name) {
+ return readFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name));
+ }
+
+ public void deleteSyntheticPasswordState(int userId, long handle, String name) {
+ String path = getSynthenticPasswordStateFilePathForUser(userId, handle, name);
+ File file = new File(path);
+ if (file.exists()) {
+ try {
+ mContext.getSystemService(StorageManager.class).secdiscard(file.getAbsolutePath());
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to secdiscard " + path, e);
+ } finally {
+ file.delete();
+ }
+ mCache.putFile(path, null);
+ }
+ }
+
+ public Map<Integer, List<Long>> listSyntheticPasswordHandlesForAllUsers(String stateName) {
+ Map<Integer, List<Long>> result = new ArrayMap<>();
+ final UserManager um = UserManager.get(mContext);
+ for (UserInfo user : um.getUsers(false)) {
+ result.put(user.id, listSyntheticPasswordHandlesForUser(stateName, user.id));
+ }
+ return result;
+ }
+
+ public List<Long> listSyntheticPasswordHandlesForUser(String stateName, int userId) {
+ File baseDir = getSyntheticPasswordDirectoryForUser(userId);
+ List<Long> result = new ArrayList<>();
+ File[] files = baseDir.listFiles();
+ if (files == null) {
+ return result;
+ }
+ for (File file : files) {
+ String[] parts = file.getName().split("\\.");
+ if (parts.length == 2 && parts[1].equals(stateName)) {
+ try {
+ result.add(Long.parseUnsignedLong(parts[0], 16));
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Failed to parse handle " + parts[0]);
+ }
+ }
+ }
+ return result;
+ }
+
+ @VisibleForTesting
+ protected File getSyntheticPasswordDirectoryForUser(int userId) {
+ return new File(Environment.getDataSystemDeDirectory(userId) ,SYNTHETIC_PASSWORD_DIRECTORY);
+ }
+
+ @VisibleForTesting
+ protected String getSynthenticPasswordStateFilePathForUser(int userId, long handle,
+ String name) {
+ File baseDir = getSyntheticPasswordDirectoryForUser(userId);
+ String baseName = String.format("%016x.%s", handle, name);
+ if (!baseDir.exists()) {
+ baseDir.mkdir();
+ }
+ return new File(baseDir, baseName).getAbsolutePath();
+ }
+
+ public void removeUser(int userId) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+
+ final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
+ final UserInfo parentInfo = um.getProfileParent(userId);
+
+ if (parentInfo == null) {
+ // This user owns its lock settings files - safe to delete them
+ synchronized (mFileWriteLock) {
+ String name = getLockPasswordFilename(userId);
+ File file = new File(name);
+ if (file.exists()) {
+ file.delete();
+ mCache.putFile(name, null);
+ }
+ name = getLockPatternFilename(userId);
+ file = new File(name);
+ if (file.exists()) {
+ file.delete();
+ mCache.putFile(name, null);
+ }
+ }
+ } else {
+ // Managed profile
+ removeChildProfileLock(userId);
+ }
+
+ File spStateDir = getSyntheticPasswordDirectoryForUser(userId);
+ try {
+ db.beginTransaction();
+ db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
+ db.setTransactionSuccessful();
+ mCache.removeUser(userId);
+ // The directory itself will be deleted as part of user deletion operation by the
+ // framework, so only need to purge cache here.
+ //TODO: (b/34600579) invoke secdiscardable
+ mCache.purgePath(spStateDir.getAbsolutePath());
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ @VisibleForTesting
+ void closeDatabase() {
+ mOpenHelper.close();
+ }
+
+ @VisibleForTesting
+ void clearCache() {
+ mCache.clear();
+ }
+
+ @Nullable
+ public PersistentDataBlockManagerInternal getPersistentDataBlock() {
+ if (mPersistentDataBlockManagerInternal == null) {
+ mPersistentDataBlockManagerInternal =
+ LocalServices.getService(PersistentDataBlockManagerInternal.class);
+ }
+ return mPersistentDataBlockManagerInternal;
+ }
+
+ public void writePersistentDataBlock(int persistentType, int userId, int qualityForUi,
+ byte[] payload) {
+ PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlock();
+ if (persistentDataBlock == null) {
+ return;
+ }
+ persistentDataBlock.setFrpCredentialHandle(PersistentData.toBytes(
+ persistentType, userId, qualityForUi, payload));
+ }
+
+ public PersistentData readPersistentDataBlock() {
+ PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlock();
+ if (persistentDataBlock == null) {
+ return PersistentData.NONE;
+ }
+ return PersistentData.fromBytes(persistentDataBlock.getFrpCredentialHandle());
+ }
+
+ public static class PersistentData {
+ static final byte VERSION_1 = 1;
+ static final int VERSION_1_HEADER_SIZE = 1 + 1 + 4 + 4;
+
+ public static final int TYPE_NONE = 0;
+ public static final int TYPE_GATEKEEPER = 1;
+ public static final int TYPE_SP = 2;
+ public static final int TYPE_SP_WEAVER = 3;
+
+ public static final PersistentData NONE = new PersistentData(TYPE_NONE,
+ UserHandle.USER_NULL, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, null);
+
+ final int type;
+ final int userId;
+ final int qualityForUi;
+ final byte[] payload;
+
+ private PersistentData(int type, int userId, int qualityForUi, byte[] payload) {
+ this.type = type;
+ this.userId = userId;
+ this.qualityForUi = qualityForUi;
+ this.payload = payload;
+ }
+
+ public static PersistentData fromBytes(byte[] frpData) {
+ if (frpData == null || frpData.length == 0) {
+ return NONE;
+ }
+
+ DataInputStream is = new DataInputStream(new ByteArrayInputStream(frpData));
+ try {
+ byte version = is.readByte();
+ if (version == PersistentData.VERSION_1) {
+ int type = is.readByte() & 0xFF;
+ int userId = is.readInt();
+ int qualityForUi = is.readInt();
+ byte[] payload = new byte[frpData.length - VERSION_1_HEADER_SIZE];
+ System.arraycopy(frpData, VERSION_1_HEADER_SIZE, payload, 0, payload.length);
+ return new PersistentData(type, userId, qualityForUi, payload);
+ } else {
+ Slog.wtf(TAG, "Unknown PersistentData version code: " + version);
+ return null;
+ }
+ } catch (IOException e) {
+ Slog.wtf(TAG, "Could not parse PersistentData", e);
+ return null;
+ }
+ }
+
+ public static byte[] toBytes(int persistentType, int userId, int qualityForUi,
+ byte[] payload) {
+ if (persistentType == PersistentData.TYPE_NONE) {
+ Preconditions.checkArgument(payload == null,
+ "TYPE_NONE must have empty payload");
+ return null;
+ }
+ Preconditions.checkArgument(payload != null && payload.length > 0,
+ "empty payload must only be used with TYPE_NONE");
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream(
+ VERSION_1_HEADER_SIZE + payload.length);
+ DataOutputStream dos = new DataOutputStream(os);
+ try {
+ dos.writeByte(PersistentData.VERSION_1);
+ dos.writeByte(persistentType);
+ dos.writeInt(userId);
+ dos.writeInt(qualityForUi);
+ dos.write(payload);
+ } catch (IOException e) {
+ throw new RuntimeException("ByteArrayOutputStream cannot throw IOException");
+ }
+ return os.toByteArray();
+ }
+ }
+
+ public interface Callback {
+ void initialize(SQLiteDatabase db);
+ }
+
+ static class DatabaseHelper extends SQLiteOpenHelper {
+ private static final String TAG = "LockSettingsDB";
+ private static final String DATABASE_NAME = "locksettings.db";
+
+ private static final int DATABASE_VERSION = 2;
+
+ private Callback mCallback;
+
+ public DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ setWriteAheadLoggingEnabled(true);
+ }
+
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ private void createTable(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE + " (" +
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
+ COLUMN_KEY + " TEXT," +
+ COLUMN_USERID + " INTEGER," +
+ COLUMN_VALUE + " TEXT" +
+ ");");
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ createTable(db);
+ if (mCallback != null) {
+ mCallback.initialize(db);
+ }
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
+ int upgradeVersion = oldVersion;
+ if (upgradeVersion == 1) {
+ // Previously migrated lock screen widget settings. Now defunct.
+ upgradeVersion = 2;
+ }
+
+ if (upgradeVersion != DATABASE_VERSION) {
+ Log.w(TAG, "Failed to upgrade database!");
+ }
+ }
+ }
+
+ /**
+ * Cache consistency model:
+ * - Writes to storage write directly to the cache, but this MUST happen within the atomic
+ * section either provided by the database transaction or mWriteLock, such that writes to the
+ * cache and writes to the backing storage are guaranteed to occur in the same order
+ *
+ * - Reads can populate the cache, but because they are no strong ordering guarantees with
+ * respect to writes this precaution is taken:
+ * - The cache is assigned a version number that increases every time the cache is modified.
+ * Reads from backing storage can only populate the cache if the backing storage
+ * has not changed since the load operation has begun.
+ * This guarantees that no read operation can shadow a write to the cache that happens
+ * after it had begun.
+ */
+ private static class Cache {
+ private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>();
+ private final CacheKey mCacheKey = new CacheKey();
+ private int mVersion = 0;
+
+ String peekKeyValue(String key, String defaultValue, int userId) {
+ Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId);
+ return cached == DEFAULT ? defaultValue : (String) cached;
+ }
+
+ boolean hasKeyValue(String key, int userId) {
+ return contains(CacheKey.TYPE_KEY_VALUE, key, userId);
+ }
+
+ void putKeyValue(String key, String value, int userId) {
+ put(CacheKey.TYPE_KEY_VALUE, key, value, userId);
+ }
+
+ void putKeyValueIfUnchanged(String key, Object value, int userId, int version) {
+ putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version);
+ }
+
+ byte[] peekFile(String fileName) {
+ return (byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
+ }
+
+ boolean hasFile(String fileName) {
+ return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
+ }
+
+ void putFile(String key, byte[] value) {
+ put(CacheKey.TYPE_FILE, key, value, -1 /* userId */);
+ }
+
+ void putFileIfUnchanged(String key, byte[] value, int version) {
+ putIfUnchanged(CacheKey.TYPE_FILE, key, value, -1 /* userId */, version);
+ }
+
+ void setFetched(int userId) {
+ put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId);
+ }
+
+ boolean isFetched(int userId) {
+ return contains(CacheKey.TYPE_FETCHED, "", userId);
+ }
+
+
+ private synchronized void put(int type, String key, Object value, int userId) {
+ // Create a new CachKey here because it may be saved in the map if the key is absent.
+ mCache.put(new CacheKey().set(type, key, userId), value);
+ mVersion++;
+ }
+
+ private synchronized void putIfUnchanged(int type, String key, Object value, int userId,
+ int version) {
+ if (!contains(type, key, userId) && mVersion == version) {
+ put(type, key, value, userId);
+ }
+ }
+
+ private synchronized boolean contains(int type, String key, int userId) {
+ return mCache.containsKey(mCacheKey.set(type, key, userId));
+ }
+
+ private synchronized Object peek(int type, String key, int userId) {
+ return mCache.get(mCacheKey.set(type, key, userId));
+ }
+
+ private synchronized int getVersion() {
+ return mVersion;
+ }
+
+ synchronized void removeUser(int userId) {
+ for (int i = mCache.size() - 1; i >= 0; i--) {
+ if (mCache.keyAt(i).userId == userId) {
+ mCache.removeAt(i);
+ }
+ }
+
+ // Make sure in-flight loads can't write to cache.
+ mVersion++;
+ }
+
+ synchronized void purgePath(String path) {
+ for (int i = mCache.size() - 1; i >= 0; i--) {
+ CacheKey entry = mCache.keyAt(i);
+ if (entry.type == CacheKey.TYPE_FILE && entry.key.startsWith(path)) {
+ mCache.removeAt(i);
+ }
+ }
+ mVersion++;
+ }
+
+ synchronized void clear() {
+ mCache.clear();
+ mVersion++;
+ }
+
+ private static final class CacheKey {
+ static final int TYPE_KEY_VALUE = 0;
+ static final int TYPE_FILE = 1;
+ static final int TYPE_FETCHED = 2;
+
+ String key;
+ int userId;
+ int type;
+
+ public CacheKey set(int type, String key, int userId) {
+ this.type = type;
+ this.key = key;
+ this.userId = userId;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof CacheKey))
+ return false;
+ CacheKey o = (CacheKey) obj;
+ return userId == o.userId && type == o.type && key.equals(o.key);
+ }
+
+ @Override
+ public int hashCode() {
+ return key.hashCode() ^ userId ^ type;
+ }
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
new file mode 100644
index 0000000..0966153
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2015 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.locksettings;
+
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
+
+import android.app.AlarmManager;
+import android.app.AlarmManager.OnAlarmListener;
+import android.app.admin.DevicePolicyManager;
+import android.app.trust.IStrongAuthTracker;
+import android.content.Context;
+import android.os.Binder;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.SparseIntArray;
+
+import java.util.ArrayList;
+
+/**
+ * Keeps track of requests for strong authentication.
+ */
+public class LockSettingsStrongAuth {
+
+ private static final String TAG = "LockSettings";
+
+ private static final int MSG_REQUIRE_STRONG_AUTH = 1;
+ private static final int MSG_REGISTER_TRACKER = 2;
+ private static final int MSG_UNREGISTER_TRACKER = 3;
+ private static final int MSG_REMOVE_USER = 4;
+ private static final int MSG_SCHEDULE_STRONG_AUTH_TIMEOUT = 5;
+
+ private static final String STRONG_AUTH_TIMEOUT_ALARM_TAG =
+ "LockSettingsStrongAuth.timeoutForUser";
+
+ private final ArrayList<IStrongAuthTracker> mStrongAuthTrackers = new ArrayList<>();
+ private final SparseIntArray mStrongAuthForUser = new SparseIntArray();
+ private final ArrayMap<Integer, StrongAuthTimeoutAlarmListener>
+ mStrongAuthTimeoutAlarmListenerForUser = new ArrayMap<>();
+ private final int mDefaultStrongAuthFlags;
+ private final Context mContext;
+
+ private AlarmManager mAlarmManager;
+
+ public LockSettingsStrongAuth(Context context) {
+ mContext = context;
+ mDefaultStrongAuthFlags = StrongAuthTracker.getDefaultFlags(context);
+ mAlarmManager = context.getSystemService(AlarmManager.class);
+ }
+
+ private void handleAddStrongAuthTracker(IStrongAuthTracker tracker) {
+ for (int i = 0; i < mStrongAuthTrackers.size(); i++) {
+ if (mStrongAuthTrackers.get(i).asBinder() == tracker.asBinder()) {
+ return;
+ }
+ }
+ mStrongAuthTrackers.add(tracker);
+
+ for (int i = 0; i < mStrongAuthForUser.size(); i++) {
+ int key = mStrongAuthForUser.keyAt(i);
+ int value = mStrongAuthForUser.valueAt(i);
+ try {
+ tracker.onStrongAuthRequiredChanged(value, key);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception while adding StrongAuthTracker.", e);
+ }
+ }
+ }
+
+ private void handleRemoveStrongAuthTracker(IStrongAuthTracker tracker) {
+ for (int i = 0; i < mStrongAuthTrackers.size(); i++) {
+ if (mStrongAuthTrackers.get(i).asBinder() == tracker.asBinder()) {
+ mStrongAuthTrackers.remove(i);
+ return;
+ }
+ }
+ }
+
+ private void handleRequireStrongAuth(int strongAuthReason, int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ for (int i = 0; i < mStrongAuthForUser.size(); i++) {
+ int key = mStrongAuthForUser.keyAt(i);
+ handleRequireStrongAuthOneUser(strongAuthReason, key);
+ }
+ } else {
+ handleRequireStrongAuthOneUser(strongAuthReason, userId);
+ }
+ }
+
+ private void handleRequireStrongAuthOneUser(int strongAuthReason, int userId) {
+ int oldValue = mStrongAuthForUser.get(userId, mDefaultStrongAuthFlags);
+ int newValue = strongAuthReason == STRONG_AUTH_NOT_REQUIRED
+ ? STRONG_AUTH_NOT_REQUIRED
+ : (oldValue | strongAuthReason);
+ if (oldValue != newValue) {
+ mStrongAuthForUser.put(userId, newValue);
+ notifyStrongAuthTrackers(newValue, userId);
+ }
+ }
+
+ private void handleRemoveUser(int userId) {
+ int index = mStrongAuthForUser.indexOfKey(userId);
+ if (index >= 0) {
+ mStrongAuthForUser.removeAt(index);
+ notifyStrongAuthTrackers(mDefaultStrongAuthFlags, userId);
+ }
+ }
+
+ private void handleScheduleStrongAuthTimeout(int userId) {
+ final DevicePolicyManager dpm =
+ (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ long when = SystemClock.elapsedRealtime() + dpm.getRequiredStrongAuthTimeout(null, userId);
+ // cancel current alarm listener for the user (if there was one)
+ StrongAuthTimeoutAlarmListener alarm = mStrongAuthTimeoutAlarmListenerForUser.get(userId);
+ if (alarm != null) {
+ mAlarmManager.cancel(alarm);
+ } else {
+ alarm = new StrongAuthTimeoutAlarmListener(userId);
+ mStrongAuthTimeoutAlarmListenerForUser.put(userId, alarm);
+ }
+ // schedule a new alarm listener for the user
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when, STRONG_AUTH_TIMEOUT_ALARM_TAG,
+ alarm, mHandler);
+ }
+
+ private void notifyStrongAuthTrackers(int strongAuthReason, int userId) {
+ for (int i = 0; i < mStrongAuthTrackers.size(); i++) {
+ try {
+ mStrongAuthTrackers.get(i).onStrongAuthRequiredChanged(strongAuthReason, userId);
+ } catch (DeadObjectException e) {
+ Slog.d(TAG, "Removing dead StrongAuthTracker.");
+ mStrongAuthTrackers.remove(i);
+ i--;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception while notifying StrongAuthTracker.", e);
+ }
+ }
+ }
+
+ public void registerStrongAuthTracker(IStrongAuthTracker tracker) {
+ mHandler.obtainMessage(MSG_REGISTER_TRACKER, tracker).sendToTarget();
+ }
+
+ public void unregisterStrongAuthTracker(IStrongAuthTracker tracker) {
+ mHandler.obtainMessage(MSG_UNREGISTER_TRACKER, tracker).sendToTarget();
+ }
+
+ public void removeUser(int userId) {
+ final int argNotUsed = 0;
+ mHandler.obtainMessage(MSG_REMOVE_USER, userId, argNotUsed).sendToTarget();
+ }
+
+ public void requireStrongAuth(int strongAuthReason, int userId) {
+ if (userId == UserHandle.USER_ALL || userId >= UserHandle.USER_SYSTEM) {
+ mHandler.obtainMessage(MSG_REQUIRE_STRONG_AUTH, strongAuthReason,
+ userId).sendToTarget();
+ } else {
+ throw new IllegalArgumentException(
+ "userId must be an explicit user id or USER_ALL");
+ }
+ }
+
+ public void reportUnlock(int userId) {
+ requireStrongAuth(STRONG_AUTH_NOT_REQUIRED, userId);
+ }
+
+ public void reportSuccessfulStrongAuthUnlock(int userId) {
+ final int argNotUsed = 0;
+ mHandler.obtainMessage(MSG_SCHEDULE_STRONG_AUTH_TIMEOUT, userId, argNotUsed).sendToTarget();
+ }
+
+ private class StrongAuthTimeoutAlarmListener implements OnAlarmListener {
+
+ private final int mUserId;
+
+ public StrongAuthTimeoutAlarmListener(int userId) {
+ mUserId = userId;
+ }
+
+ @Override
+ public void onAlarm() {
+ requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, mUserId);
+ }
+ }
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_REGISTER_TRACKER:
+ handleAddStrongAuthTracker((IStrongAuthTracker) msg.obj);
+ break;
+ case MSG_UNREGISTER_TRACKER:
+ handleRemoveStrongAuthTracker((IStrongAuthTracker) msg.obj);
+ break;
+ case MSG_REQUIRE_STRONG_AUTH:
+ handleRequireStrongAuth(msg.arg1, msg.arg2);
+ break;
+ case MSG_REMOVE_USER:
+ handleRemoveUser(msg.arg1);
+ break;
+ case MSG_SCHEDULE_STRONG_AUTH_TIMEOUT:
+ handleScheduleStrongAuthTimeout(msg.arg1);
+ break;
+ }
+ }
+ };
+}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
new file mode 100644
index 0000000..b7bca1fb
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings;
+
+import android.security.keystore.KeyProperties;
+import android.security.keystore.KeyProtection;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.Arrays;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class SyntheticPasswordCrypto {
+ private static final int PROFILE_KEY_IV_SIZE = 12;
+ private static final int AES_KEY_LENGTH = 32; // 256-bit AES key
+ private static final byte[] APPLICATION_ID_PERSONALIZATION = "application-id".getBytes();
+ // Time between the user credential is verified with GK and the decryption of synthetic password
+ // under the auth-bound key. This should always happen one after the other, but give it 15
+ // seconds just to be sure.
+ private static final int USER_AUTHENTICATION_VALIDITY = 15;
+
+ private static byte[] decrypt(SecretKey key, byte[] blob)
+ throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
+ InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+ if (blob == null) {
+ return null;
+ }
+ byte[] iv = Arrays.copyOfRange(blob, 0, PROFILE_KEY_IV_SIZE);
+ byte[] ciphertext = Arrays.copyOfRange(blob, PROFILE_KEY_IV_SIZE, blob.length);
+ Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
+ cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
+ return cipher.doFinal(ciphertext);
+ }
+
+ private static byte[] encrypt(SecretKey key, byte[] blob)
+ throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
+ InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+ if (blob == null) {
+ return null;
+ }
+ Cipher cipher = Cipher.getInstance(
+ KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
+ + KeyProperties.ENCRYPTION_PADDING_NONE);
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ byte[] ciphertext = cipher.doFinal(blob);
+ byte[] iv = cipher.getIV();
+ if (iv.length != PROFILE_KEY_IV_SIZE) {
+ throw new RuntimeException("Invalid iv length: " + iv.length);
+ }
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ outputStream.write(iv);
+ outputStream.write(ciphertext);
+ return outputStream.toByteArray();
+ }
+
+ public static byte[] encrypt(byte[] keyBytes, byte[] personalisation, byte[] message) {
+ byte[] keyHash = personalisedHash(personalisation, keyBytes);
+ SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
+ KeyProperties.KEY_ALGORITHM_AES);
+ try {
+ return encrypt(key, message);
+ } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
+ | IllegalBlockSizeException | BadPaddingException | IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public static byte[] decrypt(byte[] keyBytes, byte[] personalisation, byte[] ciphertext) {
+ byte[] keyHash = personalisedHash(personalisation, keyBytes);
+ SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
+ KeyProperties.KEY_ALGORITHM_AES);
+ try {
+ return decrypt(key, ciphertext);
+ } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
+ | IllegalBlockSizeException | BadPaddingException
+ | InvalidAlgorithmParameterException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
+ try {
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+
+ SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
+ byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob);
+ return decrypt(decryptionKey, intermediate);
+ } catch (CertificateException | IOException | BadPaddingException
+ | IllegalBlockSizeException
+ | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
+ | InvalidKeyException | UnrecoverableKeyException
+ | InvalidAlgorithmParameterException e) {
+ e.printStackTrace();
+ throw new RuntimeException("Failed to decrypt blob", e);
+ }
+ }
+
+ public static byte[] createBlob(String keyAlias, byte[] data, byte[] applicationId, long sid) {
+ try {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
+ keyGenerator.init(new SecureRandom());
+ SecretKey secretKey = keyGenerator.generateKey();
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .setCriticalToDeviceEncryption(true);
+ if (sid != 0) {
+ builder.setUserAuthenticationRequired(true)
+ .setBoundToSpecificSecureUserId(sid)
+ .setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_VALIDITY);
+ }
+
+ keyStore.setEntry(keyAlias,
+ new KeyStore.SecretKeyEntry(secretKey),
+ builder.build());
+ byte[] intermediate = encrypt(secretKey, data);
+ return encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
+
+ } catch (CertificateException | IOException | BadPaddingException
+ | IllegalBlockSizeException
+ | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
+ | InvalidKeyException e) {
+ e.printStackTrace();
+ throw new RuntimeException("Failed to encrypt blob", e);
+ }
+ }
+
+ public static void destroyBlobKey(String keyAlias) {
+ KeyStore keyStore;
+ try {
+ keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ keyStore.deleteEntry(keyAlias);
+ } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
+ | IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ protected static byte[] personalisedHash(byte[] personalisation, byte[]... message) {
+ try {
+ final int PADDING_LENGTH = 128;
+ MessageDigest digest = MessageDigest.getInstance("SHA-512");
+ if (personalisation.length > PADDING_LENGTH) {
+ throw new RuntimeException("Personalisation too long");
+ }
+ // Personalize the hash
+ // Pad it to the block size of the hash function
+ personalisation = Arrays.copyOf(personalisation, PADDING_LENGTH);
+ digest.update(personalisation);
+ for (byte[] data : message) {
+ digest.update(data);
+ }
+ return digest.digest();
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("NoSuchAlgorithmException for SHA-512", e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
new file mode 100644
index 0000000..f45c208
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -0,0 +1,1107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
+import android.hardware.weaver.V1_0.IWeaver;
+import android.hardware.weaver.V1_0.WeaverConfig;
+import android.hardware.weaver.V1_0.WeaverReadResponse;
+import android.hardware.weaver.V1_0.WeaverReadStatus;
+import android.hardware.weaver.V1_0.WeaverStatus;
+import android.security.GateKeeper;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.service.gatekeeper.GateKeeperResponse;
+import android.service.gatekeeper.IGateKeeperService;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.widget.ICheckCredentialProgressCallback;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+
+import libcore.util.HexEncoding;
+
+import java.nio.ByteBuffer;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+
+/**
+ * A class that maintains the wrapping of synthetic password by user credentials or escrow tokens.
+ * It's (mostly) a pure storage for synthetic passwords, providing APIs to creating and destroying
+ * synthetic password blobs which are wrapped by user credentials or escrow tokens.
+ *
+ * Here is the assumptions it makes:
+ * Each user has one single synthetic password at any time.
+ * The SP has an associated password handle, which binds to the SID for that user. The password
+ * handle is persisted by SyntheticPasswordManager internally.
+ * If the user credential is null, it's treated as if the credential is DEFAULT_PASSWORD
+ *
+ * Information persisted on disk:
+ * for each user (stored under DEFAULT_HANDLE):
+ * SP_HANDLE_NAME: GateKeeper password handle of synthetic password. Only available if user
+ * credential exists, cleared when user clears their credential.
+ * SP_E0_NAME, SP_P1_NAME: Secret to derive synthetic password when combined with escrow
+ * tokens. Destroyed when escrow support is turned off for the given user.
+ *
+ * for each SP blob under the user (stored under the corresponding handle):
+ * SP_BLOB_NAME: The encrypted synthetic password. Always exists.
+ * PASSWORD_DATA_NAME: Metadata about user credential. Only exists for password based SP.
+ * SECDISCARDABLE_NAME: Part of the necessary ingredient to decrypt SP_BLOB_NAME for the
+ * purpose of secure deletion. Exists if this is a non-weaver SP
+ * (both password and token based), or it's a token-based SP under weaver.
+ * WEAVER_SLOT: Metadata about the weaver slot used. Only exists if this is a SP under weaver.
+ *
+ *
+ */
+public class SyntheticPasswordManager {
+ private static final String SP_BLOB_NAME = "spblob";
+ private static final String SP_E0_NAME = "e0";
+ private static final String SP_P1_NAME = "p1";
+ private static final String SP_HANDLE_NAME = "handle";
+ private static final String SECDISCARDABLE_NAME = "secdis";
+ private static final int SECDISCARDABLE_LENGTH = 16 * 1024;
+ private static final String PASSWORD_DATA_NAME = "pwd";
+ private static final String WEAVER_SLOT_NAME = "weaver";
+
+ public static final long DEFAULT_HANDLE = 0L;
+ private static final String DEFAULT_PASSWORD = "default-password";
+
+ private static final byte WEAVER_VERSION = 1;
+ private static final int INVALID_WEAVER_SLOT = -1;
+
+ private static final byte SYNTHETIC_PASSWORD_VERSION = 1;
+ private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0;
+ private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1;
+
+ // 256-bit synthetic password
+ private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8;
+
+ private static final int PASSWORD_SCRYPT_N = 11;
+ private static final int PASSWORD_SCRYPT_R = 3;
+ private static final int PASSWORD_SCRYPT_P = 1;
+ private static final int PASSWORD_SALT_LENGTH = 16;
+ private static final int PASSWORD_TOKEN_LENGTH = 32;
+ private static final String TAG = "SyntheticPasswordManager";
+
+ private static final byte[] PERSONALISATION_SECDISCARDABLE = "secdiscardable-transform".getBytes();
+ private static final byte[] PERSONALIZATION_KEY_STORE_PASSWORD = "keystore-password".getBytes();
+ private static final byte[] PERSONALIZATION_USER_GK_AUTH = "user-gk-authentication".getBytes();
+ private static final byte[] PERSONALIZATION_SP_GK_AUTH = "sp-gk-authentication".getBytes();
+ private static final byte[] PERSONALIZATION_FBE_KEY = "fbe-key".getBytes();
+ private static final byte[] PERSONALIZATION_SP_SPLIT = "sp-split".getBytes();
+ private static final byte[] PERSONALIZATION_E0 = "e0-encryption".getBytes();
+ private static final byte[] PERSONALISATION_WEAVER_PASSWORD = "weaver-pwd".getBytes();
+ private static final byte[] PERSONALISATION_WEAVER_KEY = "weaver-key".getBytes();
+ private static final byte[] PERSONALISATION_WEAVER_TOKEN = "weaver-token".getBytes();
+
+ static class AuthenticationResult {
+ public AuthenticationToken authToken;
+ public VerifyCredentialResponse gkResponse;
+ }
+
+ static class AuthenticationToken {
+ /*
+ * Here is the relationship between all three fields:
+ * P0 and P1 are two randomly-generated blocks. P1 is stored on disk but P0 is not.
+ * syntheticPassword = hash(P0 || P1)
+ * E0 = P0 encrypted under syntheticPassword, stored on disk.
+ */
+ private @Nullable byte[] E0;
+ private @Nullable byte[] P1;
+ private @NonNull String syntheticPassword;
+
+ public String deriveKeyStorePassword() {
+ return bytesToHex(SyntheticPasswordCrypto.personalisedHash(
+ PERSONALIZATION_KEY_STORE_PASSWORD, syntheticPassword.getBytes()));
+ }
+
+ public byte[] deriveGkPassword() {
+ return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_SP_GK_AUTH,
+ syntheticPassword.getBytes());
+ }
+
+ public byte[] deriveDiskEncryptionKey() {
+ return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_FBE_KEY,
+ syntheticPassword.getBytes());
+ }
+
+ private void initialize(byte[] P0, byte[] P1) {
+ this.P1 = P1;
+ this.syntheticPassword = String.valueOf(HexEncoding.encode(
+ SyntheticPasswordCrypto.personalisedHash(
+ PERSONALIZATION_SP_SPLIT, P0, P1)));
+ this.E0 = SyntheticPasswordCrypto.encrypt(this.syntheticPassword.getBytes(),
+ PERSONALIZATION_E0, P0);
+ }
+
+ public void recreate(byte[] secret) {
+ initialize(secret, this.P1);
+ }
+
+ protected static AuthenticationToken create() {
+ AuthenticationToken result = new AuthenticationToken();
+ result.initialize(secureRandom(SYNTHETIC_PASSWORD_LENGTH),
+ secureRandom(SYNTHETIC_PASSWORD_LENGTH));
+ return result;
+ }
+
+ public byte[] computeP0() {
+ if (E0 == null) {
+ return null;
+ }
+ return SyntheticPasswordCrypto.decrypt(syntheticPassword.getBytes(), PERSONALIZATION_E0,
+ E0);
+ }
+ }
+
+ static class PasswordData {
+ byte scryptN;
+ byte scryptR;
+ byte scryptP;
+ public int passwordType;
+ byte[] salt;
+ // For GateKeeper-based credential, this is the password handle returned by GK,
+ // for weaver-based credential, this is empty.
+ public byte[] passwordHandle;
+
+ public static PasswordData create(int passwordType) {
+ PasswordData result = new PasswordData();
+ result.scryptN = PASSWORD_SCRYPT_N;
+ result.scryptR = PASSWORD_SCRYPT_R;
+ result.scryptP = PASSWORD_SCRYPT_P;
+ result.passwordType = passwordType;
+ result.salt = secureRandom(PASSWORD_SALT_LENGTH);
+ return result;
+ }
+
+ public static PasswordData fromBytes(byte[] data) {
+ PasswordData result = new PasswordData();
+ ByteBuffer buffer = ByteBuffer.allocate(data.length);
+ buffer.put(data, 0, data.length);
+ buffer.flip();
+ result.passwordType = buffer.getInt();
+ result.scryptN = buffer.get();
+ result.scryptR = buffer.get();
+ result.scryptP = buffer.get();
+ int saltLen = buffer.getInt();
+ result.salt = new byte[saltLen];
+ buffer.get(result.salt);
+ int handleLen = buffer.getInt();
+ if (handleLen > 0) {
+ result.passwordHandle = new byte[handleLen];
+ buffer.get(result.passwordHandle);
+ } else {
+ result.passwordHandle = null;
+ }
+ return result;
+ }
+
+ public byte[] toBytes() {
+
+ ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + 3 * Byte.BYTES
+ + Integer.BYTES + salt.length + Integer.BYTES +
+ (passwordHandle != null ? passwordHandle.length : 0));
+ buffer.putInt(passwordType);
+ buffer.put(scryptN);
+ buffer.put(scryptR);
+ buffer.put(scryptP);
+ buffer.putInt(salt.length);
+ buffer.put(salt);
+ if (passwordHandle != null && passwordHandle.length > 0) {
+ buffer.putInt(passwordHandle.length);
+ buffer.put(passwordHandle);
+ } else {
+ buffer.putInt(0);
+ }
+ return buffer.array();
+ }
+ }
+
+ static class TokenData {
+ byte[] secdiscardableOnDisk;
+ byte[] weaverSecret;
+ byte[] aggregatedSecret;
+ }
+
+ private LockSettingsStorage mStorage;
+ private IWeaver mWeaver;
+ private WeaverConfig mWeaverConfig;
+
+ private final UserManager mUserManager;
+
+ public SyntheticPasswordManager(LockSettingsStorage storage, UserManager userManager) {
+ mStorage = storage;
+ mUserManager = userManager;
+ }
+
+ @VisibleForTesting
+ protected IWeaver getWeaverService() throws RemoteException {
+ try {
+ return IWeaver.getService();
+ } catch (NoSuchElementException e) {
+ Slog.i(TAG, "Device does not support weaver");
+ return null;
+ }
+ }
+
+ public synchronized void initWeaverService() {
+ if (mWeaver != null) {
+ return;
+ }
+ try {
+ mWeaverConfig = null;
+ mWeaver = getWeaverService();
+ if (mWeaver != null) {
+ mWeaver.getConfig((int status, WeaverConfig config) -> {
+ if (status == WeaverStatus.OK && config.slots > 0) {
+ mWeaverConfig = config;
+ } else {
+ Slog.e(TAG, "Failed to get weaver config, status " + status
+ + " slots: " + config.slots);
+ mWeaver = null;
+ }
+ });
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get weaver service", e);
+ }
+ }
+
+ private synchronized boolean isWeaverAvailable() {
+ if (mWeaver == null) {
+ //Re-initializing weaver in case there was a transient error preventing access to it.
+ initWeaverService();
+ }
+ return mWeaver != null && mWeaverConfig.slots > 0;
+ }
+
+ /**
+ * Enroll the given key value pair into the specified weaver slot. if the given key is null,
+ * a default all-zero key is used. If the value is not specified, a fresh random secret is
+ * generated as the value.
+ *
+ * @return the value stored in the weaver slot
+ * @throws RemoteException
+ */
+ private byte[] weaverEnroll(int slot, byte[] key, @Nullable byte[] value)
+ throws RemoteException {
+ if (slot == INVALID_WEAVER_SLOT || slot >= mWeaverConfig.slots) {
+ throw new RuntimeException("Invalid slot for weaver");
+ }
+ if (key == null) {
+ key = new byte[mWeaverConfig.keySize];
+ } else if (key.length != mWeaverConfig.keySize) {
+ throw new RuntimeException("Invalid key size for weaver");
+ }
+ if (value == null) {
+ value = secureRandom(mWeaverConfig.valueSize);
+ }
+ int writeStatus = mWeaver.write(slot, toByteArrayList(key), toByteArrayList(value));
+ if (writeStatus != WeaverStatus.OK) {
+ Log.e(TAG, "weaver write failed, slot: " + slot + " status: " + writeStatus);
+ return null;
+ }
+ return value;
+ }
+
+ /**
+ * Verify the supplied key against a weaver slot, returning a response indicating whether
+ * the verification is successful, throttled or failed. If successful, the bound secret
+ * is also returned.
+ * @throws RemoteException
+ */
+ private VerifyCredentialResponse weaverVerify(int slot, byte[] key) throws RemoteException {
+ if (slot == INVALID_WEAVER_SLOT || slot >= mWeaverConfig.slots) {
+ throw new RuntimeException("Invalid slot for weaver");
+ }
+ if (key == null) {
+ key = new byte[mWeaverConfig.keySize];
+ } else if (key.length != mWeaverConfig.keySize) {
+ throw new RuntimeException("Invalid key size for weaver");
+ }
+ final VerifyCredentialResponse[] response = new VerifyCredentialResponse[1];
+ mWeaver.read(slot, toByteArrayList(key), (int status, WeaverReadResponse readResponse) -> {
+ switch (status) {
+ case WeaverReadStatus.OK:
+ response[0] = new VerifyCredentialResponse(
+ fromByteArrayList(readResponse.value));
+ break;
+ case WeaverReadStatus.THROTTLE:
+ response[0] = new VerifyCredentialResponse(readResponse.timeout);
+ Log.e(TAG, "weaver read failed (THROTTLE), slot: " + slot);
+ break;
+ case WeaverReadStatus.INCORRECT_KEY:
+ if (readResponse.timeout == 0) {
+ response[0] = VerifyCredentialResponse.ERROR;
+ Log.e(TAG, "weaver read failed (INCORRECT_KEY), slot: " + slot);
+ } else {
+ response[0] = new VerifyCredentialResponse(readResponse.timeout);
+ Log.e(TAG, "weaver read failed (INCORRECT_KEY/THROTTLE), slot: " + slot);
+ }
+ break;
+ case WeaverReadStatus.FAILED:
+ response[0] = VerifyCredentialResponse.ERROR;
+ Log.e(TAG, "weaver read failed (FAILED), slot: " + slot);
+ break;
+ default:
+ response[0] = VerifyCredentialResponse.ERROR;
+ Log.e(TAG, "weaver read unknown status " + status + ", slot: " + slot);
+ break;
+ }
+ });
+ return response[0];
+ }
+
+ public void removeUser(int userId) {
+ if (isWeaverAvailable()) {
+ for (long handle : mStorage.listSyntheticPasswordHandlesForUser(WEAVER_SLOT_NAME,
+ userId)) {
+ destroyWeaverSlot(handle, userId);
+ }
+ }
+ }
+
+ public int getCredentialType(long handle, int userId) {
+ byte[] passwordData = loadState(PASSWORD_DATA_NAME, handle, userId);
+ if (passwordData == null) {
+ Log.w(TAG, "getCredentialType: encountered empty password data for user " + userId);
+ return LockPatternUtils.CREDENTIAL_TYPE_NONE;
+ }
+ return PasswordData.fromBytes(passwordData).passwordType;
+ }
+
+ /**
+ * Initializing a new Authentication token, possibly from an existing credential and hash.
+ *
+ * The authentication token would bear a randomly-generated synthetic password.
+ *
+ * This method has the side effect of rebinding the SID of the given user to the
+ * newly-generated SP.
+ *
+ * If the existing credential hash is non-null, the existing SID mill be migrated so
+ * the synthetic password in the authentication token will produce the same SID
+ * (the corresponding synthetic password handle is persisted by SyntheticPasswordManager
+ * in a per-user data storage.)
+ *
+ * If the existing credential hash is null, it means the given user should have no SID so
+ * SyntheticPasswordManager will nuke any SP handle previously persisted. In this case,
+ * the supplied credential parameter is also ignored.
+ *
+ * Also saves the escrow information necessary to re-generate the synthetic password under
+ * an escrow scheme. This information can be removed with {@link #destroyEscrowData} if
+ * password escrow should be disabled completely on the given user.
+ *
+ */
+ public AuthenticationToken newSyntheticPasswordAndSid(IGateKeeperService gatekeeper,
+ byte[] hash, String credential, int userId) throws RemoteException {
+ AuthenticationToken result = AuthenticationToken.create();
+ GateKeeperResponse response;
+ if (hash != null) {
+ response = gatekeeper.enroll(userId, hash, credential.getBytes(),
+ result.deriveGkPassword());
+ if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
+ Log.w(TAG, "Fail to migrate SID, assuming no SID, user " + userId);
+ clearSidForUser(userId);
+ } else {
+ saveSyntheticPasswordHandle(response.getPayload(), userId);
+ }
+ } else {
+ clearSidForUser(userId);
+ }
+ saveEscrowData(result, userId);
+ return result;
+ }
+
+ /**
+ * Enroll a new password handle and SID for the given synthetic password and persist it on disk.
+ * Used when adding password to previously-unsecured devices.
+ */
+ public void newSidForUser(IGateKeeperService gatekeeper, AuthenticationToken authToken,
+ int userId) throws RemoteException {
+ GateKeeperResponse response = gatekeeper.enroll(userId, null, null,
+ authToken.deriveGkPassword());
+ if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
+ Log.e(TAG, "Fail to create new SID for user " + userId);
+ return;
+ }
+ saveSyntheticPasswordHandle(response.getPayload(), userId);
+ }
+
+ // Nuke the SP handle (and as a result, its SID) for the given user.
+ public void clearSidForUser(int userId) {
+ destroyState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId);
+ }
+
+ public boolean hasSidForUser(int userId) {
+ return hasState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId);
+ }
+
+ // if null, it means there is no SID associated with the user
+ // This can happen if the user is migrated to SP but currently
+ // do not have a lockscreen password.
+ private byte[] loadSyntheticPasswordHandle(int userId) {
+ return loadState(SP_HANDLE_NAME, DEFAULT_HANDLE, userId);
+ }
+
+ private void saveSyntheticPasswordHandle(byte[] spHandle, int userId) {
+ saveState(SP_HANDLE_NAME, spHandle, DEFAULT_HANDLE, userId);
+ }
+
+ private boolean loadEscrowData(AuthenticationToken authToken, int userId) {
+ authToken.E0 = loadState(SP_E0_NAME, DEFAULT_HANDLE, userId);
+ authToken.P1 = loadState(SP_P1_NAME, DEFAULT_HANDLE, userId);
+ return authToken.E0 != null && authToken.P1 != null;
+ }
+
+ private void saveEscrowData(AuthenticationToken authToken, int userId) {
+ saveState(SP_E0_NAME, authToken.E0, DEFAULT_HANDLE, userId);
+ saveState(SP_P1_NAME, authToken.P1, DEFAULT_HANDLE, userId);
+ }
+
+ public boolean hasEscrowData(int userId) {
+ return hasState(SP_E0_NAME, DEFAULT_HANDLE, userId)
+ && hasState(SP_P1_NAME, DEFAULT_HANDLE, userId);
+ }
+
+ public void destroyEscrowData(int userId) {
+ destroyState(SP_E0_NAME, DEFAULT_HANDLE, userId);
+ destroyState(SP_P1_NAME, DEFAULT_HANDLE, userId);
+ }
+
+ private int loadWeaverSlot(long handle, int userId) {
+ final int LENGTH = Byte.BYTES + Integer.BYTES;
+ byte[] data = loadState(WEAVER_SLOT_NAME, handle, userId);
+ if (data == null || data.length != LENGTH) {
+ return INVALID_WEAVER_SLOT;
+ }
+ ByteBuffer buffer = ByteBuffer.allocate(LENGTH);
+ buffer.put(data, 0, data.length);
+ buffer.flip();
+ if (buffer.get() != WEAVER_VERSION) {
+ Log.e(TAG, "Invalid weaver slot version of handle " + handle);
+ return INVALID_WEAVER_SLOT;
+ }
+ return buffer.getInt();
+ }
+
+ private void saveWeaverSlot(int slot, long handle, int userId) {
+ ByteBuffer buffer = ByteBuffer.allocate(Byte.BYTES + Integer.BYTES);
+ buffer.put(WEAVER_VERSION);
+ buffer.putInt(slot);
+ saveState(WEAVER_SLOT_NAME, buffer.array(), handle, userId);
+ }
+
+ private void destroyWeaverSlot(long handle, int userId) {
+ int slot = loadWeaverSlot(handle, userId);
+ if (slot != INVALID_WEAVER_SLOT) {
+ try {
+ weaverEnroll(slot, null, null);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to destroy slot", e);
+ }
+ }
+ destroyState(WEAVER_SLOT_NAME, handle, userId);
+ }
+
+ private int getNextAvailableWeaverSlot() {
+ Map<Integer, List<Long>> slotHandles = mStorage.listSyntheticPasswordHandlesForAllUsers(
+ WEAVER_SLOT_NAME);
+ HashSet<Integer> slots = new HashSet<>();
+ for (Map.Entry<Integer, List<Long>> entry : slotHandles.entrySet()) {
+ for (Long handle : entry.getValue()) {
+ int slot = loadWeaverSlot(handle, entry.getKey());
+ slots.add(slot);
+ }
+ }
+ for (int i = 0; i < mWeaverConfig.slots; i++) {
+ if (!slots.contains(i)) {
+ return i;
+ }
+ }
+ throw new RuntimeException("Run out of weaver slots.");
+ }
+
+ /**
+ * Create a new password based SP blob based on the supplied authentication token, such that
+ * a future successful authentication with unwrapPasswordBasedSyntheticPassword() would result
+ * in the same authentication token.
+ *
+ * This method only creates SP blob wrapping around the given synthetic password and does not
+ * handle logic around SID or SP handle. The caller should separately ensure that the user's SID
+ * is consistent with the device state by calling other APIs in this class.
+ *
+ * @see #newSidForUser
+ * @see #clearSidForUser
+ */
+ public long createPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
+ String credential, int credentialType, AuthenticationToken authToken,
+ int requestedQuality, int userId)
+ throws RemoteException {
+ if (credential == null || credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ credentialType = LockPatternUtils.CREDENTIAL_TYPE_NONE;
+ credential = DEFAULT_PASSWORD;
+ }
+
+ long handle = generateHandle();
+ PasswordData pwd = PasswordData.create(credentialType);
+ byte[] pwdToken = computePasswordToken(credential, pwd);
+ final long sid;
+ final byte[] applicationId;
+
+ if (isWeaverAvailable()) {
+ // Weaver based user password
+ int weaverSlot = getNextAvailableWeaverSlot();
+ byte[] weaverSecret = weaverEnroll(weaverSlot, passwordTokenToWeaverKey(pwdToken), null);
+ if (weaverSecret == null) {
+ Log.e(TAG, "Fail to enroll user password under weaver " + userId);
+ return DEFAULT_HANDLE;
+ }
+ saveWeaverSlot(weaverSlot, handle, userId);
+ synchronizeWeaverFrpPassword(pwd, requestedQuality, userId, weaverSlot);
+
+ pwd.passwordHandle = null;
+ sid = GateKeeper.INVALID_SECURE_USER_ID;
+ applicationId = transformUnderWeaverSecret(pwdToken, weaverSecret);
+ } else {
+ // In case GK enrollment leaves persistent state around (in RPMB), this will nuke them
+ // to prevent them from accumulating and causing problems.
+ gatekeeper.clearSecureUserId(fakeUid(userId));
+ // GateKeeper based user password
+ GateKeeperResponse response = gatekeeper.enroll(fakeUid(userId), null, null,
+ passwordTokenToGkInput(pwdToken));
+ if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
+ Log.e(TAG, "Fail to enroll user password when creating SP for user " + userId);
+ return DEFAULT_HANDLE;
+ }
+ pwd.passwordHandle = response.getPayload();
+ sid = sidFromPasswordHandle(pwd.passwordHandle);
+ applicationId = transformUnderSecdiscardable(pwdToken,
+ createSecdiscardable(handle, userId));
+ synchronizeFrpPassword(pwd, requestedQuality, userId);
+ }
+ saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
+
+ createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED, authToken,
+ applicationId, sid, userId);
+ return handle;
+ }
+
+ public VerifyCredentialResponse verifyFrpCredential(IGateKeeperService gatekeeper,
+ String userCredential, int credentialType,
+ ICheckCredentialProgressCallback progressCallback) throws RemoteException {
+ PersistentData persistentData = mStorage.readPersistentDataBlock();
+ if (persistentData.type == PersistentData.TYPE_SP) {
+ PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
+ byte[] pwdToken = computePasswordToken(userCredential, pwd);
+
+ GateKeeperResponse response = gatekeeper.verify(fakeUid(persistentData.userId),
+ pwd.passwordHandle, passwordTokenToGkInput(pwdToken));
+ return VerifyCredentialResponse.fromGateKeeperResponse(response);
+ } else if (persistentData.type == PersistentData.TYPE_SP_WEAVER) {
+ PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
+ byte[] pwdToken = computePasswordToken(userCredential, pwd);
+ int weaverSlot = persistentData.userId;
+
+ return weaverVerify(weaverSlot, passwordTokenToWeaverKey(pwdToken)).stripPayload();
+ } else {
+ Log.e(TAG, "persistentData.type must be TYPE_SP or TYPE_SP_WEAVER, but is "
+ + persistentData.type);
+ return VerifyCredentialResponse.ERROR;
+ }
+ }
+
+
+ private void synchronizeFrpPassword(PasswordData pwd,
+ int requestedQuality, int userId) {
+ if (mStorage.getPersistentDataBlock() != null
+ && LockPatternUtils.userOwnsFrpCredential(mUserManager.getUserInfo(userId))) {
+ if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality,
+ pwd.toBytes());
+ } else {
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, userId, 0, null);
+ }
+ }
+ }
+
+ private void synchronizeWeaverFrpPassword(PasswordData pwd, int requestedQuality, int userId,
+ int weaverSlot) {
+ if (mStorage.getPersistentDataBlock() != null
+ && LockPatternUtils.userOwnsFrpCredential(mUserManager.getUserInfo(userId))) {
+ if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot,
+ requestedQuality, pwd.toBytes());
+ } else {
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, 0, 0, null);
+ }
+ }
+ }
+
+ private ArrayMap<Integer, ArrayMap<Long, TokenData>> tokenMap = new ArrayMap<>();
+
+ public long createTokenBasedSyntheticPassword(byte[] token, int userId) {
+ long handle = generateHandle();
+ if (!tokenMap.containsKey(userId)) {
+ tokenMap.put(userId, new ArrayMap<>());
+ }
+ TokenData tokenData = new TokenData();
+ final byte[] secdiscardable = secureRandom(SECDISCARDABLE_LENGTH);
+ if (isWeaverAvailable()) {
+ tokenData.weaverSecret = secureRandom(mWeaverConfig.valueSize);
+ tokenData.secdiscardableOnDisk = SyntheticPasswordCrypto.encrypt(tokenData.weaverSecret,
+ PERSONALISATION_WEAVER_TOKEN, secdiscardable);
+ } else {
+ tokenData.secdiscardableOnDisk = secdiscardable;
+ tokenData.weaverSecret = null;
+ }
+ tokenData.aggregatedSecret = transformUnderSecdiscardable(token, secdiscardable);
+
+ tokenMap.get(userId).put(handle, tokenData);
+ return handle;
+ }
+
+ public Set<Long> getPendingTokensForUser(int userId) {
+ if (!tokenMap.containsKey(userId)) {
+ return Collections.emptySet();
+ }
+ return tokenMap.get(userId).keySet();
+ }
+
+ public boolean removePendingToken(long handle, int userId) {
+ if (!tokenMap.containsKey(userId)) {
+ return false;
+ }
+ return tokenMap.get(userId).remove(handle) != null;
+ }
+
+ public boolean activateTokenBasedSyntheticPassword(long handle, AuthenticationToken authToken,
+ int userId) {
+ if (!tokenMap.containsKey(userId)) {
+ return false;
+ }
+ TokenData tokenData = tokenMap.get(userId).get(handle);
+ if (tokenData == null) {
+ return false;
+ }
+ if (!loadEscrowData(authToken, userId)) {
+ Log.w(TAG, "User is not escrowable");
+ return false;
+ }
+ if (isWeaverAvailable()) {
+ int slot = getNextAvailableWeaverSlot();
+ try {
+ weaverEnroll(slot, null, tokenData.weaverSecret);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to enroll weaver secret when activating token", e);
+ return false;
+ }
+ saveWeaverSlot(slot, handle, userId);
+ }
+ saveSecdiscardable(handle, tokenData.secdiscardableOnDisk, userId);
+ createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, authToken,
+ tokenData.aggregatedSecret, 0L, userId);
+ tokenMap.get(userId).remove(handle);
+ return true;
+ }
+
+ private void createSyntheticPasswordBlob(long handle, byte type, AuthenticationToken authToken,
+ byte[] applicationId, long sid, int userId) {
+ final byte[] secret;
+ if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
+ secret = authToken.computeP0();
+ } else {
+ secret = authToken.syntheticPassword.getBytes();
+ }
+ byte[] content = createSPBlob(getHandleName(handle), secret, applicationId, sid);
+ byte[] blob = new byte[content.length + 1 + 1];
+ blob[0] = SYNTHETIC_PASSWORD_VERSION;
+ blob[1] = type;
+ System.arraycopy(content, 0, blob, 2, content.length);
+ saveState(SP_BLOB_NAME, blob, handle, userId);
+ }
+
+ /**
+ * Decrypt a synthetic password by supplying the user credential and corresponding password
+ * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
+ * verification to referesh the SID & Auth token maintained by the system.
+ */
+ public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
+ long handle, String credential, int userId) throws RemoteException {
+ if (credential == null) {
+ credential = DEFAULT_PASSWORD;
+ }
+ AuthenticationResult result = new AuthenticationResult();
+ PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle, userId));
+ byte[] pwdToken = computePasswordToken(credential, pwd);
+
+ final byte[] applicationId;
+ int weaverSlot = loadWeaverSlot(handle, userId);
+ if (weaverSlot != INVALID_WEAVER_SLOT) {
+ // Weaver based user password
+ if (!isWeaverAvailable()) {
+ Log.e(TAG, "No weaver service to unwrap password based SP");
+ result.gkResponse = VerifyCredentialResponse.ERROR;
+ return result;
+ }
+ result.gkResponse = weaverVerify(weaverSlot, passwordTokenToWeaverKey(pwdToken));
+ if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
+ return result;
+ }
+ applicationId = transformUnderWeaverSecret(pwdToken, result.gkResponse.getPayload());
+ } else {
+ byte[] gkPwdToken = passwordTokenToGkInput(pwdToken);
+ GateKeeperResponse response = gatekeeper.verifyChallenge(fakeUid(userId), 0L,
+ pwd.passwordHandle, gkPwdToken);
+ int responseCode = response.getResponseCode();
+ if (responseCode == GateKeeperResponse.RESPONSE_OK) {
+ result.gkResponse = VerifyCredentialResponse.OK;
+ if (response.getShouldReEnroll()) {
+ GateKeeperResponse reenrollResponse = gatekeeper.enroll(fakeUid(userId),
+ pwd.passwordHandle, gkPwdToken, gkPwdToken);
+ if (reenrollResponse.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
+ pwd.passwordHandle = reenrollResponse.getPayload();
+ saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
+ synchronizeFrpPassword(pwd,
+ pwd.passwordType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN
+ ? DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ : DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
+ /* TODO(roosa): keep the same password quality */,
+ userId);
+ } else {
+ Log.w(TAG, "Fail to re-enroll user password for user " + userId);
+ // continue the flow anyway
+ }
+ }
+ } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
+ result.gkResponse = new VerifyCredentialResponse(response.getTimeout());
+ return result;
+ } else {
+ result.gkResponse = VerifyCredentialResponse.ERROR;
+ return result;
+ }
+ applicationId = transformUnderSecdiscardable(pwdToken,
+ loadSecdiscardable(handle, userId));
+ }
+
+ result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED,
+ applicationId, userId);
+
+ // Perform verifyChallenge to refresh auth tokens for GK if user password exists.
+ result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
+ return result;
+ }
+
+ /**
+ * Decrypt a synthetic password by supplying an escrow token and corresponding token
+ * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
+ * verification to referesh the SID & Auth token maintained by the system.
+ */
+ public @NonNull AuthenticationResult unwrapTokenBasedSyntheticPassword(
+ IGateKeeperService gatekeeper, long handle, byte[] token, int userId)
+ throws RemoteException {
+ AuthenticationResult result = new AuthenticationResult();
+ byte[] secdiscardable = loadSecdiscardable(handle, userId);
+ int slotId = loadWeaverSlot(handle, userId);
+ if (slotId != INVALID_WEAVER_SLOT) {
+ if (!isWeaverAvailable()) {
+ Log.e(TAG, "No weaver service to unwrap token based SP");
+ result.gkResponse = VerifyCredentialResponse.ERROR;
+ return result;
+ }
+ VerifyCredentialResponse response = weaverVerify(slotId, null);
+ if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK ||
+ response.getPayload() == null) {
+ Log.e(TAG, "Failed to retrieve weaver secret when unwrapping token");
+ result.gkResponse = VerifyCredentialResponse.ERROR;
+ return result;
+ }
+ secdiscardable = SyntheticPasswordCrypto.decrypt(response.getPayload(),
+ PERSONALISATION_WEAVER_TOKEN, secdiscardable);
+ }
+ byte[] applicationId = transformUnderSecdiscardable(token, secdiscardable);
+ result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED,
+ applicationId, userId);
+ if (result.authToken != null) {
+ result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
+ if (result.gkResponse == null) {
+ // The user currently has no password. return OK with null payload so null
+ // is propagated to unlockUser()
+ result.gkResponse = VerifyCredentialResponse.OK;
+ }
+ } else {
+ result.gkResponse = VerifyCredentialResponse.ERROR;
+ }
+ return result;
+ }
+
+ private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type,
+ byte[] applicationId, int userId) {
+ byte[] blob = loadState(SP_BLOB_NAME, handle, userId);
+ if (blob == null) {
+ return null;
+ }
+ if (blob[0] != SYNTHETIC_PASSWORD_VERSION) {
+ throw new RuntimeException("Unknown blob version");
+ }
+ if (blob[1] != type) {
+ throw new RuntimeException("Invalid blob type");
+ }
+ byte[] secret = decryptSPBlob(getHandleName(handle),
+ Arrays.copyOfRange(blob, 2, blob.length), applicationId);
+ if (secret == null) {
+ Log.e(TAG, "Fail to decrypt SP for user " + userId);
+ return null;
+ }
+ AuthenticationToken result = new AuthenticationToken();
+ if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
+ if (!loadEscrowData(result, userId)) {
+ Log.e(TAG, "User is not escrowable: " + userId);
+ return null;
+ }
+ result.recreate(secret);
+ } else {
+ result.syntheticPassword = new String(secret);
+ }
+ return result;
+ }
+
+ /**
+ * performs GK verifyChallenge and returns auth token, re-enrolling SP password handle
+ * if required.
+ *
+ * Normally performing verifyChallenge with an AuthenticationToken should always return
+ * RESPONSE_OK, since user authentication failures are detected earlier when trying to
+ * decrypt SP.
+ */
+ public @Nullable VerifyCredentialResponse verifyChallenge(IGateKeeperService gatekeeper,
+ @NonNull AuthenticationToken auth, long challenge, int userId) throws RemoteException {
+ byte[] spHandle = loadSyntheticPasswordHandle(userId);
+ if (spHandle == null) {
+ // There is no password handle associated with the given user, i.e. the user is not
+ // secured by lockscreen and has no SID, so just return here;
+ return null;
+ }
+ VerifyCredentialResponse result;
+ GateKeeperResponse response = gatekeeper.verifyChallenge(userId, challenge,
+ spHandle, auth.deriveGkPassword());
+ int responseCode = response.getResponseCode();
+ if (responseCode == GateKeeperResponse.RESPONSE_OK) {
+ result = new VerifyCredentialResponse(response.getPayload());
+ if (response.getShouldReEnroll()) {
+ response = gatekeeper.enroll(userId, spHandle,
+ spHandle, auth.deriveGkPassword());
+ if (response.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
+ spHandle = response.getPayload();
+ saveSyntheticPasswordHandle(spHandle, userId);
+ // Call self again to re-verify with updated handle
+ return verifyChallenge(gatekeeper, auth, challenge, userId);
+ } else {
+ Log.w(TAG, "Fail to re-enroll SP handle for user " + userId);
+ // Fall through, return existing handle
+ }
+ }
+ } else if (responseCode == GateKeeperResponse.RESPONSE_RETRY) {
+ result = new VerifyCredentialResponse(response.getTimeout());
+ } else {
+ result = VerifyCredentialResponse.ERROR;
+ }
+ return result;
+ }
+
+ public boolean existsHandle(long handle, int userId) {
+ return hasState(SP_BLOB_NAME, handle, userId);
+ }
+
+ public void destroyTokenBasedSyntheticPassword(long handle, int userId) {
+ destroySyntheticPassword(handle, userId);
+ destroyState(SECDISCARDABLE_NAME, handle, userId);
+ }
+
+ public void destroyPasswordBasedSyntheticPassword(long handle, int userId) {
+ destroySyntheticPassword(handle, userId);
+ destroyState(SECDISCARDABLE_NAME, handle, userId);
+ destroyState(PASSWORD_DATA_NAME, handle, userId);
+ }
+
+ private void destroySyntheticPassword(long handle, int userId) {
+ destroyState(SP_BLOB_NAME, handle, userId);
+ destroySPBlobKey(getHandleName(handle));
+ if (hasState(WEAVER_SLOT_NAME, handle, userId)) {
+ destroyWeaverSlot(handle, userId);
+ }
+ }
+
+ private byte[] transformUnderWeaverSecret(byte[] data, byte[] secret) {
+ byte[] weaverSecret = SyntheticPasswordCrypto.personalisedHash(
+ PERSONALISATION_WEAVER_PASSWORD, secret);
+ byte[] result = new byte[data.length + weaverSecret.length];
+ System.arraycopy(data, 0, result, 0, data.length);
+ System.arraycopy(weaverSecret, 0, result, data.length, weaverSecret.length);
+ return result;
+ }
+
+ private byte[] transformUnderSecdiscardable(byte[] data, byte[] rawSecdiscardable) {
+ byte[] secdiscardable = SyntheticPasswordCrypto.personalisedHash(
+ PERSONALISATION_SECDISCARDABLE, rawSecdiscardable);
+ byte[] result = new byte[data.length + secdiscardable.length];
+ System.arraycopy(data, 0, result, 0, data.length);
+ System.arraycopy(secdiscardable, 0, result, data.length, secdiscardable.length);
+ return result;
+ }
+
+ private byte[] createSecdiscardable(long handle, int userId) {
+ byte[] data = secureRandom(SECDISCARDABLE_LENGTH);
+ saveSecdiscardable(handle, data, userId);
+ return data;
+ }
+
+ private void saveSecdiscardable(long handle, byte[] secdiscardable, int userId) {
+ saveState(SECDISCARDABLE_NAME, secdiscardable, handle, userId);
+ }
+
+ private byte[] loadSecdiscardable(long handle, int userId) {
+ return loadState(SECDISCARDABLE_NAME, handle, userId);
+ }
+
+ private boolean hasState(String stateName, long handle, int userId) {
+ return !ArrayUtils.isEmpty(loadState(stateName, handle, userId));
+ }
+
+ private byte[] loadState(String stateName, long handle, int userId) {
+ return mStorage.readSyntheticPasswordState(userId, handle, stateName);
+ }
+
+ private void saveState(String stateName, byte[] data, long handle, int userId) {
+ mStorage.writeSyntheticPasswordState(userId, handle, stateName, data);
+ }
+
+ private void destroyState(String stateName, long handle, int userId) {
+ mStorage.deleteSyntheticPasswordState(userId, handle, stateName);
+ }
+
+ protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] applicationId) {
+ return SyntheticPasswordCrypto.decryptBlob(blobKeyName, blob, applicationId);
+ }
+
+ protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] applicationId, long sid) {
+ return SyntheticPasswordCrypto.createBlob(blobKeyName, data, applicationId, sid);
+ }
+
+ protected void destroySPBlobKey(String keyAlias) {
+ SyntheticPasswordCrypto.destroyBlobKey(keyAlias);
+ }
+
+ public static long generateHandle() {
+ SecureRandom rng = new SecureRandom();
+ long result;
+ do {
+ result = rng.nextLong();
+ } while (result == DEFAULT_HANDLE);
+ return result;
+ }
+
+ private int fakeUid(int uid) {
+ return 100000 + uid;
+ }
+
+ protected static byte[] secureRandom(int length) {
+ try {
+ return SecureRandom.getInstance("SHA1PRNG").generateSeed(length);
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private String getHandleName(long handle) {
+ return String.format("%s%x", LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX, handle);
+ }
+
+ private byte[] computePasswordToken(String password, PasswordData data) {
+ return scrypt(password, data.salt, 1 << data.scryptN, 1 << data.scryptR, 1 << data.scryptP,
+ PASSWORD_TOKEN_LENGTH);
+ }
+
+ private byte[] passwordTokenToGkInput(byte[] token) {
+ return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_USER_GK_AUTH, token);
+ }
+
+ private byte[] passwordTokenToWeaverKey(byte[] token) {
+ byte[] key = SyntheticPasswordCrypto.personalisedHash(PERSONALISATION_WEAVER_KEY, token);
+ if (key.length < mWeaverConfig.keySize) {
+ throw new RuntimeException("weaver key length too small");
+ }
+ return Arrays.copyOf(key, mWeaverConfig.keySize);
+ }
+
+ protected long sidFromPasswordHandle(byte[] handle) {
+ return nativeSidFromPasswordHandle(handle);
+ }
+
+ protected byte[] scrypt(String password, byte[] salt, int N, int r, int p, int outLen) {
+ return nativeScrypt(password.getBytes(), salt, N, r, p, outLen);
+ }
+
+ native long nativeSidFromPasswordHandle(byte[] handle);
+ native byte[] nativeScrypt(byte[] password, byte[] salt, int N, int r, int p, int outLen);
+
+ protected static ArrayList<Byte> toByteArrayList(byte[] data) {
+ ArrayList<Byte> result = new ArrayList<Byte>(data.length);
+ for (int i = 0; i < data.length; i++) {
+ result.add(data[i]);
+ }
+ return result;
+ }
+
+ protected static byte[] fromByteArrayList(ArrayList<Byte> data) {
+ byte[] result = new byte[data.size()];
+ for (int i = 0; i < data.size(); i++) {
+ result[i] = data.get(i);
+ }
+ return result;
+ }
+
+ final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
+ public static String bytesToHex(byte[] bytes) {
+ if (bytes == null) {
+ return "null";
+ }
+ char[] hexChars = new char[bytes.length * 2];
+ for ( int j = 0; j < bytes.length; j++ ) {
+ int v = bytes[j] & 0xFF;
+ hexChars[j * 2] = hexArray[v >>> 4];
+ hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+ }
+ return new String(hexChars);
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 7b0e51e..d1fa5ef 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -198,7 +198,7 @@
// A null routeId means that the client wants to unselect its current route.
// The explicit flag indicates whether the change was explicitly requested by the
// user or the application which may cause changes to propagate out to the rest
- // of the system. Should be false when the change is in response to a new globally
+ // of the system. Should be false when the change is in response to a new
// selected route or a default selection.
@Override
public void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit) {
@@ -387,15 +387,14 @@
}
clientRecord.mSelectedRouteId = routeId;
- if (explicit) {
- // Any app can disconnect from the globally selected route.
+ // Only let the system connect to new global routes for now.
+ // A similar check exists in the display manager for wifi display.
+ if (explicit && clientRecord.mTrusted) {
if (oldRouteId != null) {
clientRecord.mUserRecord.mHandler.obtainMessage(
UserHandler.MSG_UNSELECT_ROUTE, oldRouteId).sendToTarget();
}
- // Only let the system connect to new global routes for now.
- // A similar check exists in the display manager for wifi display.
- if (routeId != null && clientRecord.mTrusted) {
+ if (routeId != null) {
clientRecord.mUserRecord.mHandler.obtainMessage(
UserHandler.MSG_SELECT_ROUTE, routeId).sendToTarget();
}
@@ -517,7 +516,7 @@
}
MediaRouterClientState getState() {
- return mTrusted ? mUserRecord.mTrustedState : mUserRecord.mUntrustedState;
+ return mTrusted ? mUserRecord.mRouterState : null;
}
public void dump(PrintWriter pw, String prefix) {
@@ -544,8 +543,7 @@
public final int mUserId;
public final ArrayList<ClientRecord> mClientRecords = new ArrayList<ClientRecord>();
public final UserHandler mHandler;
- public MediaRouterClientState mTrustedState;
- public MediaRouterClientState mUntrustedState;
+ public MediaRouterClientState mRouterState;
public UserRecord(int userId) {
mUserId = userId;
@@ -566,8 +564,7 @@
}
pw.println(indent + "State");
- pw.println(indent + "mTrustedState=" + mTrustedState);
- pw.println(indent + "mUntrustedState=" + mUntrustedState);
+ pw.println(indent + "mRouterState=" + mRouterState);
if (!mHandler.runWithScissors(new Runnable() {
@Override
@@ -592,13 +589,6 @@
* this class encapsulates all of the associated functionality and exports state
* to the service as it evolves.
* </p><p>
- * One important task of this class is to keep track of the current globally selected
- * route id for certain routes that have global effects, such as remote displays.
- * Global route selections override local selections made within apps. The change
- * is propagated to all apps so that they are all in sync. Synchronization works
- * both ways. Whenever the globally selected route is explicitly unselected by any
- * app, then it becomes unselected globally and all apps are informed.
- * </p><p>
* This class is currently hardcoded to work with remote display providers but
* it is intended to be eventually extended to support more general route providers
* similar to the support library media router.
@@ -639,7 +629,7 @@
private boolean mRunning;
private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
- private RouteRecord mGloballySelectedRouteRecord;
+ private RouteRecord mSelectedRouteRecord;
private int mConnectionPhase = PHASE_NOT_AVAILABLE;
private int mConnectionTimeoutReason;
private long mConnectionTimeoutStartTime;
@@ -701,7 +691,7 @@
final String indent = prefix + " ";
pw.println(indent + "mRunning=" + mRunning);
pw.println(indent + "mDiscoveryMode=" + mDiscoveryMode);
- pw.println(indent + "mGloballySelectedRouteRecord=" + mGloballySelectedRouteRecord);
+ pw.println(indent + "mSelectedRouteRecord=" + mSelectedRouteRecord);
pw.println(indent + "mConnectionPhase=" + mConnectionPhase);
pw.println(indent + "mConnectionTimeoutReason=" + mConnectionTimeoutReason);
pw.println(indent + "mConnectionTimeoutStartTime=" + (mConnectionTimeoutReason != 0 ?
@@ -729,7 +719,7 @@
private void stop() {
if (mRunning) {
mRunning = false;
- unselectGloballySelectedRoute();
+ unselectSelectedRoute();
mWatcher.stop(); // also stops all providers
}
}
@@ -768,15 +758,15 @@
private void selectRoute(String routeId) {
if (routeId != null
- && (mGloballySelectedRouteRecord == null
- || !routeId.equals(mGloballySelectedRouteRecord.getUniqueId()))) {
+ && (mSelectedRouteRecord == null
+ || !routeId.equals(mSelectedRouteRecord.getUniqueId()))) {
RouteRecord routeRecord = findRouteRecord(routeId);
if (routeRecord != null) {
- unselectGloballySelectedRoute();
+ unselectSelectedRoute();
- Slog.i(TAG, "Selected global route:" + routeRecord);
- mGloballySelectedRouteRecord = routeRecord;
- checkGloballySelectedRouteState();
+ Slog.i(TAG, "Selected route:" + routeRecord);
+ mSelectedRouteRecord = routeRecord;
+ checkSelectedRouteState();
routeRecord.getProvider().setSelectedDisplay(routeRecord.getDescriptorId());
scheduleUpdateClientState();
@@ -786,34 +776,34 @@
private void unselectRoute(String routeId) {
if (routeId != null
- && mGloballySelectedRouteRecord != null
- && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
- unselectGloballySelectedRoute();
+ && mSelectedRouteRecord != null
+ && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
+ unselectSelectedRoute();
}
}
- private void unselectGloballySelectedRoute() {
- if (mGloballySelectedRouteRecord != null) {
- Slog.i(TAG, "Unselected global route:" + mGloballySelectedRouteRecord);
- mGloballySelectedRouteRecord.getProvider().setSelectedDisplay(null);
- mGloballySelectedRouteRecord = null;
- checkGloballySelectedRouteState();
+ private void unselectSelectedRoute() {
+ if (mSelectedRouteRecord != null) {
+ Slog.i(TAG, "Unselected route:" + mSelectedRouteRecord);
+ mSelectedRouteRecord.getProvider().setSelectedDisplay(null);
+ mSelectedRouteRecord = null;
+ checkSelectedRouteState();
scheduleUpdateClientState();
}
}
private void requestSetVolume(String routeId, int volume) {
- if (mGloballySelectedRouteRecord != null
- && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
- mGloballySelectedRouteRecord.getProvider().setDisplayVolume(volume);
+ if (mSelectedRouteRecord != null
+ && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
+ mSelectedRouteRecord.getProvider().setDisplayVolume(volume);
}
}
private void requestUpdateVolume(String routeId, int direction) {
- if (mGloballySelectedRouteRecord != null
- && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
- mGloballySelectedRouteRecord.getProvider().adjustDisplayVolume(direction);
+ if (mSelectedRouteRecord != null
+ && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
+ mSelectedRouteRecord.getProvider().adjustDisplayVolume(direction);
}
}
@@ -839,7 +829,7 @@
provider.setCallback(null);
provider.setDiscoveryMode(RemoteDisplayState.DISCOVERY_MODE_NONE);
- checkGloballySelectedRouteState();
+ checkSelectedRouteState();
scheduleUpdateClientState();
}
}
@@ -856,35 +846,34 @@
if (index >= 0) {
ProviderRecord providerRecord = mProviderRecords.get(index);
if (providerRecord.updateDescriptor(state)) {
- checkGloballySelectedRouteState();
+ checkSelectedRouteState();
scheduleUpdateClientState();
}
}
}
/**
- * This function is called whenever the state of the globally selected route
- * may have changed. It checks the state and updates timeouts or unselects
- * the route as appropriate.
+ * This function is called whenever the state of the selected route may have changed.
+ * It checks the state and updates timeouts or unselects the route as appropriate.
*/
- private void checkGloballySelectedRouteState() {
+ private void checkSelectedRouteState() {
// Unschedule timeouts when the route is unselected.
- if (mGloballySelectedRouteRecord == null) {
+ if (mSelectedRouteRecord == null) {
mConnectionPhase = PHASE_NOT_AVAILABLE;
updateConnectionTimeout(0);
return;
}
// Ensure that the route is still present and enabled.
- if (!mGloballySelectedRouteRecord.isValid()
- || !mGloballySelectedRouteRecord.isEnabled()) {
+ if (!mSelectedRouteRecord.isValid()
+ || !mSelectedRouteRecord.isEnabled()) {
updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
return;
}
// Make sure we haven't lost our connection.
final int oldPhase = mConnectionPhase;
- mConnectionPhase = getConnectionPhase(mGloballySelectedRouteRecord.getStatus());
+ mConnectionPhase = getConnectionPhase(mSelectedRouteRecord.getStatus());
if (oldPhase >= PHASE_CONNECTING && mConnectionPhase < PHASE_CONNECTING) {
updateConnectionTimeout(TIMEOUT_REASON_CONNECTION_LOST);
return;
@@ -894,15 +883,13 @@
switch (mConnectionPhase) {
case PHASE_CONNECTED:
if (oldPhase != PHASE_CONNECTED) {
- Slog.i(TAG, "Connected to global route: "
- + mGloballySelectedRouteRecord);
+ Slog.i(TAG, "Connected to route: " + mSelectedRouteRecord);
}
updateConnectionTimeout(0);
break;
case PHASE_CONNECTING:
if (oldPhase != PHASE_CONNECTING) {
- Slog.i(TAG, "Connecting to global route: "
- + mGloballySelectedRouteRecord);
+ Slog.i(TAG, "Connecting to route: " + mSelectedRouteRecord);
}
updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTED);
break;
@@ -943,7 +930,7 @@
}
private void connectionTimedOut() {
- if (mConnectionTimeoutReason == 0 || mGloballySelectedRouteRecord == null) {
+ if (mConnectionTimeoutReason == 0 || mSelectedRouteRecord == null) {
// Shouldn't get here. There must be a bug somewhere.
Log.wtf(TAG, "Handled connection timeout for no reason.");
return;
@@ -951,28 +938,28 @@
switch (mConnectionTimeoutReason) {
case TIMEOUT_REASON_NOT_AVAILABLE:
- Slog.i(TAG, "Global route no longer available: "
- + mGloballySelectedRouteRecord);
+ Slog.i(TAG, "Selected route no longer available: "
+ + mSelectedRouteRecord);
break;
case TIMEOUT_REASON_CONNECTION_LOST:
- Slog.i(TAG, "Global route connection lost: "
- + mGloballySelectedRouteRecord);
+ Slog.i(TAG, "Selected route connection lost: "
+ + mSelectedRouteRecord);
break;
case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
- Slog.i(TAG, "Global route timed out while waiting for "
+ Slog.i(TAG, "Selected route timed out while waiting for "
+ "connection attempt to begin after "
+ (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
- + " ms: " + mGloballySelectedRouteRecord);
+ + " ms: " + mSelectedRouteRecord);
break;
case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
- Slog.i(TAG, "Global route timed out while connecting after "
+ Slog.i(TAG, "Selected route timed out while connecting after "
+ (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
- + " ms: " + mGloballySelectedRouteRecord);
+ + " ms: " + mSelectedRouteRecord);
break;
}
mConnectionTimeoutReason = 0;
- unselectGloballySelectedRoute();
+ unselectSelectedRoute();
}
private void scheduleUpdateClientState() {
@@ -985,30 +972,17 @@
private void updateClientState() {
mClientStateUpdateScheduled = false;
- final String globallySelectedRouteId = mGloballySelectedRouteRecord != null ?
- mGloballySelectedRouteRecord.getUniqueId() : null;
-
// Build a new client state for trusted clients.
- MediaRouterClientState trustedState = new MediaRouterClientState();
- trustedState.globallySelectedRouteId = globallySelectedRouteId;
+ MediaRouterClientState routerState = new MediaRouterClientState();
final int providerCount = mProviderRecords.size();
for (int i = 0; i < providerCount; i++) {
- mProviderRecords.get(i).appendClientState(trustedState);
- }
-
- // Build a new client state for untrusted clients that can only see
- // the currently selected route.
- MediaRouterClientState untrustedState = new MediaRouterClientState();
- untrustedState.globallySelectedRouteId = globallySelectedRouteId;
- if (globallySelectedRouteId != null) {
- untrustedState.routes.add(trustedState.getRoute(globallySelectedRouteId));
+ mProviderRecords.get(i).appendClientState(routerState);
}
try {
synchronized (mService.mLock) {
// Update the UserRecord.
- mUserRecord.mTrustedState = trustedState;
- mUserRecord.mUntrustedState = untrustedState;
+ mUserRecord.mRouterState = routerState;
// Collect all clients.
final int count = mUserRecord.mClientRecords.size();
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index a275f49..89e1050 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -320,6 +320,15 @@
}
/**
+ * Get the playback state.
+ *
+ * @return The current playback state.
+ */
+ public PlaybackState getPlaybackState() {
+ return mPlaybackState;
+ }
+
+ /**
* Check if the session is currently performing playback.
*
* @return True if the session is performing playback, false otherwise.
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index f03f630..d9fe72e 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -153,7 +153,7 @@
mCachedVolumeDefault = null;
}
- // In most cases, playback state isn't needed for finding media buttion session,
+ // In most cases, playback state isn't needed for finding media button session,
// but we only use it as a hint if an app has multiple local media sessions.
// In that case, we pick the media session whose PlaybackState matches
// the audio playback configuration.
@@ -204,8 +204,9 @@
/**
* Find the media button session with the given {@param uid}.
- * If the app has multiple media sessions, the media session matches the audio playback state
- * becomes the media button session.
+ * If the app has multiple media sessions, the media session whose playback state is not null
+ * and matches the audio playback state becomes the media button session. Otherwise the top
+ * priority session becomes the media button session.
*
* @return The media button session. Returns {@code null} if the app doesn't have a media
* session.
@@ -214,7 +215,7 @@
MediaSessionRecord mediaButtonSession = null;
for (MediaSessionRecord session : mSessions) {
if (uid == session.getUid()) {
- if (session.isPlaybackActive() ==
+ if (session.getPlaybackState() != null && session.isPlaybackActive() ==
mAudioPlaybackMonitor.isPlaybackActive(session.getUid())) {
// If there's a media session whose PlaybackState matches
// the audio playback state, return it immediately.
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 5eb1277..9e5b92a 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -141,7 +141,6 @@
" " + mAcceptedEgressIface + "->" + egressIface);
if (egressDisconnected || egressChanged) {
- clearSourceRulesLocked();
mAcceptedEgressIface = null;
mVpn.stopLegacyVpnPrivileged();
}
@@ -193,24 +192,6 @@
EventLogTags.writeLockdownVpnConnected(egressType);
showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
- try {
- clearSourceRulesLocked();
-
- mNetService.setFirewallInterfaceRule(iface, true);
- for (LinkAddress addr : sourceAddrs) {
- setFirewallEgressSourceRule(addr, true);
- }
-
- mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, ROOT_UID, FIREWALL_RULE_ALLOW);
- mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, Os.getuid(), FIREWALL_RULE_ALLOW);
-
- mErrorCount = 0;
- mAcceptedIface = iface;
- mAcceptedSourceAddr = sourceAddrs;
- } catch (RemoteException e) {
- throw new RuntimeException("Problem setting firewall rules", e);
- }
-
final NetworkInfo clone = new NetworkInfo(egressInfo);
augmentNetworkInfo(clone);
mConnService.sendConnectedBroadcast(clone);
@@ -227,19 +208,11 @@
Slog.d(TAG, "initLocked()");
mVpn.setEnableTeardown(false);
+ mVpn.setLockdown(true);
final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
- try {
- // TODO: support non-standard port numbers
- mNetService.setFirewallEgressDestRule(mProfile.server, 500, true);
- mNetService.setFirewallEgressDestRule(mProfile.server, 4500, true);
- mNetService.setFirewallEgressDestRule(mProfile.server, 1701, true);
- } catch (RemoteException e) {
- throw new RuntimeException("Problem setting firewall rules", e);
- }
-
handleStateChangedLocked();
}
@@ -256,14 +229,7 @@
mErrorCount = 0;
mVpn.stopLegacyVpnPrivileged();
- try {
- mNetService.setFirewallEgressDestRule(mProfile.server, 500, false);
- mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false);
- mNetService.setFirewallEgressDestRule(mProfile.server, 1701, false);
- } catch (RemoteException e) {
- throw new RuntimeException("Problem setting firewall rules", e);
- }
- clearSourceRulesLocked();
+ mVpn.setLockdown(false);
hideNotification();
mContext.unregisterReceiver(mResetReceiver);
@@ -280,35 +246,6 @@
}
}
- private void clearSourceRulesLocked() {
- try {
- if (mAcceptedIface != null) {
- mNetService.setFirewallInterfaceRule(mAcceptedIface, false);
- mAcceptedIface = null;
- }
- if (mAcceptedSourceAddr != null) {
- for (LinkAddress addr : mAcceptedSourceAddr) {
- setFirewallEgressSourceRule(addr, false);
- }
-
- mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, ROOT_UID, FIREWALL_RULE_DEFAULT);
- mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE,Os.getuid(), FIREWALL_RULE_DEFAULT);
-
- mAcceptedSourceAddr = null;
- }
- } catch (RemoteException e) {
- throw new RuntimeException("Problem setting firewall rules", e);
- }
- }
-
- private void setFirewallEgressSourceRule(
- LinkAddress address, boolean allow) throws RemoteException {
- // Our source address based firewall rules must only cover our own source address, not the
- // whole subnet
- final String addrString = address.getAddress().getHostAddress();
- mNetService.setFirewallEgressSourceRule(addrString, allow);
- }
-
public void onNetworkInfoChanged() {
synchronized (mStateLock) {
handleStateChangedLocked();
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index d9ca00c..b89d247 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -3332,7 +3332,7 @@
// Second step: apply bw changes based on change of state.
if (newRule != oldRule) {
- if ((newRule & RULE_TEMPORARY_ALLOW_METERED) != 0) {
+ if (hasRule(newRule, RULE_TEMPORARY_ALLOW_METERED)) {
// Temporarily whitelist foreground app, removing from blacklist if necessary
// (since bw_penalty_box prevails over bw_happy_box).
@@ -3343,7 +3343,7 @@
if (isBlacklisted) {
setMeteredNetworkBlacklist(uid, false);
}
- } else if ((oldRule & RULE_TEMPORARY_ALLOW_METERED) != 0) {
+ } else if (hasRule(oldRule, RULE_TEMPORARY_ALLOW_METERED)) {
// Remove temporary whitelist from app that is not on foreground anymore.
// TODO: if statements below are used to avoid unnecessary calls to netd / iptables,
@@ -3356,18 +3356,18 @@
if (isBlacklisted) {
setMeteredNetworkBlacklist(uid, true);
}
- } else if ((newRule & RULE_REJECT_METERED) != 0
- || (oldRule & RULE_REJECT_METERED) != 0) {
+ } else if (hasRule(newRule, RULE_REJECT_METERED)
+ || hasRule(oldRule, RULE_REJECT_METERED)) {
// Flip state because app was explicitly added or removed to blacklist.
setMeteredNetworkBlacklist(uid, isBlacklisted);
- if ((oldRule & RULE_REJECT_METERED) != 0 && isWhitelisted) {
+ if (hasRule(oldRule, RULE_REJECT_METERED) && isWhitelisted) {
// Since blacklist prevails over whitelist, we need to handle the special case
// where app is whitelisted and blacklisted at the same time (although such
// scenario should be blocked by the UI), then blacklist is removed.
setMeteredNetworkWhitelist(uid, isWhitelisted);
}
- } else if ((newRule & RULE_ALLOW_METERED) != 0
- || (oldRule & RULE_ALLOW_METERED) != 0) {
+ } else if (hasRule(newRule, RULE_ALLOW_METERED)
+ || hasRule(oldRule, RULE_ALLOW_METERED)) {
// Flip state because app was explicitly added or removed to whitelist.
setMeteredNetworkWhitelist(uid, isWhitelisted);
} else {
@@ -3480,9 +3480,9 @@
// Second step: notify listeners if state changed.
if (newRule != oldRule) {
- if (newRule == RULE_NONE || (newRule & RULE_ALLOW_ALL) != 0) {
+ if (newRule == RULE_NONE || hasRule(newRule, RULE_ALLOW_ALL)) {
if (LOGV) Log.v(TAG, "Allowing non-metered access for UID " + uid);
- } else if ((newRule & RULE_REJECT_ALL) != 0) {
+ } else if (hasRule(newRule, RULE_REJECT_ALL)) {
if (LOGV) Log.v(TAG, "Rejecting non-metered access for UID " + uid);
} else {
// All scenarios should have been covered above
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index e746355..ab685ca 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -18,7 +18,6 @@
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
-import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.MODIFY_NETWORK_ACCOUNTING;
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
import static android.content.Intent.ACTION_SHUTDOWN;
@@ -31,7 +30,6 @@
import static android.net.NetworkStats.SET_ALL;
import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.SET_FOREGROUND;
-import static android.net.NetworkStats.TAG_ALL;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static android.net.NetworkTemplate.buildTemplateMobileWildcard;
@@ -58,14 +56,13 @@
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
-import static com.android.internal.util.Preconditions.checkArgument;
+
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats;
import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet;
import android.app.AlarmManager;
-import android.app.IAlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -1041,7 +1038,7 @@
// snapshot and record current counters; read UID stats first to
// avoid over counting dev stats.
final NetworkStats uidSnapshot = getNetworkStatsUidDetail();
- final NetworkStats xtSnapshot = getNetworkStatsXtAndVt();
+ final NetworkStats xtSnapshot = getNetworkStatsXt();
final NetworkStats devSnapshot = mNetworkManager.getNetworkStatsSummaryDev();
@@ -1367,7 +1364,8 @@
/**
* Return snapshot of current UID statistics, including any
- * {@link TrafficStats#UID_TETHERING} and {@link #mUidOperations} values.
+ * {@link TrafficStats#UID_TETHERING}, video calling data usage, and {@link #mUidOperations}
+ * values.
*/
private NetworkStats getNetworkStatsUidDetail() throws RemoteException {
final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
@@ -1375,43 +1373,34 @@
// fold tethering stats and operations into uid snapshot
final NetworkStats tetherSnapshot = getNetworkStatsTethering();
uidSnapshot.combineAllValues(tetherSnapshot);
+
+ final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
+
+ // fold video calling data usage stats into uid snapshot
+ final NetworkStats vtStats = telephonyManager.getVtDataUsage(true);
+ if (vtStats != null) {
+ uidSnapshot.combineAllValues(vtStats);
+ }
uidSnapshot.combineAllValues(mUidOperations);
return uidSnapshot;
}
/**
- * Return snapshot of current XT plus VT statistics.
+ * Return snapshot of current XT statistics with video calling data usage statistics.
*/
- private NetworkStats getNetworkStatsXtAndVt() throws RemoteException {
+ private NetworkStats getNetworkStatsXt() throws RemoteException {
final NetworkStats xtSnapshot = mNetworkManager.getNetworkStatsSummaryXt();
- TelephonyManager tm = (TelephonyManager) mContext.getSystemService(
+ final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
Context.TELEPHONY_SERVICE);
- long usage = tm.getVtDataUsage();
-
- if (LOGV) Slog.d(TAG, "VT call data usage = " + usage);
-
- final NetworkStats vtSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 1);
-
- final NetworkStats.Entry entry = new NetworkStats.Entry();
- entry.iface = VT_INTERFACE;
- entry.uid = -1;
- entry.set = TAG_ALL;
- entry.tag = TAG_NONE;
-
- // Since modem only tell us the total usage instead of each usage for RX and TX,
- // we need to split it up (though it might not quite accurate). At
- // least we can make sure the data usage report to the user will still be accurate.
- entry.rxBytes = usage / 2;
- entry.rxPackets = 0;
- entry.txBytes = usage - entry.rxBytes;
- entry.txPackets = 0;
- vtSnapshot.combineValues(entry);
-
- // Merge VT int XT
- xtSnapshot.combineAllValues(vtSnapshot);
+ // Merge video calling data usage into XT
+ final NetworkStats vtSnapshot = telephonyManager.getVtDataUsage(false);
+ if (vtSnapshot != null) {
+ xtSnapshot.combineAllValues(vtSnapshot);
+ }
return xtSnapshot;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 19d5f3c..f038f9a 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -945,8 +945,8 @@
public void update(Uri uri) {
ContentResolver resolver = getContext().getContentResolver();
if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
- boolean pulseEnabled = Settings.System.getInt(resolver,
- Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0;
+ boolean pulseEnabled = Settings.System.getIntForUser(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT) != 0;
if (mNotificationPulseEnabled != pulseEnabled) {
mNotificationPulseEnabled = pulseEnabled;
updateNotificationPulse();
diff --git a/services/core/java/com/android/server/oemlock/OemLock.java b/services/core/java/com/android/server/oemlock/OemLock.java
new file mode 100644
index 0000000..ee70c29
--- /dev/null
+++ b/services/core/java/com/android/server/oemlock/OemLock.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.oemlock;
+
+import android.annotation.Nullable;
+
+abstract class OemLock {
+ abstract void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature);
+ abstract boolean isOemUnlockAllowedByCarrier();
+
+ abstract void setOemUnlockAllowedByDevice(boolean allowedByDevice);
+ abstract boolean isOemUnlockAllowedByDevice();
+}
diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java
new file mode 100644
index 0000000..5e19b13
--- /dev/null
+++ b/services/core/java/com/android/server/oemlock/OemLockService.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.oemlock;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.oemlock.V1_0.IOemLock;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.UserManagerInternal;
+import android.os.UserManagerInternal.UserRestrictionsListener;
+import android.service.oemlock.IOemLockService;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.pm.UserRestrictionsUtils;
+
+/**
+ * Service for managing the OEM lock state of the device.
+ *
+ * The OemLock HAL will be used if it is available, otherwise the persistent data block will be
+ * used.
+ */
+public class OemLockService extends SystemService {
+ private static final String TAG = "OemLock";
+
+ private static final String FLASH_LOCK_PROP = "ro.boot.flash.locked";
+ private static final String FLASH_LOCK_UNLOCKED = "0";
+
+ private Context mContext;
+ private OemLock mOemLock;
+
+ public static boolean isHalPresent() {
+ return VendorLock.getOemLockHalService() != null;
+ }
+
+ /** Select the OEM lock implementation */
+ private static OemLock getOemLock(Context context) {
+ final IOemLock oemLockHal = VendorLock.getOemLockHalService();
+ if (oemLockHal != null) {
+ Slog.i(TAG, "Using vendor lock via the HAL");
+ return new VendorLock(context, oemLockHal);
+ } else {
+ Slog.i(TAG, "Using persistent data block based lock");
+ return new PersistentDataBlockLock(context);
+ }
+ }
+
+ public OemLockService(Context context) {
+ this(context, getOemLock(context));
+ }
+
+ OemLockService(Context context, OemLock oemLock) {
+ super(context);
+ mContext = context;
+ mOemLock = oemLock;
+
+ LocalServices.getService(UserManagerInternal.class)
+ .addUserRestrictionsListener(mUserRestrictionsListener);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.OEM_LOCK_SERVICE, mService);
+ }
+
+ private final UserRestrictionsListener mUserRestrictionsListener =
+ new UserRestrictionsListener() {
+ @Override
+ public void onUserRestrictionsChanged(int userId, Bundle newRestrictions,
+ Bundle prevRestrictions) {
+ // The admin can prevent OEM unlock with the DISALLOW_FACTORY_RESET user restriction
+ if (UserRestrictionsUtils.restrictionsChanged(prevRestrictions, newRestrictions,
+ UserManager.DISALLOW_FACTORY_RESET)) {
+ final boolean unlockAllowedByAdmin =
+ !newRestrictions.getBoolean(UserManager.DISALLOW_FACTORY_RESET);
+ if (!unlockAllowedByAdmin) {
+ mOemLock.setOemUnlockAllowedByDevice(false);
+ }
+ }
+ }
+ };
+
+ /**
+ * Implements the binder interface for the service.
+ *
+ * This checks for the relevant permissions before forwarding the call to the OEM lock
+ * implementation being used on this device.
+ */
+ private final IBinder mService = new IOemLockService.Stub() {
+ @Override
+ public void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) {
+ enforceManageCarrierOemUnlockPermission();
+ enforceUserIsAdmin();
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mOemLock.setOemUnlockAllowedByCarrier(allowed, signature);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean isOemUnlockAllowedByCarrier() {
+ enforceManageCarrierOemUnlockPermission();
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mOemLock.isOemUnlockAllowedByCarrier();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // The user has the final say so if they allow unlock, then the device allows the bootloader
+ // to OEM unlock it.
+ @Override
+ public void setOemUnlockAllowedByUser(boolean allowedByUser) {
+ if (ActivityManager.isUserAMonkey()) {
+ // Prevent a monkey from changing this
+ return;
+ }
+
+ enforceManageUserOemUnlockPermission();
+ enforceUserIsAdmin();
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!canUserAllowOemUnlock()) {
+ throw new SecurityException("User cannot allow OEM unlock");
+ }
+
+ mOemLock.setOemUnlockAllowedByDevice(allowedByUser);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean isOemUnlockAllowedByUser() {
+ enforceManageUserOemUnlockPermission();
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mOemLock.isOemUnlockAllowedByDevice();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean canUserAllowOemUnlock() {
+ enforceOemUnlockReadPermission();
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return isOemUnlockAllowedByAdmin() && mOemLock.isOemUnlockAllowedByCarrier();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean isOemUnlockAllowed() {
+ enforceOemUnlockReadPermission();
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mOemLock.isOemUnlockAllowedByCarrier() &&
+ mOemLock.isOemUnlockAllowedByDevice();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean isDeviceOemUnlocked() {
+ enforceOemUnlockReadPermission();
+
+ String locked = SystemProperties.get(FLASH_LOCK_PROP);
+ switch (locked) {
+ case FLASH_LOCK_UNLOCKED:
+ return true;
+ default:
+ return false;
+ }
+ }
+ };
+
+ private boolean isOemUnlockAllowedByAdmin() {
+ return !UserManager.get(mContext)
+ .hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET, UserHandle.SYSTEM);
+ }
+
+ private void enforceManageCarrierOemUnlockPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_CARRIER_OEM_UNLOCK_STATE,
+ "Can't manage OEM unlock allowed by carrier");
+ }
+
+ private void enforceManageUserOemUnlockPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_USER_OEM_UNLOCK_STATE,
+ "Can't manage OEM unlock allowed by user");
+ }
+
+ private void enforceOemUnlockReadPermission() {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.READ_OEM_UNLOCK_STATE)
+ == PackageManager.PERMISSION_DENIED
+ && mContext.checkCallingOrSelfPermission(Manifest.permission.OEM_UNLOCK_STATE)
+ == PackageManager.PERMISSION_DENIED) {
+ throw new SecurityException("Can't access OEM unlock state. Requires "
+ + "READ_OEM_UNLOCK_STATE or OEM_UNLOCK_STATE permission.");
+ }
+ }
+
+ private void enforceUserIsAdmin() {
+ final int userId = UserHandle.getCallingUserId();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!UserManager.get(mContext).isUserAdmin(userId)) {
+ throw new SecurityException("Must be an admin user");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/oemlock/PersistentDataBlockLock.java b/services/core/java/com/android/server/oemlock/PersistentDataBlockLock.java
new file mode 100644
index 0000000..d9362d4
--- /dev/null
+++ b/services/core/java/com/android/server/oemlock/PersistentDataBlockLock.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.oemlock;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.service.persistentdata.PersistentDataBlockManager;
+import android.util.Slog;
+
+/**
+ * Implementation of the OEM lock using the persistent data block to communicate with the
+ * bootloader.
+ *
+ * The carrier flag is stored as a user restriction on the system user. The user flag is set in the
+ * presistent data block but depends on the carrier flag.
+ */
+class PersistentDataBlockLock extends OemLock {
+ private static final String TAG = "OemLock";
+
+ private Context mContext;
+
+ PersistentDataBlockLock(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) {
+ // Note: this implementation does not require a signature
+ if (signature != null) {
+ Slog.w(TAG, "Signature provided but is not being used");
+ }
+
+ // Continue using user restriction for backwards compatibility
+ UserManager.get(mContext).setUserRestriction(
+ UserManager.DISALLOW_OEM_UNLOCK, !allowed, UserHandle.SYSTEM);
+
+ if (!allowed) {
+ disallowUnlockIfNotUnlocked();
+ }
+ }
+
+ @Override
+ boolean isOemUnlockAllowedByCarrier() {
+ return !UserManager.get(mContext)
+ .hasUserRestriction(UserManager.DISALLOW_OEM_UNLOCK, UserHandle.SYSTEM);
+ }
+
+ @Override
+ void setOemUnlockAllowedByDevice(boolean allowedByDevice) {
+ // The method name is misleading as it really just means whether or not the device can be
+ // unlocked but doesn't actually do any unlocking.
+ final PersistentDataBlockManager pdbm = (PersistentDataBlockManager)
+ mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+ pdbm.setOemUnlockEnabled(allowedByDevice);
+ }
+
+ @Override
+ boolean isOemUnlockAllowedByDevice() {
+ final PersistentDataBlockManager pdbm = (PersistentDataBlockManager)
+ mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+ return pdbm.getOemUnlockEnabled();
+ }
+
+ /**
+ * Update state to prevent the bootloader from being able to unlock the device unless the device
+ * has already been unlocked by the bootloader in which case it is too late as it would remain
+ * unlocked.
+ */
+ private void disallowUnlockIfNotUnlocked() {
+ final PersistentDataBlockManager pdbm = (PersistentDataBlockManager)
+ mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+ if (pdbm.getFlashLockState() != PersistentDataBlockManager.FLASH_LOCK_UNLOCKED) {
+ pdbm.setOemUnlockEnabled(false);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/oemlock/VendorLock.java b/services/core/java/com/android/server/oemlock/VendorLock.java
new file mode 100644
index 0000000..1b9de36
--- /dev/null
+++ b/services/core/java/com/android/server/oemlock/VendorLock.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.oemlock;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.oemlock.V1_0.IOemLock;
+import android.hardware.oemlock.V1_0.OemLockSecureStatus;
+import android.hardware.oemlock.V1_0.OemLockStatus;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+
+/**
+ * Uses the OEM lock HAL.
+ */
+class VendorLock extends OemLock {
+ private static final String TAG = "OemLock";
+
+ private Context mContext;
+ private IOemLock mOemLock;
+
+ static IOemLock getOemLockHalService() {
+ try {
+ return IOemLock.getService();
+ } catch (NoSuchElementException e) {
+ Slog.i(TAG, "OemLock HAL not present on device");
+ return null;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ VendorLock(Context context, IOemLock oemLock) {
+ mContext = context;
+ mOemLock = oemLock;
+ }
+
+ @Override
+ void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) {
+ try {
+ switch (mOemLock.setOemUnlockAllowedByCarrier(allowed, toByteArrayList(signature))) {
+ case OemLockSecureStatus.OK:
+ Slog.i(TAG, "Updated carrier allows OEM lock state to: " + allowed);
+ return;
+
+ case OemLockSecureStatus.INVALID_SIGNATURE:
+ throw new SecurityException(
+ "Invalid signature used in attempt to carrier unlock");
+
+ default:
+ Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL");
+ // Fallthrough
+ case OemLockSecureStatus.FAILED:
+ throw new RuntimeException("Failed to set carrier OEM unlock state");
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set carrier state with HAL", e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ boolean isOemUnlockAllowedByCarrier() {
+ final Integer[] requestStatus = new Integer[1];
+ final Boolean[] allowedByCarrier = new Boolean[1];
+
+ try {
+ mOemLock.isOemUnlockAllowedByCarrier((status, allowed) -> {
+ requestStatus[0] = status;
+ allowedByCarrier[0] = allowed;
+ });
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get carrier state from HAL");
+ throw e.rethrowFromSystemServer();
+ }
+
+ switch (requestStatus[0]) {
+ case OemLockStatus.OK:
+ // Success
+ return allowedByCarrier[0];
+
+ default:
+ Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL");
+ // Fallthrough
+ case OemLockStatus.FAILED:
+ throw new RuntimeException("Failed to get carrier OEM unlock state");
+ }
+ }
+
+ @Override
+ void setOemUnlockAllowedByDevice(boolean allowedByDevice) {
+ try {
+ switch (mOemLock.setOemUnlockAllowedByDevice(allowedByDevice)) {
+ case OemLockSecureStatus.OK:
+ Slog.i(TAG, "Updated device allows OEM lock state to: " + allowedByDevice);
+ return;
+
+ default:
+ Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL");
+ // Fallthrough
+ case OemLockSecureStatus.FAILED:
+ throw new RuntimeException("Failed to set device OEM unlock state");
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set device state with HAL", e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ boolean isOemUnlockAllowedByDevice() {
+ final Integer[] requestStatus = new Integer[1];
+ final Boolean[] allowedByDevice = new Boolean[1];
+
+ try {
+ mOemLock.isOemUnlockAllowedByDevice((status, allowed) -> {
+ requestStatus[0] = status;
+ allowedByDevice[0] = allowed;
+ });
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get devie state from HAL");
+ throw e.rethrowFromSystemServer();
+ }
+
+ switch (requestStatus[0]) {
+ case OemLockStatus.OK:
+ // Success
+ return allowedByDevice[0];
+
+ default:
+ Slog.e(TAG, "Unknown return value indicates code is out of sync with HAL");
+ // Fallthrough
+ case OemLockStatus.FAILED:
+ throw new RuntimeException("Failed to get device OEM unlock state");
+ }
+ }
+
+ private ArrayList toByteArrayList(byte[] data) {
+ if (data == null) {
+ return null;
+ }
+ ArrayList<Byte> result = new ArrayList<Byte>(data.length);
+ for (final byte b : data) {
+ result.add(b);
+ }
+ return result;
+ }
+}
diff --git a/services/core/java/com/android/server/os/SchedulingPolicyService.java b/services/core/java/com/android/server/os/SchedulingPolicyService.java
index 46be232..e0b8426 100644
--- a/services/core/java/com/android/server/os/SchedulingPolicyService.java
+++ b/services/core/java/com/android/server/os/SchedulingPolicyService.java
@@ -57,7 +57,7 @@
try {
// make good use of our CAP_SYS_NICE capability
Process.setThreadGroup(tid, !isForApp ?
- Process.THREAD_GROUP_AUDIO_SYS : Process.THREAD_GROUP_AUDIO_APP);
+ Process.THREAD_GROUP_AUDIO_SYS : Process.THREAD_GROUP_RT_APP);
} catch (RuntimeException e) {
Log.e(TAG, "Failed setThreadGroup: " + e);
return PackageManager.PERMISSION_DENIED;
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 0d1f58a..6749afb 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -214,7 +214,8 @@
int result = pm.performDexOptWithStatus(pkg,
/* checkProfiles */ false,
PackageManagerService.REASON_BOOT,
- /* force */ false);
+ /* force */ false,
+ /* bootComplete */ true);
if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
updatedPackages.add(pkg);
}
@@ -297,7 +298,8 @@
int result = pm.performDexOptWithStatus(pkg,
/* checkProfiles */ true,
PackageManagerService.REASON_BACKGROUND_DEXOPT,
- /* force */ false);
+ /* force */ false,
+ /* bootComplete */ true);
success = result != PackageDexOptimizer.DEX_OPT_FAILED;
if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
updatedPackages.add(pkg);
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index ac7b763..d1aecb1 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -936,7 +936,7 @@
// permissions if the version on the system image does not declare them.
if (!isDefaultPhoneOrSms && pkg.isUpdatedSystemApp()) {
PackageSetting sysPs = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);
- if (sysPs != null) {
+ if (sysPs != null && sysPs.pkg != null) {
if (sysPs.pkg.requestedPermissions.isEmpty()) {
return;
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index c95b5c5..5c4c040 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -310,6 +310,16 @@
}
}
+ public boolean copySystemProfile(String systemProfile, int uid, String packageName)
+ throws InstallerException {
+ if (!checkBeforeRemote()) return false;
+ try {
+ return mInstalld.copySystemProfile(systemProfile, uid, packageName);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
public void idmap(String targetApkPath, String overlayApkPath, int uid)
throws InstallerException {
if (!checkBeforeRemote()) return;
diff --git a/services/core/java/com/android/server/pm/InstructionSets.java b/services/core/java/com/android/server/pm/InstructionSets.java
index 5092ebf..f326f1d 100644
--- a/services/core/java/com/android/server/pm/InstructionSets.java
+++ b/services/core/java/com/android/server/pm/InstructionSets.java
@@ -34,7 +34,7 @@
*/
public class InstructionSets {
private static final String PREFERRED_INSTRUCTION_SET =
- VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);;
+ VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);
public static String[] getAppDexInstructionSets(ApplicationInfo info) {
if (info.primaryCpuAbi != null) {
if (info.secondaryCpuAbi != null) {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 29f9f7c..4a5ce12 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -34,6 +34,7 @@
import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
@@ -100,7 +101,6 @@
private static final boolean DEBUG = false;
private static final String TAG = "LauncherAppsService";
private final Context mContext;
- private final PackageManager mPm;
private final UserManager mUm;
private final ActivityManagerInternal mActivityManagerInternal;
private final ShortcutServiceInternal mShortcutServiceInternal;
@@ -113,7 +113,6 @@
public LauncherAppsImpl(Context context) {
mContext = context;
- mPm = mContext.getPackageManager();
mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mActivityManagerInternal = Preconditions.checkNotNull(
LocalServices.getService(ActivityManagerInternal.class));
@@ -263,15 +262,17 @@
void verifyCallingPackage(String callingPackage) {
int packageUid = -1;
try {
- packageUid = mPm.getPackageUidAsUser(callingPackage,
+ packageUid = AppGlobals.getPackageManager().getPackageUid(callingPackage,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_UNINSTALLED_PACKAGES,
UserHandle.getUserId(getCallingUid()));
- } catch (NameNotFoundException e) {
+ } catch (RemoteException ignore) {
+ }
+ if (packageUid < 0) {
Log.e(TAG, "Package not found: " + callingPackage);
}
- if (packageUid != Binder.getCallingUid()) {
+ if (packageUid != injectBinderCallingUid()) {
throw new SecurityException("Calling package name mismatch");
}
}
@@ -315,13 +316,15 @@
return null;
}
+ final int callingUid = injectBinderCallingUid();
long ident = Binder.clearCallingIdentity();
try {
- IPackageManager pm = AppGlobals.getPackageManager();
- return pm.getActivityInfo(component,
+ final PackageManagerInternal pmInt =
+ LocalServices.getService(PackageManagerInternal.class);
+ return pmInt.getActivityInfo(component,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- user.getIdentifier());
+ callingUid, user.getIdentifier());
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -344,12 +347,15 @@
return null;
}
+ final int callingUid = injectBinderCallingUid();
long ident = injectClearCallingIdentity();
try {
- List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(intent,
+ final PackageManagerInternal pmInt =
+ LocalServices.getService(PackageManagerInternal.class);
+ List<ResolveInfo> apps = pmInt.queryIntentActivities(intent,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- user.getIdentifier());
+ callingUid, user.getIdentifier());
return new ParceledListSlice<>(apps);
} finally {
injectRestoreCallingIdentity(ident);
@@ -390,13 +396,15 @@
return false;
}
+ final int callingUid = injectBinderCallingUid();
long ident = Binder.clearCallingIdentity();
try {
- IPackageManager pm = AppGlobals.getPackageManager();
- PackageInfo info = pm.getPackageInfo(packageName,
+ final PackageManagerInternal pmInt =
+ LocalServices.getService(PackageManagerInternal.class);
+ PackageInfo info = pmInt.getPackageInfo(packageName,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- user.getIdentifier());
+ callingUid, user.getIdentifier());
return info != null && info.applicationInfo.enabled;
} finally {
Binder.restoreCallingIdentity(ident);
@@ -414,11 +422,13 @@
return null;
}
+ final int callingUid = injectBinderCallingUid();
long ident = Binder.clearCallingIdentity();
try {
- IPackageManager pm = AppGlobals.getPackageManager();
- ApplicationInfo info = pm.getApplicationInfo(packageName, flags,
- user.getIdentifier());
+ final PackageManagerInternal pmInt =
+ LocalServices.getService(PackageManagerInternal.class);
+ ApplicationInfo info = pmInt.getApplicationInfo(packageName, flags,
+ callingUid, user.getIdentifier());
return info;
} finally {
Binder.restoreCallingIdentity(ident);
@@ -573,13 +583,15 @@
return false;
}
+ final int callingUid = injectBinderCallingUid();
long ident = Binder.clearCallingIdentity();
try {
- IPackageManager pm = AppGlobals.getPackageManager();
- ActivityInfo info = pm.getActivityInfo(component,
+ final PackageManagerInternal pmInt =
+ LocalServices.getService(PackageManagerInternal.class);
+ ActivityInfo info = pmInt.getActivityInfo(component,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- user.getIdentifier());
+ callingUid, user.getIdentifier());
return info != null;
} finally {
Binder.restoreCallingIdentity(ident);
@@ -604,13 +616,15 @@
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
launchIntent.setPackage(component.getPackageName());
+ final int callingUid = injectBinderCallingUid();
long ident = Binder.clearCallingIdentity();
try {
- IPackageManager pm = AppGlobals.getPackageManager();
- ActivityInfo info = pm.getActivityInfo(component,
+ final PackageManagerInternal pmInt =
+ LocalServices.getService(PackageManagerInternal.class);
+ ActivityInfo info = pmInt.getActivityInfo(component,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- user.getIdentifier());
+ callingUid, user.getIdentifier());
if (!info.exported) {
throw new SecurityException("Cannot launch non-exported components "
+ component);
@@ -619,10 +633,10 @@
// Check that the component actually has Intent.CATEGORY_LAUCNCHER
// as calling startActivityAsUser ignores the category and just
// resolves based on the component if present.
- List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(launchIntent,
+ List<ResolveInfo> apps = pmInt.queryIntentActivities(launchIntent,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- user.getIdentifier());
+ callingUid, user.getIdentifier());
final int size = apps.size();
for (int i = 0; i < size; ++i) {
ActivityInfo activityInfo = apps.get(i).activityInfo;
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 271045a..54ca6b9 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -342,7 +342,8 @@
null /* ISAs */, false /* checkProfiles */,
getCompilerFilterForReason(compilationReason),
null /* CompilerStats.PackageStats */,
- mPackageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName));
+ mPackageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName),
+ true /* bootComplete */);
return commands;
}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 5ea6362..2b7bd34 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -113,7 +113,8 @@
*/
int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries,
String[] instructionSets, boolean checkProfiles, String targetCompilationFilter,
- CompilerStats.PackageStats packageStats, boolean isUsedByOtherApps) {
+ CompilerStats.PackageStats packageStats, boolean isUsedByOtherApps,
+ boolean bootComplete) {
if (!canOptimizePackage(pkg)) {
return DEX_OPT_SKIPPED;
}
@@ -121,7 +122,7 @@
final long acquireTime = acquireWakeLockLI(pkg.applicationInfo.uid);
try {
return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles,
- targetCompilationFilter, packageStats, isUsedByOtherApps);
+ targetCompilationFilter, packageStats, isUsedByOtherApps, bootComplete);
} finally {
releaseWakeLockLI(acquireTime);
}
@@ -136,7 +137,7 @@
private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries,
String[] targetInstructionSets, boolean checkForProfileUpdates,
String targetCompilerFilter, CompilerStats.PackageStats packageStats,
- boolean isUsedByOtherApps) {
+ boolean isUsedByOtherApps, boolean bootComplete) {
final String[] instructionSets = targetInstructionSets != null ?
targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
@@ -150,7 +151,7 @@
final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries);
// Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
- final int dexoptFlags = getDexFlags(pkg, compilerFilter);
+ final int dexoptFlags = getDexFlags(pkg, compilerFilter, bootComplete);
// Get the dependencies of each split in the package. For each code path in the package,
// this array contains the relative paths of each split it depends on, separated by colons.
String[] splitDependencies = getSplitDependencies(pkg);
@@ -296,7 +297,9 @@
String compilerFilter, boolean isUsedByOtherApps) {
compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps);
// Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
- int dexoptFlags = getDexFlags(info, compilerFilter) | DEXOPT_SECONDARY_DEX;
+ // Secondary dex files are currently not compiled at boot.
+ int dexoptFlags = getDexFlags(info, compilerFilter, /* bootComplete */ true)
+ | DEXOPT_SECONDARY_DEX;
// Check the app storage and add the appropriate flags.
if (info.deviceProtectedDataDir != null &&
FileUtils.contains(info.deviceProtectedDataDir, path)) {
@@ -397,11 +400,12 @@
* Computes the dex flags that needs to be pass to installd for the given package and compiler
* filter.
*/
- private int getDexFlags(PackageParser.Package pkg, String compilerFilter) {
- return getDexFlags(pkg.applicationInfo, compilerFilter);
+ private int getDexFlags(PackageParser.Package pkg, String compilerFilter,
+ boolean bootComplete) {
+ return getDexFlags(pkg.applicationInfo, compilerFilter, bootComplete);
}
- private int getDexFlags(ApplicationInfo info, String compilerFilter) {
+ private int getDexFlags(ApplicationInfo info, String compilerFilter, boolean bootComplete) {
int flags = info.flags;
boolean debuggable = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
// Profile guide compiled oat files should not be public.
@@ -412,7 +416,7 @@
(isPublic ? DEXOPT_PUBLIC : 0)
| (debuggable ? DEXOPT_DEBUGGABLE : 0)
| profileFlag
- | DEXOPT_BOOTCOMPLETE;
+ | (bootComplete ? DEXOPT_BOOTCOMPLETE : 0);
return adjustDexoptFlags(dexFlags);
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 75e7a1e..6f07973 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -848,7 +848,7 @@
for (int i = 0; i < mSessions.size(); i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
if (session.userId == userId) {
- result.add(session.generateInfo());
+ result.add(session.generateInfo(false));
}
}
}
@@ -866,7 +866,7 @@
final PackageInstallerSession session = mSessions.valueAt(i);
if (Objects.equals(session.installerPackageName, installerPackageName)
&& session.userId == userId) {
- result.add(session.generateInfo());
+ result.add(session.generateInfo(false));
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 4540d2d..f5808af 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -288,6 +288,10 @@
}
public SessionInfo generateInfo() {
+ return generateInfo(true);
+ }
+
+ public SessionInfo generateInfo(boolean includeIcon) {
final SessionInfo info = new SessionInfo();
synchronized (mLock) {
info.sessionId = sessionId;
@@ -302,7 +306,9 @@
info.installReason = params.installReason;
info.sizeBytes = params.sizeBytes;
info.appPackageName = params.appPackageName;
- info.appIcon = params.appIcon;
+ if (includeIcon) {
+ info.appIcon = params.appIcon;
+ }
info.appLabel = params.appLabel;
}
return info;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index aad7df3..48b9c7d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.DELETE_PACKAGES;
import static android.Manifest.permission.INSTALL_PACKAGES;
+import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.REQUEST_DELETE_PACKAGES;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
@@ -129,6 +130,7 @@
import android.content.pm.ChangedPackages;
import android.content.pm.FallbackCategoryProvider;
import android.content.pm.FeatureInfo;
+import android.content.pm.IDexModuleRegisterCallback;
import android.content.pm.IOnPermissionsChangeListener;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
@@ -2186,12 +2188,12 @@
private void grantRequestedRuntimePermissionsForUser(PackageParser.Package pkg, int userId,
String[] grantedPermissions) {
- SettingBase sb = (SettingBase) pkg.mExtras;
- if (sb == null) {
+ PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (ps == null) {
return;
}
- PermissionsState permissionsState = sb.getPermissionsState();
+ PermissionsState permissionsState = ps.getPermissionsState();
final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
| PackageManager.FLAG_PERMISSION_POLICY_FIXED;
@@ -2946,7 +2948,6 @@
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "write settings");
mSettings.writeLPr();
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
SystemClock.uptimeMillis());
@@ -3516,7 +3517,7 @@
* and {@code 0}</li>
* <li>The calling application has the permission
* {@link android.Manifest.permission#ACCESS_INSTANT_APPS}</li>
- * <li>[TODO] The calling application is the default launcher on the
+ * <li>The calling application is the default launcher on the
* system partition.</li>
* </ol>
*/
@@ -3651,22 +3652,27 @@
@Override
public PackageInfo getPackageInfo(String packageName, int flags, int userId) {
return getPackageInfoInternal(packageName, PackageManager.VERSION_CODE_HIGHEST,
- flags, userId);
+ flags, Binder.getCallingUid(), userId);
}
@Override
public PackageInfo getPackageInfoVersioned(VersionedPackage versionedPackage,
int flags, int userId) {
return getPackageInfoInternal(versionedPackage.getPackageName(),
- versionedPackage.getVersionCode(), flags, userId);
+ versionedPackage.getVersionCode(), flags, Binder.getCallingUid(), userId);
}
+ /**
+ * Important: The provided filterCallingUid is used exclusively to filter out packages
+ * that can be seen based on user state. It's typically the original caller uid prior
+ * to clearing. Because it can only be provided by trusted code, it's value can be
+ * trusted and will be used as-is; unlike userId which will be validated by this method.
+ */
private PackageInfo getPackageInfoInternal(String packageName, int versionCode,
- int flags, int userId) {
+ int flags, int filterCallingUid, int userId) {
if (!sUserManager.exists(userId)) return null;
- final int callingUid = Binder.getCallingUid();
flags = updateFlagsForPackage(flags, userId, packageName);
- enforceCrossUserPermission(callingUid, userId,
+ enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */, "get package info");
// reader
@@ -3678,10 +3684,10 @@
if (matchFactoryOnly) {
final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName);
if (ps != null) {
- if (filterSharedLibPackageLPr(ps, callingUid, userId, flags)) {
+ if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) {
return null;
}
- if (filterAppAccessLPr(ps, callingUid, userId)) {
+ if (filterAppAccessLPr(ps, filterCallingUid, userId)) {
return null;
}
return generatePackageInfo(ps, flags, userId);
@@ -3696,10 +3702,10 @@
Log.v(TAG, "getPackageInfo " + packageName + ": " + p);
if (p != null) {
final PackageSetting ps = (PackageSetting) p.mExtras;
- if (filterSharedLibPackageLPr(ps, callingUid, userId, flags)) {
+ if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) {
return null;
}
- if (ps != null && filterAppAccessLPr(ps, callingUid, userId)) {
+ if (ps != null && filterAppAccessLPr(ps, filterCallingUid, userId)) {
return null;
}
return generatePackageInfo((PackageSetting)p.mExtras, flags, userId);
@@ -3707,10 +3713,10 @@
if (!matchFactoryOnly && (flags & MATCH_KNOWN_PACKAGES) != 0) {
final PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps == null) return null;
- if (filterSharedLibPackageLPr(ps, callingUid, userId, flags)) {
+ if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) {
return null;
}
- if (filterAppAccessLPr(ps, callingUid, userId)) {
+ if (filterAppAccessLPr(ps, filterCallingUid, userId)) {
return null;
}
return generatePackageInfo(ps, flags, userId);
@@ -4071,14 +4077,14 @@
}
private ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags,
- int uid, int userId) {
+ int filterCallingUid, int userId) {
if (!sUserManager.exists(userId)) return null;
PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps != null) {
- if (filterSharedLibPackageLPr(ps, uid, userId, flags)) {
+ if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) {
return null;
}
- if (filterAppAccessLPr(ps, uid, userId)) {
+ if (filterAppAccessLPr(ps, filterCallingUid, userId)) {
return null;
}
if (ps.pkg == null) {
@@ -4101,6 +4107,17 @@
@Override
public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) {
+ return getApplicationInfoInternal(packageName, flags, Binder.getCallingUid(), userId);
+ }
+
+ /**
+ * Important: The provided filterCallingUid is used exclusively to filter out applications
+ * that can be seen based on user state. It's typically the original caller uid prior
+ * to clearing. Because it can only be provided by trusted code, it's value can be
+ * trusted and will be used as-is; unlike userId which will be validated by this method.
+ */
+ private ApplicationInfo getApplicationInfoInternal(String packageName, int flags,
+ int filterCallingUid, int userId) {
if (!sUserManager.exists(userId)) return null;
flags = updateFlagsForApplication(flags, userId, packageName);
enforceCrossUserPermission(Binder.getCallingUid(), userId,
@@ -4119,10 +4136,10 @@
if (p != null) {
PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps == null) return null;
- if (filterSharedLibPackageLPr(ps, Binder.getCallingUid(), userId, flags)) {
+ if (filterSharedLibPackageLPr(ps, filterCallingUid, userId, flags)) {
return null;
}
- if (filterAppAccessLPr(ps, Binder.getCallingUid(), userId)) {
+ if (filterAppAccessLPr(ps, filterCallingUid, userId)) {
return null;
}
// Note: isEnabledLP() does not apply here - always return info
@@ -4140,7 +4157,7 @@
if ((flags & MATCH_KNOWN_PACKAGES) != 0) {
// Already generates the external package name
return generateApplicationInfoFromSettingsLPw(packageName,
- Binder.getCallingUid(), flags, userId);
+ flags, filterCallingUid, userId);
}
}
return null;
@@ -4570,10 +4587,20 @@
@Override
public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
+ return getActivityInfoInternal(component, flags, Binder.getCallingUid(), userId);
+ }
+
+ /**
+ * Important: The provided filterCallingUid is used exclusively to filter out activities
+ * that can be seen based on user state. It's typically the original caller uid prior
+ * to clearing. Because it can only be provided by trusted code, it's value can be
+ * trusted and will be used as-is; unlike userId which will be validated by this method.
+ */
+ private ActivityInfo getActivityInfoInternal(ComponentName component, int flags,
+ int filterCallingUid, int userId) {
if (!sUserManager.exists(userId)) return null;
- final int callingUid = Binder.getCallingUid();
flags = updateFlagsForComponent(flags, userId, component);
- enforceCrossUserPermission(callingUid, userId,
+ enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */, "get activity info");
synchronized (mPackages) {
PackageParser.Activity a = mActivities.mActivities.get(component);
@@ -4582,7 +4609,7 @@
if (a != null && mSettings.isEnabledAndMatchLPr(a.info, flags, userId)) {
PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
if (ps == null) return null;
- if (filterAppAccessLPr(ps, callingUid, component, TYPE_ACTIVITY, userId)) {
+ if (filterAppAccessLPr(ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) {
return null;
}
return generateActivityInfo(a, flags, ps.readUserState(userId), userId);
@@ -4878,9 +4905,13 @@
}
}
- private void updateSequenceNumberLP(String packageName, int[] userList) {
+ private void updateSequenceNumberLP(PackageSetting pkgSetting, int[] userList) {
for (int i = userList.length - 1; i >= 0; --i) {
final int userId = userList[i];
+ // don't add instant app to the list of updates
+ if (pkgSetting.getInstantApp(userId)) {
+ continue;
+ }
SparseArray<String> changedPackages = mChangedPackages.get(userId);
if (changedPackages == null) {
changedPackages = new SparseArray<>();
@@ -4891,12 +4922,12 @@
sequenceNumbers = new HashMap<>();
mChangedPackagesSequenceNumbers.put(userId, sequenceNumbers);
}
- final Integer sequenceNumber = sequenceNumbers.get(packageName);
+ final Integer sequenceNumber = sequenceNumbers.get(pkgSetting.name);
if (sequenceNumber != null) {
changedPackages.remove(sequenceNumber);
}
- changedPackages.put(mChangedPackagesSequenceNumber, packageName);
- sequenceNumbers.put(packageName, mChangedPackagesSequenceNumber);
+ changedPackages.put(mChangedPackagesSequenceNumber, pkgSetting.name);
+ sequenceNumbers.put(pkgSetting.name, mChangedPackagesSequenceNumber);
}
mChangedPackagesSequenceNumber++;
}
@@ -5338,7 +5369,7 @@
"grantRuntimePermission");
final int uid;
- final SettingBase sb;
+ final PackageSetting ps;
synchronized (mPackages) {
final PackageParser.Package pkg = mPackages.get(packageName);
@@ -5349,12 +5380,9 @@
if (bp == null) {
throw new IllegalArgumentException("Unknown permission: " + name);
}
- sb = (SettingBase) pkg.mExtras;
- if (sb == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
- if (sb instanceof PackageSetting
- && filterAppAccessLPr((PackageSetting) sb, callingUid, userId)) {
+ ps = (PackageSetting) pkg.mExtras;
+ if (ps == null
+ || filterAppAccessLPr(ps, callingUid, userId)) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
@@ -5372,7 +5400,7 @@
uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
- final PermissionsState permissionsState = sb.getPermissionsState();
+ final PermissionsState permissionsState = ps.getPermissionsState();
final int flags = permissionsState.getPermissionFlags(name, userId);
if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
@@ -5394,7 +5422,6 @@
return;
}
- final PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps.getInstantApp(userId) && !bp.isInstant()) {
throw new SecurityException("Cannot grant non-ephemeral permission"
+ name + " for package " + packageName);
@@ -5478,7 +5505,11 @@
if (pkg == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
-
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (ps == null
+ || filterAppAccessLPr(ps, Binder.getCallingUid(), userId)) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
final BasePermission bp = mSettings.mPermissions.get(name);
if (bp == null) {
throw new IllegalArgumentException("Unknown permission: " + name);
@@ -5496,12 +5527,7 @@
return;
}
- SettingBase sb = (SettingBase) pkg.mExtras;
- if (sb == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
-
- final PermissionsState permissionsState = sb.getPermissionsState();
+ final PermissionsState permissionsState = ps.getPermissionsState();
final int flags = permissionsState.getPermissionFlags(name, userId);
if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
@@ -5654,15 +5680,12 @@
if (bp == null) {
return 0;
}
- final SettingBase sb = (SettingBase) pkg.mExtras;
- if (sb == null) {
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (ps == null
+ || filterAppAccessLPr(ps, callingUid, userId)) {
return 0;
}
- if (sb instanceof PackageSetting
- && filterAppAccessLPr((PackageSetting) sb, callingUid, userId)) {
- return 0;
- }
- PermissionsState permissionsState = sb.getPermissionsState();
+ PermissionsState permissionsState = ps.getPermissionsState();
return permissionsState.getPermissionFlags(name, userId);
}
}
@@ -5676,7 +5699,8 @@
enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlags");
- enforceCrossUserPermission(Binder.getCallingUid(), userId,
+ final int callingUid = Binder.getCallingUid();
+ enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, true /* checkShell */,
"updatePermissionFlags");
@@ -5694,18 +5718,18 @@
if (pkg == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (ps == null
+ || filterAppAccessLPr(ps, callingUid, userId)) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
final BasePermission bp = mSettings.mPermissions.get(name);
if (bp == null) {
throw new IllegalArgumentException("Unknown permission: " + name);
}
- SettingBase sb = (SettingBase) pkg.mExtras;
- if (sb == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
-
- PermissionsState permissionsState = sb.getPermissionsState();
+ PermissionsState permissionsState = ps.getPermissionsState();
boolean hadState = permissionsState.getRuntimePermissionState(name, userId) != null;
@@ -5749,11 +5773,11 @@
final int packageCount = mPackages.size();
for (int pkgIndex = 0; pkgIndex < packageCount; pkgIndex++) {
final PackageParser.Package pkg = mPackages.valueAt(pkgIndex);
- SettingBase sb = (SettingBase) pkg.mExtras;
- if (sb == null) {
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (ps == null) {
continue;
}
- PermissionsState permissionsState = sb.getPermissionsState();
+ PermissionsState permissionsState = ps.getPermissionsState();
changed |= permissionsState.updatePermissionFlagsForAllPermissions(
userId, flagMask, flagValues);
}
@@ -6303,7 +6327,7 @@
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities");
final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType,
- flags, userId, resolveForStart);
+ flags, callingUid, userId, resolveForStart);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
final ResolveInfo bestChoice =
@@ -6847,15 +6871,16 @@
private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
String resolvedType, int flags, int userId) {
- return queryIntentActivitiesInternal(intent, resolvedType, flags, userId, false);
+ return queryIntentActivitiesInternal(
+ intent, resolvedType, flags, Binder.getCallingUid(), userId, false);
}
private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
- String resolvedType, int flags, int userId, boolean resolveForStart) {
+ String resolvedType, int flags, int filterCallingUid, int userId,
+ boolean resolveForStart) {
if (!sUserManager.exists(userId)) return Collections.emptyList();
- final int callingUid = Binder.getCallingUid();
- final String instantAppPkgName = getInstantAppPackageName(callingUid);
- enforceCrossUserPermission(callingUid, userId,
+ final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
+ enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */,
"query intent activities");
final String pkgName = intent.getPackage();
@@ -6867,7 +6892,7 @@
}
}
- flags = updateFlagsForResolve(flags, userId, intent, callingUid, resolveForStart,
+ flags = updateFlagsForResolve(flags, userId, intent, filterCallingUid, resolveForStart,
comp != null || pkgName != null /*onlyExposedExplicitly*/);
if (comp != null) {
final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
@@ -9267,7 +9292,8 @@
final long startTime = System.nanoTime();
final int[] stats = performDexOptUpgrade(pkgs, mIsPreNUpgrade /* showDialog */,
- getCompilerFilterForReason(causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT));
+ getCompilerFilterForReason(causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT),
+ false /* bootComplete */);
final int elapsedTimeSeconds =
(int) TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime);
@@ -9279,6 +9305,13 @@
MetricsLogger.histogram(mContext, "opt_dialog_time_s", elapsedTimeSeconds);
}
+ /*
+ * Return the prebuilt profile path given a package base code path.
+ */
+ private static String getPrebuildProfilePath(PackageParser.Package pkg) {
+ return pkg.baseCodePath + ".prof";
+ }
+
/**
* Performs dexopt on the set of packages in {@code packages} and returns an int array
* containing statistics about the invocation. The array consists of three elements,
@@ -9286,7 +9319,7 @@
* and {@code numberOfPackagesFailed}.
*/
private int[] performDexOptUpgrade(List<PackageParser.Package> pkgs, boolean showDialog,
- String compilerFilter) {
+ String compilerFilter, boolean bootComplete) {
int numberOfPackagesVisited = 0;
int numberOfPackagesOptimized = 0;
@@ -9297,6 +9330,27 @@
for (PackageParser.Package pkg : pkgs) {
numberOfPackagesVisited++;
+ if ((isFirstBoot() || isUpgrade()) && isSystemApp(pkg)) {
+ // Copy over initial preopt profiles since we won't get any JIT samples for methods
+ // that are already compiled.
+ File profileFile = new File(getPrebuildProfilePath(pkg));
+ // Copy profile if it exists.
+ if (profileFile.exists()) {
+ try {
+ // We could also do this lazily before calling dexopt in
+ // PackageDexOptimizer to prevent this happening on first boot. The issue
+ // is that we don't have a good way to say "do this only once".
+ if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
+ pkg.applicationInfo.uid, pkg.packageName)) {
+ Log.e(TAG, "Installer failed to copy system profile!");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath() + " ",
+ e);
+ }
+ }
+ }
+
if (!PackageDexOptimizer.canOptimizePackage(pkg)) {
if (DEBUG_DEXOPT) {
Log.i(TAG, "Skipping update of of non-optimizable app " + pkg.packageName);
@@ -9343,7 +9397,8 @@
int dexOptStatus = performDexOptTraced(pkg.packageName,
false /* checkProfiles */,
compilerFilter,
- false /* force */);
+ false /* force */,
+ bootComplete);
switch (dexOptStatus) {
case PackageDexOptimizer.DEX_OPT_PERFORMED:
numberOfPackagesOptimized++;
@@ -9399,15 +9454,41 @@
}
@Override
+ public void registerDexModule(String packageName, String dexModulePath, boolean isSharedModule,
+ IDexModuleRegisterCallback callback) {
+ int userId = UserHandle.getCallingUserId();
+ ApplicationInfo ai = getApplicationInfo(packageName, /*flags*/ 0, userId);
+ DexManager.RegisterDexModuleResult result;
+ if (ai == null) {
+ Slog.w(TAG, "Registering a dex module for a package that does not exist for the" +
+ " calling user. package=" + packageName + ", user=" + userId);
+ result = new DexManager.RegisterDexModuleResult(false, "Package not installed");
+ } else {
+ result = mDexManager.registerDexModule(ai, dexModulePath, isSharedModule, userId);
+ }
+
+ if (callback != null) {
+ mHandler.post(() -> {
+ try {
+ callback.onDexModuleRegistered(dexModulePath, result.success, result.message);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to callback after module registration " + dexModulePath, e);
+ }
+ });
+ }
+ }
+
+ @Override
public boolean performDexOpt(String packageName,
- boolean checkProfiles, int compileReason, boolean force) {
+ boolean checkProfiles, int compileReason, boolean force, boolean bootComplete) {
if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
return false;
} else if (isInstantApp(packageName, UserHandle.getCallingUserId())) {
return false;
}
- return performDexOptWithStatus(packageName, checkProfiles, compileReason, force) !=
- PackageDexOptimizer.DEX_OPT_FAILED;
+ int dexoptStatus = performDexOptWithStatus(
+ packageName, checkProfiles, compileReason, force, bootComplete);
+ return dexoptStatus != PackageDexOptimizer.DEX_OPT_FAILED;
}
/**
@@ -9417,30 +9498,32 @@
* {@link PackageDexOptimizer#DEX_OPT_FAILED}
*/
/* package */ int performDexOptWithStatus(String packageName,
- boolean checkProfiles, int compileReason, boolean force) {
+ boolean checkProfiles, int compileReason, boolean force, boolean bootComplete) {
return performDexOptTraced(packageName, checkProfiles,
- getCompilerFilterForReason(compileReason), force);
+ getCompilerFilterForReason(compileReason), force, bootComplete);
}
@Override
public boolean performDexOptMode(String packageName,
- boolean checkProfiles, String targetCompilerFilter, boolean force) {
+ boolean checkProfiles, String targetCompilerFilter, boolean force,
+ boolean bootComplete) {
if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
return false;
} else if (isInstantApp(packageName, UserHandle.getCallingUserId())) {
return false;
}
int dexOptStatus = performDexOptTraced(packageName, checkProfiles,
- targetCompilerFilter, force);
+ targetCompilerFilter, force, bootComplete);
return dexOptStatus != PackageDexOptimizer.DEX_OPT_FAILED;
}
private int performDexOptTraced(String packageName,
- boolean checkProfiles, String targetCompilerFilter, boolean force) {
+ boolean checkProfiles, String targetCompilerFilter, boolean force,
+ boolean bootComplete) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
try {
return performDexOptInternal(packageName, checkProfiles,
- targetCompilerFilter, force);
+ targetCompilerFilter, force, bootComplete);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -9449,7 +9532,8 @@
// Run dexopt on a given package. Returns true if dexopt did not fail, i.e.
// if the package can now be considered up to date for the given filter.
private int performDexOptInternal(String packageName,
- boolean checkProfiles, String targetCompilerFilter, boolean force) {
+ boolean checkProfiles, String targetCompilerFilter, boolean force,
+ boolean bootComplete) {
PackageParser.Package p;
synchronized (mPackages) {
p = mPackages.get(packageName);
@@ -9464,7 +9548,7 @@
try {
synchronized (mInstallLock) {
return performDexOptInternalWithDependenciesLI(p, checkProfiles,
- targetCompilerFilter, force);
+ targetCompilerFilter, force, bootComplete);
}
} finally {
Binder.restoreCallingIdentity(callingId);
@@ -9485,7 +9569,7 @@
private int performDexOptInternalWithDependenciesLI(PackageParser.Package p,
boolean checkProfiles, String targetCompilerFilter,
- boolean force) {
+ boolean force, boolean bootComplete) {
// Select the dex optimizer based on the force parameter.
// Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
// allocate an object here.
@@ -9509,12 +9593,13 @@
false /* checkProfiles */,
targetCompilerFilter,
getOrCreateCompilerPackageStats(depPackage),
- true /* isUsedByOtherApps */);
+ true /* isUsedByOtherApps */,
+ bootComplete);
}
}
return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets, checkProfiles,
targetCompilerFilter, getOrCreateCompilerPackageStats(p),
- mDexManager.isUsedByOtherApps(p.packageName));
+ mDexManager.isUsedByOtherApps(p.packageName), bootComplete);
}
// Performs dexopt on the used secondary dex files belonging to the given package.
@@ -9546,6 +9631,8 @@
public void reconcileSecondaryDexFiles(String packageName) {
if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
return;
+ } else if (isInstantApp(packageName, UserHandle.getCallingUserId())) {
+ return;
}
mDexManager.reconcileSecondaryDexFiles(packageName);
}
@@ -9710,7 +9797,8 @@
// Don't use profiles since that may cause compilation to be skipped.
final int res = performDexOptInternalWithDependenciesLI(pkg,
false /* checkProfiles */, getDefaultCompilerFilter(),
- true /* force */);
+ true /* force */,
+ true /* bootComplete */);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -14323,8 +14411,8 @@
int userId) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
PackageSetting pkgSetting;
- final int uid = Binder.getCallingUid();
- enforceCrossUserPermission(uid, userId,
+ final int callingUid = Binder.getCallingUid();
+ enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, true /* checkShell */,
"setApplicationHiddenSetting for user " + userId);
@@ -14343,6 +14431,9 @@
if (pkgSetting == null) {
return false;
}
+ if (filterAppAccessLPr(pkgSetting, callingUid, userId)) {
+ return false;
+ }
// Do not allow "android" is being disabled
if ("android".equals(packageName)) {
Slog.w(TAG, "Cannot hide package: android");
@@ -14359,7 +14450,7 @@
return false;
}
// Only allow protected packages to hide themselves.
- if (hidden && !UserHandle.isSameApp(uid, pkgSetting.appId)
+ if (hidden && !UserHandle.isSameApp(callingUid, pkgSetting.appId)
&& mProtectedPackages.isPackageStateProtected(userId, packageName)) {
Slog.w(TAG, "Not hiding protected package: " + packageName);
return false;
@@ -14476,6 +14567,20 @@
if (pkgSetting == null) {
return PackageManager.INSTALL_FAILED_INVALID_URI;
}
+ if (!canViewInstantApps(callingUid, UserHandle.getUserId(callingUid))) {
+ // only allow the existing package to be used if it's installed as a full
+ // application for at least one user
+ boolean installAllowed = false;
+ for (int checkUserId : sUserManager.getUserIds()) {
+ installAllowed = !pkgSetting.getInstantApp(checkUserId);
+ if (installAllowed) {
+ break;
+ }
+ }
+ if (!installAllowed) {
+ return PackageManager.INSTALL_FAILED_INVALID_URI;
+ }
+ }
if (!pkgSetting.getInstalled(userId)) {
pkgSetting.setInstalled(true, userId);
pkgSetting.setHidden(false, userId);
@@ -14499,7 +14604,7 @@
}
sendPackageAddedForUser(packageName, pkgSetting, userId);
synchronized (mPackages) {
- updateSequenceNumberLP(packageName, new int[]{ userId });
+ updateSequenceNumberLP(pkgSetting, new int[]{ userId });
}
}
} finally {
@@ -14545,7 +14650,8 @@
public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
int userId) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
- enforceCrossUserPermission(Binder.getCallingUid(), userId,
+ final int callingUid = Binder.getCallingUid();
+ enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, true /* checkShell */,
"setPackagesSuspended for user " + userId);
@@ -14566,7 +14672,8 @@
final int appId;
synchronized (mPackages) {
final PackageSetting pkgSetting = mSettings.mPackages.get(packageName);
- if (pkgSetting == null) {
+ if (pkgSetting == null
+ || filterAppAccessLPr(pkgSetting, callingUid, userId)) {
Slog.w(TAG, "Could not find package setting for package \"" + packageName
+ "\". Skipping suspending/un-suspending.");
unactionedPackages.add(packageName);
@@ -14976,6 +15083,10 @@
boolean result = false;
synchronized (mPackages) {
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (filterAppAccessLPr(ps, Binder.getCallingUid(), UserHandle.getCallingUserId())) {
+ return false;
+ }
result = mSettings.updateIntentFilterVerificationStatusLPw(packageName, status, userId);
}
if (result) {
@@ -15076,7 +15187,9 @@
// writer
synchronized (mPackages) {
PackageSetting targetPackageSetting = mSettings.mPackages.get(targetPackage);
- if (targetPackageSetting == null) {
+ if (targetPackageSetting == null
+ || filterAppAccessLPr(
+ targetPackageSetting, callingUid, UserHandle.getUserId(callingUid))) {
throw new IllegalArgumentException("Unknown target package: " + targetPackage);
}
@@ -15157,7 +15270,9 @@
if (ps == null) {
throw new IllegalArgumentException("Unknown target package " + packageName);
}
-
+ if (filterAppAccessLPr(ps, Binder.getCallingUid(), UserHandle.getCallingUserId())) {
+ throw new IllegalArgumentException("Unknown target package " + packageName);
+ }
if (!Objects.equals(callerPackageName, ps.installerPackageName)) {
throw new IllegalArgumentException("Calling package " + callerPackageName
+ " is not installer for " + packageName);
@@ -18118,7 +18233,8 @@
null /* instructionSets */, false /* checkProfiles */,
getCompilerFilterForReason(REASON_INSTALL),
getOrCreateCompilerPackageStats(pkg),
- mDexManager.isUsedByOtherApps(pkg.packageName));
+ mDexManager.isUsedByOtherApps(pkg.packageName),
+ true /* bootComplete */);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -18178,7 +18294,7 @@
}
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
- updateSequenceNumberLP(pkgName, res.newUsers);
+ updateSequenceNumberLP(ps, res.newUsers);
updateInstantAppInstallerLocked(pkgName);
}
}
@@ -18404,8 +18520,7 @@
final int callingUid = Binder.getCallingUid();
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DELETE_PACKAGES, null);
- final int hasAccessInstantApps = mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.ACCESS_INSTANT_APPS);
+ final boolean canViewInstantApps = canViewInstantApps(callingUid, userId);
Preconditions.checkNotNull(versionedPackage);
Preconditions.checkNotNull(observer);
Preconditions.checkArgumentInRange(versionedPackage.getVersionCode(),
@@ -18476,7 +18591,7 @@
final boolean targetIsInstantApp =
ps.getInstantApp(UserHandle.getUserId(callingUid));
doDeletePackage = !targetIsInstantApp
- || hasAccessInstantApps == PackageManager.PERMISSION_GRANTED;
+ || canViewInstantApps;
}
if (doDeletePackage) {
if (!deleteAllUsers) {
@@ -18620,6 +18735,14 @@
callingUid == getPackageUid(mStorageManagerPackage, 0, callingUserId)) {
return true;
}
+
+ // Allow caller having MANAGE_PROFILE_AND_DEVICE_OWNERS permission to silently
+ // uninstall for device owner provisioning.
+ if (checkUidPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, callingUid)
+ == PERMISSION_GRANTED) {
+ return true;
+ }
+
return false;
}
@@ -18779,7 +18902,7 @@
if (pkg != null) {
mInstantAppRegistry.onPackageUninstalledLPw(pkg, info.removedUsers);
}
- updateSequenceNumberLP(packageName, info.removedUsers);
+ updateSequenceNumberLP(uninstalledPs, info.removedUsers);
updateInstantAppInstallerLocked(packageName);
}
}
@@ -18913,8 +19036,9 @@
}
}
if (removedAppId >= 0) {
- packageSender.sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras,
- Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, null, null, broadcastUsers);
+ packageSender.sendPackageBroadcast(Intent.ACTION_UID_REMOVED,
+ null, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
+ null, null, broadcastUsers);
}
}
@@ -21135,7 +21259,8 @@
// Limit who can change which apps
if (!UserHandle.isSameApp(callingUid, pkgSetting.appId)) {
// Don't allow apps that don't have permission to modify other apps
- if (!allowedByPermission) {
+ if (!allowedByPermission
+ || filterAppAccessLPr(pkgSetting, callingUid, userId)) {
throw new SecurityException(
"Attempt to change component state; "
+ "pid=" + Binder.getCallingPid()
@@ -21222,7 +21347,7 @@
}
}
scheduleWritePackageRestrictionsLocked(userId);
- updateSequenceNumberLP(packageName, new int[] { userId });
+ updateSequenceNumberLP(pkgSetting, new int[] { userId });
final long callingId = Binder.clearCallingIdentity();
try {
updateInstantAppInstallerLocked(packageName);
@@ -21319,8 +21444,10 @@
true /* requireFullPermission */, true /* checkShell */, "stop package");
// writer
synchronized (mPackages) {
- if (mSettings.setPackageStoppedStateLPw(this, packageName, stopped,
- allowedByPermission, callingUid, userId)) {
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (!filterAppAccessLPr(ps, callingUid, userId)
+ && mSettings.setPackageStoppedStateLPw(this, packageName, stopped,
+ allowedByPermission, callingUid, userId)) {
scheduleWritePackageRestrictionsLocked(userId);
}
}
@@ -21328,11 +21455,16 @@
@Override
public String getInstallerPackageName(String packageName) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
+ final int callingUid = Binder.getCallingUid();
+ if (getInstantAppPackageName(callingUid) != null) {
return null;
}
// reader
synchronized (mPackages) {
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (filterAppAccessLPr(ps, callingUid, UserHandle.getUserId(callingUid))) {
+ return null;
+ }
return mSettings.getInstallerPackageNameLPr(packageName);
}
}
@@ -23485,7 +23617,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mCloseGuard.warnIfOpen();
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
close();
} finally {
super.finalize();
@@ -24081,7 +24216,8 @@
@Override
public boolean isPackageSignedByKeySet(String packageName, KeySet ks) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
+ final int callingUid = Binder.getCallingUid();
+ if (getInstantAppPackageName(callingUid) != null) {
return false;
}
if (packageName == null || ks == null) {
@@ -24089,7 +24225,9 @@
}
synchronized(mPackages) {
final PackageParser.Package pkg = mPackages.get(packageName);
- if (pkg == null) {
+ if (pkg == null
+ || filterAppAccessLPr((PackageSetting) pkg.mExtras, callingUid,
+ UserHandle.getUserId(callingUid))) {
Slog.w(TAG, "KeySet requested for unknown package: " + packageName);
throw new IllegalArgumentException("Unknown package: " + packageName);
}
@@ -24104,7 +24242,8 @@
@Override
public boolean isPackageSignedByKeySetExactly(String packageName, KeySet ks) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
+ final int callingUid = Binder.getCallingUid();
+ if (getInstantAppPackageName(callingUid) != null) {
return false;
}
if (packageName == null || ks == null) {
@@ -24112,7 +24251,9 @@
}
synchronized(mPackages) {
final PackageParser.Package pkg = mPackages.get(packageName);
- if (pkg == null) {
+ if (pkg == null
+ || filterAppAccessLPr((PackageSetting) pkg.mExtras, callingUid,
+ UserHandle.getUserId(callingUid))) {
Slog.w(TAG, "KeySet requested for unknown package: " + packageName);
throw new IllegalArgumentException("Unknown package: " + packageName);
}
@@ -24429,8 +24570,34 @@
}
@Override
- public ApplicationInfo getApplicationInfo(String packageName, int userId) {
- return PackageManagerService.this.getApplicationInfo(packageName, 0 /*flags*/, userId);
+ public PackageInfo getPackageInfo(
+ String packageName, int flags, int filterCallingUid, int userId) {
+ return PackageManagerService.this
+ .getPackageInfoInternal(packageName, PackageManager.VERSION_CODE_HIGHEST,
+ flags, filterCallingUid, userId);
+ }
+
+ @Override
+ public ApplicationInfo getApplicationInfo(
+ String packageName, int flags, int filterCallingUid, int userId) {
+ return PackageManagerService.this
+ .getApplicationInfoInternal(packageName, flags, filterCallingUid, userId);
+ }
+
+ @Override
+ public ActivityInfo getActivityInfo(
+ ComponentName component, int flags, int filterCallingUid, int userId) {
+ return PackageManagerService.this
+ .getActivityInfoInternal(component, flags, filterCallingUid, userId);
+ }
+
+ @Override
+ public List<ResolveInfo> queryIntentActivities(
+ Intent intent, int flags, int filterCallingUid, int userId) {
+ final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver());
+ return PackageManagerService.this
+ .queryIntentActivitiesInternal(intent, resolvedType, flags, filterCallingUid,
+ userId, false /*resolveForStart*/);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 184129e..f3a292b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -471,7 +471,8 @@
? mInterface.performDexOptSecondary(packageName,
targetCompilerFilter, forceCompilation)
: mInterface.performDexOptMode(packageName,
- checkProfiles, targetCompilerFilter, forceCompilation);
+ checkProfiles, targetCompilerFilter, forceCompilation,
+ true /* bootComplete */);
if (!result) {
failedPackages.add(packageName);
}
diff --git a/services/core/java/com/android/server/pm/ShortcutDumpFiles.java b/services/core/java/com/android/server/pm/ShortcutDumpFiles.java
new file mode 100644
index 0000000..dc380cc
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ShortcutDumpFiles.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm;
+
+import android.util.Slog;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.function.Consumer;
+
+public class ShortcutDumpFiles {
+ private static final String TAG = ShortcutService.TAG;
+ private static final boolean DEBUG = ShortcutService.DEBUG;
+ private final ShortcutService mService;
+
+ public ShortcutDumpFiles(ShortcutService service) {
+ mService = service;
+ }
+
+ public boolean save(String filename, Consumer<PrintWriter> dumper) {
+ try {
+ final File directory = mService.getDumpPath();
+ directory.mkdirs();
+ if (!directory.exists()) {
+ Slog.e(TAG, "Failed to create directory: " + directory);
+ return false;
+ }
+
+ final File path = new File(directory, filename);
+
+ if (DEBUG) {
+ Slog.d(TAG, "Dumping to " + path);
+ }
+
+ try (PrintWriter pw = new PrintWriter(new BufferedOutputStream(
+ new FileOutputStream(path)))) {
+ dumper.accept(pw);
+ }
+ return true;
+ } catch (RuntimeException|IOException e) {
+ Slog.w(TAG, "Failed to create dump file: " + filename, e);
+ return false;
+ }
+ }
+
+ public boolean save(String filename, byte[] utf8bytes) {
+ return save(filename, pw -> pw.println(StandardCharsets.UTF_8.decode(
+ ByteBuffer.wrap(utf8bytes)).toString()));
+ }
+
+ public void dumpAll(PrintWriter pw) {
+ try {
+ final File directory = mService.getDumpPath();
+ final File[] files = directory.listFiles(f -> f.isFile());
+ if (!directory.exists() || ArrayUtils.isEmpty(files)) {
+ pw.print(" No dump files found.");
+ return;
+ }
+ Arrays.sort(files, Comparator.comparing(f -> f.getName()));
+
+ for (File path : files) {
+ pw.print("*** Dumping: ");
+ pw.println(path.getName());
+
+ pw.print("mtime: ");
+ pw.println(ShortcutService.formatTime(path.lastModified()));
+
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+ new FileInputStream(path)))) {
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ pw.println(line);
+ }
+ }
+ }
+ } catch (RuntimeException|IOException e) {
+ Slog.w(TAG, "Failed to print dump files", e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 103b25d..6f70f4c 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -150,6 +150,10 @@
getPackageName(), getPackageUserId());
}
+ public int getShortcutCount() {
+ return mShortcuts.size();
+ }
+
@Override
protected void onRestoreBlocked() {
// Can't restore due to version/signature mismatch. Remove all shortcuts.
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 6d48a05..7911972 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -181,6 +181,9 @@
static final String DIRECTORY_PER_USER = "shortcut_service";
@VisibleForTesting
+ static final String DIRECTORY_DUMP = "shortcut_dump";
+
+ @VisibleForTesting
static final String FILENAME_USER_PACKAGES = "shortcuts.xml";
static final String DIRECTORY_BITMAPS = "bitmaps";
@@ -308,6 +311,7 @@
private final ShortcutRequestPinProcessor mShortcutRequestPinProcessor;
private final ShortcutBitmapSaver mShortcutBitmapSaver;
+ private final ShortcutDumpFiles mShortcutDumpFiles;
@GuardedBy("mLock")
final SparseIntArray mUidState = new SparseIntArray();
@@ -429,6 +433,7 @@
mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock);
mShortcutBitmapSaver = new ShortcutBitmapSaver(this);
+ mShortcutDumpFiles = new ShortcutDumpFiles(this);
if (onlyForPackageManagerApis) {
return; // Don't do anything further. For unit tests only.
@@ -3395,6 +3400,16 @@
wtf("Can't restore: user " + userId + " is locked or not running");
return;
}
+
+ // Note we print the file timestamps in dumpsys too, but also printing the timestamp
+ // in the files anyway.
+ mShortcutDumpFiles.save("restore-0-start.txt", pw -> {
+ pw.print("Start time: ");
+ dumpCurrentTime(pw);
+ pw.println();
+ });
+ mShortcutDumpFiles.save("restore-1-payload.xml", payload);
+
// Actually do restore.
final ShortcutUser restored;
final ByteArrayInputStream is = new ByteArrayInputStream(payload);
@@ -3404,13 +3419,25 @@
Slog.w(TAG, "Restoration failed.", e);
return;
}
+ mShortcutDumpFiles.save("restore-2.txt", this::dumpInner);
+
getUserShortcutsLocked(userId).mergeRestoredFile(restored);
+ mShortcutDumpFiles.save("restore-3.txt", this::dumpInner);
+
// Rescan all packages to re-publish manifest shortcuts and do other checks.
rescanUpdatedPackagesLocked(userId,
0 // lastScanTime = 0; rescan all packages.
);
+ mShortcutDumpFiles.save("restore-4.txt", this::dumpInner);
+
+ mShortcutDumpFiles.save("restore-5-finish.txt", pw -> {
+ pw.print("Finish time: ");
+ dumpCurrentTime(pw);
+ pw.println();
+ });
+
saveUserLocked(userId);
}
}
@@ -3425,23 +3452,54 @@
@VisibleForTesting
void dumpNoCheck(FileDescriptor fd, PrintWriter pw, String[] args) {
+
+ boolean dumpMain = true;
boolean checkin = false;
boolean clear = false;
+ boolean dumpUid = false;
+ boolean dumpFiles = false;
+
if (args != null) {
for (String arg : args) {
if ("-c".equals(arg)) {
checkin = true;
+
} else if ("--checkin".equals(arg)) {
checkin = true;
clear = true;
+
+ } else if ("-a".equals(arg) || "--all".equals(arg)) {
+ dumpUid = true;
+ dumpFiles = true;
+
+ } else if ("-u".equals(arg) || "--uid".equals(arg)) {
+ dumpUid = true;
+
+ } else if ("-f".equals(arg) || "--files".equals(arg)) {
+ dumpFiles = true;
+
+ } else if ("-n".equals(arg) || "--no-main".equals(arg)) {
+ dumpMain = false;
}
}
}
if (checkin) {
+ // Other flags are not supported for checkin.
dumpCheckin(pw, clear);
} else {
- dumpInner(pw);
+ if (dumpMain) {
+ dumpInner(pw);
+ pw.println();
+ }
+ if (dumpUid) {
+ dumpUid(pw);
+ pw.println();
+ }
+ if (dumpFiles) {
+ dumpDumpFiles(pw);
+ pw.println();
+ }
}
}
@@ -3510,9 +3568,12 @@
pw.println();
mUsers.valueAt(i).dump(pw, " ");
}
+ }
+ }
- pw.println();
- pw.println(" UID state:");
+ private void dumpUid(PrintWriter pw) {
+ synchronized (mLock) {
+ pw.println("** SHORTCUT MANAGER UID STATES (dumpsys shortcut -n -u)");
for (int i = 0; i < mUidState.size(); i++) {
final int uid = mUidState.keyAt(i);
@@ -3537,6 +3598,10 @@
return tobj.format("%Y-%m-%d %H:%M:%S");
}
+ private void dumpCurrentTime(PrintWriter pw) {
+ pw.print(formatTime(injectCurrentTimeMillis()));
+ }
+
private void dumpStatLS(PrintWriter pw, String prefix, int statId) {
pw.print(prefix);
final int count = mCountStats[statId];
@@ -3574,6 +3639,13 @@
}
}
+ private void dumpDumpFiles(PrintWriter pw) {
+ synchronized (mLock) {
+ pw.println("** SHORTCUT MANAGER FILES (dumpsys shortcut -n -f)");
+ mShortcutDumpFiles.dumpAll(pw);
+ }
+ }
+
// === Shell support ===
@Override
@@ -3876,6 +3948,10 @@
return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER);
}
+ public File getDumpPath() {
+ return new File(injectUserDataPath(UserHandle.USER_SYSTEM), DIRECTORY_DUMP);
+ }
+
@VisibleForTesting
boolean injectIsLowRamDevice() {
return ActivityManager.isLowRamDeviceStatic();
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 5d4bfa4..2c388c4 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -25,9 +25,7 @@
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
-import android.util.SparseArray;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.server.pm.ShortcutService.InvalidFileFormatException;
@@ -492,6 +490,10 @@
// without users interaction it's really not a big deal, so we just clear existing
// ShortcutLauncher instances in mLaunchers and add all the restored ones here.
+ int[] restoredLaunchers = new int[1];
+ int[] restoredPackages = new int[1];
+ int[] restoredShortcuts = new int[1];
+
mLaunchers.clear();
restored.forAllLaunchers(sl -> {
// If the app is already installed and allowbackup = false, then ignore the restored
@@ -501,6 +503,7 @@
return;
}
addLauncher(sl);
+ restoredLaunchers[0]++;
});
restored.forAllPackages(sp -> {
// If the app is already installed and allowbackup = false, then ignore the restored
@@ -516,10 +519,16 @@
+ " Existing non-manifeset shortcuts will be overwritten.");
}
addPackage(sp);
+ restoredPackages[0]++;
+ restoredShortcuts[0] += sp.getShortcutCount();
});
// Empty the launchers and packages in restored to avoid accidentally using them.
restored.mLaunchers.clear();
restored.mPackages.clear();
+
+ Slog.i(TAG, "Restored: L=" + restoredLaunchers[0]
+ + " P=" + restoredPackages[0]
+ + " S=" + restoredShortcuts[0]);
}
public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b115422..a64ab43 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -223,6 +223,7 @@
// Tron counters
private static final String TRON_GUEST_CREATED = "users_guest_created";
private static final String TRON_USER_CREATED = "users_user_created";
+ private static final String TRON_DEMO_CREATED = "users_demo_created";
private final Context mContext;
private final PackageManagerService mPm;
@@ -2523,7 +2524,8 @@
addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
android.Manifest.permission.MANAGE_USERS);
- MetricsLogger.count(mContext, isGuest ? TRON_GUEST_CREATED : TRON_USER_CREATED, 1);
+ MetricsLogger.count(mContext, isGuest ? TRON_GUEST_CREATED
+ : (isDemo ? TRON_DEMO_CREATED : TRON_USER_CREATED), 1);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index c6667a7..a6b05d7 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -31,7 +31,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
-import android.service.persistentdata.PersistentDataBlockManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.util.Log;
@@ -519,22 +518,6 @@
android.provider.Settings.Global.SAFE_BOOT_DISALLOWED,
newValue ? 1 : 0);
break;
- case UserManager.DISALLOW_FACTORY_RESET:
- case UserManager.DISALLOW_OEM_UNLOCK:
- if (newValue) {
- PersistentDataBlockManager manager = (PersistentDataBlockManager) context
- .getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
- if (manager != null
- && manager.getOemUnlockEnabled()
- && manager.getFlashLockState()
- != PersistentDataBlockManager.FLASH_LOCK_UNLOCKED) {
- // Only disable OEM unlock if the bootloader is locked. If it's already
- // unlocked, setting the OEM unlock enabled flag to false has no effect
- // (the bootloader would remain unlocked).
- manager.setOemUnlockEnabled(false);
- }
- }
- break;
}
} finally {
Binder.restoreCallingIdentity(id);
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 9b44646..be50eee 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -30,6 +30,7 @@
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.PackageDexOptimizer;
+import com.android.server.pm.PackageManagerService;
import com.android.server.pm.PackageManagerServiceUtils;
import com.android.server.pm.PackageManagerServiceCompilerMapping;
@@ -41,6 +42,7 @@
import java.util.Map;
import java.util.Set;
+import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
@@ -437,6 +439,52 @@
}
}
+ public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath,
+ boolean isUsedByOtherApps, int userId) {
+ // Find the owning package record.
+ DexSearchResult searchResult = getDexPackage(info, dexPath, userId);
+
+ if (searchResult.mOutcome == DEX_SEARCH_NOT_FOUND) {
+ return new RegisterDexModuleResult(false, "Package not found");
+ }
+ if (!info.packageName.equals(searchResult.mOwningPackageName)) {
+ return new RegisterDexModuleResult(false, "Dex path does not belong to package");
+ }
+ if (searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
+ searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT) {
+ return new RegisterDexModuleResult(false, "Main apks cannot be registered");
+ }
+
+ // We found the package. Now record the usage for all declared ISAs.
+ boolean update = false;
+ Set<String> isas = new HashSet<>();
+ for (String isa : getAppDexInstructionSets(info)) {
+ isas.add(isa);
+ boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName,
+ dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false);
+ update |= newUpdate;
+ }
+ if (update) {
+ mPackageDexUsage.maybeWriteAsync();
+ }
+
+ // Try to optimize the package according to the install reason.
+ String compilerFilter = PackageManagerServiceCompilerMapping.getCompilerFilterForReason(
+ PackageManagerService.REASON_INSTALL);
+ int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, isas,
+ compilerFilter, isUsedByOtherApps);
+
+ // If we fail to optimize the package log an error but don't propagate the error
+ // back to the app. The app cannot do much about it and the background job
+ // will rety again when it executes.
+ // TODO(calin): there might be some value to return the error here but it may
+ // cause red herrings since that doesn't mean the app cannot use the module.
+ if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
+ Slog.e(TAG, "Failed to optimize dex module " + dexPath);
+ }
+ return new RegisterDexModuleResult(true, "Dex module registered successfully");
+ }
+
/**
* Return all packages that contain records of secondary dex files.
*/
@@ -518,6 +566,20 @@
return existingValue == null ? newValue : existingValue;
}
+ public static class RegisterDexModuleResult {
+ public RegisterDexModuleResult() {
+ this(false, null);
+ }
+
+ public RegisterDexModuleResult(boolean success, String message) {
+ this.success = success;
+ this.message = message;
+ }
+
+ public final boolean success;
+ public final String message;
+ }
+
/**
* Convenience class to store the different locations where a package might
* own code.
@@ -597,6 +659,4 @@
return mOwningPackageName + "-" + mOutcome;
}
}
-
-
}
diff --git a/services/core/java/com/android/server/policy/AccessibilityShortcutController.java b/services/core/java/com/android/server/policy/AccessibilityShortcutController.java
index 7d53310..5090083 100644
--- a/services/core/java/com/android/server/policy/AccessibilityShortcutController.java
+++ b/services/core/java/com/android/server/policy/AccessibilityShortcutController.java
@@ -78,6 +78,7 @@
public AccessibilityShortcutController(Context context, Handler handler, int initialUserId) {
mContext = context;
+ mUserId = initialUserId;
// Keep track of state of shortcut settings
final ContentObserver co = new ContentObserver(handler) {
@@ -233,7 +234,7 @@
private AccessibilityServiceInfo getInfoForTargetService() {
final String currentShortcutServiceString = getTargetServiceComponentNameString(
- mContext, UserHandle.myUserId());
+ mContext, UserHandle.USER_CURRENT);
if (currentShortcutServiceString == null) {
return null;
}
diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
index a71bc4c..14fabc5 100644
--- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java
+++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
@@ -228,6 +228,7 @@
R.string.global_actions_airplane_mode_on_status,
R.string.global_actions_airplane_mode_off_status) {
+ @Override
void onToggle(boolean on) {
if (mHasTelephony && Boolean.parseBoolean(
SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
@@ -254,10 +255,12 @@
}
}
+ @Override
public boolean showDuringKeyguard() {
return true;
}
+ @Override
public boolean showBeforeProvisioning() {
return false;
}
@@ -457,6 +460,7 @@
return false;
}
+ @Override
public boolean showDuringKeyguard() {
return true;
}
@@ -616,6 +620,7 @@
com.android.internal.R.drawable.ic_menu_cc, icon,
(user.name != null ? user.name : "Primary")
+ (isCurrentUser ? " \u2714" : "")) {
+ @Override
public void onPress() {
try {
ActivityManager.getService().switchUser(user.id);
@@ -624,10 +629,12 @@
}
}
+ @Override
public boolean showDuringKeyguard() {
return true;
}
+ @Override
public boolean showBeforeProvisioning() {
return false;
}
@@ -659,6 +666,7 @@
}
/** {@inheritDoc} */
+ @Override
public void onDismiss(DialogInterface dialog) {
if (mOnDismiss != null) {
mOnDismiss.run();
@@ -674,6 +682,7 @@
}
/** {@inheritDoc} */
+ @Override
public void onClick(DialogInterface dialog, int which) {
if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) {
dialog.dismiss();
@@ -689,6 +698,7 @@
*/
private class MyAdapter extends BaseAdapter {
+ @Override
public int getCount() {
int count = 0;
@@ -716,6 +726,7 @@
return false;
}
+ @Override
public Action getItem(int position) {
int filteredPos = 0;
@@ -740,11 +751,12 @@
+ ", provisioned=" + mDeviceProvisioned);
}
-
+ @Override
public long getItemId(int position) {
return position;
}
+ @Override
public View getView(int position, View convertView, ViewGroup parent) {
Action action = getItem(position);
return action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
@@ -817,6 +829,7 @@
mIcon = icon;
}
+ @Override
public boolean isEnabled() {
return true;
}
@@ -825,8 +838,10 @@
return null;
}
+ @Override
abstract public void onPress();
+ @Override
public CharSequence getLabelForAccessibility(Context context) {
if (mMessage != null) {
return mMessage;
@@ -835,6 +850,7 @@
}
}
+ @Override
public View create(
Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
View v = inflater.inflate(R.layout.global_actions_item, parent, false);
@@ -929,6 +945,7 @@
return context.getString(mMessageResId);
}
+ @Override
public View create(Context context, View convertView, ViewGroup parent,
LayoutInflater inflater) {
willCreate();
@@ -963,6 +980,7 @@
return v;
}
+ @Override
public final void onPress() {
if (mState.inTransition()) {
Log.w(TAG, "shouldn't be able to toggle when in transition");
@@ -974,6 +992,7 @@
changeStateFromPress(nowOn);
}
+ @Override
public boolean isEnabled() {
return !mState.inTransition();
}
@@ -1004,6 +1023,7 @@
R.string.global_action_silent_mode_off_status);
}
+ @Override
void onToggle(boolean on) {
if (on) {
mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
@@ -1012,10 +1032,12 @@
}
}
+ @Override
public boolean showDuringKeyguard() {
return true;
}
+ @Override
public boolean showBeforeProvisioning() {
return false;
}
@@ -1050,6 +1072,7 @@
return null;
}
+ @Override
public View create(Context context, View convertView, ViewGroup parent,
LayoutInflater inflater) {
View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false);
@@ -1065,17 +1088,21 @@
return v;
}
+ @Override
public void onPress() {
}
+ @Override
public boolean showDuringKeyguard() {
return true;
}
+ @Override
public boolean showBeforeProvisioning() {
return false;
}
+ @Override
public boolean isEnabled() {
return true;
}
@@ -1083,6 +1110,7 @@
void willCreate() {
}
+ @Override
public void onClick(View v) {
if (!(v.getTag() instanceof Integer)) return;
@@ -1093,6 +1121,7 @@
}
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
@@ -1146,6 +1175,7 @@
private static final int DIALOG_DISMISS_DELAY = 300; // ms
private Handler mHandler = new Handler() {
+ @Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_DISMISS:
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 6d46fda..4bc3725 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -221,6 +221,7 @@
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
+import android.view.autofill.AutofillManagerInternal;
import android.view.inputmethod.InputMethodManagerInternal;
import com.android.internal.R;
@@ -407,6 +408,7 @@
WindowManagerInternal mWindowManagerInternal;
PowerManager mPowerManager;
ActivityManagerInternal mActivityManagerInternal;
+ AutofillManagerInternal mAutofillManagerInternal;
InputManagerInternal mInputManagerInternal;
InputMethodManagerInternal mInputMethodManagerInternal;
DreamManagerInternal mDreamManagerInternal;
@@ -429,15 +431,6 @@
// Vibrator pattern for haptic feedback of a long press.
long[] mLongPressVibePattern;
- // Vibrator pattern for haptic feedback of virtual key press.
- long[] mVirtualKeyVibePattern;
-
- // Vibrator pattern for a short vibration.
- long[] mKeyboardTapVibePattern;
-
- // Vibrator pattern for a short vibration when tapping on an hour/minute tick of a Clock.
- long[] mClockTickVibePattern;
-
// Vibrator pattern for a short vibration when tapping on a day/month/year date of a Calendar.
long[] mCalendarDateVibePattern;
@@ -447,9 +440,6 @@
// Vibrator pattern for haptic feedback during boot when safe mode is enabled.
long[] mSafeModeEnabledVibePattern;
- // Vibrator pattern for haptic feedback of a context click.
- long[] mContextClickVibePattern;
-
/** If true, hitting shift & menu will broadcast Intent.ACTION_BUG_REPORT */
boolean mEnableShiftMenuBugReports = false;
@@ -831,6 +821,7 @@
private static final int MSG_ACCESSIBILITY_SHORTCUT = 21;
private static final int MSG_BUGREPORT_TV = 22;
private static final int MSG_ACCESSIBILITY_TV = 23;
+ private static final int MSG_DISPATCH_BACK_KEY_TO_AUTOFILL = 24;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
@@ -917,6 +908,9 @@
accessibilityShortcutActivated();
}
break;
+ case MSG_DISPATCH_BACK_KEY_TO_AUTOFILL:
+ mAutofillManagerInternal.onBackKeyPressed();
+ break;
}
}
}
@@ -1224,6 +1218,10 @@
}
}
+ if (mAutofillManagerInternal != null && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_BACK_KEY_TO_AUTOFILL));
+ }
+
return handled;
}
@@ -1337,7 +1335,7 @@
Message msg = mHandler.obtainMessage(MSG_POWER_DELAYED_PRESS,
interactive ? 1 : 0, mPowerKeyPressCounter, eventTime);
msg.setAsynchronous(true);
- mHandler.sendMessageDelayed(msg, ViewConfiguration.getDoubleTapTimeout());
+ mHandler.sendMessageDelayed(msg, ViewConfiguration.getMultiPressTimeout());
return;
}
@@ -2060,20 +2058,12 @@
mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
mLongPressVibePattern = getLongIntArray(mContext.getResources(),
com.android.internal.R.array.config_longPressVibePattern);
- mVirtualKeyVibePattern = getLongIntArray(mContext.getResources(),
- com.android.internal.R.array.config_virtualKeyVibePattern);
- mKeyboardTapVibePattern = getLongIntArray(mContext.getResources(),
- com.android.internal.R.array.config_keyboardTapVibePattern);
- mClockTickVibePattern = getLongIntArray(mContext.getResources(),
- com.android.internal.R.array.config_clockTickVibePattern);
mCalendarDateVibePattern = getLongIntArray(mContext.getResources(),
com.android.internal.R.array.config_calendarDateVibePattern);
mSafeModeDisabledVibePattern = getLongIntArray(mContext.getResources(),
com.android.internal.R.array.config_safeModeDisabledVibePattern);
mSafeModeEnabledVibePattern = getLongIntArray(mContext.getResources(),
com.android.internal.R.array.config_safeModeEnabledVibePattern);
- mContextClickVibePattern = getLongIntArray(mContext.getResources(),
- com.android.internal.R.array.config_contextClickVibePattern);
mScreenshotChordEnabled = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_enableScreenshotChord);
@@ -5043,17 +5033,27 @@
// A window that has requested to fill the entire screen just
// gets everything, period.
if (attrs.type == TYPE_STATUS_BAR_PANEL
- || attrs.type == TYPE_STATUS_BAR_SUB_PANEL
- || attrs.type == TYPE_VOLUME_OVERLAY) {
+ || attrs.type == TYPE_STATUS_BAR_SUB_PANEL) {
pf.left = df.left = of.left = cf.left = hasNavBar
? mDockLeft : mUnrestrictedScreenLeft;
pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
pf.right = df.right = of.right = cf.right = hasNavBar
- ? mRestrictedScreenLeft+mRestrictedScreenWidth
- : mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
+ ? mRestrictedScreenLeft + mRestrictedScreenWidth
+ : mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
pf.bottom = df.bottom = of.bottom = cf.bottom = hasNavBar
- ? mRestrictedScreenTop+mRestrictedScreenHeight
- : mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+ ? mRestrictedScreenTop + mRestrictedScreenHeight
+ : mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+ if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
+ "Laying out IN_SCREEN status bar window: (%d,%d - %d,%d)",
+ pf.left, pf.top, pf.right, pf.bottom));
+ } else if (attrs.type == TYPE_VOLUME_OVERLAY) {
+ // Volume overlay covers everything, including the status and navbar
+ pf.left = df.left = of.left = cf.left = mUnrestrictedScreenLeft;
+ pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
+ pf.right = df.right = of.right = cf.right =
+ mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
+ pf.bottom = df.bottom = of.bottom = cf.bottom =
+ mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
"Laying out IN_SCREEN status bar window: (%d,%d - %d,%d)",
pf.left, pf.top, pf.right, pf.bottom));
@@ -7239,6 +7239,8 @@
mSystemGestures.systemReady();
mImmersiveModeConfirmation.systemReady();
+
+ mAutofillManagerInternal = LocalServices.getService(AutofillManagerInternal.class);
}
/** {@inheritDoc} */
@@ -7647,7 +7649,7 @@
private boolean areSystemNavigationKeysEnabled() {
return Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
+ Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED, 1, UserHandle.USER_CURRENT) == 1;
}
@Override
@@ -7690,8 +7692,7 @@
case HapticFeedbackConstants.KEYBOARD_TAP:
return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
case HapticFeedbackConstants.CLOCK_TICK:
- pattern = mClockTickVibePattern;
- break;
+ return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
case HapticFeedbackConstants.CALENDAR_DATE:
pattern = mCalendarDateVibePattern;
break;
@@ -7702,8 +7703,9 @@
pattern = mSafeModeEnabledVibePattern;
break;
case HapticFeedbackConstants.CONTEXT_CLICK:
- pattern = mContextClickVibePattern;
- break;
+ return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+ case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
+ return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
default:
return null;
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 3b5db29..a94484a 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1398,13 +1398,16 @@
try {
switch (mWakefulness) {
case WAKEFULNESS_ASLEEP:
- Slog.i(TAG, "Waking up from sleep (uid " + reasonUid +")...");
+ Slog.i(TAG, "Waking up from sleep (uid=" + reasonUid + " reason=" + reason
+ + ")...");
break;
case WAKEFULNESS_DREAMING:
- Slog.i(TAG, "Waking up from dream (uid " + reasonUid +")...");
+ Slog.i(TAG, "Waking up from dream (uid=" + reasonUid + " reason=" + reason
+ + ")...");
break;
case WAKEFULNESS_DOZING:
- Slog.i(TAG, "Waking up from dozing (uid " + reasonUid +")...");
+ Slog.i(TAG, "Waking up from dozing (uid=" + reasonUid + " reason=" + reason
+ + ")...");
break;
}
diff --git a/services/core/java/com/android/server/radio/RadioService.java b/services/core/java/com/android/server/radio/RadioService.java
new file mode 100644
index 0000000..34bbffd
--- /dev/null
+++ b/services/core/java/com/android/server/radio/RadioService.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.radio;
+
+import android.content.Context;
+import android.hardware.radio.IRadioService;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.RadioManager;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+public class RadioService extends SystemService {
+ // TODO(b/36863239): rename to RadioService when native service goes away
+ private static final String TAG = "RadioServiceJava";
+
+ private final RadioServiceImpl mServiceImpl = new RadioServiceImpl();
+
+ /**
+ * This field is used by native code, do not access or modify.
+ */
+ private final long mNativeContext = nativeInit();
+
+ private final Object mLock = new Object();
+
+ public RadioService(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ nativeFinalize(mNativeContext);
+ super.finalize();
+ }
+
+ private native long nativeInit();
+ private native void nativeFinalize(long nativeContext);
+ private native Tuner nativeOpenTuner(long nativeContext, int moduleId,
+ RadioManager.BandConfig config, boolean withAudio, ITunerCallback callback);
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.RADIO_SERVICE, mServiceImpl);
+ Slog.v(TAG, "RadioService started");
+ }
+
+ private class RadioServiceImpl extends IRadioService.Stub {
+ @Override
+ public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
+ boolean withAudio, ITunerCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("Callback must not be empty");
+ }
+ synchronized (mLock) {
+ // TODO(b/36863239): add death monitoring for binder
+ return nativeOpenTuner(mNativeContext, moduleId, bandConfig, withAudio, callback);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/radio/Tuner.java b/services/core/java/com/android/server/radio/Tuner.java
new file mode 100644
index 0000000..9915c34
--- /dev/null
+++ b/services/core/java/com/android/server/radio/Tuner.java
@@ -0,0 +1,225 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.radio;
+
+import android.annotation.NonNull;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.RadioManager;
+import android.util.Slog;
+
+import java.util.List;
+
+class Tuner extends ITuner.Stub {
+ // TODO(b/36863239): rename to RadioService.Tuner when native service goes away
+ private static final String TAG = "RadioServiceJava.Tuner";
+
+ /**
+ * This field is used by native code, do not access or modify.
+ */
+ private final long mNativeContext;
+
+ @NonNull private final TunerCallback mTunerCallback;
+ private final Object mLock = new Object();
+ private boolean mIsClosed = false;
+ private boolean mIsMuted = false;
+ private int mRegion; // TODO(b/36863239): find better solution to manage regions
+ private final boolean mWithAudio;
+
+ Tuner(@NonNull ITunerCallback clientCallback, int halRev, int region, boolean withAudio) {
+ mTunerCallback = new TunerCallback(this, clientCallback, halRev);
+ mRegion = region;
+ mWithAudio = withAudio;
+ mNativeContext = nativeInit(halRev);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ nativeFinalize(mNativeContext);
+ super.finalize();
+ }
+
+ private native long nativeInit(int halRev);
+ private native void nativeFinalize(long nativeContext);
+ private native void nativeClose(long nativeContext);
+
+ private native void nativeSetConfiguration(long nativeContext,
+ @NonNull RadioManager.BandConfig config);
+ private native RadioManager.BandConfig nativeGetConfiguration(long nativeContext, int region);
+
+ private native void nativeStep(long nativeContext, boolean directionDown, boolean skipSubChannel);
+ private native void nativeScan(long nativeContext, boolean directionDown, boolean skipSubChannel);
+ private native void nativeTune(long nativeContext, int channel, int subChannel);
+ private native void nativeCancel(long nativeContext);
+
+ private native RadioManager.ProgramInfo nativeGetProgramInformation(long nativeContext);
+ private native boolean nativeStartBackgroundScan(long nativeContext);
+ private native List<RadioManager.ProgramInfo> nativeGetProgramList(long nativeContext,
+ String filter);
+
+ private native boolean nativeIsAnalogForced(long nativeContext);
+ private native void nativeSetAnalogForced(long nativeContext, boolean isForced);
+
+ private native boolean nativeIsAntennaConnected(long nativeContext);
+
+ @Override
+ public void close() {
+ synchronized (mLock) {
+ if (mIsClosed) return;
+ mTunerCallback.detach();
+ nativeClose(mNativeContext);
+ mIsClosed = true;
+ }
+ }
+
+ private void checkNotClosedLocked() {
+ if (mIsClosed) {
+ throw new IllegalStateException("Tuner is closed, no further operations are allowed");
+ }
+ }
+
+ @Override
+ public void setConfiguration(RadioManager.BandConfig config) {
+ if (config == null) {
+ throw new IllegalArgumentException("The argument must not be a null pointer");
+ }
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ nativeSetConfiguration(mNativeContext, config);
+ mRegion = config.getRegion();
+ }
+ }
+
+ @Override
+ public RadioManager.BandConfig getConfiguration() {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ return nativeGetConfiguration(mNativeContext, mRegion);
+ }
+ }
+
+ @Override
+ public void setMuted(boolean mute) {
+ if (!mWithAudio) {
+ throw new IllegalStateException("Can't operate on mute - no audio requested");
+ }
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ if (mIsMuted == mute) return;
+ mIsMuted = mute;
+
+ // TODO(b/34348946): notifify audio policy manager of media activity on radio audio
+ // device. This task is pulled directly from previous implementation of native service.
+ }
+ }
+
+ @Override
+ public boolean isMuted() {
+ if (!mWithAudio) {
+ Slog.w(TAG, "Tuner did not request audio, pretending it was muted");
+ return true;
+ }
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ return mIsMuted;
+ }
+ }
+
+ @Override
+ public void step(boolean directionDown, boolean skipSubChannel) {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ nativeStep(mNativeContext, directionDown, skipSubChannel);
+ }
+ }
+
+ @Override
+ public void scan(boolean directionDown, boolean skipSubChannel) {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ nativeScan(mNativeContext, directionDown, skipSubChannel);
+ }
+ }
+
+ @Override
+ public void tune(int channel, int subChannel) {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ nativeTune(mNativeContext, channel, subChannel);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ nativeCancel(mNativeContext);
+ }
+ }
+
+ @Override
+ public RadioManager.ProgramInfo getProgramInformation() {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ return nativeGetProgramInformation(mNativeContext);
+ }
+ }
+
+ @Override
+ public boolean startBackgroundScan() {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ return nativeStartBackgroundScan(mNativeContext);
+ }
+ }
+
+ @Override
+ public List<RadioManager.ProgramInfo> getProgramList(String filter) {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ List<RadioManager.ProgramInfo> list = nativeGetProgramList(mNativeContext, filter);
+ if (list == null) {
+ throw new IllegalStateException("Program list is not ready");
+ }
+ return list;
+ }
+ }
+
+ @Override
+ public boolean isAnalogForced() {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ return nativeIsAnalogForced(mNativeContext);
+ }
+ }
+
+ @Override
+ public void setAnalogForced(boolean isForced) {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ nativeSetAnalogForced(mNativeContext, isForced);
+ }
+ }
+
+ @Override
+ public boolean isAntennaConnected() {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ return nativeIsAntennaConnected(mNativeContext);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/radio/TunerCallback.java b/services/core/java/com/android/server/radio/TunerCallback.java
new file mode 100644
index 0000000..fcc874b
--- /dev/null
+++ b/services/core/java/com/android/server/radio/TunerCallback.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.radio;
+
+import android.annotation.NonNull;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioTuner;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+class TunerCallback implements ITunerCallback {
+ // TODO(b/36863239): rename to RadioService.TunerCallback when native service goes away
+ private static final String TAG = "RadioServiceJava.TunerCallback";
+
+ /**
+ * This field is used by native code, do not access or modify.
+ */
+ private final long mNativeContext;
+
+ @NonNull private final Tuner mTuner;
+ @NonNull private final ITunerCallback mClientCallback;
+
+ TunerCallback(@NonNull Tuner tuner, @NonNull ITunerCallback clientCallback, int halRev) {
+ mTuner = tuner;
+ mClientCallback = clientCallback;
+ mNativeContext = nativeInit(tuner, halRev);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ nativeFinalize(mNativeContext);
+ super.finalize();
+ }
+
+ private native long nativeInit(@NonNull Tuner tuner, int halRev);
+ private native void nativeFinalize(long nativeContext);
+ private native void nativeDetach(long nativeContext);
+
+ public void detach() {
+ nativeDetach(mNativeContext);
+ }
+
+ // called from native side
+ private void handleHwFailure() {
+ onError(RadioTuner.ERROR_HARDWARE_FAILURE);
+ mTuner.close();
+ }
+
+ @Override
+ public void onError(int status) {
+ try {
+ mClientCallback.onError(status);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "client died", e);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(RadioManager.BandConfig config) {
+ try {
+ mClientCallback.onConfigurationChanged(config);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "client died", e);
+ }
+ }
+
+ @Override
+ public void onProgramInfoChanged(RadioManager.ProgramInfo info) {
+ try {
+ mClientCallback.onProgramInfoChanged(info);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "client died", e);
+ }
+ }
+
+ @Override
+ public IBinder asBinder() {
+ throw new RuntimeException("Not a binder");
+ }
+}
diff --git a/services/core/java/com/android/server/timezone/CheckToken.java b/services/core/java/com/android/server/timezone/CheckToken.java
new file mode 100644
index 0000000..5128360
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/CheckToken.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * A deserialized version of the byte[] sent to the time zone update application to identify a
+ * triggered time zone update check. It encodes the optimistic lock ID used to detect
+ * concurrent checks and the minimal package versions that will have been checked.
+ */
+final class CheckToken {
+
+ final int mOptimisticLockId;
+ final PackageVersions mPackageVersions;
+
+ CheckToken(int optimisticLockId, PackageVersions packageVersions) {
+ this.mOptimisticLockId = optimisticLockId;
+
+ if (packageVersions == null) {
+ throw new NullPointerException("packageVersions == null");
+ }
+ this.mPackageVersions = packageVersions;
+ }
+
+ byte[] toByteArray() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(12 /* (3 * sizeof(int)) */);
+ try (DataOutputStream dos = new DataOutputStream(baos)) {
+ dos.writeInt(mOptimisticLockId);
+ dos.writeInt(mPackageVersions.mUpdateAppVersion);
+ dos.writeInt(mPackageVersions.mDataAppVersion);
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to write into a ByteArrayOutputStream", e);
+ }
+ return baos.toByteArray();
+ }
+
+ static CheckToken fromByteArray(byte[] tokenBytes) throws IOException {
+ ByteArrayInputStream bais = new ByteArrayInputStream(tokenBytes);
+ try (DataInputStream dis = new DataInputStream(bais)) {
+ int versionId = dis.readInt();
+ int updateAppVersion = dis.readInt();
+ int dataAppVersion = dis.readInt();
+ return new CheckToken(versionId, new PackageVersions(updateAppVersion, dataAppVersion));
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ CheckToken checkToken = (CheckToken) o;
+
+ if (mOptimisticLockId != checkToken.mOptimisticLockId) {
+ return false;
+ }
+ return mPackageVersions.equals(checkToken.mPackageVersions);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mOptimisticLockId;
+ result = 31 * result + mPackageVersions.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "Token{" +
+ "mOptimisticLockId=" + mOptimisticLockId +
+ ", mPackageVersions=" + mPackageVersions +
+ '}';
+ }
+}
diff --git a/services/core/java/com/android/server/timezone/ClockHelper.java b/services/core/java/com/android/server/timezone/ClockHelper.java
new file mode 100644
index 0000000..353728a
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/ClockHelper.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+/**
+ * An easy-to-mock interface for obtaining a monotonically increasing time value in milliseconds.
+ */
+interface ClockHelper {
+
+ long currentTimestamp();
+}
diff --git a/services/core/java/com/android/server/timezone/ConfigHelper.java b/services/core/java/com/android/server/timezone/ConfigHelper.java
new file mode 100644
index 0000000..f9984fa
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/ConfigHelper.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+/**
+ * An easy-to-mock interface around device config for use by {@link PackageTracker}; it is not
+ * possible to test various states with the real one because config is fixed in the system image.
+ */
+interface ConfigHelper {
+
+ boolean isTrackingEnabled();
+
+ String getUpdateAppPackageName();
+
+ String getDataAppPackageName();
+
+ int getCheckTimeAllowedMillis();
+
+ int getFailedCheckRetryCount();
+}
diff --git a/services/core/java/com/android/server/timezone/FileDescriptorHelper.java b/services/core/java/com/android/server/timezone/FileDescriptorHelper.java
new file mode 100644
index 0000000..c3b1101
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/FileDescriptorHelper.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import android.os.ParcelFileDescriptor;
+
+import java.io.IOException;
+
+/**
+ * An easy-to-mock interface around use of {@link ParcelFileDescriptor} for use by
+ * {@link RulesManagerService}.
+ */
+interface FileDescriptorHelper {
+
+ byte[] readFully(ParcelFileDescriptor parcelFileDescriptor) throws IOException;
+}
diff --git a/services/core/java/com/android/server/timezone/IntentHelper.java b/services/core/java/com/android/server/timezone/IntentHelper.java
new file mode 100644
index 0000000..0cb9065
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/IntentHelper.java
@@ -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.
+ */
+
+package com.android.server.timezone;
+
+/**
+ * An easy-to-mock interface around intent sending / receiving for use by {@link PackageTracker};
+ * it is not possible to test various cases with the real one because of the need to simulate
+ * receiving and broadcasting intents.
+ */
+interface IntentHelper {
+
+ void initialize(String updateAppPackageName, String dataAppPackageName, Listener listener);
+
+ void sendTriggerUpdateCheck(CheckToken checkToken);
+
+ void enableReliabilityTriggering();
+
+ void disableReliabilityTriggering();
+
+ interface Listener {
+ void triggerUpdateIfNeeded(boolean packageUpdated);
+ }
+}
diff --git a/services/core/java/com/android/server/timezone/IntentHelperImpl.java b/services/core/java/com/android/server/timezone/IntentHelperImpl.java
new file mode 100644
index 0000000..3ffbb2d
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/IntentHelperImpl.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import android.app.timezone.RulesUpdaterContract;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PatternMatcher;
+import android.util.Slog;
+
+import java.util.regex.Pattern;
+
+/**
+ * The bona fide implementation of {@link IntentHelper}.
+ */
+final class IntentHelperImpl implements IntentHelper {
+
+ private final static String TAG = "timezone.IntentHelperImpl";
+
+ private final Context mContext;
+ private String mUpdaterAppPackageName;
+
+ private boolean mReliabilityReceiverEnabled;
+ private Receiver mReliabilityReceiver;
+
+ IntentHelperImpl(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void initialize(
+ String updaterAppPackageName, String dataAppPackageName, Listener listener) {
+ mUpdaterAppPackageName = updaterAppPackageName;
+
+ // Register for events of interest.
+
+ // The intent filter that triggers when package update events happen that indicate there may
+ // be work to do.
+ IntentFilter packageIntentFilter = new IntentFilter();
+ // Either of these mean a downgrade?
+ packageIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ packageIntentFilter.addDataScheme("package");
+ packageIntentFilter.addDataSchemeSpecificPart(
+ updaterAppPackageName, PatternMatcher.PATTERN_LITERAL);
+ packageIntentFilter.addDataSchemeSpecificPart(
+ dataAppPackageName, PatternMatcher.PATTERN_LITERAL);
+ Receiver packageUpdateReceiver = new Receiver(listener, true /* packageUpdated */);
+ mContext.registerReceiver(packageUpdateReceiver, packageIntentFilter);
+
+ // TODO(nfuller): Add more exotic intents as needed. e.g.
+ // packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ // Also, disabled...?
+ mReliabilityReceiver = new Receiver(listener, false /* packageUpdated */);
+ }
+
+ /** Sends an intent to trigger an update check. */
+ @Override
+ public void sendTriggerUpdateCheck(CheckToken checkToken) {
+ RulesUpdaterContract.sendBroadcast(
+ mContext, mUpdaterAppPackageName, checkToken.toByteArray());
+ }
+
+ @Override
+ public synchronized void enableReliabilityTriggering() {
+ if (!mReliabilityReceiverEnabled) {
+ // The intent filter that exists to make updates reliable in the event of failures /
+ // reboots.
+ IntentFilter reliabilityIntentFilter = new IntentFilter();
+ reliabilityIntentFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_START);
+ mContext.registerReceiver(mReliabilityReceiver, reliabilityIntentFilter);
+ mReliabilityReceiverEnabled = true;
+ }
+ }
+
+ @Override
+ public synchronized void disableReliabilityTriggering() {
+ if (mReliabilityReceiverEnabled) {
+ mContext.unregisterReceiver(mReliabilityReceiver);
+ mReliabilityReceiverEnabled = false;
+ }
+ }
+
+ private static class Receiver extends BroadcastReceiver {
+ private final Listener mListener;
+ private final boolean mPackageUpdated;
+
+ private Receiver(Listener listener, boolean packageUpdated) {
+ mListener = listener;
+ mPackageUpdated = packageUpdated;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Slog.d(TAG, "Received intent: " + intent.toString());
+ mListener.triggerUpdateIfNeeded(mPackageUpdated);
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/timezone/PackageManagerHelper.java b/services/core/java/com/android/server/timezone/PackageManagerHelper.java
new file mode 100644
index 0000000..804941a
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageManagerHelper.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+
+/**
+ * An easy-to-mock facade around PackageManager for use by {@link PackageTracker}; it is not
+ * possible to test various cases with the real one because of the need to simulate package versions
+ * and manifest configurations.
+ */
+interface PackageManagerHelper {
+
+ int getInstalledPackageVersion(String packageName)
+ throws PackageManager.NameNotFoundException;
+
+ boolean isPrivilegedApp(String packageName) throws PackageManager.NameNotFoundException;
+
+ boolean usesPermission(String packageName, String requiredPermissionName)
+ throws PackageManager.NameNotFoundException;
+
+ boolean contentProviderRegistered(String authority, String requiredPackageName);
+
+ boolean receiverRegistered(Intent intent, String requiredPermissionName)
+ throws PackageManager.NameNotFoundException;
+}
diff --git a/services/core/java/com/android/server/timezone/PackageStatus.java b/services/core/java/com/android/server/timezone/PackageStatus.java
new file mode 100644
index 0000000..6379096
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageStatus.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Information about the status of the time zone update / data packages that are persisted by the
+ * Android system.
+ */
+final class PackageStatus {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ CHECK_STARTED, CHECK_COMPLETED_SUCCESS, CHECK_COMPLETED_FAILURE })
+ @interface CheckStatus {}
+
+ /** A time zone update check has been started but not yet completed. */
+ static final int CHECK_STARTED = 1;
+ /** A time zone update check has been completed and succeeded. */
+ static final int CHECK_COMPLETED_SUCCESS = 2;
+ /** A time zone update check has been completed and failed. */
+ static final int CHECK_COMPLETED_FAILURE = 3;
+
+ @CheckStatus
+ final int mCheckStatus;
+
+ // Non-null
+ final PackageVersions mVersions;
+
+ PackageStatus(@CheckStatus int checkStatus, PackageVersions versions) {
+ this.mCheckStatus = checkStatus;
+ if (checkStatus < 1 || checkStatus > 3) {
+ throw new IllegalArgumentException("Unknown checkStatus " + checkStatus);
+ }
+ if (versions == null) {
+ throw new NullPointerException("versions == null");
+ }
+ this.mVersions = versions;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ PackageStatus that = (PackageStatus) o;
+
+ if (mCheckStatus != that.mCheckStatus) {
+ return false;
+ }
+ return mVersions.equals(that.mVersions);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mCheckStatus;
+ result = 31 * result + mVersions.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "PackageStatus{" +
+ "mCheckStatus=" + mCheckStatus +
+ ", mVersions=" + mVersions +
+ '}';
+ }
+}
diff --git a/services/core/java/com/android/server/timezone/PackageStatusStorage.java b/services/core/java/com/android/server/timezone/PackageStatusStorage.java
new file mode 100644
index 0000000..31f0e31
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageStatusStorage.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Slog;
+
+import java.io.File;
+
+import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_FAILURE;
+import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_SUCCESS;
+import static com.android.server.timezone.PackageStatus.CHECK_STARTED;
+
+/**
+ * Storage logic for accessing/mutating the Android system's persistent state related to time zone
+ * update checking. There is expected to be a single instance and all methods synchronized on
+ * {@code this} for thread safety.
+ */
+final class PackageStatusStorage {
+
+ private static final String TAG = "timezone.PackageStatusStorage";
+
+ private static final String DATABASE_NAME = "timezonepackagestatus.db";
+ private static final int DATABASE_VERSION = 1;
+
+ /** The table name. It will have a single row with _id == {@link #SINGLETON_ID} */
+ private static final String TABLE = "status";
+ private static final String COLUMN_ID = "_id";
+
+ /**
+ * Column that stores a monotonically increasing lock ID, used to detect concurrent update
+ * issues without on-line locks. Incremented on every write.
+ */
+ private static final String COLUMN_OPTIMISTIC_LOCK_ID = "optimistic_lock_id";
+
+ /**
+ * Column that stores the current "check status" of the time zone update application packages.
+ */
+ private static final String COLUMN_CHECK_STATUS = "check_status";
+
+ /**
+ * Column that stores the version of the time zone rules update application being checked / last
+ * checked.
+ */
+ private static final String COLUMN_UPDATE_APP_VERSION = "update_app_package_version";
+
+ /**
+ * Column that stores the version of the time zone rules data application being checked / last
+ * checked.
+ */
+ private static final String COLUMN_DATA_APP_VERSION = "data_app_package_version";
+
+ /**
+ * The ID of the one row.
+ */
+ private static final int SINGLETON_ID = 1;
+
+ private static final int UNKNOWN_PACKAGE_VERSION = -1;
+
+ private final DatabaseHelper mDatabaseHelper;
+
+ PackageStatusStorage(Context context) {
+ mDatabaseHelper = new DatabaseHelper(context);
+ }
+
+ void deleteDatabaseForTests() {
+ SQLiteDatabase.deleteDatabase(mDatabaseHelper.getDatabaseFile());
+ }
+
+ /**
+ * Obtain the current check status of the application packages. Returns {@code null} the first
+ * time it is called, or after {@link #resetCheckState()}.
+ */
+ PackageStatus getPackageStatus() {
+ synchronized (this) {
+ try {
+ return getPackageStatusInternal();
+ } catch (IllegalArgumentException e) {
+ // This means that data exists in the table but it was bad.
+ Slog.e(TAG, "Package status invalid, resetting and retrying", e);
+
+ // Reset the storage so it is in a good state again.
+ mDatabaseHelper.recoverFromBadData();
+ return getPackageStatusInternal();
+ }
+ }
+ }
+
+ private PackageStatus getPackageStatusInternal() {
+ String[] columns = {
+ COLUMN_CHECK_STATUS, COLUMN_UPDATE_APP_VERSION, COLUMN_DATA_APP_VERSION
+ };
+ Cursor cursor = mDatabaseHelper.getReadableDatabase()
+ .query(TABLE, columns, COLUMN_ID + " = ?",
+ new String[] { Integer.toString(SINGLETON_ID) },
+ null /* groupBy */, null /* having */, null /* orderBy */);
+ if (cursor.getCount() != 1) {
+ Slog.e(TAG, "Unable to find package status from package status row. Rows returned: "
+ + cursor.getCount());
+ return null;
+ }
+ cursor.moveToFirst();
+
+ // Determine check status.
+ if (cursor.isNull(0)) {
+ // This is normal the first time getPackageStatus() is called, or after
+ // resetCheckState().
+ return null;
+ }
+ int checkStatus = cursor.getInt(0);
+
+ // Determine package version.
+ if (cursor.isNull(1) || cursor.isNull(2)) {
+ Slog.e(TAG, "Package version information unexpectedly null");
+ return null;
+ }
+ PackageVersions packageVersions = new PackageVersions(cursor.getInt(1), cursor.getInt(2));
+
+ return new PackageStatus(checkStatus, packageVersions);
+ }
+
+ /**
+ * Generate a new {@link CheckToken} that can be passed to the time zone rules update
+ * application.
+ */
+ CheckToken generateCheckToken(PackageVersions currentInstalledVersions) {
+ if (currentInstalledVersions == null) {
+ throw new NullPointerException("currentInstalledVersions == null");
+ }
+
+ synchronized (this) {
+ Integer optimisticLockId = getCurrentOptimisticLockId();
+ if (optimisticLockId == null) {
+ Slog.w(TAG, "Unable to find optimistic lock ID from package status row");
+
+ // Recover.
+ optimisticLockId = mDatabaseHelper.recoverFromBadData();
+ }
+
+ int newOptimisticLockId = optimisticLockId + 1;
+ boolean statusRowUpdated = writeStatusRow(
+ optimisticLockId, newOptimisticLockId, CHECK_STARTED, currentInstalledVersions);
+ if (!statusRowUpdated) {
+ Slog.e(TAG, "Unable to update status to CHECK_STARTED in package status row."
+ + " synchronization failure?");
+ return null;
+ }
+ return new CheckToken(newOptimisticLockId, currentInstalledVersions);
+ }
+ }
+
+ /**
+ * Reset the current device state to "unknown".
+ */
+ void resetCheckState() {
+ synchronized(this) {
+ Integer optimisticLockId = getCurrentOptimisticLockId();
+ if (optimisticLockId == null) {
+ Slog.w(TAG, "resetCheckState: Unable to find optimistic lock ID from package"
+ + " status row");
+ // Attempt to recover the storage state.
+ optimisticLockId = mDatabaseHelper.recoverFromBadData();
+ }
+
+ int newOptimisticLockId = optimisticLockId + 1;
+ if (!writeStatusRow(optimisticLockId, newOptimisticLockId,
+ null /* status */, null /* packageVersions */)) {
+ Slog.e(TAG, "resetCheckState: Unable to reset package status row,"
+ + " newOptimisticLockId=" + newOptimisticLockId);
+ }
+ }
+ }
+
+ /**
+ * Update the current device state if possible. Returns true if the update was successful.
+ * {@code false} indicates the storage has been changed since the {@link CheckToken} was
+ * generated and the update was discarded.
+ */
+ boolean markChecked(CheckToken checkToken, boolean succeeded) {
+ synchronized (this) {
+ int optimisticLockId = checkToken.mOptimisticLockId;
+ int newOptimisticLockId = optimisticLockId + 1;
+ int status = succeeded ? CHECK_COMPLETED_SUCCESS : CHECK_COMPLETED_FAILURE;
+ return writeStatusRow(optimisticLockId, newOptimisticLockId,
+ status, checkToken.mPackageVersions);
+ }
+ }
+
+ // Caller should be synchronized(this)
+ private Integer getCurrentOptimisticLockId() {
+ final String[] columns = { COLUMN_OPTIMISTIC_LOCK_ID };
+ final String querySelection = COLUMN_ID + " = ?";
+ final String[] querySelectionArgs = { Integer.toString(SINGLETON_ID) };
+
+ SQLiteDatabase database = mDatabaseHelper.getReadableDatabase();
+ try (Cursor cursor = database.query(TABLE, columns, querySelection, querySelectionArgs,
+ null /* groupBy */, null /* having */, null /* orderBy */)) {
+ if (cursor.getCount() != 1) {
+ Slog.w(TAG, cursor.getCount() + " rows returned, expected exactly one.");
+ return null;
+ }
+ cursor.moveToFirst();
+ return cursor.getInt(0);
+ }
+ }
+
+ // Caller should be synchronized(this)
+ private boolean writeStatusRow(int optimisticLockId, int newOptimisticLockId, Integer status,
+ PackageVersions packageVersions) {
+ if ((status == null) != (packageVersions == null)) {
+ throw new IllegalArgumentException(
+ "Provide both status and packageVersions, or neither.");
+ }
+
+ SQLiteDatabase database = mDatabaseHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(COLUMN_OPTIMISTIC_LOCK_ID, newOptimisticLockId);
+ if (status == null) {
+ values.putNull(COLUMN_CHECK_STATUS);
+ values.put(COLUMN_UPDATE_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
+ values.put(COLUMN_DATA_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
+ } else {
+ values.put(COLUMN_CHECK_STATUS, status);
+ values.put(COLUMN_UPDATE_APP_VERSION, packageVersions.mUpdateAppVersion);
+ values.put(COLUMN_DATA_APP_VERSION, packageVersions.mDataAppVersion);
+ }
+
+ String updateSelection = COLUMN_ID + " = ? AND " + COLUMN_OPTIMISTIC_LOCK_ID + " = ?";
+ String[] updateSelectionArgs = {
+ Integer.toString(SINGLETON_ID), Integer.toString(optimisticLockId)
+ };
+ int count = database.update(TABLE, values, updateSelection, updateSelectionArgs);
+ if (count > 1) {
+ // This has to be because of corruption: there should only ever be one row.
+ Slog.w(TAG, "writeStatusRow: " + count + " rows updated, expected exactly one.");
+ // Reset the table.
+ mDatabaseHelper.recoverFromBadData();
+ }
+
+ // 1 is the success case. 0 rows updated means the row is missing or the optimistic lock ID
+ // was not as expected, this could be because of corruption but is most likely due to an
+ // optimistic lock failure. Callers can decide on a case-by-case basis.
+ return count == 1;
+ }
+
+ /** Only used during tests to force an empty table. */
+ void deleteRowForTests() {
+ mDatabaseHelper.getWritableDatabase().delete(TABLE, null, null);
+ }
+
+ /** Only used during tests to force a known table state. */
+ public void forceCheckStateForTests(int checkStatus, PackageVersions packageVersions) {
+ int optimisticLockId = getCurrentOptimisticLockId();
+ writeStatusRow(optimisticLockId, optimisticLockId, checkStatus, packageVersions);
+ }
+
+ static class DatabaseHelper extends SQLiteOpenHelper {
+
+ private final Context mContext;
+
+ public DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ mContext = context;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE + " (" +
+ "_id INTEGER PRIMARY KEY," +
+ COLUMN_OPTIMISTIC_LOCK_ID + " INTEGER NOT NULL," +
+ COLUMN_CHECK_STATUS + " INTEGER," +
+ COLUMN_UPDATE_APP_VERSION + " INTEGER NOT NULL," +
+ COLUMN_DATA_APP_VERSION + " INTEGER NOT NULL" +
+ ");");
+ insertInitialRowState(db);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
+ // no-op: nothing to upgrade
+ }
+
+ /** Recover the initial data row state, returning the new current optimistic lock ID */
+ int recoverFromBadData() {
+ // Delete the table content.
+ SQLiteDatabase writableDatabase = getWritableDatabase();
+ writableDatabase.delete(TABLE, null /* whereClause */, null /* whereArgs */);
+
+ // Insert the initial content.
+ return insertInitialRowState(writableDatabase);
+ }
+
+ /** Insert the initial data row, returning the optimistic lock ID */
+ private static int insertInitialRowState(SQLiteDatabase db) {
+ // Doesn't matter what it is, but we avoid the obvious starting value each time the row
+ // is reset to ensure that old tokens are unlikely to work.
+ final int initialOptimisticLockId = (int) System.currentTimeMillis();
+
+ // Insert the one row.
+ ContentValues values = new ContentValues();
+ values.put(COLUMN_ID, SINGLETON_ID);
+ values.put(COLUMN_OPTIMISTIC_LOCK_ID, initialOptimisticLockId);
+ values.putNull(COLUMN_CHECK_STATUS);
+ values.put(COLUMN_UPDATE_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
+ values.put(COLUMN_DATA_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
+ long id = db.insert(TABLE, null, values);
+ if (id == -1) {
+ Slog.w(TAG, "insertInitialRow: could not insert initial row, id=" + id);
+ return -1;
+ }
+ return initialOptimisticLockId;
+ }
+
+ File getDatabaseFile() {
+ return mContext.getDatabasePath(DATABASE_NAME);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/timezone/PackageTracker.java b/services/core/java/com/android/server/timezone/PackageTracker.java
new file mode 100644
index 0000000..8abf7df
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageTracker.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import android.app.timezone.RulesUpdaterContract;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.TimeZoneRulesDataContract;
+import android.util.Slog;
+
+/**
+ * Monitors the installed applications associated with time zone updates. If the app packages are
+ * updated it indicates there <em>might</em> be a time zone rules update to apply so a targeted
+ * broadcast intent is used to trigger the time zone updater app.
+ *
+ * <p>The "update triggering" behavior of this component can be disabled via device configuration.
+ *
+ * <p>The package tracker listens for package updates of the time zone "updater app" and "data app".
+ * It also listens for "reliability" triggers. Reliability triggers are there to ensure that the
+ * package tracker handles failures reliably and are "idle maintenance" events or something similar.
+ * Reliability triggers can cause a time zone update check to take place if the current state is
+ * unclear. For example, it can be unclear after boot or after a failure. If there are repeated
+ * failures reliability updates are halted until the next boot.
+ *
+ * <p>This component keeps persistent track of the most recent app packages checked to avoid
+ * unnecessary expense from broadcasting intents (which will cause other app processes to spawn).
+ * The current status is also stored to detect whether the most recently-generated check is
+ * complete successfully. For example, if the device was interrupted while doing a check and never
+ * acknowledged a check then a check will be retried the next time a "reliability trigger" event
+ * happens.
+ */
+// Also made non-final so it can be mocked.
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class PackageTracker implements IntentHelper.Listener {
+ private static final String TAG = "timezone.PackageTracker";
+
+ private final PackageManagerHelper mPackageManagerHelper;
+ private final IntentHelper mIntentHelper;
+ private final ConfigHelper mConfigHelper;
+ private final PackageStatusStorage mPackageStatusStorage;
+ private final ClockHelper mClockHelper;
+
+ // False if tracking is disabled.
+ private boolean mTrackingEnabled;
+
+ // These fields may be null if package tracking is disabled.
+ private String mUpdateAppPackageName;
+ private String mDataAppPackageName;
+
+ // The time a triggered check is allowed to take before it is considered overdue.
+ private int mCheckTimeAllowedMillis;
+ // The number of failed checks in a row before reliability checks should stop happening.
+ private long mFailedCheckRetryCount;
+
+ // Reliability check state: If a check was triggered but not acknowledged within
+ // mCheckTimeAllowedMillis then another one can be triggered.
+ private Long mLastTriggerTimestamp = null;
+
+ // Reliability check state: Whether any checks have been triggered at all.
+ private boolean mCheckTriggered;
+
+ // Reliability check state: A count of how many failures have occurred consecutively.
+ private int mCheckFailureCount;
+
+ /** Creates the {@link PackageTracker} for normal use. */
+ static PackageTracker create(Context context) {
+ PackageTrackerHelperImpl helperImpl = new PackageTrackerHelperImpl(context);
+ return new PackageTracker(
+ helperImpl /* clock */,
+ helperImpl /* configHelper */,
+ helperImpl /* packageManagerHelper */,
+ new PackageStatusStorage(context),
+ new IntentHelperImpl(context));
+ }
+
+ // A constructor that can be used by tests to supply mocked / faked dependencies.
+ PackageTracker(ClockHelper clockHelper, ConfigHelper configHelper,
+ PackageManagerHelper packageManagerHelper, PackageStatusStorage packageStatusStorage,
+ IntentHelper intentHelper) {
+ mClockHelper = clockHelper;
+ mConfigHelper = configHelper;
+ mPackageManagerHelper = packageManagerHelper;
+ mPackageStatusStorage = packageStatusStorage;
+ mIntentHelper = intentHelper;
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected synchronized void start() {
+ mTrackingEnabled = mConfigHelper.isTrackingEnabled();
+ if (!mTrackingEnabled) {
+ Slog.i(TAG, "Time zone updater / data package tracking explicitly disabled.");
+ return;
+ }
+
+ mUpdateAppPackageName = mConfigHelper.getUpdateAppPackageName();
+ mDataAppPackageName = mConfigHelper.getDataAppPackageName();
+ mCheckTimeAllowedMillis = mConfigHelper.getCheckTimeAllowedMillis();
+ mFailedCheckRetryCount = mConfigHelper.getFailedCheckRetryCount();
+
+ // Validate the device configuration including the application packages.
+ // The manifest entries in the apps themselves are not validated until use as they can
+ // change and we don't want to prevent the system server starting due to a bad application.
+ throwIfDeviceSettingsOrAppsAreBad();
+
+ // Explicitly start in a reliability state where reliability triggering will do something.
+ mCheckTriggered = false;
+ mCheckFailureCount = 0;
+
+ // Initialize the intent helper.
+ mIntentHelper.initialize(mUpdateAppPackageName, mDataAppPackageName, this);
+
+ // Enable the reliability triggering so we will have at least one reliability trigger if
+ // a package isn't updated.
+ mIntentHelper.enableReliabilityTriggering();
+
+ Slog.i(TAG, "Time zone updater / data package tracking enabled");
+ }
+
+ /**
+ * Performs checks that confirm the system image has correctly configured package
+ * tracking configuration. Only called if package tracking is enabled. Throws an exception if
+ * the device is configured badly which will prevent the device booting.
+ */
+ private void throwIfDeviceSettingsOrAppsAreBad() {
+ // None of the checks below can be based on application manifest settings, otherwise a bad
+ // update could leave the device in an unbootable state. See validateDataAppManifest() and
+ // validateUpdaterAppManifest() for softer errors.
+
+ throwRuntimeExceptionIfNullOrEmpty(
+ mUpdateAppPackageName, "Update app package name missing.");
+ throwRuntimeExceptionIfNullOrEmpty(mDataAppPackageName, "Data app package name missing.");
+ if (mFailedCheckRetryCount < 1) {
+ throw logAndThrowRuntimeException("mFailedRetryCount=" + mFailedCheckRetryCount, null);
+ }
+ if (mCheckTimeAllowedMillis < 1000) {
+ throw logAndThrowRuntimeException(
+ "mCheckTimeAllowedMillis=" + mCheckTimeAllowedMillis, null);
+ }
+
+ // Validate the updater application package.
+ // TODO(nfuller) Uncomment or remove the code below. Currently an app stops being a priv-app
+ // after it is replaced by one in data so this check fails. http://b/35995024
+ // try {
+ // if (!mPackageManagerHelper.isPrivilegedApp(mUpdateAppPackageName)) {
+ // throw failWithException(
+ // "Update app " + mUpdateAppPackageName + " must be a priv-app.", null);
+ // }
+ // } catch (PackageManager.NameNotFoundException e) {
+ // throw failWithException("Could not determine update app package details for "
+ // + mUpdateAppPackageName, e);
+ // }
+ // TODO(nfuller) Consider permission checks. While an updated system app retains permissions
+ // obtained by the system version it's not clear how to check them.
+ Slog.d(TAG, "Update app " + mUpdateAppPackageName + " is valid.");
+
+ // Validate the data application package.
+ // TODO(nfuller) Uncomment or remove the code below. Currently an app stops being a priv-app
+ // after it is replaced by one in data. http://b/35995024
+ // try {
+ // if (!mPackageManagerHelper.isPrivilegedApp(mDataAppPackageName)) {
+ // throw failWithException(
+ // "Data app " + mDataAppPackageName + " must be a priv-app.", null);
+ // }
+ // } catch (PackageManager.NameNotFoundException e) {
+ // throw failWithException("Could not determine data app package details for "
+ // + mDataAppPackageName, e);
+ // }
+ // TODO(nfuller) Consider permission checks. While an updated system app retains permissions
+ // obtained by the system version it's not clear how to check them.
+ Slog.d(TAG, "Data app " + mDataAppPackageName + " is valid.");
+ }
+
+ /**
+ * Inspects the current in-memory state, installed packages and storage state to determine if an
+ * update check is needed and then trigger if it is.
+ *
+ * @param packageChanged true if this method was called because a known packaged definitely
+ * changed, false if the cause is a reliability trigger
+ */
+ @Override
+ public synchronized void triggerUpdateIfNeeded(boolean packageChanged) {
+ if (!mTrackingEnabled) {
+ throw new IllegalStateException("Unexpected call. Tracking is disabled.");
+ }
+
+ // Validate the applications' current manifest entries: make sure they are configured as
+ // they should be. These are not fatal and just means that no update is triggered: we don't
+ // want to take down the system server if an OEM or Google have pushed a bad update to
+ // an application.
+ boolean updaterAppManifestValid = validateUpdaterAppManifest();
+ boolean dataAppManifestValid = validateDataAppManifest();
+ if (!updaterAppManifestValid || !dataAppManifestValid) {
+ Slog.e(TAG, "No update triggered due to invalid application manifest entries."
+ + " updaterApp=" + updaterAppManifestValid
+ + ", dataApp=" + dataAppManifestValid);
+
+ // There's no point in doing reliability checks if the current packages are bad.
+ mIntentHelper.disableReliabilityTriggering();
+ return;
+ }
+
+ if (!packageChanged) {
+ // This call was made because the device is doing a "reliability" check.
+ // 4 possible cases:
+ // 1) No check has previously triggered since restart. We want to trigger in this case.
+ // 2) A check has previously triggered and it is in progress. We want to trigger if
+ // the response is overdue.
+ // 3) A check has previously triggered and it failed. We want to trigger, but only if
+ // we're not in a persistent failure state.
+ // 4) A check has previously triggered and it succeeded.
+ // We don't want to trigger, and want to stop future triggers.
+
+ if (!mCheckTriggered) {
+ // Case 1.
+ Slog.d(TAG, "triggerUpdateIfNeeded: First reliability trigger.");
+ } else if (isCheckInProgress()) {
+ // Case 2.
+ if (!isCheckResponseOverdue()) {
+ // A check is in progress but hasn't been given time to succeed.
+ Slog.d(TAG,
+ "triggerUpdateIfNeeded: checkComplete call is not yet overdue."
+ + " Not triggering.");
+ // Not doing any work, but also not disabling future reliability triggers.
+ return;
+ }
+ } else if (mCheckFailureCount > mFailedCheckRetryCount) {
+ // Case 3. If the system is in some kind of persistent failure state we don't want
+ // to keep checking, so just stop.
+ Slog.i(TAG, "triggerUpdateIfNeeded: number of allowed consecutive check failures"
+ + " exceeded. Stopping reliability triggers until next reboot or package"
+ + " update.");
+ mIntentHelper.disableReliabilityTriggering();
+ return;
+ } else if (mCheckFailureCount == 0) {
+ // Case 4.
+ Slog.i(TAG, "triggerUpdateIfNeeded: No reliability check required. Last check was"
+ + " successful.");
+ mIntentHelper.disableReliabilityTriggering();
+ return;
+ }
+ }
+
+ // Read the currently installed data / updater package versions.
+ PackageVersions currentInstalledVersions = lookupInstalledPackageVersions();
+ if (currentInstalledVersions == null) {
+ // This should not happen if the device is configured in a valid way.
+ Slog.e(TAG, "triggerUpdateIfNeeded: currentInstalledVersions was null");
+ mIntentHelper.disableReliabilityTriggering();
+ return;
+ }
+
+ // Establish the current state using package manager and stored state. Determine if we have
+ // already successfully checked the installed versions.
+ PackageStatus packageStatus = mPackageStatusStorage.getPackageStatus();
+ if (packageStatus == null) {
+ // This can imply corrupt, uninitialized storage state (e.g. first check ever on a
+ // device) or after some kind of reset.
+ Slog.i(TAG, "triggerUpdateIfNeeded: No package status data found. Data check needed.");
+ } else if (!packageStatus.mVersions.equals(currentInstalledVersions)) {
+ // The stored package version information differs from the installed version.
+ // Trigger the check in all cases.
+ Slog.i(TAG, "triggerUpdateIfNeeded: Stored package versions="
+ + packageStatus.mVersions + ", do not match current package versions="
+ + currentInstalledVersions + ". Triggering check.");
+ } else {
+ Slog.i(TAG, "triggerUpdateIfNeeded: Stored package versions match currently"
+ + " installed versions, currentInstalledVersions=" + currentInstalledVersions
+ + ", packageStatus.mCheckStatus=" + packageStatus.mCheckStatus);
+ if (packageStatus.mCheckStatus == PackageStatus.CHECK_COMPLETED_SUCCESS) {
+ // The last check succeeded and nothing has changed. Do nothing and disable
+ // reliability checks.
+ Slog.i(TAG, "triggerUpdateIfNeeded: Prior check succeeded. No need to trigger.");
+ mIntentHelper.disableReliabilityTriggering();
+ return;
+ }
+ }
+
+ // Generate a token to send to the updater app.
+ CheckToken checkToken =
+ mPackageStatusStorage.generateCheckToken(currentInstalledVersions);
+ if (checkToken == null) {
+ Slog.w(TAG, "triggerUpdateIfNeeded: Unable to generate check token."
+ + " Not sending check request.");
+ return;
+ }
+
+ // Trigger the update check.
+ mIntentHelper.sendTriggerUpdateCheck(checkToken);
+ mCheckTriggered = true;
+
+ // Update the reliability check state in case the update fails.
+ setCheckInProgress();
+
+ // Enable reliability triggering in case the check doesn't succeed and there is no
+ // response at all. Enabling reliability triggering is idempotent.
+ mIntentHelper.enableReliabilityTriggering();
+ }
+
+ /**
+ * Used to record the result of a check. Can be called even if active package tracking is
+ * disabled.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected synchronized void recordCheckResult(CheckToken checkToken, boolean success) {
+ Slog.i(TAG, "recordOperationResult: checkToken=" + checkToken + " success=" + success);
+
+ // If package tracking is disabled it means no record-keeping is required. However, we do
+ // want to clear out any stored state to make it clear that the current state is unknown and
+ // should tracking become enabled again (perhaps through an OTA) we'd need to perform an
+ // update check.
+ if (!mTrackingEnabled) {
+ // This means an updater has spontaneously modified time zone data without having been
+ // triggered. This can happen if the OEM is handling their own updates, but we don't
+ // need to do any tracking in this case.
+
+ if (checkToken == null) {
+ // This is the expected case if tracking is disabled but an OEM is handling time
+ // zone installs using their own mechanism.
+ Slog.d(TAG, "recordCheckResult: Tracking is disabled and no token has been"
+ + " provided. Resetting tracking state.");
+ } else {
+ // This is unexpected. If tracking is disabled then no check token should have been
+ // generated by the package tracker. An updater should never create its own token.
+ // This could be a bug in the updater.
+ Slog.w(TAG, "recordCheckResult: Tracking is disabled and a token " + checkToken
+ + " has been unexpectedly provided. Resetting tracking state.");
+ }
+ mPackageStatusStorage.resetCheckState();
+ return;
+ }
+
+ if (checkToken == null) {
+ /*
+ * If the checkToken is null it suggests an install / uninstall / acknowledgement has
+ * occurred without a prior trigger (or the client didn't return the token it was given
+ * for some reason, perhaps a bug).
+ *
+ * This shouldn't happen under normal circumstances:
+ *
+ * If package tracking is enabled, we assume it is the package tracker responsible for
+ * triggering updates and a token should have been produced and returned.
+ *
+ * If the OEM is handling time zone updates case package tracking should be disabled.
+ *
+ * This could happen in tests. The device should recover back to a known state by
+ * itself rather than be left in an invalid state.
+ *
+ * We treat this as putting the device into an unknown state and make sure that
+ * reliability triggering is enabled so we should recover.
+ */
+ Slog.i(TAG, "recordCheckResult: Unexpectedly missing checkToken, resetting"
+ + " storage state.");
+ mPackageStatusStorage.resetCheckState();
+
+ // Enable reliability triggering and reset the failure count so we know that the
+ // next reliability trigger will do something.
+ mIntentHelper.enableReliabilityTriggering();
+ mCheckFailureCount = 0;
+ } else {
+ // This is the expected case when tracking is enabled: a check was triggered and it has
+ // completed.
+ boolean recordedCheckCompleteSuccessfully =
+ mPackageStatusStorage.markChecked(checkToken, success);
+ if (recordedCheckCompleteSuccessfully) {
+ // If we have recorded the result (whatever it was) we know there is no check in
+ // progress.
+ setCheckComplete();
+
+ if (success) {
+ // Since the check was successful, no more reliability checks are required until
+ // there is a package change.
+ mIntentHelper.disableReliabilityTriggering();
+ mCheckFailureCount = 0;
+ } else {
+ // Enable reliability triggering to potentially check again in future.
+ mIntentHelper.enableReliabilityTriggering();
+ mCheckFailureCount++;
+ }
+ } else {
+ // The failure to record the check means an optimistic lock failure and suggests
+ // that another check was triggered after the token was generated.
+ Slog.i(TAG, "recordCheckResult: could not update token=" + checkToken
+ + " with success=" + success + ". Optimistic lock failure");
+
+ // Enable reliability triggering to potentially try again in future.
+ mIntentHelper.enableReliabilityTriggering();
+ mCheckFailureCount++;
+ }
+ }
+ }
+
+ /** Access to consecutive failure counts for use in tests. */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected int getCheckFailureCountForTests() {
+ return mCheckFailureCount;
+ }
+
+ private void setCheckInProgress() {
+ mLastTriggerTimestamp = mClockHelper.currentTimestamp();
+ }
+
+ private void setCheckComplete() {
+ mLastTriggerTimestamp = null;
+ }
+
+ private boolean isCheckInProgress() {
+ return mLastTriggerTimestamp != null;
+ }
+
+ private boolean isCheckResponseOverdue() {
+ if (mLastTriggerTimestamp == null) {
+ return false;
+ }
+ // Risk of overflow, but highly unlikely given the implementation and not problematic.
+ return mClockHelper.currentTimestamp() > mLastTriggerTimestamp + mCheckTimeAllowedMillis;
+ }
+
+ private PackageVersions lookupInstalledPackageVersions() {
+ int updatePackageVersion;
+ int dataPackageVersion;
+ try {
+ updatePackageVersion =
+ mPackageManagerHelper.getInstalledPackageVersion(mUpdateAppPackageName);
+ dataPackageVersion =
+ mPackageManagerHelper.getInstalledPackageVersion(mDataAppPackageName);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "lookupInstalledPackageVersions: Unable to resolve installed package"
+ + " versions", e);
+ return null;
+ }
+ return new PackageVersions(updatePackageVersion, dataPackageVersion);
+ }
+
+ private boolean validateDataAppManifest() {
+ // We only want to talk to a provider that exposed by the known data app package
+ // so we look up the providers exposed by that app and check the well-known authority is
+ // there. This prevents the case where *even if* the data app doesn't expose the provider
+ // required, another app cannot expose one to replace it.
+ if (!mPackageManagerHelper.contentProviderRegistered(
+ TimeZoneRulesDataContract.AUTHORITY, mDataAppPackageName)) {
+ // Error! Found the package but it didn't expose the correct provider.
+ Slog.w(TAG, "validateDataAppManifest: Data app " + mDataAppPackageName
+ + " does not expose the required provider with authority="
+ + TimeZoneRulesDataContract.AUTHORITY);
+ return false;
+ }
+ // TODO(nfuller) Add any permissions checks needed.
+ return true;
+ }
+
+ private boolean validateUpdaterAppManifest() {
+ try {
+ // The updater app is expected to have the UPDATE_TIME_ZONE_RULES permission.
+ // The updater app is expected to have a receiver for the intent we are going to trigger
+ // and require the TRIGGER_TIME_ZONE_RULES_CHECK.
+ if (!mPackageManagerHelper.usesPermission(
+ mUpdateAppPackageName,
+ RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION)) {
+ Slog.w(TAG, "validateUpdaterAppManifest: Updater app " + mDataAppPackageName
+ + " does not use permission="
+ + RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION);
+ return false;
+ }
+ if (!mPackageManagerHelper.receiverRegistered(
+ RulesUpdaterContract.createUpdaterIntent(mUpdateAppPackageName),
+ RulesUpdaterContract.TRIGGER_TIME_ZONE_RULES_CHECK_PERMISSION)) {
+ return false;
+ }
+
+ return true;
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "validateUpdaterAppManifest: Updater app " + mDataAppPackageName
+ + " does not expose the required broadcast receiver.", e);
+ return false;
+ }
+ }
+
+ private static void throwRuntimeExceptionIfNullOrEmpty(String value, String message) {
+ if (value == null || value.trim().isEmpty()) {
+ throw logAndThrowRuntimeException(message, null);
+ }
+ }
+
+ private static RuntimeException logAndThrowRuntimeException(String message, Throwable cause) {
+ Slog.wtf(TAG, message, cause);
+ throw new RuntimeException(message, cause);
+ }
+}
diff --git a/services/core/java/com/android/server/timezone/PackageTrackerHelperImpl.java b/services/core/java/com/android/server/timezone/PackageTrackerHelperImpl.java
new file mode 100644
index 0000000..2e0c21b
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageTrackerHelperImpl.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import com.android.internal.R;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import java.util.List;
+
+/**
+ * A single class that implements multiple helper interfaces for use by {@link PackageTracker}.
+ */
+final class PackageTrackerHelperImpl implements ClockHelper, ConfigHelper, PackageManagerHelper {
+
+ private static final String TAG = "PackageTrackerHelperImpl";
+
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+
+ PackageTrackerHelperImpl(Context context) {
+ mContext = context;
+ mPackageManager = context.getPackageManager();
+ }
+
+ @Override
+ public boolean isTrackingEnabled() {
+ return mContext.getResources().getBoolean(R.bool.config_timeZoneRulesUpdateTrackingEnabled);
+ }
+
+ @Override
+ public String getUpdateAppPackageName() {
+ return mContext.getResources().getString(R.string.config_timeZoneRulesUpdaterPackage);
+ }
+
+ @Override
+ public String getDataAppPackageName() {
+ Resources resources = mContext.getResources();
+ return resources.getString(R.string.config_timeZoneRulesDataPackage);
+ }
+
+ @Override
+ public int getCheckTimeAllowedMillis() {
+ return mContext.getResources().getInteger(
+ R.integer.config_timeZoneRulesCheckTimeMillisAllowed);
+ }
+
+ @Override
+ public int getFailedCheckRetryCount() {
+ return mContext.getResources().getInteger(R.integer.config_timeZoneRulesCheckRetryCount);
+ }
+
+ @Override
+ public long currentTimestamp() {
+ // Use of elapsedRealtime() because this is in-memory state and elapsedRealtime() shouldn't
+ // change if the system clock changes.
+ return SystemClock.elapsedRealtime();
+ }
+
+ @Override
+ public int getInstalledPackageVersion(String packageName)
+ throws PackageManager.NameNotFoundException {
+ int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, flags);
+ return packageInfo.versionCode;
+ }
+
+ @Override
+ public boolean isPrivilegedApp(String packageName) throws PackageManager.NameNotFoundException {
+ int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, flags);
+ return packageInfo.applicationInfo.isPrivilegedApp();
+ }
+
+ @Override
+ public boolean usesPermission(String packageName, String requiredPermissionName)
+ throws PackageManager.NameNotFoundException {
+ int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+ | PackageManager.GET_PERMISSIONS;
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, flags);
+ if (packageInfo.requestedPermissions == null) {
+ return false;
+ }
+ for (String requestedPermission : packageInfo.requestedPermissions) {
+ if (requiredPermissionName.equals(requestedPermission)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean contentProviderRegistered(String authority, String requiredPackageName) {
+ int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+ ProviderInfo providerInfo =
+ mPackageManager.resolveContentProvider(authority, flags);
+ if (providerInfo == null) {
+ Slog.i(TAG, "contentProviderRegistered: No content provider registered with authority="
+ + authority);
+ return false;
+ }
+ boolean packageMatches =
+ requiredPackageName.equals(providerInfo.applicationInfo.packageName);
+ if (!packageMatches) {
+ Slog.i(TAG, "contentProviderRegistered: App with packageName=" + requiredPackageName
+ + " does not expose the a content provider with authority=" + authority);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean receiverRegistered(Intent intent, String requiredPermissionName)
+ throws PackageManager.NameNotFoundException {
+
+ int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+ List<ResolveInfo> resolveInfo = mPackageManager.queryBroadcastReceivers(intent, flags);
+ if (resolveInfo.size() != 1) {
+ Slog.i(TAG, "receiverRegistered: Zero or multiple broadcast receiver registered for"
+ + " intent=" + intent + ", found=" + resolveInfo);
+ return false;
+ }
+
+ ResolveInfo matched = resolveInfo.get(0);
+ boolean requiresPermission = requiredPermissionName.equals(matched.activityInfo.permission);
+ if (!requiresPermission) {
+ Slog.i(TAG, "receiverRegistered: Broadcast receiver registered for intent="
+ + intent + " must require permission " + requiredPermissionName);
+ }
+ return requiresPermission;
+ }
+}
diff --git a/services/core/java/com/android/server/timezone/PackageVersions.java b/services/core/java/com/android/server/timezone/PackageVersions.java
new file mode 100644
index 0000000..fc0d6e1
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageVersions.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+/**
+ * Package version information about the time zone updater and time zone data application packages.
+ */
+final class PackageVersions {
+
+ final int mUpdateAppVersion;
+ final int mDataAppVersion;
+
+ PackageVersions(int updateAppVersion, int dataAppVersion) {
+ this.mUpdateAppVersion = updateAppVersion;
+ this.mDataAppVersion = dataAppVersion;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ PackageVersions that = (PackageVersions) o;
+
+ if (mUpdateAppVersion != that.mUpdateAppVersion) {
+ return false;
+ }
+ return mDataAppVersion == that.mDataAppVersion;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mUpdateAppVersion;
+ result = 31 * result + mDataAppVersion;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "PackageVersions{" +
+ "mUpdateAppVersion=" + mUpdateAppVersion +
+ ", mDataAppVersion=" + mDataAppVersion +
+ '}';
+ }
+}
diff --git a/services/core/java/com/android/server/timezone/PermissionHelper.java b/services/core/java/com/android/server/timezone/PermissionHelper.java
new file mode 100644
index 0000000..ba91c7f
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PermissionHelper.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+/**
+ * An easy-to-mock interface around permission checks for use by {@link RulesManagerService}.
+ */
+public interface PermissionHelper {
+
+ void enforceCallerHasPermission(String requiredPermission) throws SecurityException;
+}
diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java
new file mode 100644
index 0000000..82bd356
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/RulesManagerService.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+import android.app.timezone.Callback;
+import android.app.timezone.DistroFormatVersion;
+import android.app.timezone.DistroRulesVersion;
+import android.app.timezone.ICallback;
+import android.app.timezone.IRulesManager;
+import android.app.timezone.RulesManager;
+import android.app.timezone.RulesState;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import libcore.tzdata.shared2.DistroException;
+import libcore.tzdata.shared2.DistroVersion;
+import libcore.tzdata.shared2.StagedDistroOperation;
+import libcore.tzdata.update2.TimeZoneDistroInstaller;
+
+// TODO(nfuller) Add EventLog calls where useful in the system server.
+// TODO(nfuller) Check logging best practices in the system server.
+// TODO(nfuller) Check error handling best practices in the system server.
+public final class RulesManagerService extends IRulesManager.Stub {
+
+ private static final String TAG = "timezone.RulesManagerService";
+
+ /** The distro format supported by this device. */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static final DistroFormatVersion DISTRO_FORMAT_VERSION_SUPPORTED =
+ new DistroFormatVersion(
+ DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+ DistroVersion.CURRENT_FORMAT_MINOR_VERSION);
+
+ public static class Lifecycle extends SystemService {
+ private RulesManagerService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mService = RulesManagerService.create(getContext());
+ mService.start();
+
+ publishBinderService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, mService);
+ }
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static final String REQUIRED_UPDATER_PERMISSION =
+ android.Manifest.permission.UPDATE_TIME_ZONE_RULES;
+ private static final File SYSTEM_TZ_DATA_FILE = new File("/system/usr/share/zoneinfo/tzdata");
+ private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo");
+
+ private final AtomicBoolean mOperationInProgress = new AtomicBoolean(false);
+ private final PermissionHelper mPermissionHelper;
+ private final PackageTracker mPackageTracker;
+ private final Executor mExecutor;
+ private final TimeZoneDistroInstaller mInstaller;
+ private final FileDescriptorHelper mFileDescriptorHelper;
+
+ private static RulesManagerService create(Context context) {
+ RulesManagerServiceHelperImpl helper = new RulesManagerServiceHelperImpl(context);
+ return new RulesManagerService(
+ helper /* permissionHelper */,
+ helper /* executor */,
+ helper /* fileDescriptorHelper */,
+ PackageTracker.create(context),
+ new TimeZoneDistroInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR));
+ }
+
+ // A constructor that can be used by tests to supply mocked / faked dependencies.
+ RulesManagerService(PermissionHelper permissionHelper,
+ Executor executor,
+ FileDescriptorHelper fileDescriptorHelper, PackageTracker packageTracker,
+ TimeZoneDistroInstaller timeZoneDistroInstaller) {
+ mPermissionHelper = permissionHelper;
+ mExecutor = executor;
+ mFileDescriptorHelper = fileDescriptorHelper;
+ mPackageTracker = packageTracker;
+ mInstaller = timeZoneDistroInstaller;
+ }
+
+ public void start() {
+ mPackageTracker.start();
+ }
+
+ @Override // Binder call
+ public RulesState getRulesState() {
+ mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+
+ synchronized(this) {
+ String systemRulesVersion;
+ try {
+ systemRulesVersion = mInstaller.getSystemRulesVersion();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to read system rules", e);
+ return null;
+ }
+
+ boolean operationInProgress = this.mOperationInProgress.get();
+
+ // Determine the staged operation status, if possible.
+ DistroRulesVersion stagedDistroRulesVersion = null;
+ int stagedOperationStatus = RulesState.STAGED_OPERATION_UNKNOWN;
+ if (!operationInProgress) {
+ StagedDistroOperation stagedDistroOperation;
+ try {
+ stagedDistroOperation = mInstaller.getStagedDistroOperation();
+ if (stagedDistroOperation == null) {
+ stagedOperationStatus = RulesState.STAGED_OPERATION_NONE;
+ } else if (stagedDistroOperation.isUninstall) {
+ stagedOperationStatus = RulesState.STAGED_OPERATION_UNINSTALL;
+ } else {
+ // Must be an install.
+ stagedOperationStatus = RulesState.STAGED_OPERATION_INSTALL;
+ DistroVersion stagedDistroVersion = stagedDistroOperation.distroVersion;
+ stagedDistroRulesVersion = new DistroRulesVersion(
+ stagedDistroVersion.rulesVersion,
+ stagedDistroVersion.revision);
+ }
+ } catch (DistroException | IOException e) {
+ Slog.w(TAG, "Failed to read staged distro.", e);
+ }
+ }
+
+ // Determine the installed distro state, if possible.
+ DistroVersion installedDistroVersion;
+ int distroStatus = RulesState.DISTRO_STATUS_UNKNOWN;
+ DistroRulesVersion installedDistroRulesVersion = null;
+ if (!operationInProgress) {
+ try {
+ installedDistroVersion = mInstaller.getInstalledDistroVersion();
+ if (installedDistroVersion == null) {
+ distroStatus = RulesState.DISTRO_STATUS_NONE;
+ installedDistroRulesVersion = null;
+ } else {
+ distroStatus = RulesState.DISTRO_STATUS_INSTALLED;
+ installedDistroRulesVersion = new DistroRulesVersion(
+ installedDistroVersion.rulesVersion,
+ installedDistroVersion.revision);
+ }
+ } catch (DistroException | IOException e) {
+ Slog.w(TAG, "Failed to read installed distro.", e);
+ }
+ }
+ return new RulesState(systemRulesVersion, DISTRO_FORMAT_VERSION_SUPPORTED,
+ operationInProgress, stagedOperationStatus, stagedDistroRulesVersion,
+ distroStatus, installedDistroRulesVersion);
+ }
+ }
+
+ @Override
+ public int requestInstall(
+ ParcelFileDescriptor timeZoneDistro, byte[] checkTokenBytes, ICallback callback) {
+ mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+
+ CheckToken checkToken = null;
+ if (checkTokenBytes != null) {
+ checkToken = createCheckTokenOrThrow(checkTokenBytes);
+ }
+ synchronized (this) {
+ if (timeZoneDistro == null) {
+ throw new NullPointerException("timeZoneDistro == null");
+ }
+ if (callback == null) {
+ throw new NullPointerException("observer == null");
+ }
+ if (mOperationInProgress.get()) {
+ return RulesManager.ERROR_OPERATION_IN_PROGRESS;
+ }
+ mOperationInProgress.set(true);
+
+ // Execute the install asynchronously.
+ mExecutor.execute(new InstallRunnable(timeZoneDistro, checkToken, callback));
+
+ return RulesManager.SUCCESS;
+ }
+ }
+
+ private class InstallRunnable implements Runnable {
+
+ private final ParcelFileDescriptor mTimeZoneDistro;
+ private final CheckToken mCheckToken;
+ private final ICallback mCallback;
+
+ InstallRunnable(
+ ParcelFileDescriptor timeZoneDistro, CheckToken checkToken, ICallback callback) {
+ mTimeZoneDistro = timeZoneDistro;
+ mCheckToken = checkToken;
+ mCallback = callback;
+ }
+
+ @Override
+ public void run() {
+ // Adopt the ParcelFileDescriptor into this try-with-resources so it is closed
+ // when we are done.
+ boolean success = false;
+ try {
+ byte[] distroBytes =
+ RulesManagerService.this.mFileDescriptorHelper.readFully(mTimeZoneDistro);
+ int installerResult = mInstaller.stageInstallWithErrorCode(distroBytes);
+ int resultCode = mapInstallerResultToApiCode(installerResult);
+ sendFinishedStatus(mCallback, resultCode);
+
+ // All the installer failure modes are currently non-recoverable and won't be
+ // improved by trying again. Therefore success = true.
+ success = true;
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to install distro.", e);
+ sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE);
+ } finally {
+ // Notify the package tracker that the operation is now complete.
+ mPackageTracker.recordCheckResult(mCheckToken, success);
+
+ mOperationInProgress.set(false);
+ }
+ }
+
+ private int mapInstallerResultToApiCode(int installerResult) {
+ switch (installerResult) {
+ case TimeZoneDistroInstaller.INSTALL_SUCCESS:
+ return Callback.SUCCESS;
+ case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE:
+ return Callback.ERROR_INSTALL_BAD_DISTRO_STRUCTURE;
+ case TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD:
+ return Callback.ERROR_INSTALL_RULES_TOO_OLD;
+ case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION:
+ return Callback.ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION;
+ case TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR:
+ return Callback.ERROR_INSTALL_VALIDATION_ERROR;
+ default:
+ return Callback.ERROR_UNKNOWN_FAILURE;
+ }
+ }
+ }
+
+ @Override
+ public int requestUninstall(byte[] checkTokenBytes, ICallback callback) {
+ mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+
+ CheckToken checkToken = null;
+ if (checkTokenBytes != null) {
+ checkToken = createCheckTokenOrThrow(checkTokenBytes);
+ }
+ synchronized(this) {
+ if (callback == null) {
+ throw new NullPointerException("callback == null");
+ }
+
+ if (mOperationInProgress.get()) {
+ return RulesManager.ERROR_OPERATION_IN_PROGRESS;
+ }
+ mOperationInProgress.set(true);
+
+ // Execute the uninstall asynchronously.
+ mExecutor.execute(new UninstallRunnable(checkToken, callback));
+
+ return RulesManager.SUCCESS;
+ }
+ }
+
+ private class UninstallRunnable implements Runnable {
+
+ private final CheckToken mCheckToken;
+ private final ICallback mCallback;
+
+ public UninstallRunnable(CheckToken checkToken, ICallback callback) {
+ mCheckToken = checkToken;
+ mCallback = callback;
+ }
+
+ @Override
+ public void run() {
+ boolean success = false;
+ try {
+ success = mInstaller.stageUninstall();
+ // Right now we just have success (0) / failure (1). All clients should be checking
+ // against SUCCESS. More granular failures may be added in future.
+ int resultCode = success ? Callback.SUCCESS
+ : Callback.ERROR_UNKNOWN_FAILURE;
+ sendFinishedStatus(mCallback, resultCode);
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to uninstall distro.", e);
+ sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE);
+ } finally {
+ // Notify the package tracker that the operation is now complete.
+ mPackageTracker.recordCheckResult(mCheckToken, success);
+
+ mOperationInProgress.set(false);
+ }
+ }
+ }
+
+ private void sendFinishedStatus(ICallback callback, int resultCode) {
+ try {
+ callback.onFinished(resultCode);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify observer of result", e);
+ }
+ }
+
+ @Override
+ public void requestNothing(byte[] checkTokenBytes, boolean success) {
+ mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+ CheckToken checkToken = null;
+ if (checkTokenBytes != null) {
+ checkToken = createCheckTokenOrThrow(checkTokenBytes);
+ }
+ mPackageTracker.recordCheckResult(checkToken, success);
+ }
+
+ private static CheckToken createCheckTokenOrThrow(byte[] checkTokenBytes) {
+ CheckToken checkToken;
+ try {
+ checkToken = CheckToken.fromByteArray(checkTokenBytes);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Unable to read token bytes "
+ + Arrays.toString(checkTokenBytes), e);
+ }
+ return checkToken;
+ }
+}
diff --git a/services/core/java/com/android/server/timezone/RulesManagerServiceHelperImpl.java b/services/core/java/com/android/server/timezone/RulesManagerServiceHelperImpl.java
new file mode 100644
index 0000000..15a571d
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/RulesManagerServiceHelperImpl.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import libcore.io.Streams;
+
+/**
+ * A single class that implements multiple helper interfaces for use by {@link RulesManagerService}.
+ */
+final class RulesManagerServiceHelperImpl
+ implements PermissionHelper, Executor, FileDescriptorHelper {
+
+ private final Context mContext;
+
+ RulesManagerServiceHelperImpl(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void enforceCallerHasPermission(String requiredPermission) {
+ mContext.enforceCallingPermission(requiredPermission, null /* message */);
+ }
+
+ // TODO Wake lock required?
+ @Override
+ public void execute(Runnable runnable) {
+ // TODO Is there a better way?
+ new Thread(runnable).start();
+ }
+
+ @Override
+ public byte[] readFully(ParcelFileDescriptor parcelFileDescriptor) throws IOException {
+ try (ParcelFileDescriptor pfd = parcelFileDescriptor) {
+ // Read bytes
+ FileInputStream in = new FileInputStream(pfd.getFileDescriptor(), false /* isOwner */);
+ return Streams.readFully(in);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index e12dc1d..35e4e58 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -49,6 +49,7 @@
import android.os.storage.StorageManager;
import android.provider.Settings;
import android.service.trust.TrustAgentService;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
@@ -66,6 +67,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -104,6 +106,7 @@
private static final int MSG_UNLOCK_USER = 11;
private static final int MSG_STOP_USER = 12;
private static final int MSG_DISPATCH_UNLOCK_LOCKOUT = 13;
+ private static final int MSG_REFRESH_DEVICE_LOCKED_FOR_USER = 14;
private static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000;
@@ -123,9 +126,13 @@
@GuardedBy("mDeviceLockedForUser")
private final SparseBooleanArray mDeviceLockedForUser = new SparseBooleanArray();
- @GuardedBy("mDeviceLockedForUser")
+ @GuardedBy("mTrustUsuallyManagedForUser")
private final SparseBooleanArray mTrustUsuallyManagedForUser = new SparseBooleanArray();
+ // set to true only if user can skip bouncer
+ @GuardedBy("mUsersUnlockedByFingerprint")
+ private final SparseBooleanArray mUsersUnlockedByFingerprint = new SparseBooleanArray();
+
private final StrongAuthTracker mStrongAuthTracker;
private boolean mTrustAgentsCanRun = false;
@@ -407,7 +414,6 @@
+ " must be USER_ALL or a specific user.", new Throwable("here"));
userId = UserHandle.USER_ALL;
}
-
List<UserInfo> userInfos;
if (userId == UserHandle.USER_ALL) {
userInfos = mUserManager.getUsers(true /* excludeDying */);
@@ -430,13 +436,19 @@
boolean secure = mLockPatternUtils.isSecure(id);
boolean trusted = aggregateIsTrusted(id);
boolean showingKeyguard = true;
+ boolean fingerprintAuthenticated = false;
+
if (mCurrentUser == id) {
+ synchronized(mUsersUnlockedByFingerprint) {
+ fingerprintAuthenticated = mUsersUnlockedByFingerprint.get(id, false);
+ }
try {
showingKeyguard = wm.isKeyguardLocked();
} catch (RemoteException e) {
}
}
- boolean deviceLocked = secure && showingKeyguard && !trusted;
+ boolean deviceLocked = secure && showingKeyguard && !trusted &&
+ !fingerprintAuthenticated;
setDeviceLockedForUser(id, deviceLocked);
}
}
@@ -577,33 +589,56 @@
}
private void maybeEnableFactoryTrustAgents(LockPatternUtils utils, int userId) {
- if (0 != Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.TRUST_AGENTS_INITIALIZED, 0, userId)) {
- return;
- }
- PackageManager pm = mContext.getPackageManager();
- List<ResolveInfo> resolveInfos = resolveAllowedTrustAgents(pm, userId);
- ArraySet<ComponentName> discoveredAgents = new ArraySet<>();
- for (ResolveInfo resolveInfo : resolveInfos) {
- ComponentName componentName = getComponentName(resolveInfo);
- int applicationInfoFlags = resolveInfo.serviceInfo.applicationInfo.flags;
- if ((applicationInfoFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
- Log.i(TAG, "Leaving agent " + componentName + " disabled because package "
- + "is not a system package.");
- continue;
- }
- discoveredAgents.add(componentName);
- }
+ ComponentName defaultAgent = getDefaultFactoryTrustAgent(mContext);
+ boolean shouldUseDefaultAgent = defaultAgent != null;
- List<ComponentName> previouslyEnabledAgents = utils.getEnabledTrustAgents(userId);
- if (previouslyEnabledAgents != null) {
- discoveredAgents.addAll(previouslyEnabledAgents);
+ if (shouldUseDefaultAgent) {
+ Log.i(TAG, "Enabling " + defaultAgent + " because it is a default agent.");
+ utils.setEnabledTrustAgents(Collections.singleton(defaultAgent), userId);
+ } else { // A default agent is not set; perform regular trust agent discovery
+ if (0 != Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.TRUST_AGENTS_INITIALIZED, 0, userId)) {
+ return;
+ }
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> resolveInfos = resolveAllowedTrustAgents(pm, userId);
+
+ ArraySet<ComponentName> discoveredAgents = new ArraySet<>();
+
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ ComponentName componentName = getComponentName(resolveInfo);
+ int applicationInfoFlags = resolveInfo.serviceInfo.applicationInfo.flags;
+ if ((applicationInfoFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ Log.i(TAG, "Leaving agent " + componentName + " disabled because package "
+ + "is not a system package.");
+ continue;
+ }
+ discoveredAgents.add(componentName);
+ }
+
+ List<ComponentName> previouslyEnabledAgents = utils.getEnabledTrustAgents(userId);
+ if (previouslyEnabledAgents != null) {
+ discoveredAgents.addAll(previouslyEnabledAgents);
+ }
+ utils.setEnabledTrustAgents(discoveredAgents, userId);
}
- utils.setEnabledTrustAgents(discoveredAgents, userId);
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, userId);
}
+ /**
+ * Returns the {@link ComponentName} for the default trust agent, or {@code null} if there
+ * is no trust agent set.
+ */
+ private static ComponentName getDefaultFactoryTrustAgent(Context context) {
+ String defaultTrustAgent = context.getResources()
+ .getString(com.android.internal.R.string.config_defaultTrustAgent);
+ if (TextUtils.isEmpty(defaultTrustAgent)) {
+ return null;
+ }
+ return ComponentName.unflattenFromString(defaultTrustAgent);
+ }
+
private List<ResolveInfo> resolveAllowedTrustAgents(PackageManager pm, int userId) {
List<ResolveInfo> resolveInfos = pm.queryIntentServicesAsUser(TRUST_AGENT_INTENT,
PackageManager.GET_META_DATA |
@@ -965,6 +1000,26 @@
"query trust state");
return isTrustUsuallyManagedInternal(userId);
}
+
+ @Override
+ public void unlockedByFingerprintForUser(int userId) {
+ enforceReportPermission();
+ synchronized(mUsersUnlockedByFingerprint) {
+ mUsersUnlockedByFingerprint.put(userId, true);
+ }
+ mHandler.obtainMessage(MSG_REFRESH_DEVICE_LOCKED_FOR_USER, userId,
+ 0 /* arg2 */).sendToTarget();
+ }
+
+ @Override
+ public void clearAllFingerprints() {
+ enforceReportPermission();
+ synchronized(mUsersUnlockedByFingerprint) {
+ mUsersUnlockedByFingerprint.clear();
+ }
+ mHandler.obtainMessage(MSG_REFRESH_DEVICE_LOCKED_FOR_USER, UserHandle.USER_ALL,
+ 0 /* arg2 */).sendToTarget();
+ }
};
private boolean isTrustUsuallyManagedInternal(int userId) {
@@ -1052,6 +1107,9 @@
}
}
break;
+ case MSG_REFRESH_DEVICE_LOCKED_FOR_USER:
+ refreshDeviceLockedForUser(msg.arg1);
+ break;
}
}
};
@@ -1111,6 +1169,9 @@
synchronized (mTrustUsuallyManagedForUser) {
mTrustUsuallyManagedForUser.delete(userId);
}
+ synchronized (mUsersUnlockedByFingerprint) {
+ mUsersUnlockedByFingerprint.delete(userId);
+ }
refreshAgentList(userId);
refreshDeviceLockedForUser(userId);
}
diff --git a/services/core/java/com/android/server/tv/UinputBridge.java b/services/core/java/com/android/server/tv/UinputBridge.java
index f910332..1a984f9 100644
--- a/services/core/java/com/android/server/tv/UinputBridge.java
+++ b/services/core/java/com/android/server/tv/UinputBridge.java
@@ -63,7 +63,7 @@
@Override
protected void finalize() throws Throwable {
try {
- if (mPtr != 0) {
+ if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
close(mToken);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 39b902e..71c92b8 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -52,8 +52,11 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
@@ -74,10 +77,12 @@
import android.service.wallpaper.IWallpaperConnection;
import android.service.wallpaper.IWallpaperEngine;
import android.service.wallpaper.IWallpaperService;
+import android.app.WallpaperColors;
import android.service.wallpaper.WallpaperService;
import android.system.ErrnoException;
import android.system.Os;
import android.util.EventLog;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
@@ -87,6 +92,7 @@
import com.android.internal.R;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.graphics.palette.Palette;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
@@ -95,6 +101,7 @@
import com.android.server.FgThread;
import com.android.server.SystemService;
+import java.util.ArrayList;
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -161,6 +168,7 @@
static final String WALLPAPER_LOCK_ORIG = "wallpaper_lock_orig";
static final String WALLPAPER_LOCK_CROP = "wallpaper_lock";
static final String WALLPAPER_INFO = "wallpaper_info.xml";
+ static final int MAX_WALLPAPER_EXTRACTION_AREA = 112 * 112;
// All the various per-user state files we need to be aware of
static final String[] sPerUserFiles = new String[] {
@@ -220,6 +228,7 @@
// lock-only changes happen on the dedicated lock wallpaper input file
final boolean sysWallpaperChanged = (mWallpaperFile.equals(changedFile));
final boolean lockWallpaperChanged = (mWallpaperLockFile.equals(changedFile));
+ int notifyColorsWhich = 0;
WallpaperData wallpaper = dataForEvent(sysWallpaperChanged, lockWallpaperChanged);
if (DEBUG) {
@@ -241,6 +250,7 @@
}
SELinux.restorecon(changedFile);
notifyLockWallpaperChanged();
+ notifyWallpaperColorsChanged(wallpaper, FLAG_LOCK);
return;
}
@@ -285,6 +295,7 @@
// If this was the system wallpaper, rebind...
bindWallpaperComponentLocked(mImageWallpaper, true,
false, wallpaper, null);
+ notifyColorsWhich |= FLAG_SYSTEM;
}
if (lockWallpaperChanged
|| (wallpaper.whichPending & FLAG_LOCK) != 0) {
@@ -299,12 +310,19 @@
}
// and in any case, tell keyguard about it
notifyLockWallpaperChanged();
+ notifyColorsWhich |= FLAG_LOCK;
}
+
saveSettingsLocked(wallpaper.userId);
}
}
}
}
+
+ // Outside of the lock since it will synchronize itself
+ if (notifyColorsWhich != 0) {
+ notifyWallpaperColorsChanged(wallpaper, notifyColorsWhich);
+ }
}
}
@@ -319,6 +337,136 @@
}
}
+ private void notifyWallpaperColorsChanged(WallpaperData wallpaper, int which) {
+ boolean needsExtraction;
+ synchronized (mLock) {
+ if (mColorsChangedListeners.getRegisteredCallbackCount() == 0)
+ return;
+
+ if (DEBUG) {
+ Slog.v(TAG, "notifyWallpaperColorsChanged " + which);
+ }
+
+ needsExtraction = wallpaper.primaryColors == null;
+ }
+
+ // This should not be synchronized because color extraction
+ // might take a while.
+ if (needsExtraction) {
+ extractColors(wallpaper);
+ }
+
+ synchronized (mLock) {
+ final int n = mColorsChangedListeners.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ IWallpaperManagerCallback callback = mColorsChangedListeners.getBroadcastItem(i);
+ try {
+ callback.onWallpaperColorsChanged(wallpaper.primaryColors, which);
+ } catch (RemoteException e) {
+ // Callback is gone, it's not necessary to unregister it since
+ // RemoteCallbackList#getBroadcastItem will take care of it.
+ }
+ }
+ mColorsChangedListeners.finishBroadcast();
+
+ final IWallpaperManagerCallback cb = mKeyguardListener;
+ if (cb != null) {
+ try {
+ cb.onWallpaperColorsChanged(wallpaper.primaryColors, which);
+ } catch (RemoteException e) {
+ // Oh well it went away; no big deal
+ }
+ }
+ }
+ }
+
+ /**
+ * We can easily extract colors from an ImageWallpaper since it's only a bitmap.
+ * In this case, using the crop is more than enough.
+ *
+ * In case of a live wallpaper, the best we can do is to extract colors from its
+ * preview image. Anyway, the live wallpaper can also implement the wallpaper colors API
+ * to report when colors change.
+ *
+ * @param wallpaper a wallpaper representation
+ */
+ private void extractColors(WallpaperData wallpaper) {
+ String cropFile = null;
+ Drawable thumbnail = null;
+ // This represents a maximum pixel count in an image.
+ // It prevents color extraction on big bitmaps.
+ int wallpaperId = -1;
+
+ synchronized (mLock) {
+ final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.wallpaperComponent)
+ || wallpaper.wallpaperComponent == null;
+ if (imageWallpaper) {
+ if (wallpaper.cropFile != null && wallpaper.cropFile.exists()) {
+ cropFile = wallpaper.cropFile.getAbsolutePath();
+ }
+ } else {
+ if (wallpaper.connection == null) {
+ Slog.w(TAG, "Can't extract colors, wallpaper not connected. " +
+ wallpaper.wallpaperId);
+ return;
+ }
+ WallpaperInfo info = wallpaper.connection.mInfo;
+ if (info == null) {
+ Slog.w(TAG, "Something is really wrong, live wallpaper doesn't have " +
+ "a WallpaperInfo object! " + wallpaper.wallpaperId);
+ return;
+ }
+ thumbnail = info.loadThumbnail(mContext.getPackageManager());
+ }
+
+ wallpaperId = wallpaper.wallpaperId;
+ }
+
+ Bitmap bitmap = null;
+ if (cropFile != null) {
+ bitmap = BitmapFactory.decodeFile(cropFile);
+ } else if (thumbnail != null) {
+ // Calculate how big the bitmap needs to be.
+ // This avoids unnecessary processing and allocation inside Palette.
+ final int requestedArea = thumbnail.getIntrinsicWidth() *
+ thumbnail.getIntrinsicHeight();
+ double scale = 1;
+ if (requestedArea > MAX_WALLPAPER_EXTRACTION_AREA) {
+ scale = Math.sqrt(MAX_WALLPAPER_EXTRACTION_AREA / (double) requestedArea);
+ }
+ bitmap = Bitmap.createBitmap((int) (thumbnail.getIntrinsicWidth() * scale),
+ (int) (thumbnail.getIntrinsicHeight() * scale), Bitmap.Config.ARGB_8888);
+ final Canvas bmpCanvas = new Canvas(bitmap);
+ thumbnail.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ thumbnail.draw(bmpCanvas);
+ }
+
+ if (bitmap == null) {
+ Slog.w(TAG, "Cannot extract colors because wallpaper could not be read.");
+ return;
+ }
+
+ Palette palette = Palette
+ .from(bitmap)
+ .resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA)
+ .generate();
+ bitmap.recycle();
+
+ final List<Pair<Color, Integer>> colors = new ArrayList<>();
+ for (Palette.Swatch swatch : palette.getSwatches()) {
+ colors.add(new Pair<>(Color.valueOf(swatch.getRgb()),
+ swatch.getPopulation()));
+ }
+
+ synchronized (mLock) {
+ if (wallpaper.wallpaperId == wallpaperId) {
+ wallpaper.primaryColors = new WallpaperColors(colors);
+ } else {
+ Slog.w(TAG, "Not setting primary colors since wallpaper changed");
+ }
+ }
+ }
+
/**
* Once a new wallpaper has been written via setWallpaper(...), it needs to be cropped
* for display.
@@ -482,6 +630,7 @@
final IPackageManager mIPackageManager;
final MyPackageMonitor mMonitor;
final AppOpsManager mAppOpsManager;
+ final RemoteCallbackList<IWallpaperManagerCallback> mColorsChangedListeners;
WallpaperData mLastWallpaper;
IWallpaperManagerCallback mKeyguardListener;
boolean mWaitingForUnlock;
@@ -558,6 +707,11 @@
*/
int wallpaperId;
+ /**
+ * Primary colors histogram
+ */
+ WallpaperColors primaryColors;
+
WallpaperConnection connection;
long lastDiedTime;
boolean wallpaperUpdating;
@@ -733,6 +887,40 @@
}
}
+ /**
+ * Called by a live wallpaper if its colors have changed.
+ * @param primaryColors representation of wallpaper primary colors
+ */
+ @Override
+ public void onWallpaperColorsChanged(WallpaperColors primaryColors) {
+ // Do not override default color extraction if API isn't implemented.
+ if (primaryColors == null) {
+ return;
+ }
+
+ int which;
+ synchronized (mLock) {
+ // Do not broadcast changes on ImageWallpaper since it's handled
+ // internally by this class.
+ if (mImageWallpaper.equals(mWallpaper.wallpaperComponent)) {
+ return;
+ }
+
+ mWallpaper.primaryColors = primaryColors;
+
+ // Live wallpapers always are system wallpapers.
+ which = FLAG_SYSTEM;
+ // It's also the lock screen wallpaper when we don't have a bitmap in there
+ WallpaperData lockedWallpaper = mLockWallpaperMap.get(mCurrentUserId);
+ if (lockedWallpaper == null) {
+ which |= FLAG_LOCK;
+ }
+ }
+ if (which != 0) {
+ notifyWallpaperColorsChanged(mWallpaper, which);
+ }
+ }
+
@Override
public void attachEngine(IWallpaperEngine engine) {
synchronized (mLock) {
@@ -943,6 +1131,7 @@
mMonitor.register(context, null, UserHandle.ALL, true);
getWallpaperDir(UserHandle.USER_SYSTEM).mkdirs();
loadSettingsLocked(UserHandle.USER_SYSTEM, false);
+ mColorsChangedListeners = new RemoteCallbackList<>();
}
private static File getWallpaperDir(int userId) {
@@ -1154,8 +1343,21 @@
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, true, "clearWallpaper", null);
+ WallpaperData data = null;
synchronized (mLock) {
clearWallpaperLocked(false, which, userId, null);
+
+ if (which == FLAG_LOCK) {
+ data = mLockWallpaperMap.get(userId);
+ }
+ if (which == FLAG_SYSTEM || data == null) {
+ data = mWallpaperMap.get(userId);
+ }
+ }
+
+ // When clearing a wallpaper, broadcast new valid colors
+ if (data != null) {
+ notifyWallpaperColorsChanged(data, which);
}
}
@@ -1449,6 +1651,20 @@
}
@Override
+ public void registerWallpaperColorsCallback(IWallpaperManagerCallback cb) {
+ synchronized (mLock) {
+ mColorsChangedListeners.register(cb);
+ }
+ }
+
+ @Override
+ public void unregisterWallpaperColorsCallback(IWallpaperManagerCallback cb) {
+ synchronized (mLock) {
+ mColorsChangedListeners.unregister(cb);
+ }
+ }
+
+ @Override
public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
synchronized (mLock) {
@@ -1458,6 +1674,41 @@
}
@Override
+ public WallpaperColors getWallpaperColors(int which) throws RemoteException {
+ if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
+ throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM");
+ }
+
+ WallpaperData wallpaperData = null;
+ boolean shouldExtract;
+
+ synchronized (mLock) {
+ if (which == FLAG_LOCK) {
+ wallpaperData = mLockWallpaperMap.get(mCurrentUserId);
+ }
+
+ // Try to get the system wallpaper anyway since it might
+ // also be the lock screen wallpaper
+ if (wallpaperData == null) {
+ wallpaperData = mWallpaperMap.get(mCurrentUserId);
+ }
+
+ if (wallpaperData == null) {
+ return null;
+ }
+ shouldExtract = wallpaperData.primaryColors == null;
+ }
+
+ if (shouldExtract) {
+ extractColors(wallpaperData);
+ }
+
+ synchronized (mLock) {
+ return wallpaperData.primaryColors;
+ }
+ }
+
+ @Override
public ParcelFileDescriptor setWallpaper(String name, String callingPackage,
Rect cropHint, boolean allowBackup, Bundle extras, int which,
IWallpaperManagerCallback completion, int userId) {
@@ -1536,6 +1787,7 @@
lockWP.width = sysWP.width;
lockWP.height = sysWP.height;
lockWP.allowBackup = sysWP.allowBackup;
+ lockWP.primaryColors = sysWP.primaryColors;
// Migrate the bitmap files outright; no need to copy
try {
@@ -1573,6 +1825,8 @@
if (extras != null) {
extras.putInt(WallpaperManager.EXTRA_NEW_WALLPAPER_ID, wallpaper.wallpaperId);
}
+ // Nullify field to require new computation
+ wallpaper.primaryColors = null;
if (DEBUG) {
Slog.v(TAG, "updateWallpaperBitmapLocked() : id=" + wallpaper.wallpaperId
+ " name=" + name + " file=" + wallpaper.wallpaperFile.getName());
@@ -1604,9 +1858,13 @@
false /* all */, true /* full */, "changing live wallpaper", null /* pkg */);
checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
+ int which = FLAG_SYSTEM;
+ boolean shouldNotifyColors = false;
+ WallpaperData wallpaper;
+
synchronized (mLock) {
if (DEBUG) Slog.v(TAG, "setWallpaperComponent name=" + name);
- WallpaperData wallpaper = mWallpaperMap.get(userId);
+ wallpaper = mWallpaperMap.get(userId);
if (wallpaper == null) {
throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
}
@@ -1624,16 +1882,26 @@
}
}
+ // New live wallpaper is also a lock wallpaper if nothing is set
+ if (mLockWallpaperMap.get(userId) == null) {
+ which |= FLAG_LOCK;
+ }
+
try {
wallpaper.imageWallpaperPending = false;
if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) {
wallpaper.wallpaperId = makeWallpaperIdLocked();
notifyCallbacksLocked(wallpaper);
+ shouldNotifyColors = true;
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
+
+ if (shouldNotifyColors) {
+ notifyWallpaperColorsChanged(wallpaper, which);
+ }
}
boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
@@ -1758,6 +2026,7 @@
}
wallpaper.wallpaperComponent = componentName;
wallpaper.connection = newConn;
+ wallpaper.primaryColors = null;
newConn.mReply = reply;
try {
if (wallpaper.userId == mCurrentUserId) {
@@ -1837,6 +2106,7 @@
}
}
wallpaper.callbacks.finishBroadcast();
+
final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
}
@@ -1927,6 +2197,9 @@
private void writeWallpaperAttributes(XmlSerializer out, String tag, WallpaperData wallpaper)
throws IllegalArgumentException, IllegalStateException, IOException {
+ if (DEBUG) {
+ Slog.v(TAG, "writeWallpaperAttributes");
+ }
out.startTag(null, tag);
out.attribute(null, "id", Integer.toString(wallpaper.wallpaperId));
out.attribute(null, "width", Integer.toString(wallpaper.width));
@@ -1950,6 +2223,20 @@
out.attribute(null, "paddingBottom", Integer.toString(wallpaper.padding.bottom));
}
+ if (wallpaper.primaryColors != null) {
+ int colorsCount = wallpaper.primaryColors.getColors().size();
+ out.attribute(null, "colorsCount", Integer.toString(colorsCount));
+ if (colorsCount > 0) {
+ for (int i = 0; i < colorsCount; i++) {
+ Pair<Color, Integer> wc = wallpaper.primaryColors.getColors().get(i);
+ out.attribute(null, "colorValue"+i, Integer.toString(wc.first.toArgb()));
+ out.attribute(null, "colorWeight"+i, Integer.toString(wc.second));
+ }
+ }
+ out.attribute(null, "supportsDarkText",
+ Integer.toString(wallpaper.primaryColors.supportsDarkText() ? 1 : 0));
+ }
+
out.attribute(null, "name", wallpaper.name);
if (wallpaper.wallpaperComponent != null
&& !wallpaper.wallpaperComponent.equals(mImageWallpaper)) {
@@ -2081,6 +2368,7 @@
Slog.v(TAG, "mWidth:" + wallpaper.width);
Slog.v(TAG, "mHeight:" + wallpaper.height);
Slog.v(TAG, "cropRect:" + wallpaper.cropHint);
+ Slog.v(TAG, "primaryColors:" + wallpaper.primaryColors);
Slog.v(TAG, "mName:" + wallpaper.name);
Slog.v(TAG, "mNextWallpaperComponent:"
+ wallpaper.nextWallpaperComponent);
@@ -2179,6 +2467,18 @@
wallpaper.padding.top = getAttributeInt(parser, "paddingTop", 0);
wallpaper.padding.right = getAttributeInt(parser, "paddingRight", 0);
wallpaper.padding.bottom = getAttributeInt(parser, "paddingBottom", 0);
+ int colorsCount = getAttributeInt(parser, "colorsCount", 0);
+ if (colorsCount > 0) {
+ List<Pair<Color, Integer>> colors = new ArrayList<>();
+ for (int i = 0; i < colorsCount; i++) {
+ colors.add(new Pair<>(
+ Color.valueOf(getAttributeInt(parser, "colorValue"+i, 0)),
+ getAttributeInt(parser, "colorWeight"+i, 0)
+ ));
+ }
+ boolean dark = getAttributeInt(parser, "supportsDarkText", 0) == 1;
+ wallpaper.primaryColors = new WallpaperColors(colors, dark);
+ }
wallpaper.name = parser.getAttributeValue(null, "name");
wallpaper.allowBackup = "true".equals(parser.getAttributeValue(null, "backup"));
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index fe90ba9..d4949b6 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -278,6 +278,7 @@
pw.println("Current WebView Update Service state");
pw.println(String.format(" Fallback logic enabled: %b",
mSystemInterface.isFallbackLogicEnabled()));
+ pw.println(String.format(" Multiprocess enabled: %b", isMultiProcessEnabled()));
mWebViewUpdater.dumpState(pw);
}
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdater.java b/services/core/java/com/android/server/webkit/WebViewUpdater.java
index 37479c8..203bbf6 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdater.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdater.java
@@ -68,7 +68,7 @@
// The WebView package currently in use (or the one we are preparing).
private PackageInfo mCurrentWebViewPackage = null;
- private Object mLock = new Object();
+ private final Object mLock = new Object();
WebViewUpdater(Context context, SystemInterface systemInterface) {
mContext = context;
diff --git a/services/core/java/com/android/server/wm/AlertWindowNotification.java b/services/core/java/com/android/server/wm/AlertWindowNotification.java
index 7eebe39..3f32079 100644
--- a/services/core/java/com/android/server/wm/AlertWindowNotification.java
+++ b/services/core/java/com/android/server/wm/AlertWindowNotification.java
@@ -63,12 +63,6 @@
mNotificationTag = CHANNEL_PREFIX + mPackageName;
mRequestCode = sNextRequestCode++;
mIconUtilities = new IconUtilities(mService.mContext);
- if (sChannelGroup == null) {
- sChannelGroup = new NotificationChannelGroup(CHANNEL_PREFIX,
- mService.mContext.getString(
- R.string.alert_windows_notification_channel_group_name));
- mNotificationManager.createNotificationChannelGroup(sChannelGroup);
- }
}
void post() {
@@ -143,6 +137,13 @@
}
private void createNotificationChannel(Context context, String appName) {
+ if (sChannelGroup == null) {
+ sChannelGroup = new NotificationChannelGroup(CHANNEL_PREFIX,
+ mService.mContext.getString(
+ R.string.alert_windows_notification_channel_group_name));
+ mNotificationManager.createNotificationChannelGroup(sChannelGroup);
+ }
+
final String nameChannel =
context.getString(R.string.alert_windows_notification_channel_name, appName);
final NotificationChannel channel =
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 9d8f124..c1c72ca 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -313,6 +313,7 @@
mNextAppTransition = transit;
mNextAppTransitionFlags |= flags;
setLastAppTransition(TRANSIT_UNSET, null, null);
+ updateBooster();
}
void setLastAppTransition(int transit, AppWindowToken openingApp, AppWindowToken closingApp) {
@@ -327,7 +328,7 @@
}
void setReady() {
- mAppTransitionState = APP_STATE_READY;
+ setAppTransitionState(APP_STATE_READY);
fetchAppTransitionSpecsFromFuture();
}
@@ -336,7 +337,7 @@
}
void setIdle() {
- mAppTransitionState = APP_STATE_IDLE;
+ setAppTransitionState(APP_STATE_IDLE);
}
boolean isTimeout() {
@@ -344,7 +345,7 @@
}
void setTimeout() {
- mAppTransitionState = APP_STATE_TIMEOUT;
+ setAppTransitionState(APP_STATE_TIMEOUT);
}
GraphicBuffer getAppTransitionThumbnailHeader(int taskId) {
@@ -386,7 +387,7 @@
private boolean prepare() {
if (!isRunning()) {
- mAppTransitionState = APP_STATE_IDLE;
+ setAppTransitionState(APP_STATE_IDLE);
notifyAppTransitionPendingLocked();
mLastHadClipReveal = false;
mLastClipRevealMaxTranslation = 0;
@@ -405,7 +406,7 @@
ArraySet<AppWindowToken> closingApps) {
mNextAppTransition = TRANSIT_UNSET;
mNextAppTransitionFlags = 0;
- mAppTransitionState = APP_STATE_RUNNING;
+ setAppTransitionState(APP_STATE_RUNNING);
int redoLayout = notifyAppTransitionStartingLocked(transit,
topOpeningAppAnimator != null ? topOpeningAppAnimator.mAppToken.token : null,
topClosingAppAnimator != null ? topClosingAppAnimator.mAppToken.token : null,
@@ -450,6 +451,22 @@
notifyAppTransitionCancelledLocked(transit);
}
+ private void setAppTransitionState(int state) {
+ mAppTransitionState = state;
+ updateBooster();
+ }
+
+ /**
+ * Updates whether we currently boost wm locked sections and the animation thread. We want to
+ * boost the priorities to a more important value whenever an app transition is going to happen
+ * soon or an app transition is running.
+ */
+ private void updateBooster() {
+ WindowManagerService.sThreadPriorityBooster.setAppTransitionRunning(
+ mNextAppTransition != TRANSIT_UNSET || mAppTransitionState == APP_STATE_READY
+ || mAppTransitionState == APP_STATE_RUNNING);
+ }
+
void registerListenerLocked(AppTransitionListener listener) {
mListeners.add(listener);
}
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 6640184..8cfbf68 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -18,6 +18,8 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+
+import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
@@ -581,8 +583,12 @@
private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning,
boolean allowTaskSnapshot, boolean activityCreated) {
- if (newTask || !processRunning
- || (taskSwitch && !activityCreated)) {
+ if (mService.mAppTransition.getAppTransition() == TRANSIT_DOCK_TASK_FROM_RECENTS) {
+ // TODO(b/34099271): Remove this statement to add back the starting window and figure
+ // out why it causes flickering, the starting window appears over the thumbnail while
+ // the docked from recents transition occurs
+ return STARTING_WINDOW_TYPE_NONE;
+ } else if (newTask || !processRunning || (taskSwitch && !activityCreated)) {
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
} else if (taskSwitch && allowTaskSnapshot) {
return STARTING_WINDOW_TYPE_SNAPSHOT;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a8e0d76..ccc8f63 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1162,8 +1162,6 @@
final int dh = displayInfo.logicalHeight;
config.orientation = (dw <= dh) ? Configuration.ORIENTATION_PORTRAIT :
Configuration.ORIENTATION_LANDSCAPE;
- config.setRotation(displayInfo.rotation);
-
config.screenWidthDp =
(int)(mService.mPolicy.getConfigDisplayWidth(dw, dh, displayInfo.rotation,
config.uiMode, mDisplayId) / mDisplayMetrics.density);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index fb500bc..233e75b 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -858,20 +858,14 @@
final int privateflags = attrs.privateFlags;
boolean displayHasContent = false;
- if (DEBUG_KEEP_SCREEN_ON && (attrFlags & FLAG_KEEP_SCREEN_ON) != 0
- && w != mService.mLastWakeLockHoldingWindow) {
- Slog.d(TAG_KEEP_SCREEN_ON, "handleNotObscuredLocked: " + w
- + " has FLAG_KEEP_SCREEN_ON set, hasSurface=" + w.mHasSurface
- + ", canBeSeen=" + canBeSeen);
- }
-
if (w.mHasSurface && canBeSeen) {
if ((attrFlags & FLAG_KEEP_SCREEN_ON) != 0) {
mHoldScreen = w.mSession;
mHoldScreenWindow = w;
} else if (DEBUG_KEEP_SCREEN_ON && w == mService.mLastWakeLockHoldingWindow) {
Slog.d(TAG_KEEP_SCREEN_ON, "handleNotObscuredLocked: " + w + " was holding "
- + "screen wakelock but no longer has FLAG_KEEP_SCREEN_ON!!!");
+ + "screen wakelock but no longer has FLAG_KEEP_SCREEN_ON!!! called by"
+ + Debug.getCallers(10));
}
if (!syswin && w.mAttrs.screenBrightness >= 0 && mScreenBrightness < 0) {
mScreenBrightness = w.mAttrs.screenBrightness;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 6a7123c..091e1cb 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -159,6 +159,7 @@
}
}
+ @Override
public void binderDied() {
// Note: it is safe to call in to the input method manager
// here because we are not holding our lock.
@@ -205,6 +206,7 @@
outContentInsets, outStableInsets, null /* outOutsets */, null);
}
+ @Override
public void remove(IWindow window) {
mService.removeWindow(this, window);
}
@@ -214,6 +216,7 @@
mService.setWillReplaceWindows(appToken, childrenOnly);
}
+ @Override
public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewFlags,
int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
@@ -230,42 +233,50 @@
return res;
}
+ @Override
public boolean outOfMemory(IWindow window) {
return mService.outOfMemoryWindow(this, window);
}
+ @Override
public void setTransparentRegion(IWindow window, Region region) {
mService.setTransparentRegionWindow(this, window, region);
}
+ @Override
public void setInsets(IWindow window, int touchableInsets,
Rect contentInsets, Rect visibleInsets, Region touchableArea) {
mService.setInsetsWindow(this, window, touchableInsets, contentInsets,
visibleInsets, touchableArea);
}
+ @Override
public void getDisplayFrame(IWindow window, Rect outDisplayFrame) {
mService.getWindowDisplayFrame(this, window, outDisplayFrame);
}
+ @Override
public void finishDrawing(IWindow window) {
if (WindowManagerService.localLOGV) Slog.v(
TAG_WM, "IWindow finishDrawing called for " + window);
mService.finishDrawingWindow(this, window);
}
+ @Override
public void setInTouchMode(boolean mode) {
synchronized(mService.mWindowMap) {
mService.mInTouchMode = mode;
}
}
+ @Override
public boolean getInTouchMode() {
synchronized(mService.mWindowMap) {
return mService.mInTouchMode;
}
}
+ @Override
public boolean performHapticFeedback(IWindow window, int effectId,
boolean always) {
synchronized(mService.mWindowMap) {
@@ -281,12 +292,14 @@
}
/* Drag/drop */
+ @Override
public IBinder prepareDrag(IWindow window, int flags,
int width, int height, Surface outSurface) {
return mService.prepareDragSurface(window, mSurfaceSession, flags,
width, height, outSurface);
}
+ @Override
public boolean performDrag(IWindow window, IBinder dragToken,
int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
ClipData data) {
@@ -370,6 +383,7 @@
return true; // success!
}
+ @Override
public boolean startMovingTask(IWindow window, float startX, float startY) {
if (DEBUG_TASK_POSITIONING) Slog.d(
TAG_WM, "startMovingTask: {" + startX + "," + startY + "}");
@@ -382,6 +396,7 @@
}
}
+ @Override
public void reportDropResult(IWindow window, boolean consumed) {
IBinder token = window.asBinder();
if (DEBUG_DRAG) {
@@ -422,6 +437,7 @@
}
}
+ @Override
public void cancelDragAndDrop(IBinder dragToken) {
if (DEBUG_DRAG) {
Slog.d(TAG_WM, "cancelDragAndDrop");
@@ -450,18 +466,21 @@
}
}
+ @Override
public void dragRecipientEntered(IWindow window) {
if (DEBUG_DRAG) {
Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder());
}
}
+ @Override
public void dragRecipientExited(IWindow window) {
if (DEBUG_DRAG) {
Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder());
}
}
+ @Override
public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) {
synchronized(mService.mWindowMap) {
long ident = Binder.clearCallingIdentity();
@@ -475,12 +494,14 @@
}
}
+ @Override
public void wallpaperOffsetsComplete(IBinder window) {
synchronized (mService.mWindowMap) {
mService.mRoot.mWallpaperController.wallpaperOffsetsComplete(window);
}
}
+ @Override
public void setWallpaperDisplayOffset(IBinder window, int x, int y) {
synchronized(mService.mWindowMap) {
long ident = Binder.clearCallingIdentity();
@@ -493,6 +514,7 @@
}
}
+ @Override
public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
int z, Bundle extras, boolean sync) {
synchronized(mService.mWindowMap) {
@@ -507,12 +529,14 @@
}
}
+ @Override
public void wallpaperCommandComplete(IBinder window, Bundle result) {
synchronized (mService.mWindowMap) {
mService.mRoot.mWallpaperController.wallpaperCommandComplete(window);
}
}
+ @Override
public void onRectangleOnScreenRequested(IBinder token, Rect rectangle) {
synchronized(mService.mWindowMap) {
final long identity = Binder.clearCallingIdentity();
@@ -524,6 +548,7 @@
}
}
+ @Override
public IWindowId getWindowId(IBinder window) {
return mService.getWindowId(window);
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 3a116bb..551e3bf 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -102,6 +102,8 @@
| FLAG_SCALED
| FLAG_SECURE;
+ private static final int PRIVATE_FLAG_INHERITS = PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
+
private static final String TAG = TAG_WITH_CLASS_NAME ? "SnapshotStartingWindow" : TAG_WM;
private static final int MSG_REPORT_DRAW = 0;
private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s";
@@ -160,7 +162,8 @@
layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES)
| FLAG_NOT_FOCUSABLE
| FLAG_NOT_TOUCHABLE;
- layoutParams.privateFlags = PRIVATE_FLAG_TASK_SNAPSHOT;
+ layoutParams.privateFlags = PRIVATE_FLAG_TASK_SNAPSHOT
+ | (windowPrivateFlags & PRIVATE_FLAG_INHERITS);
layoutParams.token = token.token;
layoutParams.width = LayoutParams.MATCH_PARENT;
layoutParams.height = LayoutParams.MATCH_PARENT;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index eff8ed9..39878cc 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -109,6 +109,7 @@
/** Application tokens that are exiting, but still on screen for animations. */
final AppTokenList mExitingAppTokens = new AppTokenList();
+ final AppTokenList mTmpAppTokens = new AppTokenList();
/** Detach this stack from its display when animation completes. */
// TODO: maybe tie this to WindowContainer#removeChild some how...
@@ -1626,9 +1627,14 @@
// TODO: Why aren't we just using the loop above for this? mAppAnimator.animating isn't set
// below but is set in the loop above. See if it really matters...
- final int exitingCount = mExitingAppTokens.size();
- for (int i = 0; i < exitingCount; i++) {
- final AppWindowAnimator appAnimator = mExitingAppTokens.get(i).mAppAnimator;
+
+ // Clear before using.
+ mTmpAppTokens.clear();
+ // We copy the list as things can be removed from the exiting token list while we are
+ // processing.
+ mTmpAppTokens.addAll(mExitingAppTokens);
+ for (int i = 0; i < mTmpAppTokens.size(); i++) {
+ final AppWindowAnimator appAnimator = mTmpAppTokens.get(i).mAppAnimator;
appAnimator.wasAnimating = appAnimator.animating;
if (appAnimator.stepAnimationLocked(currentTime)) {
mService.mAnimator.setAnimating(true);
@@ -1641,6 +1647,8 @@
"updateWindowsApps...: done animating exiting " + appAnimator.mAppToken);
}
}
+ // Clear to avoid holding reference to tokens.
+ mTmpAppTokens.clear();
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index d85dd0c..03b5b827 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -83,10 +83,6 @@
// check if some got replaced and can be removed.
private boolean mRemoveReplacedWindows = false;
- private long mCurrentFrameTime;
- private final Runnable mAnimationTick;
- private final SurfaceFlingerVsyncChoreographer mSfChoreographer;
-
private Choreographer mChoreographer;
/**
@@ -95,40 +91,19 @@
*/
private boolean mAnimationFrameCallbackScheduled;
- /**
- * Indicates whether we have an animation tick scheduled. The tick is the thing that actually
- * executes the animation step, which will happen at vsync-sf.
- */
- private boolean mAnimationTickScheduled;
-
WindowAnimator(final WindowManagerService service) {
mService = service;
mContext = service.mContext;
mPolicy = service.mPolicy;
mWindowPlacerLocked = service.mWindowPlacerLocked;
AnimationThread.getHandler().runWithScissors(
- () -> mChoreographer = Choreographer.getInstance(), 0 /* timeout */);
+ () -> mChoreographer = Choreographer.getSfInstance(), 0 /* timeout */);
- // TODO: Multi-display: If displays have different vsync tick, have a separate tick per
- // display.
- mSfChoreographer = new SurfaceFlingerVsyncChoreographer(AnimationThread.getHandler(),
- mService.getDefaultDisplayContentLocked().getDisplay(), mChoreographer);
- mAnimationTick = () -> {
- synchronized (mService.mWindowMap) {
- mAnimationTickScheduled = false;
- }
- animate(mCurrentFrameTime);
- };
mAnimationFrameCallback = frameTimeNs -> {
synchronized (mService.mWindowMap) {
- mCurrentFrameTime = frameTimeNs;
mAnimationFrameCallbackScheduled = false;
- if (mAnimationTickScheduled) {
- return;
- }
- mAnimationTickScheduled = true;
}
- mSfChoreographer.scheduleAtSfVsync(mAnimationTick);
+ animate(frameTimeNs);
};
}
@@ -422,7 +397,7 @@
}
boolean isAnimationScheduled() {
- return mAnimationFrameCallbackScheduled || mAnimationTickScheduled;
+ return mAnimationFrameCallbackScheduled;
}
Choreographer getChoreographer() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 4da9c06..6d5673e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -77,5 +77,5 @@
static final boolean DEBUG_UNKNOWN_APP_VISIBILITY = false;
static final String TAG_KEEP_SCREEN_ON = "DebugKeepScreenOn";
- static final boolean DEBUG_KEEP_SCREEN_ON = true;
+ static final boolean DEBUG_KEEP_SCREEN_ON = false;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4b066c0..7ef1bd5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -158,6 +158,7 @@
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
+import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
@@ -869,8 +870,8 @@
// since they won't be notified through the app window animator.
final List<IBinder> mNoAnimationNotifyOnTransitionFinished = new ArrayList<>();
- private static ThreadPriorityBooster sThreadPriorityBooster = new ThreadPriorityBooster(
- THREAD_PRIORITY_DISPLAY, INDEX_WINDOW);
+ static WindowManagerThreadPriorityBooster sThreadPriorityBooster =
+ new WindowManagerThreadPriorityBooster();
static void boostPriorityForLockedSection() {
sThreadPriorityBooster.boost();
@@ -3600,8 +3601,16 @@
// only allow disables from pids which have count on, etc.
@Override
public void showStrictModeViolation(boolean on) {
- int pid = Binder.getCallingPid();
- mH.sendMessage(mH.obtainMessage(H.SHOW_STRICT_MODE_VIOLATION, on ? 1 : 0, pid));
+ final int pid = Binder.getCallingPid();
+ if (on) {
+ // Show the visualization, and enqueue a second message to tear it
+ // down if we don't hear back from the app.
+ mH.sendMessage(mH.obtainMessage(H.SHOW_STRICT_MODE_VIOLATION, 1, pid));
+ mH.sendMessageDelayed(mH.obtainMessage(H.SHOW_STRICT_MODE_VIOLATION, 0, pid),
+ DateUtils.SECOND_IN_MILLIS);
+ } else {
+ mH.sendMessage(mH.obtainMessage(H.SHOW_STRICT_MODE_VIOLATION, 0, pid));
+ }
}
private void showStrictModeViolation(int arg, int pid) {
@@ -6188,6 +6197,17 @@
}
@Override
+ public Region getCurrentImeTouchRegion() {
+ synchronized (mWindowMap) {
+ final Region r = new Region();
+ if (mInputMethodWindow != null) {
+ mInputMethodWindow.getTouchableRegion(r);
+ }
+ return r;
+ }
+ }
+
+ @Override
public boolean hasNavigationBar() {
return mPolicy.hasNavigationBar();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
new file mode 100644
index 0000000..6a244a2
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+import static android.os.Process.myTid;
+import static android.os.Process.setThreadPriority;
+
+import static com.android.server.LockGuard.INDEX_WINDOW;
+import static com.android.server.am.ActivityManagerService.TOP_APP_PRIORITY_BOOST;
+
+import com.android.server.AnimationThread;
+import com.android.server.ThreadPriorityBooster;
+
+/**
+ * Window manager version of {@link ThreadPriorityBooster} that boosts even more during app
+ * transitions.
+ */
+class WindowManagerThreadPriorityBooster extends ThreadPriorityBooster {
+
+ private final AnimationThread mAnimationThread;
+ private boolean mAppTransitionRunning;
+
+ WindowManagerThreadPriorityBooster() {
+ super(THREAD_PRIORITY_DISPLAY, INDEX_WINDOW);
+ mAnimationThread = AnimationThread.get();
+ }
+
+ @Override
+ public void boost() {
+
+ // Do not boost the animation thread. As the animation thread is changing priorities,
+ // boosting it might mess up the priority because we reset it the the previous priority.
+ if (myTid() == mAnimationThread.getThreadId()) {
+ return;
+ }
+ super.boost();
+ }
+
+ @Override
+ public void reset() {
+
+ // See comment in boost().
+ if (myTid() == mAnimationThread.getThreadId()) {
+ return;
+ }
+ super.reset();
+ }
+
+ void setAppTransitionRunning(boolean running) {
+ if (mAppTransitionRunning == running) {
+ return;
+ }
+
+ final int priority = calculatePriority(running);
+ setBoostToPriority(priority);
+ setThreadPriority(mAnimationThread.getThreadId(), priority);
+ mAppTransitionRunning = running;
+ }
+
+ private int calculatePriority(boolean appTransitionRunning) {
+ return appTransitionRunning ? TOP_APP_PRIORITY_BOOST : THREAD_PRIORITY_DISPLAY;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 1d08c2e..f74948f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2231,14 +2231,15 @@
mWinAnimator.applyEnterAnimationLocked();
}
- // always report back the new configuration
- final Configuration globalConfig = mService.mRoot.getConfiguration();
- final Configuration overrideConfig = getMergedOverrideConfiguration();
- mergedConfiguration.setConfiguration(globalConfig, overrideConfig);
- if (DEBUG_CONFIGURATION) Slog.i(TAG, "Window " + this
- + " reporting new global config: " + globalConfig
- + " merged override config: " + overrideConfig);
- mLastReportedConfiguration.setTo(getConfiguration());
+ if (isConfigChanged()) {
+ final Configuration globalConfig = mService.mRoot.getConfiguration();
+ final Configuration overrideConfig = getMergedOverrideConfiguration();
+ mergedConfiguration.setConfiguration(globalConfig, overrideConfig);
+ if (DEBUG_CONFIGURATION) Slog.i(TAG, "Window " + this
+ + " visible with new global config: " + globalConfig
+ + " merged override config: " + overrideConfig);
+ mLastReportedConfiguration.setTo(getConfiguration());
+ }
}
void adjustStartingWindowFlags() {
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 23637de..a3d28bb 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -5,9 +5,12 @@
LOCAL_CFLAGS += -Wall -Werror -Wno-unused-parameter
LOCAL_SRC_FILES += \
+ $(LOCAL_REL_DIR)/JavaRef.cpp \
+ $(LOCAL_REL_DIR)/NativeCallbackThread.cpp \
$(LOCAL_REL_DIR)/com_android_server_AlarmManagerService.cpp \
$(LOCAL_REL_DIR)/com_android_server_am_BatteryStatsService.cpp \
$(LOCAL_REL_DIR)/com_android_server_connectivity_Vpn.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp \
$(LOCAL_REL_DIR)/com_android_server_ConsumerIrService.cpp \
$(LOCAL_REL_DIR)/com_android_server_HardwarePropertiesManagerService.cpp \
$(LOCAL_REL_DIR)/com_android_server_hdmi_HdmiCecController.cpp \
@@ -17,9 +20,13 @@
$(LOCAL_REL_DIR)/com_android_server_lights_LightsService.cpp \
$(LOCAL_REL_DIR)/com_android_server_location_ContextHubService.cpp \
$(LOCAL_REL_DIR)/com_android_server_location_GnssLocationProvider.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_locksettings_SyntheticPasswordManager.cpp \
$(LOCAL_REL_DIR)/com_android_server_power_PowerManagerService.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_radio_RadioService.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_radio_Tuner.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_radio_Tuner_TunerCallback.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_radio_convert.cpp \
$(LOCAL_REL_DIR)/com_android_server_SerialService.cpp \
- $(LOCAL_REL_DIR)/com_android_server_SyntheticPasswordManager.cpp \
$(LOCAL_REL_DIR)/com_android_server_storage_AppFuseBridge.cpp \
$(LOCAL_REL_DIR)/com_android_server_SystemServer.cpp \
$(LOCAL_REL_DIR)/com_android_server_tv_TvUinputBridge.cpp \
@@ -57,6 +64,7 @@
liblog \
libhardware \
libhardware_legacy \
+ libhidlbase \
libkeystore_binder \
libnativehelper \
libutils \
@@ -80,15 +88,20 @@
libutils \
libhwui \
android.hardware.audio.common@2.0 \
+ android.hardware.broadcastradio@1.0 \
+ android.hardware.broadcastradio@1.1 \
android.hardware.contexthub@1.0 \
android.hardware.gnss@1.0 \
android.hardware.ir@1.0 \
android.hardware.light@2.0 \
android.hardware.power@1.0 \
+ android.hardware.power@1.1 \
+ android.hardware.tetheroffload.config@1.0 \
android.hardware.thermal@1.0 \
android.hardware.tv.cec@1.0 \
android.hardware.tv.input@1.0 \
android.hardware.vibrator@1.0 \
+ android.hardware.vibrator@1.1 \
android.hardware.vr@1.0 \
android.frameworks.schedulerservice@1.0 \
android.frameworks.sensorservice@1.0 \
diff --git a/services/core/jni/JavaRef.cpp b/services/core/jni/JavaRef.cpp
new file mode 100644
index 0000000..ad07afa
--- /dev/null
+++ b/services/core/jni/JavaRef.cpp
@@ -0,0 +1,30 @@
+/**
+ * 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.
+ */
+
+#define LOG_TAG "JavaRef"
+//#define LOG_NDEBUG 0
+
+#include "JavaRef.h"
+
+#include <utils/Log.h>
+
+namespace android {
+
+EnvWrapper::EnvWrapper(JNIEnv *env) : mEnv(env) {
+ ALOGE_IF(env == nullptr, "Environment is a nullptr");
+}
+
+} // namespace android
diff --git a/services/core/jni/JavaRef.h b/services/core/jni/JavaRef.h
new file mode 100644
index 0000000..8a572e2
--- /dev/null
+++ b/services/core/jni/JavaRef.h
@@ -0,0 +1,57 @@
+/**
+ * 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.
+ */
+
+#ifndef _ANDROID_JAVA_REF_H
+#define _ANDROID_JAVA_REF_H
+
+#include <android-base/macros.h>
+#include <functional>
+#include <jni.h>
+#include <memory>
+#include <type_traits>
+
+namespace android {
+
+template <typename T>
+using JavaRef = std::unique_ptr<typename std::remove_pointer<T>::type, std::function<void(T)>>;
+
+template <typename T>
+JavaRef<T> make_javaref(JNIEnv *env, T ref) {
+ return JavaRef<T>(ref, [env](T ref) {
+ if (env && ref) {
+ env->DeleteLocalRef(ref);
+ }
+ });
+}
+
+class EnvWrapper {
+public:
+ EnvWrapper(JNIEnv *env);
+
+ template <typename T>
+ JavaRef<T> operator() (T ref) const {
+ return make_javaref(mEnv, ref);
+ }
+
+private:
+ JNIEnv *mEnv;
+
+ DISALLOW_COPY_AND_ASSIGN(EnvWrapper);
+};
+
+} // namespace android
+
+#endif // _ANDROID_JAVA_REF_H
diff --git a/services/core/jni/NativeCallbackThread.cpp b/services/core/jni/NativeCallbackThread.cpp
new file mode 100644
index 0000000..7d668e3
--- /dev/null
+++ b/services/core/jni/NativeCallbackThread.cpp
@@ -0,0 +1,116 @@
+/**
+ * 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.
+ */
+
+#define LOG_TAG "NativeCallbackThread"
+//#define LOG_NDEBUG 0
+
+#include "NativeCallbackThread.h"
+
+#include <utils/Log.h>
+
+namespace android {
+
+NativeCallbackThread::NativeCallbackThread(JavaVM *vm) : mExitting(false), mvm(vm) {
+ auto res = pthread_create(&mThread, nullptr, main, this);
+ if (res != 0) {
+ ALOGE("Couldn't start NativeCallbackThread");
+ mThread = 0;
+ return;
+ }
+ ALOGD("Started native callback thread %p", this);
+}
+
+NativeCallbackThread::~NativeCallbackThread() {
+ ALOGV("~NativeCallbackThread %p", this);
+ stop();
+}
+
+void* NativeCallbackThread::main(void *args) {
+ auto self = reinterpret_cast<NativeCallbackThread*>(args);
+ self->main();
+ return nullptr;
+}
+
+void NativeCallbackThread::main() {
+ ALOGV("NativeCallbackThread::main()");
+
+ JNIEnv *env = nullptr;
+ JavaVMAttachArgs aargs = {JNI_VERSION_1_4, "NativeCallbackThread", nullptr};
+ if (mvm->AttachCurrentThread(&env, &aargs) != JNI_OK || env == nullptr) {
+ ALOGE("Couldn't attach thread");
+ return;
+ }
+
+ while (!mExitting) {
+ ALOGV("Waiting for task...");
+ Task task;
+ {
+ AutoMutex _l(mQueueMutex);
+ auto res = mQueueCond.wait(mQueueMutex);
+ ALOGE_IF(res != 0, "Wait failed: %d", res);
+ if (mExitting || res != 0) break;
+
+ if (mQueue.empty()) continue;
+ task = mQueue.front();
+ mQueue.pop();
+ }
+
+ ALOGV("Executing task...");
+ task(env);
+ if (env->ExceptionCheck()) {
+ ALOGE("Unexpected exception:");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+ }
+
+ auto res = mvm->DetachCurrentThread();
+ ALOGE_IF(res != JNI_OK, "Couldn't detach thread");
+
+ ALOGV("Native callback thread %p finished", this);
+}
+
+void NativeCallbackThread::enqueue(const Task &task) {
+ AutoMutex _l(mQueueMutex);
+
+ if (mThread == 0 || mExitting) {
+ ALOGW("Callback thread %p is not serving calls", this);
+ return;
+ }
+
+ mQueue.push(task);
+ mQueueCond.signal();
+}
+
+void NativeCallbackThread::stop() {
+ ALOGV("stop() %p", this);
+
+ {
+ AutoMutex _l(mQueueMutex);
+
+ if (mThread == 0 || mExitting) return;
+
+ mExitting = true;
+ mQueueCond.signal();
+ }
+
+ auto ret = pthread_join(mThread, nullptr);
+ ALOGE_IF(ret != 0, "Couldn't join thread: %d", ret);
+
+ ALOGD("Stopped native callback thread %p", this);
+}
+
+} // namespace android
diff --git a/services/core/jni/NativeCallbackThread.h b/services/core/jni/NativeCallbackThread.h
new file mode 100644
index 0000000..4e03b11
--- /dev/null
+++ b/services/core/jni/NativeCallbackThread.h
@@ -0,0 +1,56 @@
+/**
+ * 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.
+ */
+
+#ifndef _ANDROID_NATIVE_CALLBACK_THREAD_H
+#define _ANDROID_NATIVE_CALLBACK_THREAD_H
+
+#include <android-base/macros.h>
+#include <functional>
+#include <jni.h>
+#include <pthread.h>
+#include <queue>
+#include <utils/Condition.h>
+#include <utils/Mutex.h>
+
+namespace android {
+
+class NativeCallbackThread {
+ typedef std::function<void(JNIEnv*)> Task;
+
+ pthread_t mThread;
+ Mutex mQueueMutex;
+ Condition mQueueCond;
+ std::atomic<bool> mExitting;
+
+ JavaVM *mvm;
+ std::queue<Task> mQueue;
+
+ static void* main(void *args);
+ void main();
+
+ DISALLOW_COPY_AND_ASSIGN(NativeCallbackThread);
+
+public:
+ NativeCallbackThread(JavaVM *vm);
+ virtual ~NativeCallbackThread();
+
+ void enqueue(const Task &task);
+ void stop();
+};
+
+} // namespace android
+
+#endif // _ANDROID_NATIVE_CALLBACK_THREAD_H
diff --git a/services/core/jni/com_android_server_SyntheticPasswordManager.cpp b/services/core/jni/com_android_server_SyntheticPasswordManager.cpp
deleted file mode 100644
index a9f7b9f..0000000
--- a/services/core/jni/com_android_server_SyntheticPasswordManager.cpp
+++ /dev/null
@@ -1,90 +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.
- */
-
-#define LOG_TAG "SyntheticPasswordManager"
-
-#include "JNIHelp.h"
-#include "jni.h"
-
-#include <android_runtime/Log.h>
-#include <utils/Timers.h>
-#include <utils/misc.h>
-#include <utils/String8.h>
-#include <utils/Log.h>
-#include <gatekeeper/password_handle.h>
-
-
-extern "C" {
-#include "crypto_scrypt.h"
-}
-
-namespace android {
-
-static jlong android_server_SyntheticPasswordManager_nativeSidFromPasswordHandle(JNIEnv* env, jobject, jbyteArray handleArray) {
-
- jbyte* data = (jbyte*)env->GetPrimitiveArrayCritical(handleArray, NULL);
-
- if (data != NULL) {
- const gatekeeper::password_handle_t *handle =
- reinterpret_cast<const gatekeeper::password_handle_t *>(data);
- jlong sid = handle->user_id;
- env->ReleasePrimitiveArrayCritical(handleArray, data, JNI_ABORT);
- return sid;
- } else {
- return 0;
- }
-}
-
-static jbyteArray android_server_SyntheticPasswordManager_nativeScrypt(JNIEnv* env, jobject, jbyteArray password, jbyteArray salt, jint N, jint r, jint p, jint outLen) {
- if (!password || !salt) {
- return NULL;
- }
-
- int passwordLen = env->GetArrayLength(password);
- int saltLen = env->GetArrayLength(salt);
- jbyteArray ret = env->NewByteArray(outLen);
-
- jbyte* passwordPtr = (jbyte*)env->GetByteArrayElements(password, NULL);
- jbyte* saltPtr = (jbyte*)env->GetByteArrayElements(salt, NULL);
- jbyte* retPtr = (jbyte*)env->GetByteArrayElements(ret, NULL);
-
- int rc = crypto_scrypt((const uint8_t *)passwordPtr, passwordLen,
- (const uint8_t *)saltPtr, saltLen, N, r, p, (uint8_t *)retPtr,
- outLen);
- env->ReleaseByteArrayElements(password, passwordPtr, JNI_ABORT);
- env->ReleaseByteArrayElements(salt, saltPtr, JNI_ABORT);
- env->ReleaseByteArrayElements(ret, retPtr, 0);
-
- if (!rc) {
- return ret;
- } else {
- SLOGE("scrypt failed");
- return NULL;
- }
-}
-
-static const JNINativeMethod sMethods[] = {
- /* name, signature, funcPtr */
- {"nativeSidFromPasswordHandle", "([B)J", (void*)android_server_SyntheticPasswordManager_nativeSidFromPasswordHandle},
- {"nativeScrypt", "([B[BIIII)[B", (void*)android_server_SyntheticPasswordManager_nativeScrypt},
-};
-
-int register_android_server_SyntheticPasswordManager(JNIEnv* env) {
- return jniRegisterNativeMethods(env, "com/android/server/SyntheticPasswordManager",
- sMethods, NELEM(sMethods));
-}
-
-} /* namespace android */
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 76ce890..804cd17 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -18,6 +18,7 @@
#include <android/hardware/vibrator/1.0/IVibrator.h>
#include <android/hardware/vibrator/1.0/types.h>
+#include <android/hardware/vibrator/1.1/IVibrator.h>
#include "jni.h"
#include "JNIHelp.h"
@@ -35,6 +36,8 @@
using android::hardware::vibrator::V1_0::EffectStrength;
using android::hardware::vibrator::V1_0::IVibrator;
using android::hardware::vibrator::V1_0::Status;
+using android::hardware::vibrator::V1_1::Effect_1_1;
+using IVibrator_1_1 = android::hardware::vibrator::V1_1::IVibrator;
namespace android
{
@@ -108,11 +111,26 @@
if (mHal != nullptr) {
Status status;
uint32_t lengthMs;
- mHal->perform(static_cast<Effect>(effect), static_cast<EffectStrength>(strength),
- [&status, &lengthMs](Status retStatus, uint32_t retLengthMs) {
- status = retStatus;
- lengthMs = retLengthMs;
- });
+ auto callback = [&status, &lengthMs](Status retStatus, uint32_t retLengthMs) {
+ status = retStatus;
+ lengthMs = retLengthMs;
+ };
+ EffectStrength effectStrength(static_cast<EffectStrength>(strength));
+
+ if (effect < 0 || effect > static_cast<uint32_t>(Effect_1_1::TICK)) {
+ ALOGW("Unable to perform haptic effect, invalid effect ID (%" PRId32 ")",
+ static_cast<int32_t>(effect));
+ } else if (effect == static_cast<uint32_t>(Effect_1_1::TICK)) {
+ sp<IVibrator_1_1> hal_1_1 = IVibrator_1_1::castFrom(mHal);
+ if (hal_1_1 != nullptr) {
+ hal_1_1->perform_1_1(static_cast<Effect_1_1>(effect), effectStrength, callback);
+ } else {
+ ALOGW("Failed to perform effect (%" PRId32 "), insufficient HAL version",
+ static_cast<int32_t>(effect));
+ }
+ } else {
+ mHal->perform(static_cast<Effect>(effect), effectStrength, callback);
+ }
if (status == Status::OK) {
return lengthMs;
} else if (status != Status::UNSUPPORTED_OPERATION) {
diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
index 57bb9fe..37ae782 100644
--- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp
+++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
@@ -29,6 +29,7 @@
#include <unistd.h>
#include <android/hardware/power/1.0/IPower.h>
+#include <android/hardware/power/1.1/IPower.h>
#include <android_runtime/AndroidRuntime.h>
#include <jni.h>
@@ -46,6 +47,8 @@
using android::hardware::power::V1_0::PowerStatePlatformSleepState;
using android::hardware::power::V1_0::PowerStateVoter;
using android::hardware::power::V1_0::Status;
+using android::hardware::power::V1_1::PowerStateSubsystem;
+using android::hardware::power::V1_1::PowerStateSubsystemSleepState;
using android::hardware::hidl_vec;
namespace android
@@ -263,9 +266,105 @@
return total_added;
}
+static jint getSubsystemLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) {
+ char *output = (char*)env->GetDirectBufferAddress(outBuf);
+ char *offset = output;
+ int remaining = (int)env->GetDirectBufferCapacity(outBuf);
+ int total_added = -1;
+
+ //This is a IPower 1.1 API
+ sp<android::hardware::power::V1_1::IPower> gPowerHal_1_1 = nullptr;
+
+ if (outBuf == NULL) {
+ jniThrowException(env, "java/lang/NullPointerException", "null argument");
+ return -1;
+ }
+
+ {
+ std::lock_guard<std::mutex> lock(gPowerHalMutex);
+ if (!getPowerHal()) {
+ ALOGE("Power Hal not loaded");
+ return -1;
+ }
+
+ //Trying to cast to 1.1, this will succeed only for devices supporting 1.1
+ gPowerHal_1_1 = android::hardware::power::V1_1::IPower::castFrom(gPowerHal);
+ if (gPowerHal_1_1 == nullptr) {
+ //This device does not support IPower@1.1, exiting gracefully
+ return 0;
+ }
+
+ Return<void> ret = gPowerHal_1_1->getSubsystemLowPowerStats(
+ [&offset, &remaining, &total_added](hidl_vec<PowerStateSubsystem> subsystems,
+ Status status) {
+
+ if (status != Status::SUCCESS)
+ return;
+
+ for (size_t i = 0; i < subsystems.size(); i++) {
+ int added;
+ const PowerStateSubsystem &subsystem = subsystems[i];
+
+ added = snprintf(offset, remaining,
+ "subsystem_%zu name=%s ", i + 1, subsystem.name.c_str());
+ if (added < 0) {
+ break;
+ }
+
+ if (added > remaining) {
+ added = remaining;
+ }
+
+ offset += added;
+ remaining -= added;
+ total_added += added;
+
+ for (size_t j = 0; j < subsystem.states.size(); j++) {
+ const PowerStateSubsystemSleepState& state = subsystem.states[j];
+ added = snprintf(offset, remaining,
+ "state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " last entry TS(ms)=%" PRIu64 " ",
+ j + 1, state.name.c_str(), state.residencyInMsecSinceBoot,
+ state.totalTransitions, state.lastEntryTimestampMs);
+ if (added < 0) {
+ break;
+ }
+
+ if (added > remaining) {
+ added = remaining;
+ }
+
+ offset += added;
+ remaining -= added;
+ total_added += added;
+ }
+
+ if (remaining <= 0) {
+ /* rewrite NULL character*/
+ offset--;
+ total_added--;
+ ALOGE("PowerHal: buffer not enough");
+ break;
+ }
+ }
+ }
+ );
+
+ if (!ret.isOk()) {
+ ALOGE("getSubsystemLowPowerStats() failed: power HAL service not available");
+ gPowerHal = nullptr;
+ return -1;
+ }
+ }
+
+ *offset = 0;
+ total_added += 1;
+ return total_added;
+}
+
static const JNINativeMethod method_table[] = {
{ "nativeWaitWakeup", "(Ljava/nio/ByteBuffer;)I", (void*)nativeWaitWakeup },
{ "getPlatformLowPowerStats", "(Ljava/nio/ByteBuffer;)I", (void*)getPlatformLowPowerStats },
+ { "getSubsystemLowPowerStats", "(Ljava/nio/ByteBuffer;)I", (void*)getSubsystemLowPowerStats },
};
int register_android_server_BatteryStatsService(JNIEnv *env)
diff --git a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
new file mode 100644
index 0000000..241ccf6
--- /dev/null
+++ b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <error.h>
+#include <hidl/HidlSupport.h>
+#include <jni.h>
+#include <JNIHelp.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netlink.h>
+#include <sys/socket.h>
+#include <android-base/unique_fd.h>
+#include <android/hardware/tetheroffload/config/1.0/IOffloadConfig.h>
+
+#define LOG_TAG "OffloadHardwareInterface"
+#include <utils/Log.h>
+
+namespace android {
+
+using hardware::hidl_handle;
+using hardware::hidl_string;
+using hardware::tetheroffload::config::V1_0::IOffloadConfig;
+
+namespace {
+
+inline const sockaddr * asSockaddr(const sockaddr_nl *nladdr) {
+ return reinterpret_cast<const sockaddr *>(nladdr);
+}
+
+int conntrackSocket(unsigned groups) {
+ base::unique_fd s(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETFILTER));
+ if (s.get() < 0) return -errno;
+
+ const struct sockaddr_nl bind_addr = {
+ .nl_family = AF_NETLINK,
+ .nl_pad = 0,
+ .nl_pid = 0,
+ .nl_groups = groups,
+ };
+ if (bind(s.get(), asSockaddr(&bind_addr), sizeof(bind_addr)) != 0) {
+ return -errno;
+ }
+
+ const struct sockaddr_nl kernel_addr = {
+ .nl_family = AF_NETLINK,
+ .nl_pad = 0,
+ .nl_pid = 0,
+ .nl_groups = groups,
+ };
+ if (connect(s.get(), asSockaddr(&kernel_addr), sizeof(kernel_addr)) != 0) {
+ return -errno;
+ }
+
+ return s.release();
+}
+
+// Return a hidl_handle that owns the file descriptor owned by fd, and will
+// auto-close it (otherwise there would be double-close problems).
+//
+// Rely upon the compiler to eliminate the constexprs used for clarity.
+hidl_handle&& handleFromFileDescriptor(base::unique_fd fd) {
+ hidl_handle h;
+
+ NATIVE_HANDLE_DECLARE_STORAGE(storage, 0, 0);
+ static constexpr int kNumFds = 1;
+ static constexpr int kNumInts = 0;
+ native_handle_t *nh = native_handle_init(storage, kNumFds, kNumInts);
+ nh->data[0] = fd.release();
+
+ static constexpr bool kTakeOwnership = true;
+ h.setTo(nh, kTakeOwnership);
+
+ return std::move(h);
+}
+
+} // namespace
+
+static jboolean android_server_connectivity_tethering_OffloadHardwareInterface_configOffload(
+ JNIEnv* /* env */) {
+ sp<IOffloadConfig> configInterface = IOffloadConfig::getService();
+ if (configInterface.get() == nullptr) {
+ ALOGD("Could not find IOffloadConfig service.");
+ return false;
+ }
+
+ // Per the IConfigOffload definition:
+ //
+ // fd1 A file descriptor bound to the following netlink groups
+ // (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY).
+ //
+ // fd2 A file descriptor bound to the following netlink groups
+ // (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
+ base::unique_fd
+ fd1(conntrackSocket(NFNLGRP_CONNTRACK_NEW | NFNLGRP_CONNTRACK_DESTROY)),
+ fd2(conntrackSocket(NFNLGRP_CONNTRACK_UPDATE | NFNLGRP_CONNTRACK_DESTROY));
+ if (fd1.get() < 0 || fd2.get() < 0) {
+ ALOGE("Unable to create conntrack handles: %d/%s", errno, strerror(errno));
+ return false;
+ }
+
+ hidl_handle h1(handleFromFileDescriptor(std::move(fd1))),
+ h2(handleFromFileDescriptor(std::move(fd2)));
+
+ bool rval;
+ hidl_string msg;
+ configInterface->setHandles(h1, h2,
+ [&rval, &msg](bool success, const hidl_string& errMsg) {
+ rval = success;
+ msg = errMsg;
+ });
+ if (!rval) {
+ ALOGE("IOffloadConfig::setHandles() error: %s", msg.c_str());
+ }
+
+ return rval;
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ { "configOffload", "()Z",
+ (void*) android_server_connectivity_tethering_OffloadHardwareInterface_configOffload },
+};
+
+int register_android_server_connectivity_tethering_OffloadHardwareInterface(JNIEnv* env) {
+ return jniRegisterNativeMethods(env,
+ "com/android/server/connectivity/tethering/OffloadHardwareInterface",
+ gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 6f24e7e..41f86c9 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -36,6 +36,7 @@
#include <utils/Log.h>
#include <utils/Looper.h>
#include <utils/threads.h>
+#include <utils/SortedVector.h>
#include <input/PointerController.h>
#include <input/SpriteController.h>
@@ -158,7 +159,12 @@
status_t status = android_view_PointerIcon_loadSystemIcon(env,
contextObj, style, outPointerIcon);
if (!status) {
- outPointerIcon->bitmap.copyTo(&outSpriteIcon->bitmap, kN32_SkColorType);
+ SkBitmap* bitmapCopy = &outSpriteIcon->bitmap;
+ SkImageInfo bitmapCopyInfo = outPointerIcon->bitmap.info().makeColorType(kN32_SkColorType);
+ if (bitmapCopy->tryAllocPixels(bitmapCopyInfo)) {
+ outPointerIcon->bitmap.readPixels(bitmapCopy->info(), bitmapCopy->getPixels(),
+ bitmapCopy->rowBytes(), 0, 0);
+ }
outSpriteIcon->hotSpotX = outPointerIcon->hotSpotX;
outSpriteIcon->hotSpotY = outPointerIcon->hotSpotY;
}
@@ -203,6 +209,7 @@
void setInputDispatchMode(bool enabled, bool frozen);
void setSystemUiVisibility(int32_t visibility);
void setPointerSpeed(int32_t speed);
+ void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
void setShowTouches(bool enabled);
void setInteractive(bool interactive);
void reloadCalibration();
@@ -288,6 +295,9 @@
// Pointer controller singleton, created and destroyed as needed.
wp<PointerController> pointerController;
+
+ // Input devices to be disabled
+ SortedVector<int32_t> disabledInputDevices;
} mLocked;
std::atomic<bool> mInteractive;
@@ -512,6 +522,8 @@
outConfig->setPhysicalDisplayViewport(ViewportType::VIEWPORT_EXTERNAL,
mLocked.externalViewport);
outConfig->setVirtualDisplayViewports(mLocked.virtualViewports);
+
+ outConfig->disabledDevices = mLocked.disabledInputDevices;
} // release lock
}
@@ -801,6 +813,24 @@
InputReaderConfiguration::CHANGE_POINTER_SPEED);
}
+void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ ssize_t index = mLocked.disabledInputDevices.indexOf(deviceId);
+ bool currentlyEnabled = index < 0;
+ if (!enabled && currentlyEnabled) {
+ mLocked.disabledInputDevices.add(deviceId);
+ }
+ if (enabled && !currentlyEnabled) {
+ mLocked.disabledInputDevices.remove(deviceId);
+ }
+ } // release lock
+
+ mInputManager->getReader()->requestRefreshConfiguration(
+ InputReaderConfiguration::CHANGE_ENABLED_STATE);
+}
+
void NativeInputManager::setShowTouches(bool enabled) {
{ // acquire lock
AutoMutex _l(mLock);
@@ -1382,6 +1412,7 @@
static void nativeToggleCapsLock(JNIEnv* env, jclass /* clazz */,
jlong ptr, jint deviceId) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
im->getInputManager()->getReader()->toggleCapsLockState(deviceId);
}
@@ -1402,6 +1433,7 @@
static void nativeSetPointerCapture(JNIEnv* env, jclass /* clazz */, jlong ptr,
jboolean enabled) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
im->setPointerCapture(enabled);
}
@@ -1463,6 +1495,7 @@
static void nativeReloadCalibration(JNIEnv* env, jclass clazz, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
im->reloadCalibration();
}
@@ -1529,13 +1562,36 @@
im->getInputManager()->getDispatcher()->monitor();
}
+static jboolean nativeIsInputDeviceEnabled(JNIEnv* env /* env */,
+ jclass /* clazz */, jlong ptr, jint deviceId) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ return im->getInputManager()->getReader()->isInputDeviceEnabled(deviceId);
+}
+
+static void nativeEnableInputDevice(JNIEnv* /* env */,
+ jclass /* clazz */, jlong ptr, jint deviceId) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ im->setInputDeviceEnabled(deviceId, true);
+}
+
+static void nativeDisableInputDevice(JNIEnv* /* env */,
+ jclass /* clazz */, jlong ptr, jint deviceId) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ im->setInputDeviceEnabled(deviceId, false);
+}
+
static void nativeSetPointerIconType(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint iconId) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
im->setPointerIconType(iconId);
}
static void nativeReloadPointerIcons(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
im->reloadPointerIcons();
}
@@ -1551,7 +1607,11 @@
}
SpriteIcon spriteIcon;
- pointerIcon.bitmap.copyTo(&spriteIcon.bitmap, kN32_SkColorType);
+ SkImageInfo spriteInfo = pointerIcon.bitmap.info().makeColorType(kN32_SkColorType);
+ if (spriteIcon.bitmap.tryAllocPixels(spriteInfo)) {
+ pointerIcon.bitmap.readPixels(spriteInfo, spriteIcon.bitmap.getPixels(),
+ spriteIcon.bitmap.rowBytes(), 0, 0);
+ }
spriteIcon.hotSpotX = pointerIcon.hotSpotX;
spriteIcon.hotSpotY = pointerIcon.hotSpotY;
im->setCustomPointerIcon(spriteIcon);
@@ -1621,6 +1681,12 @@
(void*) nativeDump },
{ "nativeMonitor", "(J)V",
(void*) nativeMonitor },
+ { "nativeIsInputDeviceEnabled", "(JI)Z",
+ (void*) nativeIsInputDeviceEnabled },
+ { "nativeEnableInputDevice", "(JI)V",
+ (void*) nativeEnableInputDevice },
+ { "nativeDisableInputDevice", "(JI)V",
+ (void*) nativeDisableInputDevice },
{ "nativeSetPointerIconType", "(JI)V",
(void*) nativeSetPointerIconType },
{ "nativeReloadPointerIcons", "(J)V",
diff --git a/services/core/jni/com_android_server_location_ContextHubService.cpp b/services/core/jni/com_android_server_location_ContextHubService.cpp
index 20466eb..d59bc10 100644
--- a/services/core/jni/com_android_server_location_ContextHubService.cpp
+++ b/services/core/jni/com_android_server_location_ContextHubService.cpp
@@ -513,7 +513,7 @@
if (!txnInfo || instanceId < 0) {
returnId(instanceId);
- free(txnInfo);
+ delete txnInfo;
return -1;
}
@@ -526,7 +526,7 @@
if (db.txnManager.addTxn(CONTEXT_HUB_LOAD_APP, txnInfo) != 0) {
returnId(instanceId);
- free(txnInfo);
+ delete txnInfo;
return -1;
}
@@ -543,7 +543,7 @@
*txnData = appInstanceHandle;
if (db.txnManager.addTxn(CONTEXT_HUB_UNLOAD_APP, txnData) != 0) {
- free(txnData);
+ delete txnData;
ALOGW("Cannot start transaction to unload app");
return -1;
}
diff --git a/services/core/jni/com_android_server_locksettings_SyntheticPasswordManager.cpp b/services/core/jni/com_android_server_locksettings_SyntheticPasswordManager.cpp
new file mode 100644
index 0000000..248dedb
--- /dev/null
+++ b/services/core/jni/com_android_server_locksettings_SyntheticPasswordManager.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "SyntheticPasswordManager"
+
+#include "JNIHelp.h"
+#include "jni.h"
+
+#include <android_runtime/Log.h>
+#include <utils/Timers.h>
+#include <utils/misc.h>
+#include <utils/String8.h>
+#include <utils/Log.h>
+#include <gatekeeper/password_handle.h>
+
+
+extern "C" {
+#include "crypto_scrypt.h"
+}
+
+namespace android {
+
+static jlong android_server_SyntheticPasswordManager_nativeSidFromPasswordHandle(JNIEnv* env, jobject, jbyteArray handleArray) {
+
+ jbyte* data = (jbyte*)env->GetPrimitiveArrayCritical(handleArray, NULL);
+
+ if (data != NULL) {
+ const gatekeeper::password_handle_t *handle =
+ reinterpret_cast<const gatekeeper::password_handle_t *>(data);
+ jlong sid = handle->user_id;
+ env->ReleasePrimitiveArrayCritical(handleArray, data, JNI_ABORT);
+ return sid;
+ } else {
+ return 0;
+ }
+}
+
+static jbyteArray android_server_SyntheticPasswordManager_nativeScrypt(JNIEnv* env, jobject, jbyteArray password, jbyteArray salt, jint N, jint r, jint p, jint outLen) {
+ if (!password || !salt) {
+ return NULL;
+ }
+
+ int passwordLen = env->GetArrayLength(password);
+ int saltLen = env->GetArrayLength(salt);
+ jbyteArray ret = env->NewByteArray(outLen);
+
+ jbyte* passwordPtr = (jbyte*)env->GetByteArrayElements(password, NULL);
+ jbyte* saltPtr = (jbyte*)env->GetByteArrayElements(salt, NULL);
+ jbyte* retPtr = (jbyte*)env->GetByteArrayElements(ret, NULL);
+
+ int rc = crypto_scrypt((const uint8_t *)passwordPtr, passwordLen,
+ (const uint8_t *)saltPtr, saltLen, N, r, p, (uint8_t *)retPtr,
+ outLen);
+ env->ReleaseByteArrayElements(password, passwordPtr, JNI_ABORT);
+ env->ReleaseByteArrayElements(salt, saltPtr, JNI_ABORT);
+ env->ReleaseByteArrayElements(ret, retPtr, 0);
+
+ if (!rc) {
+ return ret;
+ } else {
+ SLOGE("scrypt failed");
+ return NULL;
+ }
+}
+
+static const JNINativeMethod sMethods[] = {
+ /* name, signature, funcPtr */
+ {"nativeSidFromPasswordHandle", "([B)J", (void*)android_server_SyntheticPasswordManager_nativeSidFromPasswordHandle},
+ {"nativeScrypt", "([B[BIIII)[B", (void*)android_server_SyntheticPasswordManager_nativeScrypt},
+};
+
+int register_android_server_SyntheticPasswordManager(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/server/locksettings/SyntheticPasswordManager",
+ sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */
diff --git a/services/core/jni/com_android_server_radio_RadioService.cpp b/services/core/jni/com_android_server_radio_RadioService.cpp
new file mode 100644
index 0000000..73887bb
--- /dev/null
+++ b/services/core/jni/com_android_server_radio_RadioService.cpp
@@ -0,0 +1,208 @@
+/**
+ * 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.
+ */
+
+#define LOG_TAG "radio.RadioService.jni"
+#define LOG_NDEBUG 0
+
+#include "com_android_server_radio_RadioService.h"
+
+#include "com_android_server_radio_Tuner.h"
+#include "com_android_server_radio_convert.h"
+
+#include <android/hardware/broadcastradio/1.1/IBroadcastRadio.h>
+#include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h>
+#include <core_jni_helpers.h>
+#include <utils/Log.h>
+#include <JNIHelp.h>
+
+namespace android {
+namespace server {
+namespace radio {
+namespace RadioService {
+
+using hardware::Return;
+using hardware::hidl_vec;
+
+namespace V1_0 = hardware::broadcastradio::V1_0;
+namespace V1_1 = hardware::broadcastradio::V1_1;
+
+using V1_0::Class;
+using V1_0::Result;
+
+using V1_0::BandConfig;
+using V1_0::ProgramInfo;
+using V1_0::MetaData;
+using V1_0::ITuner;
+
+static Mutex gContextMutex;
+
+static struct {
+ struct {
+ jclass clazz;
+ jmethodID cstor;
+ } Tuner;
+} gjni;
+
+struct ServiceContext {
+ ServiceContext() {}
+
+ sp<V1_0::IBroadcastRadio> mModule;
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(ServiceContext);
+};
+
+
+/**
+ * Always lock gContextMutex when using native context.
+ */
+static ServiceContext& getNativeContext(jlong nativeContextHandle) {
+ auto nativeContext = reinterpret_cast<ServiceContext*>(nativeContextHandle);
+ LOG_ALWAYS_FATAL_IF(nativeContext == nullptr, "Native context not initialized");
+ return *nativeContext;
+}
+
+static jlong nativeInit(JNIEnv *env, jobject obj) {
+ ALOGV("nativeInit()");
+ AutoMutex _l(gContextMutex);
+
+ auto nativeContext = new ServiceContext();
+ static_assert(sizeof(jlong) >= sizeof(nativeContext), "jlong is smaller than a pointer");
+ return reinterpret_cast<jlong>(nativeContext);
+}
+
+static void nativeFinalize(JNIEnv *env, jobject obj, jlong nativeContext) {
+ ALOGV("nativeFinalize()");
+ AutoMutex _l(gContextMutex);
+
+ auto ctx = reinterpret_cast<ServiceContext*>(nativeContext);
+ delete ctx;
+}
+
+static sp<V1_0::IBroadcastRadio> getModule(jlong nativeContext) {
+ ALOGV("getModule()");
+ AutoMutex _l(gContextMutex);
+ auto& ctx = getNativeContext(nativeContext);
+
+ if (ctx.mModule != nullptr) {
+ return ctx.mModule;
+ }
+
+ // TODO(b/36863239): what about other HAL implementations?
+ auto factory = V1_0::IBroadcastRadioFactory::getService();
+ if (factory == nullptr) {
+ ALOGE("Can't retrieve radio HAL implementation");
+ return nullptr;
+ }
+
+ sp<V1_0::IBroadcastRadio> module = nullptr;
+ // TODO(b/36863239): not only AM/FM
+ factory->connectModule(Class::AM_FM, [&](Result retval,
+ const sp<V1_0::IBroadcastRadio>& result) {
+ if (retval == Result::OK) {
+ module = result;
+ }
+ });
+
+ ALOGE_IF(module == nullptr, "Couldn't connect module");
+ ctx.mModule = module;
+ return module;
+}
+
+static jobject nativeOpenTuner(JNIEnv *env, jobject obj, long nativeContext, jint moduleId,
+ jobject bandConfig, bool withAudio, jobject callback) {
+ ALOGV("nativeOpenTuner()");
+ EnvWrapper wrap(env);
+
+ if (callback == nullptr) {
+ ALOGE("Callback is empty");
+ return nullptr;
+ }
+
+ // TODO(b/36863239): use moduleId
+ auto module = getModule(nativeContext);
+ if (module == nullptr) {
+ return nullptr;
+ }
+
+ HalRevision halRev;
+ if (V1_1::IBroadcastRadio::castFrom(module).withDefault(nullptr) != nullptr) {
+ ALOGI("Opening tuner with broadcast radio HAL 1.1");
+ halRev = HalRevision::V1_1;
+ } else {
+ ALOGI("Opening tuner with broadcast radio HAL 1.0");
+ halRev = HalRevision::V1_0;
+ }
+
+ Region region;
+ BandConfig bandConfigHal = convert::BandConfigToHal(env, bandConfig, region);
+
+ auto tuner = wrap(env->NewObject(gjni.Tuner.clazz, gjni.Tuner.cstor,
+ callback, halRev, region, withAudio));
+ if (tuner == nullptr) {
+ ALOGE("Unable to create new tuner object.");
+ return nullptr;
+ }
+
+ auto tunerCb = Tuner::getNativeCallback(env, tuner);
+ Result halResult;
+ sp<ITuner> halTuner = nullptr;
+
+ auto hidlResult = module->openTuner(bandConfigHal, withAudio, tunerCb,
+ [&](Result result, const sp<ITuner>& tuner) {
+ halResult = result;
+ halTuner = tuner;
+ });
+ if (!hidlResult.isOk() || halResult != Result::OK || halTuner == nullptr) {
+ ALOGE("Couldn't open tuner");
+ ALOGE_IF(hidlResult.isOk(), "halResult = %d", halResult);
+ ALOGE_IF(!hidlResult.isOk(), "hidlResult = %s", hidlResult.description().c_str());
+ return nullptr;
+ }
+
+ Tuner::setHalTuner(env, tuner, halTuner);
+ ALOGI("Opened tuner %p", halTuner.get());
+ return tuner.release();
+}
+
+static const JNINativeMethod gRadioServiceMethods[] = {
+ { "nativeInit", "()J", (void*)nativeInit },
+ { "nativeFinalize", "(J)V", (void*)nativeFinalize },
+ { "nativeOpenTuner", "(JILandroid/hardware/radio/RadioManager$BandConfig;Z"
+ "Landroid/hardware/radio/ITunerCallback;)Lcom/android/server/radio/Tuner;",
+ (void*)nativeOpenTuner },
+};
+
+} // namespace RadioService
+} // namespace radio
+} // namespace server
+
+void register_android_server_radio_RadioService(JNIEnv *env) {
+ using namespace server::radio::RadioService;
+
+ register_android_server_radio_convert(env);
+
+ auto tunerClass = FindClassOrDie(env, "com/android/server/radio/Tuner");
+ gjni.Tuner.clazz = MakeGlobalRefOrDie(env, tunerClass);
+ gjni.Tuner.cstor = GetMethodIDOrDie(env, tunerClass, "<init>",
+ "(Landroid/hardware/radio/ITunerCallback;IIZ)V");
+
+ auto res = jniRegisterNativeMethods(env, "com/android/server/radio/RadioService",
+ gRadioServiceMethods, NELEM(gRadioServiceMethods));
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+}
+
+} // namespace android
diff --git a/services/core/jni/com_android_server_radio_RadioService.h b/services/core/jni/com_android_server_radio_RadioService.h
new file mode 100644
index 0000000..91e119c
--- /dev/null
+++ b/services/core/jni/com_android_server_radio_RadioService.h
@@ -0,0 +1,28 @@
+/**
+ * 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.
+ */
+
+#ifndef _ANDROID_SERVER_RADIO_RADIOSERVICE_H
+#define _ANDROID_SERVER_RADIO_RADIOSERVICE_H
+
+#include <jni.h>
+
+namespace android {
+
+void register_android_server_radio_RadioService(JNIEnv *env);
+
+} // namespace android
+
+#endif // _ANDROID_SERVER_RADIO_RADIOSERVICE_H
diff --git a/services/core/jni/com_android_server_radio_Tuner.cpp b/services/core/jni/com_android_server_radio_Tuner.cpp
new file mode 100644
index 0000000..b92136a
--- /dev/null
+++ b/services/core/jni/com_android_server_radio_Tuner.cpp
@@ -0,0 +1,384 @@
+/**
+ * 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.
+ */
+
+#define LOG_TAG "radio.Tuner.jni"
+#define LOG_NDEBUG 0
+
+#include "com_android_server_radio_Tuner.h"
+
+#include "com_android_server_radio_convert.h"
+#include "com_android_server_radio_Tuner_TunerCallback.h"
+
+#include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h>
+#include <core_jni_helpers.h>
+#include <utils/Log.h>
+#include <JNIHelp.h>
+
+namespace android {
+namespace server {
+namespace radio {
+namespace Tuner {
+
+using hardware::Return;
+using hardware::hidl_vec;
+
+namespace V1_0 = hardware::broadcastradio::V1_0;
+namespace V1_1 = hardware::broadcastradio::V1_1;
+
+using V1_0::BandConfig;
+using V1_0::MetaData;
+using V1_0::Result;
+using V1_1::ITunerCallback;
+using V1_1::ProgramListResult;
+
+static Mutex gContextMutex;
+
+static struct {
+ struct {
+ jclass clazz;
+ jmethodID cstor;
+ jmethodID add;
+ } ArrayList;
+ struct {
+ jfieldID nativeContext;
+ jfieldID region;
+ jfieldID tunerCallback;
+ } Tuner;
+} gjni;
+
+struct TunerContext {
+ TunerContext() {}
+
+ HalRevision mHalRev;
+ sp<V1_0::ITuner> mHalTuner;
+ sp<V1_1::ITuner> mHalTuner11;
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(TunerContext);
+};
+
+static TunerContext& getNativeContext(jlong nativeContextHandle) {
+ auto nativeContext = reinterpret_cast<TunerContext*>(nativeContextHandle);
+ LOG_ALWAYS_FATAL_IF(nativeContext == nullptr, "Native context not initialized");
+ return *nativeContext;
+}
+
+/**
+ * Always lock gContextMutex when using native context.
+ */
+static TunerContext& getNativeContext(JNIEnv *env, JavaRef<jobject> const &jTuner) {
+ return getNativeContext(env->GetLongField(jTuner.get(), gjni.Tuner.nativeContext));
+}
+
+static jlong nativeInit(JNIEnv *env, jobject obj, jint halRev) {
+ ALOGV("nativeInit()");
+ AutoMutex _l(gContextMutex);
+
+ auto ctx = new TunerContext();
+ ctx->mHalRev = static_cast<HalRevision>(halRev);
+
+ static_assert(sizeof(jlong) >= sizeof(ctx), "jlong is smaller than a pointer");
+ return reinterpret_cast<jlong>(ctx);
+}
+
+static void nativeFinalize(JNIEnv *env, jobject obj, jlong nativeContext) {
+ ALOGV("nativeFinalize()");
+ AutoMutex _l(gContextMutex);
+
+ auto ctx = reinterpret_cast<TunerContext*>(nativeContext);
+ delete ctx;
+}
+
+void setHalTuner(JNIEnv *env, JavaRef<jobject> const &jTuner, sp<V1_0::ITuner> halTuner) {
+ ALOGV("setHalTuner(%p)", halTuner.get());
+ ALOGE_IF(halTuner == nullptr, "HAL tuner is a nullptr");
+
+ AutoMutex _l(gContextMutex);
+ auto& ctx = getNativeContext(env, jTuner);
+
+ ctx.mHalTuner = halTuner;
+ ctx.mHalTuner11 = V1_1::ITuner::castFrom(halTuner).withDefault(nullptr);
+ ALOGW_IF(ctx.mHalRev >= HalRevision::V1_1 && ctx.mHalTuner11 == nullptr,
+ "Provided tuner does not implement 1.1 HAL");
+}
+
+sp<V1_0::ITuner> getHalTuner(jlong nativeContext) {
+ AutoMutex _l(gContextMutex);
+ auto tuner = getNativeContext(nativeContext).mHalTuner;
+ LOG_ALWAYS_FATAL_IF(tuner == nullptr, "HAL tuner is not open");
+ return tuner;
+}
+
+sp<V1_1::ITuner> getHalTuner11(jlong nativeContext) {
+ AutoMutex _l(gContextMutex);
+ return getNativeContext(nativeContext).mHalTuner11;
+}
+
+sp<ITunerCallback> getNativeCallback(JNIEnv *env, JavaRef<jobject> const &tuner) {
+ return TunerCallback::getNativeCallback(env,
+ env->GetObjectField(tuner.get(), gjni.Tuner.tunerCallback));
+}
+
+Region getRegion(JNIEnv *env, jobject obj) {
+ return static_cast<Region>(env->GetIntField(obj, gjni.Tuner.region));
+}
+
+static void nativeClose(JNIEnv *env, jobject obj, jlong nativeContext) {
+ AutoMutex _l(gContextMutex);
+ auto& ctx = getNativeContext(nativeContext);
+ if (ctx.mHalTuner == nullptr) return;
+ ALOGI("Closing tuner %p", ctx.mHalTuner.get());
+ ctx.mHalTuner11 = nullptr;
+ ctx.mHalTuner = nullptr;
+}
+
+static void nativeSetConfiguration(JNIEnv *env, jobject obj, jlong nativeContext, jobject config) {
+ ALOGV("nativeSetConfiguration()");
+ auto halTuner = getHalTuner(nativeContext);
+ if (halTuner == nullptr) return;
+
+ Region region_unused;
+ BandConfig bandConfigHal = convert::BandConfigToHal(env, config, region_unused);
+
+ convert::ThrowIfFailed(env, halTuner->setConfiguration(bandConfigHal));
+}
+
+static jobject nativeGetConfiguration(JNIEnv *env, jobject obj, jlong nativeContext,
+ Region region) {
+ ALOGV("nativeSetConfiguration()");
+ auto halTuner = getHalTuner(nativeContext);
+ if (halTuner == nullptr) return nullptr;
+
+ BandConfig halConfig;
+ Result halResult;
+ auto hidlResult = halTuner->getConfiguration([&](Result result, const BandConfig& config) {
+ halResult = result;
+ halConfig = config;
+ });
+ if (convert::ThrowIfFailed(env, hidlResult, halResult)) {
+ return nullptr;
+ }
+
+ return convert::BandConfigFromHal(env, halConfig, region).release();
+}
+
+static void nativeStep(JNIEnv *env, jobject obj, jlong nativeContext,
+ bool directionDown, bool skipSubChannel) {
+ ALOGV("nativeStep()");
+ auto halTuner = getHalTuner(nativeContext);
+ if (halTuner == nullptr) return;
+
+ auto dir = convert::DirectionToHal(directionDown);
+ convert::ThrowIfFailed(env, halTuner->step(dir, skipSubChannel));
+}
+
+static void nativeScan(JNIEnv *env, jobject obj, jlong nativeContext,
+ bool directionDown, bool skipSubChannel) {
+ ALOGV("nativeScan()");
+ auto halTuner = getHalTuner(nativeContext);
+ if (halTuner == nullptr) return;
+
+ auto dir = convert::DirectionToHal(directionDown);
+ convert::ThrowIfFailed(env, halTuner->scan(dir, skipSubChannel));
+}
+
+static void nativeTune(JNIEnv *env, jobject obj, jlong nativeContext,
+ jint channel, jint subChannel) {
+ ALOGV("nativeTune(%d, %d)", channel, subChannel);
+ auto halTuner = getHalTuner(nativeContext);
+ if (halTuner == nullptr) return;
+
+ convert::ThrowIfFailed(env, halTuner->tune(channel, subChannel));
+}
+
+static void nativeCancel(JNIEnv *env, jobject obj, jlong nativeContext) {
+ ALOGV("nativeCancel()");
+ auto halTuner = getHalTuner(nativeContext);
+ if (halTuner == nullptr) return;
+
+ convert::ThrowIfFailed(env, halTuner->cancel());
+}
+
+static jobject nativeGetProgramInformation(JNIEnv *env, jobject obj, jlong nativeContext) {
+ ALOGV("nativeGetProgramInformation()");
+ auto halTuner10 = getHalTuner(nativeContext);
+ auto halTuner11 = getHalTuner11(nativeContext);
+ if (halTuner10 == nullptr) return nullptr;
+
+ JavaRef<jobject> jInfo;
+ Result halResult;
+ Return<void> hidlResult;
+ if (halTuner11 != nullptr) {
+ hidlResult = halTuner11->getProgramInformation_1_1([&](Result result,
+ const V1_1::ProgramInfo& info) {
+ halResult = result;
+ if (result != Result::OK) return;
+ jInfo = convert::ProgramInfoFromHal(env, info);
+ });
+ } else {
+ hidlResult = halTuner10->getProgramInformation([&](Result result,
+ const V1_0::ProgramInfo& info) {
+ halResult = result;
+ if (result != Result::OK) return;
+ jInfo = convert::ProgramInfoFromHal(env, info);
+ });
+ }
+
+ if (jInfo != nullptr) return jInfo.release();
+ convert::ThrowIfFailed(env, hidlResult, halResult);
+ return nullptr;
+}
+
+static bool nativeStartBackgroundScan(JNIEnv *env, jobject obj, jlong nativeContext) {
+ ALOGV("nativeStartBackgroundScan()");
+ auto halTuner = getHalTuner11(nativeContext);
+ if (halTuner == nullptr) {
+ ALOGI("Background scan is not supported with HAL < 1.1");
+ return false;
+ }
+
+ auto halResult = halTuner->startBackgroundScan();
+
+ if (halResult.isOk() && halResult == ProgramListResult::UNAVAILABLE) return false;
+ return !convert::ThrowIfFailed(env, halResult);
+}
+
+static jobject nativeGetProgramList(JNIEnv *env, jobject obj, jlong nativeContext, jstring jFilter) {
+ ALOGV("nativeGetProgramList()");
+ EnvWrapper wrap(env);
+ auto halTuner = getHalTuner11(nativeContext);
+ if (halTuner == nullptr) {
+ ALOGI("Program list is not supported with HAL < 1.1");
+ return nullptr;
+ }
+
+ JavaRef<jobject> jList;
+ ProgramListResult halResult = ProgramListResult::NOT_INITIALIZED;
+ auto filter = env->GetStringUTFChars(jFilter, nullptr);
+ auto hidlResult = halTuner->getProgramList(filter,
+ [&](ProgramListResult result, const hidl_vec<V1_1::ProgramInfo>& programList) {
+ halResult = result;
+ if (halResult != ProgramListResult::OK) return;
+
+ jList = wrap(env->NewObject(gjni.ArrayList.clazz, gjni.ArrayList.cstor));
+ for (auto& program : programList) {
+ auto jProgram = convert::ProgramInfoFromHal(env, program);
+ env->CallBooleanMethod(jList.get(), gjni.ArrayList.add, jProgram.get());
+ }
+ });
+
+ if (convert::ThrowIfFailed(env, hidlResult, halResult)) return nullptr;
+
+ return jList.release();
+}
+
+static bool nativeIsAnalogForced(JNIEnv *env, jobject obj, jlong nativeContext) {
+ ALOGV("nativeIsAnalogForced()");
+ auto halTuner = getHalTuner11(nativeContext);
+ if (halTuner == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Forced analog switch is not supported with HAL < 1.1");
+ return false;
+ }
+
+ bool isForced;
+ Result halResult;
+ auto hidlResult = halTuner->isAnalogForced([&](Result result, bool isForcedRet) {
+ halResult = result;
+ isForced = isForcedRet;
+ });
+
+ if (convert::ThrowIfFailed(env, hidlResult, halResult)) return false;
+
+ return isForced;
+}
+
+static void nativeSetAnalogForced(JNIEnv *env, jobject obj, jlong nativeContext, bool isForced) {
+ ALOGV("nativeSetAnalogForced()");
+ auto halTuner = getHalTuner11(nativeContext);
+ if (halTuner == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Forced analog switch is not supported with HAL < 1.1");
+ return;
+ }
+
+ auto halResult = halTuner->setAnalogForced(isForced);
+ convert::ThrowIfFailed(env, halResult);
+}
+
+static bool nativeIsAntennaConnected(JNIEnv *env, jobject obj, jlong nativeContext) {
+ ALOGV("nativeIsAntennaConnected()");
+ auto halTuner = getHalTuner(nativeContext);
+ if (halTuner == nullptr) return false;
+
+ bool isConnected = false;
+ Result halResult;
+ auto hidlResult = halTuner->getConfiguration([&](Result result, const BandConfig& config) {
+ halResult = result;
+ isConnected = config.antennaConnected;
+ });
+ convert::ThrowIfFailed(env, hidlResult, halResult);
+ return isConnected;
+}
+
+static const JNINativeMethod gTunerMethods[] = {
+ { "nativeInit", "(I)J", (void*)nativeInit },
+ { "nativeFinalize", "(J)V", (void*)nativeFinalize },
+ { "nativeClose", "(J)V", (void*)nativeClose },
+ { "nativeSetConfiguration", "(JLandroid/hardware/radio/RadioManager$BandConfig;)V",
+ (void*)nativeSetConfiguration },
+ { "nativeGetConfiguration", "(JI)Landroid/hardware/radio/RadioManager$BandConfig;",
+ (void*)nativeGetConfiguration },
+ { "nativeStep", "(JZZ)V", (void*)nativeStep },
+ { "nativeScan", "(JZZ)V", (void*)nativeScan },
+ { "nativeTune", "(JII)V", (void*)nativeTune },
+ { "nativeCancel", "(J)V", (void*)nativeCancel },
+ { "nativeGetProgramInformation", "(J)Landroid/hardware/radio/RadioManager$ProgramInfo;",
+ (void*)nativeGetProgramInformation },
+ { "nativeStartBackgroundScan", "(J)Z", (void*)nativeStartBackgroundScan },
+ { "nativeGetProgramList", "(JLjava/lang/String;)Ljava/util/List;",
+ (void*)nativeGetProgramList },
+ { "nativeIsAnalogForced", "(J)Z", (void*)nativeIsAnalogForced },
+ { "nativeSetAnalogForced", "(JZ)V", (void*)nativeSetAnalogForced },
+ { "nativeIsAntennaConnected", "(J)Z", (void*)nativeIsAntennaConnected },
+};
+
+} // namespace Tuner
+} // namespace radio
+} // namespace server
+
+void register_android_server_radio_Tuner(JavaVM *vm, JNIEnv *env) {
+ using namespace server::radio::Tuner;
+
+ register_android_server_radio_TunerCallback(vm, env);
+
+ auto tunerClass = FindClassOrDie(env, "com/android/server/radio/Tuner");
+ gjni.Tuner.nativeContext = GetFieldIDOrDie(env, tunerClass, "mNativeContext", "J");
+ gjni.Tuner.region = GetFieldIDOrDie(env, tunerClass, "mRegion", "I");
+ gjni.Tuner.tunerCallback = GetFieldIDOrDie(env, tunerClass, "mTunerCallback",
+ "Lcom/android/server/radio/TunerCallback;");
+
+ auto arrayListClass = FindClassOrDie(env, "java/util/ArrayList");
+ gjni.ArrayList.clazz = MakeGlobalRefOrDie(env, arrayListClass);
+ gjni.ArrayList.cstor = GetMethodIDOrDie(env, arrayListClass, "<init>", "()V");
+ gjni.ArrayList.add = GetMethodIDOrDie(env, arrayListClass, "add", "(Ljava/lang/Object;)Z");
+
+ auto res = jniRegisterNativeMethods(env, "com/android/server/radio/Tuner",
+ gTunerMethods, NELEM(gTunerMethods));
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+}
+
+} // namespace android
diff --git a/services/core/jni/com_android_server_radio_Tuner.h b/services/core/jni/com_android_server_radio_Tuner.h
new file mode 100644
index 0000000..f653832
--- /dev/null
+++ b/services/core/jni/com_android_server_radio_Tuner.h
@@ -0,0 +1,50 @@
+/**
+ * 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.
+ */
+
+#ifndef _ANDROID_SERVER_RADIO_TUNER_H
+#define _ANDROID_SERVER_RADIO_TUNER_H
+
+#include "com_android_server_radio_types.h"
+
+#include "JavaRef.h"
+
+#include <android/hardware/broadcastradio/1.1/ITuner.h>
+#include <android/hardware/broadcastradio/1.1/ITunerCallback.h>
+#include <jni.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+void register_android_server_radio_Tuner(JavaVM *vm, JNIEnv *env);
+
+namespace server {
+namespace radio {
+namespace Tuner {
+
+void setHalTuner(JNIEnv *env, JavaRef<jobject> const &jTuner,
+ sp<hardware::broadcastradio::V1_0::ITuner> halTuner);
+
+sp<hardware::broadcastradio::V1_1::ITunerCallback>
+getNativeCallback(JNIEnv *env, JavaRef<jobject> const &tuner);
+
+Region getRegion(JNIEnv *env, jobject obj);
+
+} // namespace Tuner
+} // namespace radio
+} // namespace server
+} // namespace android
+
+#endif // _ANDROID_SERVER_RADIO_TUNER_H
diff --git a/services/core/jni/com_android_server_radio_Tuner_TunerCallback.cpp b/services/core/jni/com_android_server_radio_Tuner_TunerCallback.cpp
new file mode 100644
index 0000000..1290c7a
--- /dev/null
+++ b/services/core/jni/com_android_server_radio_Tuner_TunerCallback.cpp
@@ -0,0 +1,319 @@
+/**
+ * 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.
+ */
+
+#define LOG_TAG "radio.TunerCallback.jni"
+#define LOG_NDEBUG 0
+
+#include "com_android_server_radio_Tuner_TunerCallback.h"
+
+#include "com_android_server_radio_convert.h"
+#include "com_android_server_radio_Tuner.h"
+
+#include <core_jni_helpers.h>
+#include <utils/Log.h>
+#include <JNIHelp.h>
+
+namespace android {
+namespace server {
+namespace radio {
+namespace TunerCallback {
+
+using hardware::Return;
+using hardware::hidl_vec;
+
+namespace V1_0 = hardware::broadcastradio::V1_0;
+namespace V1_1 = hardware::broadcastradio::V1_1;
+
+using V1_0::BandConfig;
+using V1_0::MetaData;
+using V1_0::Result;
+using V1_1::ITunerCallback;
+using V1_1::ProgramListResult;
+
+static JavaVM *gvm = nullptr;
+
+static struct {
+ struct {
+ jclass clazz;
+ jfieldID nativeContext;
+ jmethodID handleHwFailure;
+ jmethodID onError;
+ jmethodID onConfigurationChanged;
+ jmethodID onProgramInfoChanged;
+ } TunerCallback;
+} gjni;
+
+// from frameworks/base/core/java/android/hardware/radio/RadioTuner.java
+enum class TunerError : jint {
+ HARDWARE_FAILURE = 0,
+ SERVER_DIED = 1,
+ CANCELLED = 2,
+ SCAN_TIMEOUT = 3,
+ CONFIG = 4,
+};
+
+static Mutex gContextMutex;
+
+class NativeCallback : public ITunerCallback {
+ jobject mJTuner;
+ jobject mJCallback;
+ NativeCallbackThread mCallbackThread;
+ HalRevision mHalRev;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeCallback);
+
+public:
+ NativeCallback(JNIEnv *env, jobject jTuner, jobject jCallback, HalRevision halRev);
+ virtual ~NativeCallback();
+
+ void detach();
+
+ virtual Return<void> hardwareFailure();
+ virtual Return<void> configChange(Result result, const BandConfig& config);
+ virtual Return<void> tuneComplete(Result result, const V1_0::ProgramInfo& info);
+ virtual Return<void> afSwitch(const V1_0::ProgramInfo& info);
+ virtual Return<void> antennaStateChange(bool connected);
+ virtual Return<void> trafficAnnouncement(bool active);
+ virtual Return<void> emergencyAnnouncement(bool active);
+ virtual Return<void> newMetadata(uint32_t channel, uint32_t subChannel,
+ const hidl_vec<MetaData>& metadata);
+ virtual Return<void> tuneComplete_1_1(Result result, const V1_1::ProgramInfo& info);
+ virtual Return<void> afSwitch_1_1(const V1_1::ProgramInfo& info);
+ virtual Return<void> backgroundScanAvailable(bool isAvailable);
+ virtual Return<void> backgroundScanComplete(ProgramListResult result);
+ virtual Return<void> programListChanged();
+};
+
+struct TunerCallbackContext {
+ TunerCallbackContext() {}
+
+ sp<NativeCallback> mNativeCallback;
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(TunerCallbackContext);
+};
+
+NativeCallback::NativeCallback(JNIEnv *env, jobject jTuner, jobject jCallback, HalRevision halRev)
+ : mCallbackThread(gvm), mHalRev(halRev) {
+ ALOGV("NativeCallback()");
+ mJTuner = env->NewGlobalRef(jTuner);
+ mJCallback = env->NewGlobalRef(jCallback);
+}
+
+NativeCallback::~NativeCallback() {
+ ALOGV("~NativeCallback()");
+
+ // stop callback thread before dereferencing client callback
+ mCallbackThread.stop();
+
+ JNIEnv *env = nullptr;
+ gvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_4);
+ if (env != nullptr) {
+ env->DeleteGlobalRef(mJTuner);
+ env->DeleteGlobalRef(mJCallback);
+ }
+}
+
+void NativeCallback::detach() {
+ // stop callback thread to ignore further calls
+ mCallbackThread.stop();
+}
+
+Return<void> NativeCallback::hardwareFailure() {
+ mCallbackThread.enqueue([this](JNIEnv *env) {
+ env->CallVoidMethod(mJCallback, gjni.TunerCallback.handleHwFailure);
+ });
+
+ return Return<void>();
+}
+
+Return<void> NativeCallback::configChange(Result result, const BandConfig& config) {
+ ALOGV("configChange(%d)", result);
+
+ mCallbackThread.enqueue([result, config, this](JNIEnv *env) {
+ if (result == Result::OK) {
+ auto region = Tuner::getRegion(env, mJTuner);
+ auto jConfig = convert::BandConfigFromHal(env, config, region);
+ if (jConfig == nullptr) return;
+ env->CallVoidMethod(mJCallback, gjni.TunerCallback.onConfigurationChanged,
+ jConfig.get());
+ } else {
+ env->CallVoidMethod(mJCallback, gjni.TunerCallback.onError, TunerError::CONFIG);
+ }
+ });
+
+ return Return<void>();
+}
+
+Return<void> NativeCallback::tuneComplete(Result result, const V1_0::ProgramInfo& info) {
+ ALOGV("tuneComplete(%d)", result);
+
+ if (mHalRev > HalRevision::V1_0) {
+ ALOGD("1.0 callback was ignored");
+ return Return<void>();
+ }
+
+ V1_1::ProgramInfo info_1_1 {
+ .base = info,
+ };
+ return tuneComplete_1_1(result, info_1_1);
+}
+
+Return<void> NativeCallback::tuneComplete_1_1(Result result, const V1_1::ProgramInfo& info) {
+ ALOGV("tuneComplete_1_1(%d)", result);
+
+ mCallbackThread.enqueue([result, info, this](JNIEnv *env) {
+ if (result == Result::OK) {
+ auto jInfo = convert::ProgramInfoFromHal(env, info);
+ if (jInfo == nullptr) return;
+ env->CallVoidMethod(mJCallback, gjni.TunerCallback.onProgramInfoChanged, jInfo.get());
+ } else {
+ TunerError cause = TunerError::CANCELLED;
+ if (result == Result::TIMEOUT) cause = TunerError::SCAN_TIMEOUT;
+ env->CallVoidMethod(mJCallback, gjni.TunerCallback.onError, cause);
+ }
+ });
+
+ return Return<void>();
+}
+
+Return<void> NativeCallback::afSwitch(const V1_0::ProgramInfo& info) {
+ ALOGE("Not implemented: afSwitch");
+ return Return<void>();
+}
+
+Return<void> NativeCallback::afSwitch_1_1(const V1_1::ProgramInfo& info) {
+ ALOGE("Not implemented: afSwitch_1_1");
+ return Return<void>();
+}
+
+Return<void> NativeCallback::antennaStateChange(bool connected) {
+ ALOGE("Not implemented: antennaStateChange");
+ return Return<void>();
+}
+
+Return<void> NativeCallback::trafficAnnouncement(bool active) {
+ ALOGE("Not implemented: trafficAnnouncement");
+ return Return<void>();
+}
+
+Return<void> NativeCallback::emergencyAnnouncement(bool active) {
+ ALOGE("Not implemented: emergencyAnnouncement");
+ return Return<void>();
+}
+
+Return<void> NativeCallback::newMetadata(uint32_t channel, uint32_t subChannel,
+ const hidl_vec<MetaData>& metadata) {
+ ALOGE("Not implemented: newMetadata");
+ return Return<void>();
+}
+
+Return<void> NativeCallback::backgroundScanAvailable(bool isAvailable) {
+ ALOGE("Not implemented: backgroundScanAvailable");
+ return Return<void>();
+}
+
+Return<void> NativeCallback::backgroundScanComplete(ProgramListResult result) {
+ ALOGE("Not implemented: backgroundScanComplete");
+ return Return<void>();
+}
+
+Return<void> NativeCallback::programListChanged() {
+ ALOGE("Not implemented: programListChanged");
+ return Return<void>();
+}
+
+static TunerCallbackContext& getNativeContext(jlong nativeContextHandle) {
+ auto nativeContext = reinterpret_cast<TunerCallbackContext*>(nativeContextHandle);
+ LOG_ALWAYS_FATAL_IF(nativeContext == nullptr, "Native context not initialized");
+ return *nativeContext;
+}
+
+/**
+ * Always lock gContextMutex when using native context.
+ */
+static TunerCallbackContext& getNativeContext(JNIEnv *env, jobject jTunerCb) {
+ return getNativeContext(env->GetLongField(jTunerCb, gjni.TunerCallback.nativeContext));
+}
+
+static jlong nativeInit(JNIEnv *env, jobject obj, jobject jTuner, jint jHalRev) {
+ ALOGV("nativeInit()");
+ AutoMutex _l(gContextMutex);
+
+ auto halRev = static_cast<HalRevision>(jHalRev);
+
+ auto ctx = new TunerCallbackContext();
+ ctx->mNativeCallback = new NativeCallback(env, jTuner, obj, halRev);
+
+ static_assert(sizeof(jlong) >= sizeof(ctx), "jlong is smaller than a pointer");
+ return reinterpret_cast<jlong>(ctx);
+}
+
+static void nativeFinalize(JNIEnv *env, jobject obj, jlong nativeContext) {
+ ALOGV("nativeFinalize()");
+ AutoMutex _l(gContextMutex);
+
+ auto ctx = reinterpret_cast<TunerCallbackContext*>(nativeContext);
+ delete ctx;
+}
+
+static void nativeDetach(JNIEnv *env, jobject obj, jlong nativeContext) {
+ ALOGV("nativeDetach()");
+ AutoMutex _l(gContextMutex);
+ auto& ctx = getNativeContext(nativeContext);
+
+ if (ctx.mNativeCallback == nullptr) return;
+ ctx.mNativeCallback->detach();
+ ctx.mNativeCallback = nullptr;
+}
+
+sp<ITunerCallback> getNativeCallback(JNIEnv *env, jobject jTunerCallback) {
+ AutoMutex _l(gContextMutex);
+ auto& ctx = getNativeContext(env, jTunerCallback);
+ return ctx.mNativeCallback;
+}
+
+static const JNINativeMethod gTunerCallbackMethods[] = {
+ { "nativeInit", "(Lcom/android/server/radio/Tuner;I)J", (void*)nativeInit },
+ { "nativeFinalize", "(J)V", (void*)nativeFinalize },
+ { "nativeDetach", "(J)V", (void*)nativeDetach },
+};
+
+} // namespace TunerCallback
+} // namespace radio
+} // namespace server
+
+void register_android_server_radio_TunerCallback(JavaVM *vm, JNIEnv *env) {
+ using namespace server::radio::TunerCallback;
+
+ gvm = vm;
+
+ auto tunerCbClass = FindClassOrDie(env, "com/android/server/radio/TunerCallback");
+ gjni.TunerCallback.clazz = MakeGlobalRefOrDie(env, tunerCbClass);
+ gjni.TunerCallback.nativeContext = GetFieldIDOrDie(env, tunerCbClass, "mNativeContext", "J");
+ gjni.TunerCallback.handleHwFailure = GetMethodIDOrDie(env, tunerCbClass, "handleHwFailure", "()V");
+ gjni.TunerCallback.onError = GetMethodIDOrDie(env, tunerCbClass, "onError", "(I)V");
+ gjni.TunerCallback.onConfigurationChanged = GetMethodIDOrDie(env, tunerCbClass,
+ "onConfigurationChanged", "(Landroid/hardware/radio/RadioManager$BandConfig;)V");
+ gjni.TunerCallback.onProgramInfoChanged = GetMethodIDOrDie(env, tunerCbClass,
+ "onProgramInfoChanged", "(Landroid/hardware/radio/RadioManager$ProgramInfo;)V");
+
+ auto res = jniRegisterNativeMethods(env, "com/android/server/radio/TunerCallback",
+ gTunerCallbackMethods, NELEM(gTunerCallbackMethods));
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+}
+
+} // namespace android
diff --git a/services/core/jni/com_android_server_radio_Tuner_TunerCallback.h b/services/core/jni/com_android_server_radio_Tuner_TunerCallback.h
new file mode 100644
index 0000000..35a4d69
--- /dev/null
+++ b/services/core/jni/com_android_server_radio_Tuner_TunerCallback.h
@@ -0,0 +1,45 @@
+/**
+ * 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.
+ */
+
+// TODO(b/36863239): rename s/_Tuner_TunerCallback/_TunerCallback/, as this
+// module is no longer a part of Tuner - it's an independent java class.
+#ifndef _ANDROID_SERVER_RADIO_TUNERCALLBACK_H
+#define _ANDROID_SERVER_RADIO_TUNERCALLBACK_H
+
+#include "JavaRef.h"
+#include "NativeCallbackThread.h"
+#include "com_android_server_radio_types.h"
+
+#include <android/hardware/broadcastradio/1.1/ITunerCallback.h>
+#include <jni.h>
+
+namespace android {
+
+void register_android_server_radio_TunerCallback(JavaVM *vm, JNIEnv *env);
+
+namespace server {
+namespace radio {
+namespace TunerCallback {
+
+sp<hardware::broadcastradio::V1_1::ITunerCallback>
+getNativeCallback(JNIEnv *env, jobject jTunerCallback);
+
+} // namespace TunerCallback
+} // namespace radio
+} // namespace server
+} // namespace android
+
+#endif // _ANDROID_SERVER_RADIO_TUNERCALLBACK_H
diff --git a/services/core/jni/com_android_server_radio_convert.cpp b/services/core/jni/com_android_server_radio_convert.cpp
new file mode 100644
index 0000000..19abf8a
--- /dev/null
+++ b/services/core/jni/com_android_server_radio_convert.cpp
@@ -0,0 +1,397 @@
+/**
+ * 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.
+ */
+
+#define LOG_TAG "radio.convert.jni"
+#define LOG_NDEBUG 0
+
+#include "com_android_server_radio_convert.h"
+
+#include <core_jni_helpers.h>
+#include <utils/Log.h>
+#include <JNIHelp.h>
+
+namespace android {
+namespace server {
+namespace radio {
+namespace convert {
+
+using hardware::Return;
+using hardware::hidl_vec;
+
+using V1_0::Band;
+using V1_0::Deemphasis;
+using V1_0::Direction;
+using V1_0::MetadataType;
+using V1_0::Result;
+using V1_0::Rds;
+using V1_1::ProgramListResult;
+
+static struct {
+ struct {
+ jfieldID descriptor;
+ } BandConfig;
+ struct {
+ jclass clazz;
+ jmethodID cstor;
+ jfieldID stereo;
+ jfieldID rds;
+ jfieldID ta;
+ jfieldID af;
+ jfieldID ea;
+ } FmBandConfig;
+ struct {
+ jclass clazz;
+ jmethodID cstor;
+ jfieldID stereo;
+ } AmBandConfig;
+
+ struct {
+ jfieldID region;
+ jfieldID type;
+ jfieldID lowerLimit;
+ jfieldID upperLimit;
+ jfieldID spacing;
+ } BandDescriptor;
+
+ struct {
+ jclass clazz;
+ jmethodID cstor;
+ } ProgramInfo;
+
+ struct {
+ jclass clazz;
+ jmethodID cstor;
+ jmethodID putIntFromNative;
+ jmethodID putStringFromNative;
+ jmethodID putBitmapFromNative;
+ jmethodID putClockFromNative;
+ } RadioMetadata;
+
+ struct {
+ jclass clazz;
+ jmethodID cstor;
+ } RuntimeException;
+
+ struct {
+ jclass clazz;
+ jmethodID cstor;
+ } ParcelableException;
+} gjni;
+
+bool __ThrowIfFailedHidl(JNIEnv *env, const hardware::details::return_status &hidlResult) {
+ if (hidlResult.isOk()) return false;
+
+ ThrowParcelableRuntimeException(env, "HIDL call failed: " + hidlResult.description());
+ return true;
+}
+
+bool __ThrowIfFailed(JNIEnv *env, const Result halResult) {
+ switch (halResult) {
+ case Result::OK:
+ return false;
+ case Result::NOT_INITIALIZED:
+ ThrowParcelableRuntimeException(env, "Result::NOT_INITIALIZED");
+ return true;
+ case Result::INVALID_ARGUMENTS:
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "Result::INVALID_ARGUMENTS");
+ return true;
+ case Result::INVALID_STATE:
+ jniThrowException(env, "java/lang/IllegalStateException", "Result::INVALID_STATE");
+ return true;
+ case Result::TIMEOUT:
+ ThrowParcelableRuntimeException(env, "Result::TIMEOUT (unexpected here)");
+ return true;
+ default:
+ ThrowParcelableRuntimeException(env, "Unknown failure, result: "
+ + std::to_string(static_cast<int32_t>(halResult)));
+ return true;
+ }
+}
+
+bool __ThrowIfFailed(JNIEnv *env, const ProgramListResult halResult) {
+ switch (halResult) {
+ case ProgramListResult::NOT_READY:
+ jniThrowException(env, "java/lang/IllegalStateException", "Scan is in progress");
+ return true;
+ case ProgramListResult::NOT_STARTED:
+ jniThrowException(env, "java/lang/IllegalStateException", "Scan has not been started");
+ return true;
+ case ProgramListResult::UNAVAILABLE:
+ ThrowParcelableRuntimeException(env,
+ "ProgramListResult::UNAVAILABLE (unexpected here)");
+ return true;
+ default:
+ return __ThrowIfFailed(env, static_cast<Result>(halResult));
+ }
+}
+
+void ThrowParcelableRuntimeException(JNIEnv *env, const std::string& msg) {
+ EnvWrapper wrap(env);
+
+ auto jMsg = wrap(env->NewStringUTF(msg.c_str()));
+ auto runtimeExc = wrap(env->NewObject(gjni.RuntimeException.clazz,
+ gjni.RuntimeException.cstor, jMsg.get()));
+ auto parcelableExc = wrap(env->NewObject(gjni.ParcelableException.clazz,
+ gjni.ParcelableException.cstor, runtimeExc.get()));
+
+ auto res = env->Throw(static_cast<jthrowable>(parcelableExc.get()));
+ ALOGE_IF(res != JNI_OK, "Couldn't throw parcelable runtime exception");
+}
+
+static Rds RdsForRegion(bool rds, Region region) {
+ if (!rds) return Rds::NONE;
+
+ switch(region) {
+ case Region::ITU_1:
+ case Region::OIRT:
+ case Region::JAPAN:
+ case Region::KOREA:
+ return Rds::WORLD;
+ case Region::ITU_2:
+ return Rds::US;
+ default:
+ ALOGE("Unexpected region: %d", region);
+ return Rds::NONE;
+ }
+}
+
+static Deemphasis DeemphasisForRegion(Region region) {
+ switch(region) {
+ case Region::KOREA:
+ case Region::ITU_2:
+ return Deemphasis::D75;
+ case Region::ITU_1:
+ case Region::OIRT:
+ case Region::JAPAN:
+ return Deemphasis::D50;
+ default:
+ ALOGE("Unexpected region: %d", region);
+ return Deemphasis::D50;
+ }
+}
+
+JavaRef<jobject> BandConfigFromHal(JNIEnv *env, const V1_0::BandConfig &config, Region region) {
+ ALOGV("BandConfigFromHal()");
+ EnvWrapper wrap(env);
+
+ jint spacing = config.spacings.size() > 0 ? config.spacings[0] : 0;
+ ALOGW_IF(config.spacings.size() == 0, "No channel spacing specified");
+
+ switch (config.type) {
+ case Band::FM:
+ case Band::FM_HD: {
+ auto& fm = config.ext.fm;
+ return wrap(env->NewObject(gjni.FmBandConfig.clazz, gjni.FmBandConfig.cstor,
+ region, config.type, config.lowerLimit, config.upperLimit, spacing,
+ fm.stereo, fm.rds != Rds::NONE, fm.ta, fm.af, fm.ea));
+ }
+ case Band::AM:
+ case Band::AM_HD: {
+ auto& am = config.ext.am;
+ return wrap(env->NewObject(gjni.AmBandConfig.clazz, gjni.AmBandConfig.cstor,
+ region, config.type, config.lowerLimit, config.upperLimit, spacing,
+ am.stereo));
+ }
+ default:
+ ALOGE("Unsupported band type: %d", config.type);
+ return nullptr;
+ }
+}
+
+V1_0::BandConfig BandConfigToHal(JNIEnv *env, jobject jConfig, Region ®ion) {
+ ALOGV("BandConfigToHal()");
+ auto jDescriptor = env->GetObjectField(jConfig, gjni.BandConfig.descriptor);
+ if (jDescriptor == nullptr) {
+ ALOGE("Descriptor is missing");
+ return {};
+ }
+
+ region = static_cast<Region>(env->GetIntField(jDescriptor, gjni.BandDescriptor.region));
+
+ V1_0::BandConfig config = {};
+ config.type = static_cast<Band>(env->GetIntField(jDescriptor, gjni.BandDescriptor.type));
+ config.antennaConnected = false; // just don't set it
+ config.lowerLimit = env->GetIntField(jDescriptor, gjni.BandDescriptor.lowerLimit);
+ config.upperLimit = env->GetIntField(jDescriptor, gjni.BandDescriptor.upperLimit);
+ config.spacings = hidl_vec<uint32_t>({
+ static_cast<uint32_t>(env->GetIntField(jDescriptor, gjni.BandDescriptor.spacing))
+ });
+
+ if (env->IsInstanceOf(jConfig, gjni.FmBandConfig.clazz)) {
+ auto& fm = config.ext.fm;
+ fm.deemphasis = DeemphasisForRegion(region);
+ fm.stereo = env->GetBooleanField(jConfig, gjni.FmBandConfig.stereo);
+ fm.rds = RdsForRegion(env->GetBooleanField(jConfig, gjni.FmBandConfig.rds), region);
+ fm.ta = env->GetBooleanField(jConfig, gjni.FmBandConfig.ta);
+ fm.af = env->GetBooleanField(jConfig, gjni.FmBandConfig.af);
+ fm.ea = env->GetBooleanField(jConfig, gjni.FmBandConfig.ea);
+ } else if (env->IsInstanceOf(jConfig, gjni.AmBandConfig.clazz)) {
+ auto& am = config.ext.am;
+ am.stereo = env->GetBooleanField(jConfig, gjni.AmBandConfig.stereo);
+ } else {
+ ALOGE("Unexpected band config type");
+ return {};
+ }
+
+ return config;
+}
+
+Direction DirectionToHal(bool directionDown) {
+ return directionDown ? Direction::DOWN : Direction::UP;
+}
+
+static JavaRef<jobject> MetadataFromHal(JNIEnv *env, const hidl_vec<V1_0::MetaData> metadata) {
+ ALOGV("MetadataFromHal()");
+ EnvWrapper wrap(env);
+
+ if (metadata.size() == 0) return nullptr;
+
+ auto jMetadata = wrap(env->NewObject(gjni.RadioMetadata.clazz, gjni.RadioMetadata.cstor));
+
+ for (auto& item : metadata) {
+ jint key = static_cast<jint>(item.key);
+ jint status = 0;
+ switch (item.type) {
+ case MetadataType::INT:
+ ALOGV("metadata INT %d", key);
+ status = env->CallIntMethod(jMetadata.get(), gjni.RadioMetadata.putIntFromNative,
+ key, item.intValue);
+ break;
+ case MetadataType::TEXT: {
+ ALOGV("metadata TEXT %d", key);
+ auto value = wrap(env->NewStringUTF(item.stringValue.c_str()));
+ status = env->CallIntMethod(jMetadata.get(), gjni.RadioMetadata.putStringFromNative,
+ key, value.get());
+ break;
+ }
+ case MetadataType::RAW: {
+ ALOGV("metadata RAW %d", key);
+ auto len = item.rawValue.size();
+ if (len == 0) break;
+ auto value = wrap(env->NewByteArray(len));
+ if (value == nullptr) {
+ ALOGE("Failed to allocate byte array of len %zu", len);
+ break;
+ }
+ env->SetByteArrayRegion(value.get(), 0, len,
+ reinterpret_cast<const jbyte*>(item.rawValue.data()));
+ status = env->CallIntMethod(jMetadata.get(), gjni.RadioMetadata.putBitmapFromNative,
+ key, value.get());
+ break;
+ }
+ case MetadataType::CLOCK:
+ ALOGV("metadata CLOCK %d", key);
+ status = env->CallIntMethod(jMetadata.get(), gjni.RadioMetadata.putClockFromNative,
+ key, item.clockValue.utcSecondsSinceEpoch,
+ item.clockValue.timezoneOffsetInMinutes);
+ break;
+ default:
+ ALOGW("invalid metadata type %d", item.type);
+ }
+ ALOGE_IF(status != 0, "Failed inserting metadata %d (of type %d)", key, item.type);
+ }
+
+ return jMetadata;
+}
+
+static JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_0::ProgramInfo &info10,
+ const V1_1::ProgramInfo *info11) {
+ ALOGV("ProgramInfoFromHal()");
+ EnvWrapper wrap(env);
+
+ auto jMetadata = MetadataFromHal(env, info10.metadata);
+ auto jVendorExtension = info11 ?
+ wrap(env->NewStringUTF(info11->vendorExension.c_str())) : nullptr;
+
+ return wrap(env->NewObject(gjni.ProgramInfo.clazz, gjni.ProgramInfo.cstor, info10.channel,
+ info10.subChannel, info10.tuned, info10.stereo, info10.digital, info10.signalStrength,
+ jMetadata.get(), info11 ? info11->flags : 0, jVendorExtension.get()));
+}
+
+JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_0::ProgramInfo &info) {
+ return ProgramInfoFromHal(env, info, nullptr);
+}
+
+JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_1::ProgramInfo &info) {
+ return ProgramInfoFromHal(env, info.base, &info);
+}
+
+} // namespace convert
+} // namespace radio
+} // namespace server
+
+void register_android_server_radio_convert(JNIEnv *env) {
+ using namespace server::radio::convert;
+
+ auto bandConfigClass = FindClassOrDie(env, "android/hardware/radio/RadioManager$BandConfig");
+ gjni.BandConfig.descriptor = GetFieldIDOrDie(env, bandConfigClass,
+ "mDescriptor", "Landroid/hardware/radio/RadioManager$BandDescriptor;");
+
+ auto fmBandConfigClass = FindClassOrDie(env,
+ "android/hardware/radio/RadioManager$FmBandConfig");
+ gjni.FmBandConfig.clazz = MakeGlobalRefOrDie(env, fmBandConfigClass);
+ gjni.FmBandConfig.cstor = GetMethodIDOrDie(env, fmBandConfigClass,
+ "<init>", "(IIIIIZZZZZ)V");
+ gjni.FmBandConfig.stereo = GetFieldIDOrDie(env, fmBandConfigClass, "mStereo", "Z");
+ gjni.FmBandConfig.rds = GetFieldIDOrDie(env, fmBandConfigClass, "mRds", "Z");
+ gjni.FmBandConfig.ta = GetFieldIDOrDie(env, fmBandConfigClass, "mTa", "Z");
+ gjni.FmBandConfig.af = GetFieldIDOrDie(env, fmBandConfigClass, "mAf", "Z");
+ gjni.FmBandConfig.ea = GetFieldIDOrDie(env, fmBandConfigClass, "mEa", "Z");
+
+ auto amBandConfigClass = FindClassOrDie(env,
+ "android/hardware/radio/RadioManager$AmBandConfig");
+ gjni.AmBandConfig.clazz = MakeGlobalRefOrDie(env, amBandConfigClass);
+ gjni.AmBandConfig.cstor = GetMethodIDOrDie(env, amBandConfigClass, "<init>", "(IIIIIZ)V");
+ gjni.AmBandConfig.stereo = GetFieldIDOrDie(env, amBandConfigClass, "mStereo", "Z");
+
+ auto bandDescriptorClass = FindClassOrDie(env,
+ "android/hardware/radio/RadioManager$BandDescriptor");
+ gjni.BandDescriptor.region = GetFieldIDOrDie(env, bandDescriptorClass, "mRegion", "I");
+ gjni.BandDescriptor.type = GetFieldIDOrDie(env, bandDescriptorClass, "mType", "I");
+ gjni.BandDescriptor.lowerLimit = GetFieldIDOrDie(env, bandDescriptorClass, "mLowerLimit", "I");
+ gjni.BandDescriptor.upperLimit = GetFieldIDOrDie(env, bandDescriptorClass, "mUpperLimit", "I");
+ gjni.BandDescriptor.spacing = GetFieldIDOrDie(env, bandDescriptorClass, "mSpacing", "I");
+
+ auto programInfoClass = FindClassOrDie(env, "android/hardware/radio/RadioManager$ProgramInfo");
+ gjni.ProgramInfo.clazz = MakeGlobalRefOrDie(env, programInfoClass);
+ gjni.ProgramInfo.cstor = GetMethodIDOrDie(env, programInfoClass, "<init>",
+ "(IIZZZILandroid/hardware/radio/RadioMetadata;ILjava/lang/String;)V");
+
+ auto radioMetadataClass = FindClassOrDie(env, "android/hardware/radio/RadioMetadata");
+ gjni.RadioMetadata.clazz = MakeGlobalRefOrDie(env, radioMetadataClass);
+ gjni.RadioMetadata.cstor = GetMethodIDOrDie(env, radioMetadataClass, "<init>", "()V");
+ gjni.RadioMetadata.putIntFromNative = GetMethodIDOrDie(env, radioMetadataClass,
+ "putIntFromNative", "(II)I");
+ gjni.RadioMetadata.putStringFromNative = GetMethodIDOrDie(env, radioMetadataClass,
+ "putStringFromNative", "(ILjava/lang/String;)I");
+ gjni.RadioMetadata.putBitmapFromNative = GetMethodIDOrDie(env, radioMetadataClass,
+ "putBitmapFromNative", "(I[B)I");
+ gjni.RadioMetadata.putClockFromNative = GetMethodIDOrDie(env, radioMetadataClass,
+ "putClockFromNative", "(IJI)I");
+
+ auto runtimeExcClass = FindClassOrDie(env, "java/lang/RuntimeException");
+ gjni.RuntimeException.clazz = MakeGlobalRefOrDie(env, runtimeExcClass);
+ gjni.RuntimeException.cstor = GetMethodIDOrDie(env, runtimeExcClass, "<init>",
+ "(Ljava/lang/String;)V");
+
+ auto parcelableExcClass = FindClassOrDie(env, "android/os/ParcelableException");
+ gjni.ParcelableException.clazz = MakeGlobalRefOrDie(env, parcelableExcClass);
+ gjni.ParcelableException.cstor = GetMethodIDOrDie(env, parcelableExcClass, "<init>",
+ "(Ljava/lang/Throwable;)V");
+}
+
+} // namespace android
diff --git a/services/core/jni/com_android_server_radio_convert.h b/services/core/jni/com_android_server_radio_convert.h
new file mode 100644
index 0000000..6f6774b
--- /dev/null
+++ b/services/core/jni/com_android_server_radio_convert.h
@@ -0,0 +1,69 @@
+/**
+ * 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.
+ */
+
+#ifndef _ANDROID_SERVER_RADIO_CONVERT_H
+#define _ANDROID_SERVER_RADIO_CONVERT_H
+
+#include "JavaRef.h"
+#include "com_android_server_radio_types.h"
+
+#include <android/hardware/broadcastradio/1.1/types.h>
+#include <jni.h>
+
+namespace android {
+
+void register_android_server_radio_convert(JNIEnv *env);
+
+namespace server {
+namespace radio {
+namespace convert {
+
+namespace V1_0 = hardware::broadcastradio::V1_0;
+namespace V1_1 = hardware::broadcastradio::V1_1;
+
+JavaRef<jobject> BandConfigFromHal(JNIEnv *env, const V1_0::BandConfig &config, Region region);
+V1_0::BandConfig BandConfigToHal(JNIEnv *env, jobject jConfig, Region ®ion);
+
+V1_0::Direction DirectionToHal(bool directionDown);
+
+JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_0::ProgramInfo &info);
+JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_1::ProgramInfo &info);
+
+
+void ThrowParcelableRuntimeException(JNIEnv *env, const std::string& msg);
+
+// These three are only for internal use by template functions below.
+bool __ThrowIfFailedHidl(JNIEnv *env,
+ const hardware::details::return_status &hidlResult);
+bool __ThrowIfFailed(JNIEnv *env, const V1_0::Result halResult);
+bool __ThrowIfFailed(JNIEnv *env, const V1_1::ProgramListResult halResult);
+
+template <typename T>
+bool ThrowIfFailed(JNIEnv *env, const hardware::Return<void> &hidlResult, const T halResult) {
+ return __ThrowIfFailedHidl(env, hidlResult) || __ThrowIfFailed(env, halResult);
+}
+
+template <typename T>
+bool ThrowIfFailed(JNIEnv *env, const hardware::Return<T> &hidlResult) {
+ return __ThrowIfFailedHidl(env, hidlResult) || __ThrowIfFailed(env, static_cast<T>(hidlResult));
+}
+
+} // namespace convert
+} // namespace radio
+} // namespace server
+} // namespace android
+
+#endif // _ANDROID_SERVER_RADIO_CONVERT_H
diff --git a/services/core/jni/com_android_server_radio_types.h b/services/core/jni/com_android_server_radio_types.h
new file mode 100644
index 0000000..36f7643
--- /dev/null
+++ b/services/core/jni/com_android_server_radio_types.h
@@ -0,0 +1,60 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_SERVER_RADIO_TYPES_H
+#define _ANDROID_SERVER_RADIO_TYPES_H
+
+#include <jni.h>
+
+namespace android {
+namespace server {
+namespace radio {
+
+/* Most of these enums are dereived from Java code, based at
+ * frameworks/base/core/java/android/hardware/radio/RadioManager.java.
+ */
+
+enum class HalRevision : jint {
+ V1_0,
+ V1_1,
+};
+
+// Keep in sync with STATUS_* constants from RadioManager.java.
+enum class Status : jint {
+ OK = 0,
+ ERROR = -0x80000000ll, // Integer.MIN_VALUE
+ PERMISSION_DENIED = -1, // -EPERM
+ NO_INIT = -19, // -ENODEV
+ BAD_VALUE = -22, // -EINVAL
+ DEAD_OBJECT = -32, // -EPIPE
+ INVALID_OPERATION = -38, // -ENOSYS
+ TIMED_OUT = -110, // -ETIMEDOUT
+};
+
+// Keep in sync with REGION_* constants from RadioManager.java.
+enum class Region : jint {
+ ITU_1 = 0,
+ ITU_2 = 1,
+ OIRT = 2,
+ JAPAN = 3,
+ KOREA = 4,
+};
+
+} // namespace radio
+} // namespace server
+} // namespace android
+
+#endif // _ANDROID_SERVER_RADIO_TYPES_H
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index e96bda9..c3617e0 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -19,6 +19,9 @@
#include "utils/Log.h"
#include "utils/misc.h"
+#include "com_android_server_radio_RadioService.h"
+#include "com_android_server_radio_Tuner.h"
+
namespace android {
int register_android_server_AlarmManagerService(JNIEnv* env);
int register_android_server_BatteryStatsService(JNIEnv* env);
@@ -39,6 +42,7 @@
int register_android_server_location_ContextHubService(JNIEnv* env);
int register_android_server_location_GnssLocationProvider(JNIEnv* env);
int register_android_server_connectivity_Vpn(JNIEnv* env);
+int register_android_server_connectivity_tethering_OffloadHardwareInterface(JNIEnv*);
int register_android_server_hdmi_HdmiCecController(JNIEnv* env);
int register_android_server_tv_TvUinputBridge(JNIEnv* env);
int register_android_server_tv_TvInputHal(JNIEnv* env);
@@ -63,6 +67,8 @@
}
ALOG_ASSERT(env, "Could not retrieve the env!");
+ register_android_server_radio_RadioService(env);
+ register_android_server_radio_Tuner(vm, env);
register_android_server_PowerManagerService(env);
register_android_server_SerialService(env);
register_android_server_InputApplicationHandle(env);
@@ -79,6 +85,7 @@
register_android_server_location_ContextHubService(env);
register_android_server_location_GnssLocationProvider(env);
register_android_server_connectivity_Vpn(env);
+ register_android_server_connectivity_tethering_OffloadHardwareInterface(env);
register_android_server_ConsumerIrService(env);
register_android_server_BatteryStatsService(env);
register_android_server_hdmi_HdmiCecController(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 911bb2a..136d335 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -254,6 +254,8 @@
private static final String TAG_PASSWORD_TOKEN_HANDLE = "password-token";
+ private static final String TAG_PASSWORD_VALIDITY = "password-validity";
+
private static final int REQUEST_EXPIRE_PASSWORD = 5571;
private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1);
@@ -478,6 +480,8 @@
public static class DevicePolicyData {
@NonNull PasswordMetrics mActivePasswordMetrics = new PasswordMetrics();
int mFailedPasswordAttempts = 0;
+ boolean mPasswordStateHasBeenSetSinceBoot = false;
+ boolean mPasswordValidAtLastCheckpoint = false;
int mUserHandle;
int mPasswordOwner = -1;
@@ -2574,19 +2578,14 @@
out.endTag(null, "failed-password-attempts");
}
- // Don't save metrics for FBE devices
- final PasswordMetrics metrics = policy.mActivePasswordMetrics;
- if (!mInjector.storageManagerIsFileBasedEncryptionEnabled() && !metrics.isDefault()) {
- out.startTag(null, "active-password");
- out.attribute(null, "quality", Integer.toString(metrics.quality));
- out.attribute(null, "length", Integer.toString(metrics.length));
- out.attribute(null, "uppercase", Integer.toString(metrics.upperCase));
- out.attribute(null, "lowercase", Integer.toString(metrics.lowerCase));
- out.attribute(null, "letters", Integer.toString(metrics.letters));
- out.attribute(null, "numeric", Integer.toString(metrics.numeric));
- out.attribute(null, "symbols", Integer.toString(metrics.symbols));
- out.attribute(null, "nonletter", Integer.toString(metrics.nonLetter));
- out.endTag(null, "active-password");
+ // For FDE devices only, we save this flag so we can report on password sufficiency
+ // before the user enters their password for the first time after a reboot. For
+ // security reasons, we don't want to store the full set of active password metrics.
+ if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
+ out.startTag(null, TAG_PASSWORD_VALIDITY);
+ out.attribute(null, ATTR_VALUE,
+ Boolean.toString(policy.mPasswordValidAtLastCheckpoint));
+ out.endTag(null, TAG_PASSWORD_VALIDITY);
}
for (int i = 0; i < policy.mAcceptedCaCertificates.size(); i++) {
@@ -2864,19 +2863,14 @@
} else if (TAG_INITIALIZATION_BUNDLE.equals(tag)) {
policy.mInitBundle = PersistableBundle.restoreFromXml(parser);
} else if ("active-password".equals(tag)) {
- if (mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
- // Remove this from FBE devices
- needsRewrite = true;
- } else {
- final PasswordMetrics m = policy.mActivePasswordMetrics;
- m.quality = Integer.parseInt(parser.getAttributeValue(null, "quality"));
- m.length = Integer.parseInt(parser.getAttributeValue(null, "length"));
- m.upperCase = Integer.parseInt(parser.getAttributeValue(null, "uppercase"));
- m.lowerCase = Integer.parseInt(parser.getAttributeValue(null, "lowercase"));
- m.letters = Integer.parseInt(parser.getAttributeValue(null, "letters"));
- m.numeric = Integer.parseInt(parser.getAttributeValue(null, "numeric"));
- m.symbols = Integer.parseInt(parser.getAttributeValue(null, "symbols"));
- m.nonLetter = Integer.parseInt(parser.getAttributeValue(null, "nonletter"));
+ // Remove password metrics from saved settings, as we no longer wish to store
+ // these on disk
+ needsRewrite = true;
+ } else if (TAG_PASSWORD_VALIDITY.equals(tag)) {
+ if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
+ // This flag is only used for FDE devices
+ policy.mPasswordValidAtLastCheckpoint = Boolean.parseBoolean(
+ parser.getAttributeValue(null, ATTR_VALUE));
}
} else if (TAG_PASSWORD_TOKEN_HANDLE.equals(tag)) {
policy.mPasswordTokenHandle = Long.parseLong(
@@ -3453,11 +3447,24 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
if (ap.minimumPasswordMetrics.quality != quality) {
ap.minimumPasswordMetrics.quality = quality;
+ updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId());
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
}
+ /**
+ * Updates flag in memory that tells us whether the user's password currently satisfies the
+ * requirements set by all of the user's active admins. This should be called before
+ * {@link #saveSettingsLocked} whenever the password or the admin policies have changed.
+ */
+ @GuardedBy("DevicePolicyManagerService.this")
+ private void updatePasswordValidityCheckpointLocked(int userHandle) {
+ DevicePolicyData policy = getUserData(userHandle);
+ policy.mPasswordValidAtLastCheckpoint = isActivePasswordSufficientForUserLocked(
+ policy, policy.mUserHandle, false);
+ }
+
@Override
public int getPasswordQuality(ComponentName who, int userHandle, boolean parent) {
if (!mHasFeature) {
@@ -3540,6 +3547,7 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
if (ap.minimumPasswordMetrics.length != length) {
ap.minimumPasswordMetrics.length = length;
+ updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId());
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
@@ -3584,6 +3592,7 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
if (ap.passwordHistoryLength != length) {
ap.passwordHistoryLength = length;
+ updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId());
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
@@ -3796,6 +3805,7 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
if (ap.minimumPasswordMetrics.upperCase != length) {
ap.minimumPasswordMetrics.upperCase = length;
+ updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId());
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
@@ -3837,6 +3847,7 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
if (ap.minimumPasswordMetrics.lowerCase != length) {
ap.minimumPasswordMetrics.lowerCase = length;
+ updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId());
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
@@ -3881,6 +3892,7 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
if (ap.minimumPasswordMetrics.letters != length) {
ap.minimumPasswordMetrics.letters = length;
+ updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId());
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
@@ -3928,6 +3940,7 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
if (ap.minimumPasswordMetrics.numeric != length) {
ap.minimumPasswordMetrics.numeric = length;
+ updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId());
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
@@ -3975,6 +3988,7 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
if (ap.minimumPasswordMetrics.symbols != length) {
ap.minimumPasswordMetrics.symbols = length;
+ updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId());
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
@@ -4022,6 +4036,7 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
if (ap.minimumPasswordMetrics.nonLetter != length) {
ap.minimumPasswordMetrics.nonLetter = length;
+ updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId());
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
@@ -4093,6 +4108,16 @@
DevicePolicyData policy, int userHandle, boolean parent) {
enforceUserUnlocked(userHandle, parent);
+ if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()
+ && !policy.mPasswordStateHasBeenSetSinceBoot) {
+ // Before user enters their password for the first time after a reboot, return the
+ // value of this flag, which tells us whether the password was valid the last time
+ // settings were saved. If DPC changes password requirements on boot so that the
+ // current password no longer meets the requirements, this value will be stale until
+ // the next time the password is entered.
+ return policy.mPasswordValidAtLastCheckpoint;
+ }
+
final int requiredPasswordQuality = getPasswordQuality(null, userHandle, parent);
if (policy.mActivePasswordMetrics.quality < requiredPasswordQuality) {
return false;
@@ -5436,6 +5461,7 @@
DevicePolicyData policy = getUserData(userHandle);
synchronized (this) {
policy.mActivePasswordMetrics = metrics;
+ policy.mPasswordStateHasBeenSetSinceBoot = true;
}
}
@@ -5460,6 +5486,7 @@
try {
synchronized (this) {
policy.mFailedPasswordAttempts = 0;
+ updatePasswordValidityCheckpointLocked(userId);
saveSettingsLocked(userId);
updatePasswordExpirationsLocked(userId);
setExpirationAlarmCheckLocked(mContext, userId, /* parent */ false);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 6f53099..272c11b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -63,6 +63,7 @@
import com.android.server.am.ActivityManagerService;
import com.android.server.audio.AudioService;
import com.android.server.camera.CameraServiceProxy;
+import com.android.server.car.CarServiceHelperService;
import com.android.server.clipboard.ClipboardService;
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.coverage.CoverageService;
@@ -83,6 +84,7 @@
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
import com.android.server.notification.NotificationManagerService;
+import com.android.server.oemlock.OemLockService;
import com.android.server.om.OverlayManagerService;
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.SchedulingPolicyService;
@@ -96,6 +98,7 @@
import com.android.server.policy.PhoneWindowManager;
import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
+import com.android.server.radio.RadioService;
import com.android.server.restrictions.RestrictionsManagerService;
import com.android.server.retaildemo.RetailDemoModeService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
@@ -174,7 +177,7 @@
private static final String JOB_SCHEDULER_SERVICE_CLASS =
"com.android.server.job.JobSchedulerService";
private static final String LOCK_SETTINGS_SERVICE_CLASS =
- "com.android.server.LockSettingsService$Lifecycle";
+ "com.android.server.locksettings.LockSettingsService$Lifecycle";
private static final String STORAGE_MANAGER_SERVICE_CLASS =
"com.android.server.StorageManagerService$Lifecycle";
private static final String STORAGE_STATS_SERVICE_CLASS =
@@ -197,6 +200,8 @@
"com.android.server.wallpaper.WallpaperManagerService$Lifecycle";
private static final String AUTO_FILL_MANAGER_SERVICE_CLASS =
"com.android.server.autofill.AutofillManagerService";
+ private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS =
+ "com.android.server.timezone.RulesManagerService$Lifecycle";
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
@@ -711,6 +716,8 @@
boolean disableVrManager = SystemProperties.getBoolean("config.disable_vrmanager", false);
boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
false);
+ // TODO(b/36863239): Remove when transitioned from native service.
+ boolean enableRadioService = SystemProperties.getBoolean("config.enable_java_radio", false);
boolean isEmulator = SystemProperties.get("ro.kernel.qemu").equals("1");
@@ -977,12 +984,15 @@
}
traceEnd();
- if (!SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals("")) {
+ final boolean hasPdb = !SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals("");
+ if (hasPdb) {
traceBeginAndSlog("StartPersistentDataBlock");
mSystemServiceManager.startService(PersistentDataBlockService.class);
traceEnd();
+ }
- // Implementation depends on persistent data block
+ if (hasPdb || OemLockService.isHalPresent()) {
+ // Implementation depends on pdb or the OemLock HAL
traceBeginAndSlog("StartOemLockService");
mSystemServiceManager.startService(OemLockService.class);
traceEnd();
@@ -1197,10 +1207,24 @@
traceEnd();
}
+ if (!disableNonCoreServices && context.getResources().getBoolean(
+ R.bool.config_enableUpdateableTimeZoneRules)) {
+ traceBeginAndSlog("StartTimeZoneRulesManagerService");
+ mSystemServiceManager.startService(TIME_ZONE_RULES_MANAGER_SERVICE_CLASS);
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+
traceBeginAndSlog("StartAudioService");
mSystemServiceManager.startService(AudioService.Lifecycle.class);
traceEnd();
+ if (enableRadioService &&
+ mPackageManager.hasSystemFeature(PackageManager.FEATURE_RADIO)) {
+ traceBeginAndSlog("StartRadioService");
+ mSystemServiceManager.startService(RadioService.class);
+ traceEnd();
+ }
+
if (!disableNonCoreServices) {
traceBeginAndSlog("StartDockObserver");
mSystemServiceManager.startService(DockObserver.class);
@@ -1674,6 +1698,12 @@
}, WEBVIEW_PREPARATION);
}
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ traceBeginAndSlog("StartCarServiceHelperService");
+ mSystemServiceManager.startService(CarServiceHelperService.class);
+ traceEnd();
+ }
+
traceBeginAndSlog("StartSystemUI");
try {
startSystemUi(context, windowManagerF);
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 100e459..51478b3 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -325,9 +325,6 @@
}
IBinder binder = server.asBinder();
try {
- if (mDeviceInfo == null) {
- mDeviceInfo = server.getDeviceInfo();
- }
binder.linkToDeath(this, 0);
mServer = server;
} catch (RemoteException e) {
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index a0a9310..e4c6690 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -18,6 +18,14 @@
import static android.system.OsConstants.*;
+import static com.android.internal.util.BitUtils.bytesToBEInt;
+import static com.android.internal.util.BitUtils.getUint16;
+import static com.android.internal.util.BitUtils.getUint32;
+import static com.android.internal.util.BitUtils.getUint8;
+import static com.android.internal.util.BitUtils.uint16;
+import static com.android.internal.util.BitUtils.uint32;
+import static com.android.internal.util.BitUtils.uint8;
+
import android.os.SystemClock;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -183,6 +191,11 @@
private static final int IPV4_ANY_HOST_ADDRESS = 0;
private static final int IPV4_BROADCAST_ADDRESS = -1; // 255.255.255.255
+ // Traffic class and Flow label are not byte aligned. Luckily we
+ // don't care about either value so we'll consider bytes 1-3 of the
+ // IPv6 header as don't care.
+ private static final int IPV6_FLOW_LABEL_OFFSET = ETH_HEADER_LEN + 1;
+ private static final int IPV6_FLOW_LABEL_LEN = 3;
private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8;
private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
@@ -465,8 +478,13 @@
RaEvent.Builder builder = new RaEvent.Builder();
- // Ignore the checksum.
+ // Ignore the flow label and low 4 bits of traffic class.
int lastNonLifetimeStart = addNonLifetime(0,
+ IPV6_FLOW_LABEL_OFFSET,
+ IPV6_FLOW_LABEL_LEN);
+
+ // Ignore the checksum.
+ lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
ICMP6_RA_CHECKSUM_OFFSET,
ICMP6_RA_CHECKSUM_LEN);
@@ -557,9 +575,14 @@
for (int i = 0; (i + 1) < mNonLifetimes.size(); i++) {
int offset = mNonLifetimes.get(i).first + mNonLifetimes.get(i).second;
+ // The flow label is in mNonLifetimes, but it's not a lifetime.
+ if (offset == IPV6_FLOW_LABEL_OFFSET) {
+ continue;
+ }
+
// The checksum is in mNonLifetimes, but it's not a lifetime.
if (offset == ICMP6_RA_CHECKSUM_OFFSET) {
- continue;
+ continue;
}
final int lifetimeLength = mNonLifetimes.get(i+1).first - offset;
@@ -621,6 +644,11 @@
if ((i + 1) < mNonLifetimes.size()) {
Pair<Integer, Integer> nextNonLifetime = mNonLifetimes.get(i + 1);
int offset = nonLifetime.first + nonLifetime.second;
+
+ // Skip the Flow label.
+ if (offset == IPV6_FLOW_LABEL_OFFSET) {
+ continue;
+ }
// Skip the checksum.
if (offset == ICMP6_RA_CHECKSUM_OFFSET) {
continue;
@@ -1185,41 +1213,9 @@
}
}
- private static int uint8(byte b) {
- return b & 0xff;
- }
-
- private static int uint16(short s) {
- return s & 0xffff;
- }
-
- private static long uint32(int i) {
- return i & 0xffffffffL;
- }
-
- private static int getUint8(ByteBuffer buffer, int position) {
- return uint8(buffer.get(position));
- }
-
- private static int getUint16(ByteBuffer buffer, int position) {
- return uint16(buffer.getShort(position));
- }
-
- private static long getUint32(ByteBuffer buffer, int position) {
- return uint32(buffer.getInt(position));
- }
-
// TODO: move to android.net.NetworkUtils
@VisibleForTesting
public static int ipv4BroadcastAddress(byte[] addrBytes, int prefixLength) {
- return bytesToInt(addrBytes) | (int) (uint32(-1) >>> prefixLength);
- }
-
- @VisibleForTesting
- public static int bytesToInt(byte[] addrBytes) {
- return (uint8(addrBytes[0]) << 24)
- + (uint8(addrBytes[1]) << 16)
- + (uint8(addrBytes[2]) << 8)
- + (uint8(addrBytes[3]));
+ return bytesToBEInt(addrBytes) | (int) (uint32(-1) >>> prefixLength);
}
}
diff --git a/services/tests/notification/AndroidTest.xml b/services/tests/notification/AndroidTest.xml
index 46fdccc..fa77407 100644
--- a/services/tests/notification/AndroidTest.xml
+++ b/services/tests/notification/AndroidTest.xml
@@ -19,8 +19,9 @@
</target_preparer>
<option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
<option name="test-tag" value="FrameworksNotificationTests" />
- <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.frameworks.tests.notification" />
<option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
</test>
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index bd6e379..0a4cb10 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -56,6 +56,7 @@
import android.provider.Settings.Secure;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
+import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -73,6 +74,7 @@
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
+@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class NotificationManagerServiceTest extends NotificationTestCase {
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index d47a67c..2c3283c 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -15,6 +15,7 @@
frameworks-base-testutils \
services.accessibility \
services.appwidget \
+ services.backup \
services.core \
services.devicepolicy \
services.net \
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index 4622d50..63f3b75 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -20,7 +20,7 @@
<option name="test-suite-tag" value="apct" />
<option name="test-tag" value="FrameworksServicesTests" />
- <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.frameworks.servicestests" />
<option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
</test>
diff --git a/services/tests/servicestests/res/raw/backup_file_with_long_name b/services/tests/servicestests/res/raw/backup_file_with_long_name
new file mode 100644
index 0000000..265a0ba
--- /dev/null
+++ b/services/tests/servicestests/res/raw/backup_file_with_long_name
Binary files differ
diff --git a/services/tests/servicestests/res/raw/backup_telephony_no_password b/services/tests/servicestests/res/raw/backup_telephony_no_password
new file mode 100644
index 0000000..12b6cbf
--- /dev/null
+++ b/services/tests/servicestests/res/raw/backup_telephony_no_password
Binary files differ
diff --git a/services/tests/servicestests/res/raw/backup_telephony_with_password b/services/tests/servicestests/res/raw/backup_telephony_with_password
new file mode 100644
index 0000000..ce847a3
--- /dev/null
+++ b/services/tests/servicestests/res/raw/backup_telephony_with_password
Binary files differ
diff --git a/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java
deleted file mode 100644
index d9d06ae..0000000
--- a/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.app.IActivityManager;
-import android.app.NotificationManager;
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-import android.content.pm.UserInfo;
-import android.os.FileUtils;
-import android.os.IProgressListener;
-import android.os.UserManager;
-import android.os.storage.StorageManager;
-import android.security.KeyStore;
-import android.test.AndroidTestCase;
-
-import com.android.internal.widget.LockPatternUtils;
-
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-
-public class BaseLockSettingsServiceTests extends AndroidTestCase {
- protected static final int PRIMARY_USER_ID = 0;
- protected static final int MANAGED_PROFILE_USER_ID = 12;
- protected static final int TURNED_OFF_PROFILE_USER_ID = 17;
- protected static final int SECONDARY_USER_ID = 20;
-
- private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER_ID, null, null,
- UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
- private static final UserInfo SECONDARY_USER_INFO = new UserInfo(SECONDARY_USER_ID, null, null,
- UserInfo.FLAG_INITIALIZED);
-
- private ArrayList<UserInfo> mPrimaryUserProfiles = new ArrayList<>();
-
- LockSettingsService mService;
-
- MockLockSettingsContext mContext;
- LockSettingsStorageTestable mStorage;
-
- LockPatternUtils mLockPatternUtils;
- MockGateKeeperService mGateKeeperService;
- NotificationManager mNotificationManager;
- UserManager mUserManager;
- MockStorageManager mStorageManager;
- IActivityManager mActivityManager;
- DevicePolicyManager mDevicePolicyManager;
- KeyStore mKeyStore;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mLockPatternUtils = mock(LockPatternUtils.class);
- mGateKeeperService = new MockGateKeeperService();
- mNotificationManager = mock(NotificationManager.class);
- mUserManager = mock(UserManager.class);
- mStorageManager = new MockStorageManager();
- mActivityManager = mock(IActivityManager.class);
- mDevicePolicyManager = mock(DevicePolicyManager.class);
- mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager,
- mDevicePolicyManager, mock(StorageManager.class));
- mStorage = new LockSettingsStorageTestable(mContext,
- new File(getContext().getFilesDir(), "locksettings"));
- File storageDir = mStorage.mStorageDir;
- if (storageDir.exists()) {
- FileUtils.deleteContents(storageDir);
- } else {
- storageDir.mkdirs();
- }
-
- mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils,
- mStorage, mGateKeeperService, mKeyStore, mStorageManager, mActivityManager);
- when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
- mPrimaryUserProfiles.add(PRIMARY_USER_INFO);
- installChildProfile(MANAGED_PROFILE_USER_ID);
- installAndTurnOffChildProfile(TURNED_OFF_PROFILE_USER_ID);
- when(mUserManager.getProfiles(eq(PRIMARY_USER_ID))).thenReturn(mPrimaryUserProfiles);
- when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(SECONDARY_USER_INFO);
-
- when(mActivityManager.unlockUser(anyInt(), any(), any(), any())).thenAnswer(
- new Answer<Boolean>() {
- @Override
- public Boolean answer(InvocationOnMock invocation) throws Throwable {
- Object[] args = invocation.getArguments();
- mStorageManager.unlockUser((int)args[0], (byte[])args[2],
- (IProgressListener) args[3]);
- return true;
- }
- });
-
- when(mLockPatternUtils.getLockSettings()).thenReturn(mService);
-
- // Adding a fake Device Owner app which will enable escrow token support in LSS.
- when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(
- new ComponentName("com.dummy.package", ".FakeDeviceOwner"));
- }
-
- private UserInfo installChildProfile(int profileId) {
- final UserInfo userInfo = new UserInfo(
- profileId, null, null, UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE);
- mPrimaryUserProfiles.add(userInfo);
- when(mUserManager.getUserInfo(eq(profileId))).thenReturn(userInfo);
- when(mUserManager.getProfileParent(eq(profileId))).thenReturn(PRIMARY_USER_INFO);
- when(mUserManager.isUserRunning(eq(profileId))).thenReturn(true);
- when(mUserManager.isUserUnlocked(eq(profileId))).thenReturn(true);
- return userInfo;
- }
-
- private UserInfo installAndTurnOffChildProfile(int profileId) {
- final UserInfo userInfo = installChildProfile(profileId);
- userInfo.flags |= UserInfo.FLAG_QUIET_MODE;
- when(mUserManager.isUserRunning(eq(profileId))).thenReturn(false);
- when(mUserManager.isUserUnlocked(eq(profileId))).thenReturn(false);
- return userInfo;
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- mStorage.closeDatabase();
- File db = getContext().getDatabasePath("locksettings.db");
- assertTrue(!db.exists() || db.delete());
-
- File storageDir = mStorage.mStorageDir;
- assertTrue(FileUtils.deleteContents(storageDir));
- }
-
- protected static void assertArrayEquals(byte[] expected, byte[] actual) {
- assertTrue(Arrays.equals(expected, actual));
- }
-
- protected static void assertArrayNotSame(byte[] expected, byte[] actual) {
- assertFalse(Arrays.equals(expected, actual));
- }
-}
-
diff --git a/services/tests/servicestests/src/com/android/server/BootReceiverFixFsckFsStatTest.java b/services/tests/servicestests/src/com/android/server/BootReceiverFixFsckFsStatTest.java
index 362c47a..69c1499 100644
--- a/services/tests/servicestests/src/com/android/server/BootReceiverFixFsckFsStatTest.java
+++ b/services/tests/servicestests/src/com/android/server/BootReceiverFixFsckFsStatTest.java
@@ -52,13 +52,13 @@
" ",
"/dev/block/platform/soc/624000.ufshc/by-name/userdata: ***** FILE SYSTEM WAS MODIFIED *****"
};
- doTestFsckFsStat(logs, 0x405, 5, 0, logs.length);
+ doTestFsckFsStat(logs, 0x405, 0x5, 0, logs.length);
final String[] doubleLogs = new String[logs.length * 2];
System.arraycopy(logs, 0, doubleLogs, 0, logs.length);
System.arraycopy(logs, 0, doubleLogs, logs.length, logs.length);
- doTestFsckFsStat(doubleLogs, 0x401, 1, 0, logs.length);
- doTestFsckFsStat(doubleLogs, 0x402, 2, logs.length, logs.length * 2);
+ doTestFsckFsStat(doubleLogs, 0x401, 0x1, 0, logs.length);
+ doTestFsckFsStat(doubleLogs, 0x402, 0x2, logs.length, logs.length * 2);
}
@Test
@@ -79,6 +79,7 @@
" ",
"/dev/block/platform/soc/624000.ufshc/by-name/userdata: ***** FILE SYSTEM WAS MODIFIED *****"
};
+ // quota fix without tree optimization is an error.
doTestFsckFsStat(logs, 0x405, 0x405, 0, logs.length);
}
@@ -103,6 +104,92 @@
doTestFsckFsStat(logs, 0x405, 0x405, 0, logs.length);
}
+ @Test
+ public void testTimestampAdjustment() {
+ final String[] logs = {
+ "e2fsck 1.43.3 (04-Sep-2016)",
+ "Pass 1: Checking inodes, blocks, and sizes",
+ "Timestamp(s) on inode 508580 beyond 2310-04-04 are likely pre-1970.",
+ "Fix? yes",
+ " ",
+ "Pass 1E: Optimizing extent trees",
+ "Pass 2: Checking directory structure",
+ "Pass 3: Checking directory connectivity",
+ "Pass 4: Checking reference counts",
+ "Pass 5: Checking group summary information",
+ " ",
+ "/dev/block/platform/soc/624000.ufshc/by-name/userdata: ***** FILE SYSTEM WAS MODIFIED *****"
+ };
+ doTestFsckFsStat(logs, 0x405, 0x5, 0, logs.length);
+ }
+
+ @Test
+ public void testTimestampAdjustmentNoFixLine() {
+ final String[] logs = {
+ "e2fsck 1.43.3 (04-Sep-2016)",
+ "Pass 1: Checking inodes, blocks, and sizes",
+ "Timestamp(s) on inode 508580 beyond 2310-04-04 are likely pre-1970.",
+ " ",
+ "Pass 1E: Optimizing extent trees",
+ "Pass 2: Checking directory structure",
+ "Pass 3: Checking directory connectivity",
+ "Pass 4: Checking reference counts",
+ "Pass 5: Checking group summary information",
+ " ",
+ "/dev/block/platform/soc/624000.ufshc/by-name/userdata: ***** FILE SYSTEM WAS MODIFIED *****"
+ };
+ doTestFsckFsStat(logs, 0x405, 0x5, 0, logs.length);
+ }
+
+ @Test
+ public void testTimestampAdjustmentWithQuotaFix() {
+ final String[] logs = {
+ "e2fsck 1.43.3 (04-Sep-2016)",
+ "Pass 1: Checking inodes, blocks, and sizes",
+ "Timestamp(s) on inode 508580 beyond 2310-04-04 are likely pre-1970.",
+ "Fix? yes",
+ " ",
+ "Pass 1E: Optimizing extent trees",
+ "Pass 2: Checking directory structure",
+ "Pass 3: Checking directory connectivity",
+ "Pass 4: Checking reference counts",
+ "Pass 5: Checking group summary information",
+ "[QUOTA WARNING] Usage inconsistent for ID 10038:actual (71667712, 1000) != expected (71671808, 1000)",
+ "Update quota info for quota type 0? yes",
+ " ",
+ "[QUOTA WARNING] Usage inconsistent for ID 10038:actual (59555840, 953) != expected (59559936, 953)",
+ "Update quota info for quota type 1? yes",
+ " ",
+ "/dev/block/platform/soc/624000.ufshc/by-name/userdata: ***** FILE SYSTEM WAS MODIFIED *****"
+ };
+ doTestFsckFsStat(logs, 0x405, 0x405, 0, logs.length);
+ }
+
+ @Test
+ public void testAllNonFixes() {
+ final String[] logs = {
+ "e2fsck 1.43.3 (04-Sep-2016)",
+ "Pass 1: Checking inodes, blocks, and sizes",
+ "Timestamp(s) on inode 508580 beyond 2310-04-04 are likely pre-1970.",
+ "Fix? yes",
+ "Inode 877141 extent tree (at level 1) could be shorter. Fix? yes",
+ " ",
+ "Pass 1E: Optimizing extent trees",
+ "Pass 2: Checking directory structure",
+ "Pass 3: Checking directory connectivity",
+ "Pass 4: Checking reference counts",
+ "Pass 5: Checking group summary information",
+ "[QUOTA WARNING] Usage inconsistent for ID 10038:actual (71667712, 1000) != expected (71671808, 1000)",
+ "Update quota info for quota type 0? yes",
+ " ",
+ "[QUOTA WARNING] Usage inconsistent for ID 10038:actual (59555840, 953) != expected (59559936, 953)",
+ "Update quota info for quota type 1? yes",
+ " ",
+ "/dev/block/platform/soc/624000.ufshc/by-name/userdata: ***** FILE SYSTEM WAS MODIFIED *****"
+ };
+ doTestFsckFsStat(logs, 0x405, 0x5, 0, logs.length);
+ }
+
private void doTestFsckFsStat(String[] lines, int statOrg, int statUpdated, int startLineNumber,
int endLineNumber) {
assertEquals(statUpdated, BootReceiver.fixFsckFsStat(PARTITION, statOrg, lines,
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
new file mode 100644
index 0000000..33e4165
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -0,0 +1,846 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.mock.MockContentResolver;
+import android.view.KeyEvent;
+import android.util.MutableBoolean;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.LocalServices;
+import com.android.server.statusbar.StatusBarManagerInternal;
+
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link GestureLauncherService}.
+ * runtest frameworks-services -c com.android.server.GestureLauncherServiceTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GestureLauncherServiceTest {
+
+ private static final int FAKE_USER_ID = 1337;
+ private static final int FAKE_SOURCE = 1982;
+ private static final long INITIAL_EVENT_TIME_MILLIS = 20000L;
+ private static final long IGNORED_DOWN_TIME = 1234L;
+ private static final int IGNORED_ACTION = 13;
+ private static final int IGNORED_CODE = 1999;
+ private static final int IGNORED_REPEAT = 42;
+
+ private @Mock Context mContext;
+ private @Mock Resources mResources;
+ private @Mock StatusBarManagerInternal mStatusBarManagerInternal;
+ private @Mock MetricsLogger mMetricsLogger;
+ private MockContentResolver mContentResolver;
+ private GestureLauncherService mGestureLauncherService;
+
+ @BeforeClass
+ public static void oneTimeInitialization() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+ LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal);
+
+ final Context originalContext = InstrumentationRegistry.getContext();
+ when(mContext.getApplicationInfo()).thenReturn(originalContext.getApplicationInfo());
+ when(mContext.getResources()).thenReturn(mResources);
+ mContentResolver = new MockContentResolver(mContext);
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+
+ mGestureLauncherService = new GestureLauncherService(mContext, mMetricsLogger);
+ }
+
+ @Test
+ public void testIsCameraDoubleTapPowerEnabled_configFalse() {
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ assertFalse(mGestureLauncherService.isCameraDoubleTapPowerEnabled(mResources));
+ }
+
+ @Test
+ public void testIsCameraDoubleTapPowerEnabled_configTrue() {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ assertTrue(mGestureLauncherService.isCameraDoubleTapPowerEnabled(mResources));
+ }
+
+ @Test
+ public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingDisabled() {
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
+ assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingEnabled() {
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingDisabled() {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(1);
+ assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingEnabled() {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ public void testHandleCameraLaunchGesture_userSetupComplete() {
+ withUserSetupCompleteValue(true);
+
+ boolean useWakeLock = false;
+ assertTrue(mGestureLauncherService.handleCameraGesture(useWakeLock, FAKE_SOURCE));
+ verify(mStatusBarManagerInternal).onCameraLaunchGestureDetected(FAKE_SOURCE);
+ }
+
+ @Test
+ public void testHandleCameraLaunchGesture_userSetupNotComplete() {
+ withUserSetupCompleteValue(false);
+
+ boolean useWakeLock = false;
+ assertFalse(mGestureLauncherService.handleCameraGesture(useWakeLock, FAKE_SOURCE));
+ }
+
+ @Test
+ public void testInterceptPowerKeyDown_firstPowerDownCameraPowerGestureOnInteractive() {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+
+ long eventTime = INITIAL_EVENT_TIME_MILLIS +
+ GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = true;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+ verify(mMetricsLogger).histogram("power_consecutive_short_tap_count", 1);
+ verify(mMetricsLogger).histogram("power_double_tap_interval", (int) eventTime);
+ }
+
+ @Test
+ public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffInteractive() {
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = true;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ final long interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = true;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ verify(mMetricsLogger, never())
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+
+ final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_double_tap_interval"), intervalCaptor.capture());
+ List<Integer> intervals = intervalCaptor.getAllValues();
+ assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue());
+ assertEquals((int) interval, intervals.get(1).intValue());
+
+ final ArgumentCaptor<Integer> tapCountCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_consecutive_short_tap_count"), tapCountCaptor.capture());
+ List<Integer> tapCounts = tapCountCaptor.getAllValues();
+ assertEquals(1, tapCounts.get(0).intValue());
+ assertEquals(2, tapCounts.get(1).intValue());
+ }
+
+ @Test
+ public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOffInteractive() {
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = true;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ final long interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = true;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ verify(mMetricsLogger, never())
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+
+ final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_double_tap_interval"), intervalCaptor.capture());
+ List<Integer> intervals = intervalCaptor.getAllValues();
+ assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue());
+ assertEquals((int) interval, intervals.get(1).intValue());
+
+ final ArgumentCaptor<Integer> tapCountCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_consecutive_short_tap_count"), tapCountCaptor.capture());
+ List<Integer> tapCounts = tapCountCaptor.getAllValues();
+ assertEquals(1, tapCounts.get(0).intValue());
+ // The interval is too long to launch the camera, but short enough to count as a
+ // sequential tap.
+ assertEquals(2, tapCounts.get(1).intValue());
+ }
+
+ @Test
+ public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOffInteractive() {
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = true;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS;
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = true;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ verify(mMetricsLogger, never())
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+
+ final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_double_tap_interval"), intervalCaptor.capture());
+ List<Integer> intervals = intervalCaptor.getAllValues();
+ assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue());
+ assertEquals((int) interval, intervals.get(1).intValue());
+
+ final ArgumentCaptor<Integer> tapCountCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_consecutive_short_tap_count"), tapCountCaptor.capture());
+ List<Integer> tapCounts = tapCountCaptor.getAllValues();
+ assertEquals(1, tapCounts.get(0).intValue());
+ assertEquals(1, tapCounts.get(1).intValue());
+ }
+
+ @Test
+ public void
+ testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnInteractiveSetupComplete() {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ withUserSetupCompleteValue(true);
+
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = true;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ final long interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = false;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertTrue(intercepted);
+ assertTrue(outLaunched.value);
+
+ verify(mStatusBarManagerInternal).onCameraLaunchGestureDetected(
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
+ verify(mMetricsLogger)
+ .action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval);
+
+ final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_double_tap_interval"), intervalCaptor.capture());
+ List<Integer> intervals = intervalCaptor.getAllValues();
+ assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue());
+ assertEquals((int) interval, intervals.get(1).intValue());
+
+ final ArgumentCaptor<Integer> tapCountCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_consecutive_short_tap_count"), tapCountCaptor.capture());
+ List<Integer> tapCounts = tapCountCaptor.getAllValues();
+ assertEquals(1, tapCounts.get(0).intValue());
+ assertEquals(2, tapCounts.get(1).intValue());
+ }
+
+ @Test
+ public void
+ testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnInteractiveSetupIncomplete() {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ withUserSetupCompleteValue(false);
+
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = true;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ final long interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = true;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ verify(mMetricsLogger, never())
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+
+ final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_double_tap_interval"), intervalCaptor.capture());
+ List<Integer> intervals = intervalCaptor.getAllValues();
+ assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue());
+ assertEquals((int) interval, intervals.get(1).intValue());
+
+ final ArgumentCaptor<Integer> tapCountCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_consecutive_short_tap_count"), tapCountCaptor.capture());
+ List<Integer> tapCounts = tapCountCaptor.getAllValues();
+ assertEquals(1, tapCounts.get(0).intValue());
+ // The interval is too long to launch the camera, but short enough to count as a
+ // sequential tap.
+ assertEquals(2, tapCounts.get(1).intValue());
+ }
+
+ @Test
+ public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOnInteractive() {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = true;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ final long interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = true;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ verify(mMetricsLogger, never())
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+
+ final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_double_tap_interval"), intervalCaptor.capture());
+ List<Integer> intervals = intervalCaptor.getAllValues();
+ assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue());
+ assertEquals((int) interval, intervals.get(1).intValue());
+
+ final ArgumentCaptor<Integer> tapCountCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_consecutive_short_tap_count"), tapCountCaptor.capture());
+ List<Integer> tapCounts = tapCountCaptor.getAllValues();
+ assertEquals(1, tapCounts.get(0).intValue());
+ // The interval is too long to launch the camera, but short enough to count as a
+ // sequential tap.
+ assertEquals(2, tapCounts.get(1).intValue());
+ }
+
+ @Test
+ public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOnInteractive() {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = true;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS;
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = true;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ verify(mMetricsLogger, never())
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+
+ final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_double_tap_interval"), intervalCaptor.capture());
+ List<Integer> intervals = intervalCaptor.getAllValues();
+ assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue());
+ assertEquals((int) interval, intervals.get(1).intValue());
+
+ final ArgumentCaptor<Integer> tapCountCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_consecutive_short_tap_count"), tapCountCaptor.capture());
+ List<Integer> tapCounts = tapCountCaptor.getAllValues();
+ assertEquals(1, tapCounts.get(0).intValue());
+ assertEquals(1, tapCounts.get(1).intValue());
+ }
+
+ @Test
+ public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffNotInteractive() {
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = false;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ final long interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = true;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ verify(mMetricsLogger, never())
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+
+ final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_double_tap_interval"), intervalCaptor.capture());
+ List<Integer> intervals = intervalCaptor.getAllValues();
+ assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue());
+ assertEquals((int) interval, intervals.get(1).intValue());
+
+ final ArgumentCaptor<Integer> tapCountCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_consecutive_short_tap_count"), tapCountCaptor.capture());
+ List<Integer> tapCounts = tapCountCaptor.getAllValues();
+ assertEquals(1, tapCounts.get(0).intValue());
+ assertEquals(2, tapCounts.get(1).intValue());
+ }
+
+ @Test
+ public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOffNotInteractive() {
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = false;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ final long interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = true;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+ verify(mMetricsLogger, never())
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+
+ final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_double_tap_interval"), intervalCaptor.capture());
+ List<Integer> intervals = intervalCaptor.getAllValues();
+ assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue());
+ assertEquals((int) interval, intervals.get(1).intValue());
+
+ final ArgumentCaptor<Integer> tapCountCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_consecutive_short_tap_count"), tapCountCaptor.capture());
+ List<Integer> tapCounts = tapCountCaptor.getAllValues();
+ assertEquals(1, tapCounts.get(0).intValue());
+ // The interval is too long to launch the camera, but short enough to count as a
+ // sequential tap.
+ assertEquals(2, tapCounts.get(1).intValue());
+ }
+
+ @Test
+ public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOffNotInteractive() {
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = false;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS;
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = true;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+ verify(mMetricsLogger, never())
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+
+ final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_double_tap_interval"), intervalCaptor.capture());
+ List<Integer> intervals = intervalCaptor.getAllValues();
+ assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue());
+ assertEquals((int) interval, intervals.get(1).intValue());
+
+ final ArgumentCaptor<Integer> tapCountCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_consecutive_short_tap_count"), tapCountCaptor.capture());
+ List<Integer> tapCounts = tapCountCaptor.getAllValues();
+ assertEquals(1, tapCounts.get(0).intValue());
+ assertEquals(1, tapCounts.get(1).intValue());
+ }
+
+ @Test
+ public void
+ testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnNotInteractiveSetupComplete() {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ withUserSetupCompleteValue(true);
+
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = false;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ final long interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertTrue(outLaunched.value);
+
+ verify(mStatusBarManagerInternal).onCameraLaunchGestureDetected(
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
+ verify(mMetricsLogger)
+ .action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval);
+
+ final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_double_tap_interval"), intervalCaptor.capture());
+ List<Integer> intervals = intervalCaptor.getAllValues();
+ assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue());
+ assertEquals((int) interval, intervals.get(1).intValue());
+
+ final ArgumentCaptor<Integer> tapCountCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_consecutive_short_tap_count"), tapCountCaptor.capture());
+ List<Integer> tapCounts = tapCountCaptor.getAllValues();
+ assertEquals(1, tapCounts.get(0).intValue());
+ assertEquals(2, tapCounts.get(1).intValue());
+ }
+
+ @Test
+ public void
+ testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnNotInteractiveSetupIncomplete() {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ withUserSetupCompleteValue(false);
+
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = false;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ final long interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = true;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ verify(mMetricsLogger, never())
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+
+ final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_double_tap_interval"), intervalCaptor.capture());
+ List<Integer> intervals = intervalCaptor.getAllValues();
+ assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue());
+ assertEquals((int) interval, intervals.get(1).intValue());
+
+ final ArgumentCaptor<Integer> tapCountCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_consecutive_short_tap_count"), tapCountCaptor.capture());
+ List<Integer> tapCounts = tapCountCaptor.getAllValues();
+ assertEquals(1, tapCounts.get(0).intValue());
+ assertEquals(2, tapCounts.get(1).intValue());
+ }
+
+ @Test
+ public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOnNotInteractive() {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = false;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ final long interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = true;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ verify(mMetricsLogger, never())
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+
+ final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_double_tap_interval"), intervalCaptor.capture());
+ List<Integer> intervals = intervalCaptor.getAllValues();
+ assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue());
+ assertEquals((int) interval, intervals.get(1).intValue());
+
+ final ArgumentCaptor<Integer> tapCountCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_consecutive_short_tap_count"), tapCountCaptor.capture());
+ List<Integer> tapCounts = tapCountCaptor.getAllValues();
+ assertEquals(1, tapCounts.get(0).intValue());
+ // The interval is too long to launch the camera, but short enough to count as a
+ // sequential tap.
+ assertEquals(2, tapCounts.get(1).intValue());
+ }
+
+ @Test
+ public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOnNotInteractive() {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = false;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS;
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = true;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ verify(mMetricsLogger, never())
+ .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
+
+ final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_double_tap_interval"), intervalCaptor.capture());
+ List<Integer> intervals = intervalCaptor.getAllValues();
+ assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue());
+ assertEquals((int) interval, intervals.get(1).intValue());
+
+ final ArgumentCaptor<Integer> tapCountCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_consecutive_short_tap_count"), tapCountCaptor.capture());
+ List<Integer> tapCounts = tapCountCaptor.getAllValues();
+ assertEquals(1, tapCounts.get(0).intValue());
+ assertEquals(1, tapCounts.get(1).intValue());
+ }
+
+ private void withCameraDoubleTapPowerEnableConfigValue(boolean enableConfigValue) {
+ when(mResources.getBoolean(
+ com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled))
+ .thenReturn(enableConfigValue);
+ }
+
+ private void withCameraDoubleTapPowerDisableSettingValue(int disableSettingValue) {
+ Settings.Secure.putIntForUser(
+ mContentResolver,
+ Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED,
+ disableSettingValue,
+ UserHandle.USER_CURRENT);
+ }
+
+ private void withUserSetupCompleteValue(boolean userSetupComplete) {
+ int userSetupCompleteValue = userSetupComplete ? 1 : 0;
+ Settings.Secure.putIntForUser(
+ mContentResolver,
+ Settings.Secure.USER_SETUP_COMPLETE,
+ userSetupCompleteValue,
+ UserHandle.USER_CURRENT);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java
deleted file mode 100644
index cfdb5b1..0000000
--- a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server;
-
-import static org.mockito.Mockito.mock;
-
-import android.app.IActivityManager;
-import android.content.Context;
-import android.os.Handler;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.storage.IStorageManager;
-import android.security.KeyStore;
-import android.security.keystore.KeyPermanentlyInvalidatedException;
-
-import com.android.internal.widget.LockPatternUtils;
-
-import java.io.FileNotFoundException;
-
-public class LockSettingsServiceTestable extends LockSettingsService {
-
- private static class MockInjector extends LockSettingsService.Injector {
-
- private LockSettingsStorage mLockSettingsStorage;
- private KeyStore mKeyStore;
- private IActivityManager mActivityManager;
- private LockPatternUtils mLockPatternUtils;
- private IStorageManager mStorageManager;
- private MockGateKeeperService mGatekeeper;
-
- public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
- IActivityManager activityManager, LockPatternUtils lockPatternUtils,
- IStorageManager storageManager, MockGateKeeperService gatekeeper) {
- super(context);
- mLockSettingsStorage = storage;
- mKeyStore = keyStore;
- mActivityManager = activityManager;
- mLockPatternUtils = lockPatternUtils;
- mStorageManager = storageManager;
- mGatekeeper = gatekeeper;
- }
-
- @Override
- public Handler getHandler() {
- return mock(Handler.class);
- }
-
- @Override
- public LockSettingsStorage getStorage() {
- return mLockSettingsStorage;
- }
-
- @Override
- public LockSettingsStrongAuth getStrongAuth() {
- return mock(LockSettingsStrongAuth.class);
- }
-
- @Override
- public SynchronizedStrongAuthTracker getStrongAuthTracker() {
- return mock(SynchronizedStrongAuthTracker.class);
- }
-
- @Override
- public IActivityManager getActivityManager() {
- return mActivityManager;
- }
-
- @Override
- public LockPatternUtils getLockPatternUtils() {
- return mLockPatternUtils;
- }
-
- @Override
- public KeyStore getKeyStore() {
- return mKeyStore;
- }
-
- @Override
- public IStorageManager getStorageManager() {
- return mStorageManager;
- }
-
- @Override
- public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) {
- return new MockSyntheticPasswordManager(storage, mGatekeeper);
- }
-
- @Override
- public int binderGetCallingUid() {
- return Process.SYSTEM_UID;
- }
-
-
- }
-
- protected LockSettingsServiceTestable(Context context, LockPatternUtils lockPatternUtils,
- LockSettingsStorage storage, MockGateKeeperService gatekeeper, KeyStore keystore,
- IStorageManager storageManager, IActivityManager mActivityManager) {
- super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils,
- storageManager, gatekeeper));
- mGateKeeperService = gatekeeper;
- }
-
- @Override
- protected void tieProfileLockToParent(int userId, String password) {
- mStorage.writeChildProfileLock(userId, password.getBytes());
- }
-
- @Override
- protected String getDecryptedPasswordForTiedProfile(int userId) throws FileNotFoundException, KeyPermanentlyInvalidatedException {
- byte[] storedData = mStorage.readChildProfileLock(userId);
- if (storedData == null) {
- throw new FileNotFoundException("Child profile lock file not found");
- }
- try {
- if (mGateKeeperService.getSecureUserId(userId) == 0) {
- throw new KeyPermanentlyInvalidatedException();
- }
- } catch (RemoteException e) {
- // shouldn't happen.
- }
- return new String(storedData);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java
deleted file mode 100644
index 25cc426..0000000
--- a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server;
-
-import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
-import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
-import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
-
-import android.os.RemoteException;
-import android.service.gatekeeper.GateKeeperResponse;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.server.LockSettingsStorage.CredentialHash;
-import com.android.server.MockGateKeeperService.VerifyHandle;
-
-/**
- * runtest frameworks-services -c com.android.server.LockSettingsServiceTests
- */
-public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- }
-
- public void testCreatePasswordPrimaryUser() throws RemoteException {
- testCreateCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD);
- }
-
- public void testCreatePatternPrimaryUser() throws RemoteException {
- testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN);
- }
-
- public void testChangePasswordPrimaryUser() throws RemoteException {
- testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN,
- "asdfghjk", CREDENTIAL_TYPE_PASSWORD);
- }
-
- public void testChangePatternPrimaryUser() throws RemoteException {
- testChangeCredentials(PRIMARY_USER_ID, "!£$%^&*(())", CREDENTIAL_TYPE_PASSWORD,
- "1596321", CREDENTIAL_TYPE_PATTERN);
- }
-
- public void testChangePasswordFailPrimaryUser() throws RemoteException {
- final long sid = 1234;
- final String FAILED_MESSAGE = "Failed to enroll password";
- initializeStorageWithCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
-
- try {
- mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, "badpwd",
- PRIMARY_USER_ID);
- fail("Did not fail when enrolling using incorrect credential");
- } catch (RemoteException expected) {
- assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
- }
- try {
- mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
- fail("Did not fail when enrolling using incorrect credential");
- } catch (RemoteException expected) {
- assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
- }
- assertVerifyCredentials(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
- }
-
- public void testClearPasswordPrimaryUser() throws RemoteException {
- final String PASSWORD = "password";
- initializeStorageWithCredential(PRIMARY_USER_ID, PASSWORD, CREDENTIAL_TYPE_PASSWORD, 1234);
- mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, PASSWORD, PRIMARY_USER_ID);
- assertFalse(mService.havePassword(PRIMARY_USER_ID));
- assertFalse(mService.havePattern(PRIMARY_USER_ID));
- assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
- }
-
- public void testManagedProfileUnifiedChallenge() throws RemoteException {
- final String UnifiedPassword = "testManagedProfileUnifiedChallenge-pwd";
- mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PRIMARY_USER_ID);
- mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
- final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
- final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
- final long turnedOffProfileSid =
- mGateKeeperService.getSecureUserId(TURNED_OFF_PROFILE_USER_ID);
- assertTrue(primarySid != 0);
- assertTrue(profileSid != 0);
- assertTrue(profileSid != primarySid);
- assertTrue(turnedOffProfileSid != 0);
- assertTrue(turnedOffProfileSid != primarySid);
- assertTrue(turnedOffProfileSid != profileSid);
-
- // clear auth token and wait for verify challenge from primary user to re-generate it.
- mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID);
- mGateKeeperService.clearAuthToken(TURNED_OFF_PROFILE_USER_ID);
- // verify credential
- assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
- .getResponseCode());
-
- // Verify that we have a new auth token for the profile
- assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
- assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
-
- // Verify that profile which aren't running (e.g. turn off work) don't get unlocked
- assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
-
- /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
- * credential as part of verifyCredential() before the new credential is committed in
- * StorageManager. So we relax the check in our mock StorageManager to allow that.
- */
- mStorageManager.setIgnoreBadUnlock(true);
- // Change primary password and verify that profile SID remains
- mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- UnifiedPassword, PRIMARY_USER_ID);
- mStorageManager.setIgnoreBadUnlock(false);
- assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
- assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
-
- // Clear unified challenge
- mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, UnifiedPassword,
- PRIMARY_USER_ID);
- assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
- assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
- assertEquals(0, mGateKeeperService.getSecureUserId(TURNED_OFF_PROFILE_USER_ID));
- }
-
- public void testManagedProfileSeparateChallenge() throws RemoteException {
- final String primaryPassword = "testManagedProfileSeparateChallenge-primary";
- final String profilePassword = "testManagedProfileSeparateChallenge-profile";
- mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PRIMARY_USER_ID);
- /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
- * credential as part of verifyCredential() before the new credential is committed in
- * StorageManager. So we relax the check in our mock StorageManager to allow that.
- */
- mStorageManager.setIgnoreBadUnlock(true);
- mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- MANAGED_PROFILE_USER_ID);
- mStorageManager.setIgnoreBadUnlock(false);
-
- final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
- final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
- assertTrue(primarySid != 0);
- assertTrue(profileSid != 0);
- assertTrue(profileSid != primarySid);
-
- // clear auth token and make sure verify challenge from primary user does not regenerate it.
- mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID);
- // verify primary credential
- assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
- .getResponseCode());
- assertNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
-
- // verify profile credential
- assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
- MANAGED_PROFILE_USER_ID).getResponseCode());
- assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
- assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
-
- // Change primary credential and make sure we don't affect profile
- mStorageManager.setIgnoreBadUnlock(true);
- mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- primaryPassword, PRIMARY_USER_ID);
- mStorageManager.setIgnoreBadUnlock(false);
- assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
- profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
- MANAGED_PROFILE_USER_ID).getResponseCode());
- assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
- }
-
- private void testCreateCredential(int userId, String credential, int type)
- throws RemoteException {
- mService.setLockCredential(credential, type, null, userId);
- assertVerifyCredentials(userId, credential, type, -1);
- }
-
- private void testChangeCredentials(int userId, String newCredential, int newType,
- String oldCredential, int oldType) throws RemoteException {
- final long sid = 1234;
- initializeStorageWithCredential(userId, oldCredential, oldType, sid);
- mService.setLockCredential(newCredential, newType, oldCredential, userId);
- assertVerifyCredentials(userId, newCredential, newType, sid);
- }
-
- private void assertVerifyCredentials(int userId, String credential, int type, long sid)
- throws RemoteException{
- final long challenge = 54321;
- VerifyCredentialResponse response = mService.verifyCredential(credential, type, challenge,
- userId);
-
- assertEquals(GateKeeperResponse.RESPONSE_OK, response.getResponseCode());
- if (sid != -1) assertEquals(sid, mGateKeeperService.getSecureUserId(userId));
- final int incorrectType;
- if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
- assertTrue(mService.havePassword(userId));
- assertFalse(mService.havePattern(userId));
- incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
- } else if (type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN){
- assertFalse(mService.havePassword(userId));
- assertTrue(mService.havePattern(userId));
- incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
- } else {
- assertFalse(mService.havePassword(userId));
- assertFalse(mService.havePassword(userId));
- incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
- }
- // check for bad type
- assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential(credential,
- incorrectType, challenge, userId).getResponseCode());
- // check for bad credential
- assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential("0" + credential,
- type, challenge, userId).getResponseCode());
- }
-
- private void initializeStorageWithCredential(int userId, String credential, int type, long sid) {
- byte[] oldHash = new VerifyHandle(credential.getBytes(), sid).toBytes();
- if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
- mStorage.writeCredentialHash(CredentialHash.create(oldHash,
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), userId);
- } else {
- mStorage.writeCredentialHash(CredentialHash.create(oldHash,
- LockPatternUtils.CREDENTIAL_TYPE_PATTERN), userId);
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/LockSettingsShellCommandTest.java
deleted file mode 100644
index 84ebb19..0000000
--- a/services/tests/servicestests/src/com/android/server/LockSettingsShellCommandTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server;
-
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
-
-import static com.android.internal.widget.LockPatternUtils.stringToPattern;
-
-import static junit.framework.Assert.*;
-
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import static java.io.FileDescriptor.*;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.os.Binder;
-import android.os.Debug;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.ResultReceiver;
-import android.os.ShellCallback;
-import android.platform.test.annotations.Presubmit;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import com.android.internal.widget.LockPatternUtils;
-
-import junit.framework.Assert;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.FileDescriptor;
-
-/**
- * Test class for {@link LockSettingsShellCommand}.
- *
- * runtest frameworks-services -c com.android.server.LockSettingsShellCommandTest
- */
-@SmallTest
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class LockSettingsShellCommandTest {
-
- private LockSettingsShellCommand mCommand;
-
- private @Mock LockPatternUtils mLockPatternUtils;
- private int mUserId;
- private final Binder mBinder = new Binder();
- private final ShellCallback mShellCallback = new ShellCallback();
- private final ResultReceiver mResultReceiver = new ResultReceiver(
- new Handler(Looper.getMainLooper()));
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- final Context context = InstrumentationRegistry.getTargetContext();
- mUserId = ActivityManager.getCurrentUser();
- mCommand = new LockSettingsShellCommand(context, mLockPatternUtils);
- }
-
- @Test
- public void testWrongPassword() throws Exception {
- when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
- when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
- when(mLockPatternUtils.checkPassword("1234", mUserId)).thenReturn(false);
- assertEquals(-1, mCommand.exec(mBinder, in, out, err,
- new String[] { "set-pin", "--old", "1234" },
- mShellCallback, mResultReceiver));
- verify(mLockPatternUtils, never()).saveLockPassword(any(), any(), anyInt(), anyInt());
- }
-
- @Test
- public void testChangePin() throws Exception {
- when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
- when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
- when(mLockPatternUtils.checkPassword("1234", mUserId)).thenReturn(true);
- assertEquals(0, mCommand.exec(new Binder(), in, out, err,
- new String[] { "set-pin", "--old", "1234", "4321" },
- mShellCallback, mResultReceiver));
- verify(mLockPatternUtils).saveLockPassword("4321", "1234", PASSWORD_QUALITY_NUMERIC,
- mUserId);
- }
-
- @Test
- public void testChangePassword() throws Exception {
- when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
- when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
- when(mLockPatternUtils.checkPassword("1234", mUserId)).thenReturn(true);
- assertEquals(0, mCommand.exec(new Binder(), in, out, err,
- new String[] { "set-password", "--old", "1234", "4321" },
- mShellCallback, mResultReceiver));
- verify(mLockPatternUtils).saveLockPassword("4321", "1234", PASSWORD_QUALITY_ALPHABETIC,
- mUserId);
- }
-
- @Test
- public void testChangePattern() throws Exception {
- when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
- when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
- when(mLockPatternUtils.checkPattern(stringToPattern("1234"), mUserId)).thenReturn(true);
- assertEquals(0, mCommand.exec(new Binder(), in, out, err,
- new String[] { "set-pattern", "--old", "1234", "4321" },
- mShellCallback, mResultReceiver));
- verify(mLockPatternUtils).saveLockPattern(stringToPattern("4321"), "1234", mUserId);
- }
-
- @Test
- public void testClear() throws Exception {
- when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
- when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
- when(mLockPatternUtils.checkPattern(stringToPattern("1234"), mUserId)).thenReturn(true);
- assertEquals(0, mCommand.exec(new Binder(), in, out, err,
- new String[] { "clear", "--old", "1234" },
- mShellCallback, mResultReceiver));
- verify(mLockPatternUtils).clearLock("1234", mUserId);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java
deleted file mode 100644
index 18da1a5..0000000
--- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server;
-
-import android.content.Context;
-
-import java.io.File;
-
-public class LockSettingsStorageTestable extends LockSettingsStorage {
-
- public File mStorageDir;
-
- public LockSettingsStorageTestable(Context context, File storageDir) {
- super(context);
- mStorageDir = storageDir;
- }
-
- @Override
- String getLockPatternFilename(int userId) {
- return makeDirs(mStorageDir,
- super.getLockPatternFilename(userId)).getAbsolutePath();
- }
-
- @Override
- String getLockPasswordFilename(int userId) {
- return makeDirs(mStorageDir,
- super.getLockPasswordFilename(userId)).getAbsolutePath();
- }
-
- @Override
- String getChildProfileLockFile(int userId) {
- return makeDirs(mStorageDir,
- super.getChildProfileLockFile(userId)).getAbsolutePath();
- }
-
- @Override
- protected File getSyntheticPasswordDirectoryForUser(int userId) {
- return makeDirs(mStorageDir, super.getSyntheticPasswordDirectoryForUser(
- userId).getAbsolutePath());
- }
-
- private File makeDirs(File baseDir, String filePath) {
- File path = new File(filePath);
- if (path.getParent() == null) {
- return new File(baseDir, filePath);
- } else {
- File mappedDir = new File(baseDir, path.getParent());
- mappedDir.mkdirs();
- return new File(mappedDir, path.getName());
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
deleted file mode 100644
index f242b26..0000000
--- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server;
-
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.app.NotificationManager;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.UserInfo;
-import android.database.sqlite.SQLiteDatabase;
-import android.os.FileUtils;
-import android.os.UserManager;
-import android.os.storage.StorageManager;
-import android.test.AndroidTestCase;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.server.LockSettingsStorage.CredentialHash;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * runtest frameworks-services -c com.android.server.LockSettingsStorageTests
- */
-public class LockSettingsStorageTests extends AndroidTestCase {
- private final byte[] PASSWORD_0 = "thepassword0".getBytes();
- private final byte[] PASSWORD_1 = "password1".getBytes();
- private final byte[] PATTERN_0 = "123654".getBytes();
- private final byte[] PATTERN_1 = "147852369".getBytes();
-
- LockSettingsStorage mStorage;
- File mStorageDir;
-
- private File mDb;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mStorageDir = new File(getContext().getFilesDir(), "locksettings");
- mDb = getContext().getDatabasePath("locksettings.db");
-
- assertTrue(mStorageDir.exists() || mStorageDir.mkdirs());
- assertTrue(FileUtils.deleteContents(mStorageDir));
- assertTrue(!mDb.exists() || mDb.delete());
-
- final UserManager mockUserManager = mock(UserManager.class);
- // User 2 is a profile of user 1.
- when(mockUserManager.getProfileParent(eq(2))).thenReturn(new UserInfo(1, "name", 0));
- // User 3 is a profile of user 0.
- when(mockUserManager.getProfileParent(eq(3))).thenReturn(new UserInfo(0, "name", 0));
-
- MockLockSettingsContext context = new MockLockSettingsContext(getContext(), mockUserManager,
- mock(NotificationManager.class), mock(DevicePolicyManager.class),
- mock(StorageManager.class));
- mStorage = new LockSettingsStorageTestable(context,
- new File(getContext().getFilesDir(), "locksettings"));
- mStorage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() {
- @Override
- public void initialize(SQLiteDatabase db) {
- mStorage.writeKeyValue(db, "initializedKey", "initialValue", 0);
- }
- });
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- mStorage.closeDatabase();
- }
-
- public void testKeyValue_InitializeWorked() {
- assertEquals("initialValue", mStorage.readKeyValue("initializedKey", "default", 0));
- mStorage.clearCache();
- assertEquals("initialValue", mStorage.readKeyValue("initializedKey", "default", 0));
- }
-
- public void testKeyValue_WriteThenRead() {
- mStorage.writeKeyValue("key", "value", 0);
- assertEquals("value", mStorage.readKeyValue("key", "default", 0));
- mStorage.clearCache();
- assertEquals("value", mStorage.readKeyValue("key", "default", 0));
- }
-
- public void testKeyValue_DefaultValue() {
- assertEquals("default", mStorage.readKeyValue("unititialized key", "default", 0));
- assertEquals("default2", mStorage.readKeyValue("unititialized key", "default2", 0));
- }
-
- public void testKeyValue_Concurrency() {
- final Object monitor = new Object();
- List<Thread> threads = new ArrayList<>();
- for (int i = 0; i < 100; i++) {
- final int threadId = i;
- threads.add(new Thread() {
- @Override
- public void run() {
- synchronized (monitor) {
- try {
- monitor.wait();
- } catch (InterruptedException e) {
- return;
- }
- mStorage.writeKeyValue("key", "1 from thread " + threadId, 0);
- mStorage.readKeyValue("key", "default", 0);
- mStorage.writeKeyValue("key", "2 from thread " + threadId, 0);
- mStorage.readKeyValue("key", "default", 0);
- mStorage.writeKeyValue("key", "3 from thread " + threadId, 0);
- mStorage.readKeyValue("key", "default", 0);
- mStorage.writeKeyValue("key", "4 from thread " + threadId, 0);
- mStorage.readKeyValue("key", "default", 0);
- mStorage.writeKeyValue("key", "5 from thread " + threadId, 0);
- mStorage.readKeyValue("key", "default", 0);
- }
- }
- });
- threads.get(i).start();
- }
- mStorage.writeKeyValue("key", "initalValue", 0);
- synchronized (monitor) {
- monitor.notifyAll();
- }
- for (int i = 0; i < threads.size(); i++) {
- try {
- threads.get(i).join();
- } catch (InterruptedException e) {
- }
- }
- assertEquals('5', mStorage.readKeyValue("key", "default", 0).charAt(0));
- mStorage.clearCache();
- assertEquals('5', mStorage.readKeyValue("key", "default", 0).charAt(0));
- }
-
- public void testKeyValue_CacheStarvedWriter() {
- final CountDownLatch latch = new CountDownLatch(1);
- List<Thread> threads = new ArrayList<>();
- for (int i = 0; i < 100; i++) {
- final int threadId = i;
- threads.add(new Thread() {
- @Override
- public void run() {
- try {
- latch.await();
- } catch (InterruptedException e) {
- return;
- }
- if (threadId == 50) {
- mStorage.writeKeyValue("starvedWriterKey", "value", 0);
- } else {
- mStorage.readKeyValue("starvedWriterKey", "default", 0);
- }
- }
- });
- threads.get(i).start();
- }
- latch.countDown();
- for (int i = 0; i < threads.size(); i++) {
- try {
- threads.get(i).join();
- } catch (InterruptedException e) {
- }
- }
- String cached = mStorage.readKeyValue("key", "default", 0);
- mStorage.clearCache();
- String storage = mStorage.readKeyValue("key", "default", 0);
- assertEquals("Cached value didn't match stored value", storage, cached);
- }
-
- public void testRemoveUser() {
- mStorage.writeKeyValue("key", "value", 0);
- writePasswordBytes(PASSWORD_0, 0);
- writePatternBytes(PATTERN_0, 0);
-
- mStorage.writeKeyValue("key", "value", 1);
- writePasswordBytes(PASSWORD_1, 1);
- writePatternBytes(PATTERN_1, 1);
-
- mStorage.removeUser(0);
-
- assertEquals("value", mStorage.readKeyValue("key", "default", 1));
- assertEquals("default", mStorage.readKeyValue("key", "default", 0));
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_NONE, mStorage.readCredentialHash(0).type);
- assertPatternBytes(PATTERN_1, 1);
- }
-
- public void testCredential_Default() {
- assertEquals(mStorage.readCredentialHash(0).type, LockPatternUtils.CREDENTIAL_TYPE_NONE);
- }
-
- public void testPassword_Write() {
- writePasswordBytes(PASSWORD_0, 0);
-
- assertPasswordBytes(PASSWORD_0, 0);
- mStorage.clearCache();
- assertPasswordBytes(PASSWORD_0, 0);
- }
-
- public void testPassword_WriteProfileWritesParent() {
- writePasswordBytes(PASSWORD_0, 1);
- writePasswordBytes(PASSWORD_1, 2);
-
- assertPasswordBytes(PASSWORD_0, 1);
- assertPasswordBytes(PASSWORD_1, 2);
- mStorage.clearCache();
- assertPasswordBytes(PASSWORD_0, 1);
- assertPasswordBytes(PASSWORD_1, 2);
- }
-
- public void testLockType_WriteProfileWritesParent() {
- writePasswordBytes(PASSWORD_0, 10);
- writePatternBytes(PATTERN_0, 20);
-
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- mStorage.readCredentialHash(10).type);
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
- mStorage.readCredentialHash(20).type);
- mStorage.clearCache();
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- mStorage.readCredentialHash(10).type);
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
- mStorage.readCredentialHash(20).type);
- }
-
- public void testPassword_WriteParentWritesProfile() {
- writePasswordBytes(PASSWORD_0, 2);
- writePasswordBytes(PASSWORD_1, 1);
-
- assertPasswordBytes(PASSWORD_1, 1);
- assertPasswordBytes(PASSWORD_0, 2);
- mStorage.clearCache();
- assertPasswordBytes(PASSWORD_1, 1);
- assertPasswordBytes(PASSWORD_0, 2);
- }
-
- public void testProfileLock_ReadWriteChildProfileLock() {
- assertFalse(mStorage.hasChildProfileLock(20));
- mStorage.writeChildProfileLock(20, PASSWORD_0);
- assertArrayEquals(PASSWORD_0, mStorage.readChildProfileLock(20));
- assertTrue(mStorage.hasChildProfileLock(20));
- mStorage.clearCache();
- assertArrayEquals(PASSWORD_0, mStorage.readChildProfileLock(20));
- assertTrue(mStorage.hasChildProfileLock(20));
- }
-
- public void testPattern_Write() {
- writePatternBytes(PATTERN_0, 0);
-
- assertPatternBytes(PATTERN_0, 0);
- mStorage.clearCache();
- assertPatternBytes(PATTERN_0, 0);
- }
-
- public void testPattern_WriteProfileWritesParent() {
- writePatternBytes(PATTERN_0, 1);
- writePatternBytes(PATTERN_1, 2);
-
- assertPatternBytes(PATTERN_0, 1);
- assertPatternBytes(PATTERN_1, 2);
- mStorage.clearCache();
- assertPatternBytes(PATTERN_0, 1);
- assertPatternBytes(PATTERN_1, 2);
- }
-
- public void testPattern_WriteParentWritesProfile() {
- writePatternBytes(PATTERN_1, 2);
- writePatternBytes(PATTERN_0, 1);
-
- assertPatternBytes(PATTERN_0, 1);
- assertPatternBytes(PATTERN_1, 2);
- mStorage.clearCache();
- assertPatternBytes(PATTERN_0, 1);
- assertPatternBytes(PATTERN_1, 2);
- }
-
- public void testPrefetch() {
- mStorage.writeKeyValue("key", "toBeFetched", 0);
- writePatternBytes(PATTERN_0, 0);
-
- mStorage.clearCache();
- mStorage.prefetchUser(0);
-
- assertEquals("toBeFetched", mStorage.readKeyValue("key", "default", 0));
- assertPatternBytes(PATTERN_0, 0);
- }
-
- public void testFileLocation_Owner() {
- LockSettingsStorage storage = new LockSettingsStorage(getContext());
-
- assertEquals("/data/system/gesture.key", storage.getLegacyLockPatternFilename(0));
- assertEquals("/data/system/password.key", storage.getLegacyLockPasswordFilename(0));
- assertEquals("/data/system/gatekeeper.pattern.key", storage.getLockPatternFilename(0));
- assertEquals("/data/system/gatekeeper.password.key", storage.getLockPasswordFilename(0));
- }
-
- public void testFileLocation_SecondaryUser() {
- LockSettingsStorage storage = new LockSettingsStorage(getContext());
-
- assertEquals("/data/system/users/1/gatekeeper.pattern.key", storage.getLockPatternFilename(1));
- assertEquals("/data/system/users/1/gatekeeper.password.key", storage.getLockPasswordFilename(1));
- }
-
- public void testFileLocation_ProfileToSecondary() {
- LockSettingsStorage storage = new LockSettingsStorage(getContext());
-
- assertEquals("/data/system/users/2/gatekeeper.pattern.key", storage.getLockPatternFilename(2));
- assertEquals("/data/system/users/2/gatekeeper.password.key", storage.getLockPasswordFilename(2));
- }
-
- public void testFileLocation_ProfileToOwner() {
- LockSettingsStorage storage = new LockSettingsStorage(getContext());
-
- assertEquals("/data/system/users/3/gatekeeper.pattern.key", storage.getLockPatternFilename(3));
- assertEquals("/data/system/users/3/gatekeeper.password.key", storage.getLockPasswordFilename(3));
- }
-
- public void testSyntheticPasswordState() {
- final byte[] data = {1,2,3,4};
- mStorage.writeSyntheticPasswordState(10, 1234L, "state", data);
- assertArrayEquals(data, mStorage.readSyntheticPasswordState(10, 1234L, "state"));
- assertEquals(null, mStorage.readSyntheticPasswordState(0, 1234L, "state"));
-
- mStorage.deleteSyntheticPasswordState(10, 1234L, "state");
- assertEquals(null, mStorage.readSyntheticPasswordState(10, 1234L, "state"));
- }
-
- private static void assertArrayEquals(byte[] expected, byte[] actual) {
- if (!Arrays.equals(expected, actual)) {
- fail("expected:<" + Arrays.toString(expected) +
- "> but was:<" + Arrays.toString(actual) + ">");
- }
- }
-
- private void writePasswordBytes(byte[] password, int userId) {
- mStorage.writeCredentialHash(CredentialHash.create(
- password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), userId);
- }
-
- private void writePatternBytes(byte[] pattern, int userId) {
- mStorage.writeCredentialHash(CredentialHash.create(
- pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN), userId);
- }
-
- private void assertPasswordBytes(byte[] password, int userId) {
- CredentialHash cred = mStorage.readCredentialHash(userId);
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, cred.type);
- assertArrayEquals(password, cred.hash);
- }
-
- private void assertPatternBytes(byte[] pattern, int userId) {
- CredentialHash cred = mStorage.readCredentialHash(userId);
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN, cred.type);
- assertArrayEquals(pattern, cred.hash);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java b/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java
deleted file mode 100644
index bc93341..0000000
--- a/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server;
-
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.service.gatekeeper.GateKeeperResponse;
-import android.service.gatekeeper.IGateKeeperService;
-import android.util.ArrayMap;
-
-import junit.framework.AssertionFailedError;
-
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.Random;
-
-public class MockGateKeeperService implements IGateKeeperService {
- static class VerifyHandle {
- public byte[] password;
- public long sid;
-
- public VerifyHandle(byte[] password, long sid) {
- this.password = password;
- this.sid = sid;
- }
-
- public VerifyHandle(byte[] handle) {
- ByteBuffer buffer = ByteBuffer.allocate(handle.length);
- buffer.put(handle, 0, handle.length);
- buffer.flip();
- int version = buffer.get();
- sid = buffer.getLong();
- password = new byte[buffer.remaining()];
- buffer.get(password);
- }
-
- public byte[] toBytes() {
- ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + password.length);
- buffer.put((byte)0);
- buffer.putLong(sid);
- buffer.put(password);
- return buffer.array();
- }
- }
-
- static class AuthToken {
- public long challenge;
- public long sid;
-
- public AuthToken(long challenge, long sid) {
- this.challenge = challenge;
- this.sid = sid;
- }
-
- public AuthToken(byte[] handle) {
- ByteBuffer buffer = ByteBuffer.allocate(handle.length);
- buffer.put(handle, 0, handle.length);
- buffer.flip();
- int version = buffer.get();
- challenge = buffer.getLong();
- sid = buffer.getLong();
- }
-
- public byte[] toBytes() {
- ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + Long.BYTES);
- buffer.put((byte)0);
- buffer.putLong(challenge);
- buffer.putLong(sid);
- return buffer.array();
- }
- }
-
- private ArrayMap<Integer, Long> sidMap = new ArrayMap<>();
- private ArrayMap<Integer, AuthToken> authTokenMap = new ArrayMap<>();
-
- private ArrayMap<Integer, byte[]> handleMap = new ArrayMap<>();
-
- @Override
- public GateKeeperResponse enroll(int uid, byte[] currentPasswordHandle, byte[] currentPassword,
- byte[] desiredPassword) throws android.os.RemoteException {
-
- if (currentPasswordHandle != null) {
- VerifyHandle handle = new VerifyHandle(currentPasswordHandle);
- if (Arrays.equals(currentPassword, handle.password)) {
- // Trusted enroll
- VerifyHandle newHandle = new VerifyHandle(desiredPassword, handle.sid);
- refreshSid(uid, handle.sid, false);
- handleMap.put(uid, newHandle.toBytes());
- return GateKeeperResponse.createOkResponse(newHandle.toBytes(), false);
- } else {
- return null;
- }
- } else {
- // Untrusted enroll
- long newSid = new Random().nextLong();
- VerifyHandle newHandle = new VerifyHandle(desiredPassword, newSid);
- refreshSid(uid, newSid, true);
- handleMap.put(uid, newHandle.toBytes());
- return GateKeeperResponse.createOkResponse(newHandle.toBytes(), false);
- }
- }
-
- @Override
- public GateKeeperResponse verify(int uid, byte[] enrolledPasswordHandle,
- byte[] providedPassword) throws android.os.RemoteException {
- return verifyChallenge(uid, 0, enrolledPasswordHandle, providedPassword);
- }
-
- @Override
- public GateKeeperResponse verifyChallenge(int uid, long challenge,
- byte[] enrolledPasswordHandle, byte[] providedPassword) throws RemoteException {
-
- VerifyHandle handle = new VerifyHandle(enrolledPasswordHandle);
- if (Arrays.equals(handle.password, providedPassword)) {
- byte[] knownHandle = handleMap.get(uid);
- if (knownHandle != null) {
- if (!Arrays.equals(knownHandle, enrolledPasswordHandle)) {
- throw new AssertionFailedError("Got correct but obsolete handle");
- }
- }
- refreshSid(uid, handle.sid, false);
- AuthToken token = new AuthToken(challenge, handle.sid);
- refreshAuthToken(uid, token);
- return GateKeeperResponse.createOkResponse(token.toBytes(), false);
- } else {
- return GateKeeperResponse.createGenericResponse(GateKeeperResponse.RESPONSE_ERROR);
- }
- }
-
- private void refreshAuthToken(int uid, AuthToken token) {
- authTokenMap.put(uid, token);
- }
-
- public AuthToken getAuthToken(int uid) {
- return authTokenMap.get(uid);
- }
-
- public AuthToken getAuthTokenForSid(long sid) {
- for(AuthToken token : authTokenMap.values()) {
- if (token.sid == sid) {
- return token;
- }
- }
- return null;
- }
-
- public void clearAuthToken(int uid) {
- authTokenMap.remove(uid);
- }
-
- @Override
- public IBinder asBinder() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void clearSecureUserId(int userId) throws RemoteException {
- sidMap.remove(userId);
- }
-
- @Override
- public long getSecureUserId(int userId) throws RemoteException {
- if (sidMap.containsKey(userId)) {
- return sidMap.get(userId);
- } else {
- return 0L;
- }
- }
-
- private void refreshSid(int uid, long sid, boolean force) {
- if (!sidMap.containsKey(uid) || force) {
- sidMap.put(uid, sid);
- } else{
- if (sidMap.get(uid) != sid) {
- throw new AssertionFailedError("Inconsistent SID");
- }
- }
- }
-
-}
diff --git a/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java b/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java
deleted file mode 100644
index 9dede3b..0000000
--- a/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server;
-
-import android.app.NotificationManager;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.os.UserManager;
-import android.os.storage.StorageManager;
-
-public class MockLockSettingsContext extends ContextWrapper {
-
- private UserManager mUserManager;
- private NotificationManager mNotificationManager;
- private DevicePolicyManager mDevicePolicyManager;
- private StorageManager mStorageManager;
-
- public MockLockSettingsContext(Context base, UserManager userManager,
- NotificationManager notificationManager, DevicePolicyManager devicePolicyManager,
- StorageManager storageManager) {
- super(base);
- mUserManager = userManager;
- mNotificationManager = notificationManager;
- mDevicePolicyManager = devicePolicyManager;
- mStorageManager = storageManager;
- }
-
- @Override
- public Object getSystemService(String name) {
- if (USER_SERVICE.equals(name)) {
- return mUserManager;
- } else if (NOTIFICATION_SERVICE.equals(name)) {
- return mNotificationManager;
- } else if (DEVICE_POLICY_SERVICE.equals(name)) {
- return mDevicePolicyManager;
- } else if (STORAGE_SERVICE.equals(name)) {
- return mStorageManager;
- } else {
- throw new RuntimeException("System service not mocked: " + name);
- }
- }
-
- @Override
- public void enforceCallingOrSelfPermission(String permission, String message) {
- // Skip permission checks for unit tests.
- }
-
-}
diff --git a/services/tests/servicestests/src/com/android/server/MockStorageManager.java b/services/tests/servicestests/src/com/android/server/MockStorageManager.java
deleted file mode 100644
index 3a17718..0000000
--- a/services/tests/servicestests/src/com/android/server/MockStorageManager.java
+++ /dev/null
@@ -1,508 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server;
-
-import android.content.pm.IPackageMoveObserver;
-import android.os.IBinder;
-import android.os.IProgressListener;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.os.storage.DiskInfo;
-import android.os.storage.IObbActionListener;
-import android.os.storage.IStorageEventListener;
-import android.os.storage.IStorageManager;
-import android.os.storage.IStorageShutdownObserver;
-import android.os.storage.StorageVolume;
-import android.os.storage.VolumeInfo;
-import android.os.storage.VolumeRecord;
-import android.util.ArrayMap;
-import android.util.Pair;
-
-import com.android.internal.os.AppFuseMount;
-
-import junit.framework.AssertionFailedError;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-public class MockStorageManager implements IStorageManager {
-
- private ArrayMap<Integer, ArrayList<Pair<byte[], byte[]>>> mAuth = new ArrayMap<>();
- private boolean mIgnoreBadUnlock;
-
- @Override
- public void addUserKeyAuth(int userId, int serialNumber, byte[] token, byte[] secret)
- throws RemoteException {
- getUserAuth(userId).add(new Pair<>(token, secret));
- }
-
- @Override
- public void fixateNewestUserKeyAuth(int userId) throws RemoteException {
- ArrayList<Pair<byte[], byte[]>> auths = mAuth.get(userId);
- Pair<byte[], byte[]> latest = auths.get(auths.size() - 1);
- auths.clear();
- auths.add(latest);
- }
-
- private ArrayList<Pair<byte[], byte[]>> getUserAuth(int userId) {
- if (!mAuth.containsKey(userId)) {
- ArrayList<Pair<byte[], byte[]>> auths = new ArrayList<Pair<byte[], byte[]>>();
- auths.add(new Pair(null, null));
- mAuth.put(userId, auths);
- }
- return mAuth.get(userId);
- }
-
- public byte[] getUserUnlockToken(int userId) {
- ArrayList<Pair<byte[], byte[]>> auths = getUserAuth(userId);
- if (auths.size() != 1) {
- throw new AssertionFailedError("More than one secret exists");
- }
- return auths.get(0).second;
- }
-
- public void unlockUser(int userId, byte[] secret, IProgressListener listener)
- throws RemoteException {
- listener.onStarted(userId, null);
- listener.onFinished(userId, null);
- ArrayList<Pair<byte[], byte[]>> auths = getUserAuth(userId);
- if (secret != null) {
- if (auths.size() > 1) {
- throw new AssertionFailedError("More than one secret exists");
- }
- Pair<byte[], byte[]> auth = auths.get(0);
- if ((!mIgnoreBadUnlock) && auth.second != null && !Arrays.equals(secret, auth.second)) {
- throw new AssertionFailedError("Invalid secret to unlock user");
- }
- } else {
- if (auths != null && auths.size() > 0) {
- throw new AssertionFailedError("Cannot unlock encrypted user with empty token");
- }
- }
- }
-
- public void setIgnoreBadUnlock(boolean ignore) {
- mIgnoreBadUnlock = ignore;
- }
-
- @Override
- public IBinder asBinder() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void registerListener(IStorageEventListener listener) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void unregisterListener(IStorageEventListener listener) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isUsbMassStorageConnected() throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setUsbMassStorageEnabled(boolean enable) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isUsbMassStorageEnabled() throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int mountVolume(String mountPoint) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void unmountVolume(String mountPoint, boolean force, boolean removeEncryption)
- throws RemoteException {
- throw new UnsupportedOperationException();
-
- }
-
- @Override
- public int formatVolume(String mountPoint) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int[] getStorageUsers(String path) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getVolumeState(String mountPoint) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int createSecureContainer(String id, int sizeMb, String fstype, String key, int ownerUid,
- boolean external) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int finalizeSecureContainer(String id) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int destroySecureContainer(String id, boolean force) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly)
- throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int unmountSecureContainer(String id, boolean force) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isSecureContainerMounted(String id) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int renameSecureContainer(String oldId, String newId) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getSecureContainerPath(String id) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String[] getSecureContainerList() throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void shutdown(IStorageShutdownObserver observer) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void finishMediaUpdate() throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void mountObb(String rawPath, String canonicalPath, String key, IObbActionListener token,
- int nonce) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce)
- throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isObbMounted(String rawPath) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getMountedObbPath(String rawPath) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isExternalStorageEmulated() throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int decryptStorage(String password) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int encryptStorage(int type, String password) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int changeEncryptionPassword(int type, String password) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public StorageVolume[] getVolumeList(int uid, String packageName, int flags)
- throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getSecureContainerFilesystemPath(String cid) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getEncryptionState() throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int verifyEncryptionPassword(String password) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int fixPermissionsSecureContainer(String id, int gid, String filename)
- throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int mkdirs(String callingPkg, String path) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getPasswordType() throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getPassword() throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void clearPassword() throws RemoteException {
- throw new UnsupportedOperationException();
-
- }
-
- @Override
- public void setField(String field, String contents) throws RemoteException {
- throw new UnsupportedOperationException();
-
- }
-
- @Override
- public String getField(String field) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int resizeSecureContainer(String id, int sizeMb, String key) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public long lastMaintenance() throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void runMaintenance() throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void waitForAsecScan() throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public DiskInfo[] getDisks() throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public VolumeInfo[] getVolumes(int flags) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public VolumeRecord[] getVolumeRecords(int flags) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void mount(String volId) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void unmount(String volId) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void format(String volId) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void partitionPublic(String diskId) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void partitionPrivate(String diskId) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void partitionMixed(String diskId, int ratio) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setVolumeNickname(String fsUuid, String nickname) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setVolumeUserFlags(String fsUuid, int flags, int mask) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void forgetVolume(String fsUuid) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void forgetAllVolumes() throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getPrimaryStorageUuid() throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback)
- throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public long benchmark(String volId) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setDebugFlags(int flags, int mask) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void createUserKey(int userId, int serialNumber, boolean ephemeral)
- throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void destroyUserKey(int userId) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret)
- throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void lockUserKey(int userId) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isUserKeyUnlocked(int userId) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags)
- throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void destroyUserStorage(String volumeUuid, int userId, int flags)
- throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isConvertibleToFBE() throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void fstrim(int flags) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public AppFuseMount mountProxyFileDescriptorBridge() throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode)
- throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public long getCacheQuotaBytes(String volumeUuid, int uid) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public long getCacheSizeBytes(String volumeUuid, int uid) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public long getAllocatableBytes(String path, int flags) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void allocateBytes(String path, long bytes, int flags) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void secdiscard(String path) throws RemoteException {
- throw new UnsupportedOperationException();
- }
-
-}
diff --git a/services/tests/servicestests/src/com/android/server/MockSyntheticPasswordManager.java b/services/tests/servicestests/src/com/android/server/MockSyntheticPasswordManager.java
deleted file mode 100644
index 93e3fc6..0000000
--- a/services/tests/servicestests/src/com/android/server/MockSyntheticPasswordManager.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server;
-
-import android.util.ArrayMap;
-
-import junit.framework.AssertionFailedError;
-
-import java.nio.ByteBuffer;
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
-import java.util.Arrays;
-
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
-
-public class MockSyntheticPasswordManager extends SyntheticPasswordManager {
-
- private MockGateKeeperService mGateKeeper;
-
- public MockSyntheticPasswordManager(LockSettingsStorage storage,
- MockGateKeeperService gatekeeper) {
- super(storage);
- mGateKeeper = gatekeeper;
- }
-
- private ArrayMap<String, byte[]> mBlobs = new ArrayMap<>();
-
- @Override
- protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] applicationId) {
- if (mBlobs.containsKey(blobKeyName) && !Arrays.equals(mBlobs.get(blobKeyName), blob)) {
- throw new AssertionFailedError("blobKeyName content is overwritten: " + blobKeyName);
- }
- ByteBuffer buffer = ByteBuffer.allocate(blob.length);
- buffer.put(blob, 0, blob.length);
- buffer.flip();
- int len;
- len = buffer.getInt();
- byte[] data = new byte[len];
- buffer.get(data);
- len = buffer.getInt();
- byte[] appId = new byte[len];
- buffer.get(appId);
- long sid = buffer.getLong();
- if (!Arrays.equals(appId, applicationId)) {
- throw new AssertionFailedError("Invalid application id");
- }
- if (sid != 0 && mGateKeeper.getAuthTokenForSid(sid) == null) {
- throw new AssertionFailedError("No valid auth token");
- }
- return data;
- }
-
- @Override
- protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] applicationId, long sid) {
- ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + data.length + Integer.BYTES
- + applicationId.length + Long.BYTES);
- buffer.putInt(data.length);
- buffer.put(data);
- buffer.putInt(applicationId.length);
- buffer.put(applicationId);
- buffer.putLong(sid);
- byte[] result = buffer.array();
- mBlobs.put(blobKeyName, result);
- return result;
- }
-
- @Override
- protected void destroySPBlobKey(String keyAlias) {
- }
-
- @Override
- protected long sidFromPasswordHandle(byte[] handle) {
- return new MockGateKeeperService.VerifyHandle(handle).sid;
- }
-
- @Override
- protected byte[] scrypt(String password, byte[] salt, int N, int r, int p, int outLen) {
- try {
- PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10, outLen * 8);
- SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
- return f.generateSecret(spec).getEncoded();
- } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
- e.printStackTrace();
- return null;
- }
- }
-
-}
diff --git a/services/tests/servicestests/src/com/android/server/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/SyntheticPasswordTests.java
deleted file mode 100644
index 3ec71e4..0000000
--- a/services/tests/servicestests/src/com/android/server/SyntheticPasswordTests.java
+++ /dev/null
@@ -1,349 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server;
-
-import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
-import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
-
-import android.os.RemoteException;
-import android.os.UserHandle;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.server.SyntheticPasswordManager.AuthenticationResult;
-import com.android.server.SyntheticPasswordManager.AuthenticationToken;
-
-
-/**
- * runtest frameworks-services -c com.android.server.SyntheticPasswordTests
- */
-public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- }
-
- public void testPasswordBasedSyntheticPassword() throws RemoteException {
- final int USER_ID = 10;
- final String PASSWORD = "user-password";
- final String BADPASSWORD = "bad-password";
- MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mStorage, mGateKeeperService);
- AuthenticationToken authToken = manager.newSyntheticPasswordAndSid(mGateKeeperService, null,
- null, USER_ID);
- long handle = manager.createPasswordBasedSyntheticPassword(mGateKeeperService, PASSWORD,
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, authToken, USER_ID);
-
- AuthenticationResult result = manager.unwrapPasswordBasedSyntheticPassword(mGateKeeperService, handle, PASSWORD, USER_ID);
- assertEquals(result.authToken.deriveKeyStorePassword(), authToken.deriveKeyStorePassword());
-
- result = manager.unwrapPasswordBasedSyntheticPassword(mGateKeeperService, handle, BADPASSWORD, USER_ID);
- assertNull(result.authToken);
- }
-
- private void disableSyntheticPassword(int userId) throws RemoteException {
- mService.setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM);
- }
-
- private void enableSyntheticPassword(int userId) throws RemoteException {
- mService.setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1, UserHandle.USER_SYSTEM);
- }
-
- private boolean hasSyntheticPassword(int userId) throws RemoteException {
- return mService.getLong(SYNTHETIC_PASSWORD_HANDLE_KEY, 0, userId) != 0;
- }
-
- public void testPasswordMigration() throws RemoteException {
- final String PASSWORD = "testPasswordMigration-password";
-
- disableSyntheticPassword(PRIMARY_USER_ID);
- mService.setLockCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
- long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
- final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
- enableSyntheticPassword(PRIMARY_USER_ID);
- // Performs migration
- assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
- assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
- assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
-
- // SP-based verification
- assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
- assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
- }
-
- private void initializeCredentialUnderSP(String password, int userId) throws RemoteException {
- enableSyntheticPassword(userId);
- mService.setLockCredential(password, password != null ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD : LockPatternUtils.CREDENTIAL_TYPE_NONE, null, userId);
- }
-
- public void testSyntheticPasswordChangeCredential() throws RemoteException {
- final String PASSWORD = "testSyntheticPasswordChangeCredential-password";
- final String NEWPASSWORD = "testSyntheticPasswordChangeCredential-newpassword";
-
- initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
- long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
- mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD, PRIMARY_USER_ID);
- mGateKeeperService.clearSecureUserId(PRIMARY_USER_ID);
- assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
- assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
- }
-
- public void testSyntheticPasswordVerifyCredential() throws RemoteException {
- final String PASSWORD = "testSyntheticPasswordVerifyCredential-password";
- final String BADPASSWORD = "testSyntheticPasswordVerifyCredential-badpassword";
-
- initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
- assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
-
- assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
- mService.verifyCredential(BADPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
- }
-
- public void testSyntheticPasswordClearCredential() throws RemoteException {
- final String PASSWORD = "testSyntheticPasswordClearCredential-password";
- final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
-
- initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
- long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
- // clear password
- mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD, PRIMARY_USER_ID);
- assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
-
- // set a new password
- mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
- assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
- assertNotSame(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
- }
-
- public void testSyntheticPasswordClearCredentialUntrusted() throws RemoteException {
- final String PASSWORD = "testSyntheticPasswordClearCredential-password";
- final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
-
- initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
- long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
- // clear password
- mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
- assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
-
- // set a new password
- mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
- assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
- assertNotSame(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
- }
-
- public void testSyntheticPasswordChangeCredentialUntrusted() throws RemoteException {
- final String PASSWORD = "testSyntheticPasswordClearCredential-password";
- final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
-
- initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
- long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
- // Untrusted change password
- mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
- assertNotSame(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
- assertNotSame(sid ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
-
- // Verify the password
- assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
- }
-
-
- public void testManagedProfileUnifiedChallengeMigration() throws RemoteException {
- final String UnifiedPassword = "testManagedProfileUnifiedChallengeMigration-pwd";
- disableSyntheticPassword(PRIMARY_USER_ID);
- disableSyntheticPassword(MANAGED_PROFILE_USER_ID);
- mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
- mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
- final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
- final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
- final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
- final byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
- assertTrue(primarySid != 0);
- assertTrue(profileSid != 0);
- assertTrue(profileSid != primarySid);
-
- // do migration
- enableSyntheticPassword(PRIMARY_USER_ID);
- enableSyntheticPassword(MANAGED_PROFILE_USER_ID);
- assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
-
- // verify
- assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
- assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
- assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
- assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
- assertArrayNotSame(profileStorageKey, mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID));
- assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
- assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID));
- }
-
- public void testManagedProfileSeparateChallengeMigration() throws RemoteException {
- final String primaryPassword = "testManagedProfileSeparateChallengeMigration-primary";
- final String profilePassword = "testManagedProfileSeparateChallengeMigration-profile";
- mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
- mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, MANAGED_PROFILE_USER_ID);
- final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
- final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
- final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
- final byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
- assertTrue(primarySid != 0);
- assertTrue(profileSid != 0);
- assertTrue(profileSid != primarySid);
-
- // do migration
- enableSyntheticPassword(PRIMARY_USER_ID);
- enableSyntheticPassword(MANAGED_PROFILE_USER_ID);
- assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
- assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, MANAGED_PROFILE_USER_ID).getResponseCode());
-
- // verify
- assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
- assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, MANAGED_PROFILE_USER_ID).getResponseCode());
- assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
- assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
- assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
- assertArrayNotSame(profileStorageKey, mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID));
- assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
- assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID));
- }
-
- public void testTokenBasedResetPassword() throws RemoteException {
- final String PASSWORD = "password";
- final String PATTERN = "123654";
- final String TOKEN = "some-high-entropy-secure-token";
- initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
- final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
-
- long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
- assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
-
- mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
- assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
-
- mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
-
- assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode());
- assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
- }
-
- public void testTokenBasedClearPassword() throws RemoteException {
- final String PASSWORD = "password";
- final String PATTERN = "123654";
- final String TOKEN = "some-high-entropy-secure-token";
- initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
- final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
-
- long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
- assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
-
- mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
- assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
-
- mService.setLockCredentialWithToken(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
- mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
-
- assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode());
- assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
- }
-
- public void testTokenBasedResetPasswordAfterCredentialChanges() throws RemoteException {
- final String PASSWORD = "password";
- final String PATTERN = "123654";
- final String NEWPASSWORD = "password";
- final String TOKEN = "some-high-entropy-secure-token";
- initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
- final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
-
- long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
- assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
-
- mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
- assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
-
- mService.setLockCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, PASSWORD, PRIMARY_USER_ID);
-
- mService.setLockCredentialWithToken(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID);
-
- assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
- assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
- }
-
- public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration() throws RemoteException {
- final String TOKEN = "some-high-entropy-secure-token";
- enableSyntheticPassword(PRIMARY_USER_ID);
- long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
- assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
- assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
- assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
- }
-
- public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNoMigration() throws RemoteException {
- final String TOKEN = "some-high-entropy-secure-token";
- initializeCredentialUnderSP(null, PRIMARY_USER_ID);
- long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
- assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
- assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
- assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
- }
-
- public void testEscrowTokenActivatedLaterWithUserPasswordNeedsMigration() throws RemoteException {
- final String TOKEN = "some-high-entropy-secure-token";
- final String PASSWORD = "password";
- // Set up pre-SP user password
- disableSyntheticPassword(PRIMARY_USER_ID);
- mService.setLockCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
- PRIMARY_USER_ID);
- enableSyntheticPassword(PRIMARY_USER_ID);
-
- long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
- // Token not activated immediately since user password exists
- assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
- // Activate token (password gets migrated to SP at the same time)
- assertEquals(VerifyCredentialResponse.RESPONSE_OK,
- mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
- PRIMARY_USER_ID).getResponseCode());
- // Verify token is activated
- assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
- }
-
- // b/34600579
- //TODO: add non-migration work profile case, and unify/un-unify transition.
- //TODO: test token after user resets password
- //TODO: test token based reset after unified work challenge
- //TODO: test clear password after unified work challenge
-}
-
diff --git a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
index 2f202d98..da30c11 100644
--- a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
@@ -34,6 +34,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.AppOpsService;
+import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
@@ -59,7 +60,6 @@
* Run: adb shell am instrument -e class com.android.server.am.CoreSettingsObserverTest -w \
* com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
*/
-@Ignore
@SmallTest
@RunWith(AndroidJUnit4.class)
public class CoreSettingsObserverTest {
@@ -79,11 +79,17 @@
@BeforeClass
public static void setupOnce() {
+ FakeSettingsProvider.clearSettingsProvider();
CoreSettingsObserver.sSecureSettingToTypeMap.put(TEST_SETTING_SECURE_INT, int.class);
CoreSettingsObserver.sGlobalSettingToTypeMap.put(TEST_SETTING_GLOBAL_FLOAT, float.class);
CoreSettingsObserver.sSystemSettingToTypeMap.put(TEST_SETTING_SYSTEM_STRING, String.class);
}
+ @AfterClass
+ public static void tearDownOnce() {
+ FakeSettingsProvider.clearSettingsProvider();
+ }
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
diff --git a/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java b/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java
index f287386..54f93a8 100644
--- a/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java
@@ -32,6 +32,7 @@
import android.os.Looper;
import android.os.UserHandle;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
import android.util.Pair;
import org.mockito.ArgumentMatchers;
@@ -40,6 +41,7 @@
import java.util.Arrays;
import java.util.Collections;
+@SmallTest
public class PersistentConnectionTest extends AndroidTestCase {
private static class MyConnection extends PersistentConnection<IDeviceAdminService> {
public long uptimeMillis = 12345;
diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
new file mode 100644
index 0000000..27ef9d7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
@@ -0,0 +1,915 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.backup.BackupManager;
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IBackupObserver;
+import android.app.backup.IFullBackupRestoreObserver;
+import android.app.backup.ISelectBackupTransportCallback;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TrampolineTest {
+ private static final String PACKAGE_NAME = "some.package.name";
+ private static final String TRANSPORT_NAME = "some.transport.name";
+ private static final String CURRENT_PASSWORD = "current_password";
+ private static final String NEW_PASSWORD = "new_password";
+ private static final String ENCRYPTION_PASSWORD = "encryption_password";
+ private static final String DATA_MANAGEMENT_LABEL = "data_management_label";
+ private static final String DESTINATION_STRING = "destination_string";
+ private static final String[] PACKAGE_NAMES =
+ new String[]{"some.package.name._1", "some.package.name._2"};
+ private static final String[] TRANSPORTS =
+ new String[]{"some.transport.name._1", "some.transport.name._2"};
+ private static final ComponentName TRANSPORT_COMPONENT_NAME = new ComponentName("package",
+ "class");
+ private static final ComponentName[] TRANSPORT_COMPONENTS = new ComponentName[]{
+ new ComponentName("package1", "class1"),
+ new ComponentName("package2", "class2")
+ };
+ private final int NON_USER_SYSTEM = UserHandle.USER_SYSTEM + 1;
+
+ @Mock private BackupManagerService mBackupManagerServiceMock;
+ @Mock private RefactoredBackupManagerService mRefactoredBackupManagerServiceMock;
+ @Mock private Context mContextMock;
+ @Mock private File mSuppressFileMock;
+ @Mock private File mSuppressFileParentMock;
+ @Mock private IBinder mAgentMock;
+ @Mock private ParcelFileDescriptor mParcelFileDescriptorMock;
+ @Mock private IFullBackupRestoreObserver mFullBackupRestoreObserverMock;
+ @Mock private IBackupObserver mBackupObserverMock;
+ @Mock private IBackupManagerMonitor mBackupManagerMonitorMock;
+ @Mock private PrintWriter mPrintWriterMock;
+
+ private FileDescriptor mFileDescriptorStub = new FileDescriptor();
+
+ private TrampolineTestable mTrampoline;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ TrampolineTestable.sRefactoredBackupManagerServiceMock =
+ mRefactoredBackupManagerServiceMock;
+ TrampolineTestable.sBackupManagerServiceMock = mBackupManagerServiceMock;
+ TrampolineTestable.sSuppressFile = mSuppressFileMock;
+ TrampolineTestable.sCallingUid = Process.SYSTEM_UID;
+ TrampolineTestable.sRefactoredServiceEnabled = false;
+ TrampolineTestable.sBackupDisabled = false;
+
+ when(mSuppressFileMock.getParentFile()).thenReturn(mSuppressFileParentMock);
+
+ mTrampoline = new TrampolineTestable(mContextMock);
+ }
+
+ @Test
+ public void constructor_createsSuppressFileDirectory() {
+ verify(mSuppressFileParentMock).mkdirs();
+ }
+
+ @Test
+ public void initialize_forUserSystem_successfullyInitialized() {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+
+ assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ }
+
+ // The BackupManagerService can only be initialized by USER_SYSTEM, so we check that if any
+ // other user trying to initialize it leaves it non-active.
+ @Test
+ public void initialize_forNonUserSystem_nonInitialized() {
+ mTrampoline.initialize(NON_USER_SYSTEM);
+
+ assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+ }
+
+ @Test
+ public void initialize_globallyDisabled_nonInitialized() {
+ TrampolineTestable.sBackupDisabled = true;
+
+ TrampolineTestable trampoline = new TrampolineTestable(mContextMock);
+ trampoline.initialize(UserHandle.USER_SYSTEM);
+
+ assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ }
+
+ // Verify that BackupManagerService is not initialized if suppress file exists.
+ @Test
+ public void initialize_suppressFileExists_nonInitialized() {
+ when(mSuppressFileMock.exists()).thenReturn(true);
+
+ TrampolineTestable trampoline = new TrampolineTestable(mContextMock);
+ trampoline.initialize(UserHandle.USER_SYSTEM);
+
+ assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void isBackupServiceActive_calledBeforeInitialize_returnsFalse() {
+ assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void createService_refactoredServiceEnabled() {
+ TrampolineTestable.sRefactoredServiceEnabled = true;
+
+ assertEquals(mRefactoredBackupManagerServiceMock, mTrampoline.createService());
+ }
+
+ @Test
+ public void createService_refactoredServiceDisabled() {
+ TrampolineTestable.sRefactoredServiceEnabled = false;
+
+ assertEquals(mBackupManagerServiceMock, mTrampoline.createService());
+ }
+
+ @Test
+ public void setBackupServiceActive_callerSystemUid_serviceCreated() {
+ TrampolineTestable.sCallingUid = Process.SYSTEM_UID;
+
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+
+ assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void setBackupServiceActive_callerRootUid_serviceCreated() {
+ TrampolineTestable.sCallingUid = Process.ROOT_UID;
+
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+
+ assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void setBackupServiceActive_callerNonRootNonSystem_securityExceptionThrown() {
+ TrampolineTestable.sCallingUid = Process.FIRST_APPLICATION_UID;
+
+ try {
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ fail();
+ } catch (SecurityException expected) {
+ }
+
+ assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void setBackupServiceActive_backupDisabled_ignored() {
+ TrampolineTestable.sBackupDisabled = true;
+ TrampolineTestable trampoline = new TrampolineTestable(mContextMock);
+
+ trampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+
+ assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void setBackupServiceActive_nonUserSystem_ignored() {
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+
+ assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+ }
+
+ @Test
+ public void setBackupServiceActive_alreadyActive_ignored() {
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertEquals(1, mTrampoline.getCreateServiceCallsCount());
+
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertEquals(1, mTrampoline.getCreateServiceCallsCount());
+ }
+
+ @Test
+ public void setBackupServiceActive_makeActive_serviceCreatedAndSuppressFileDeleted() {
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+
+ assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ verify(mSuppressFileMock).delete();
+ }
+
+ @Test
+ public void setBackupServiceActive_makeNonActive_serviceDeletedAndSuppressFileCreated()
+ throws IOException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+
+ assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ verify(mSuppressFileMock).createNewFile();
+ }
+
+ @Test
+ public void
+ setBackupServiceActive_makeNonActive_serviceDeletedAndSuppressFileCreated_ioExceptionHandled()
+ throws IOException {
+ when(mSuppressFileMock.createNewFile()).thenThrow(new IOException());
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+
+ assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ verify(mSuppressFileMock).createNewFile();
+ }
+
+ @Test
+ public void setBackupServiceActive_makeNonActive_alreadyNonActive_ignored() throws IOException {
+ reset(mSuppressFileMock);
+
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+
+ verifyNoMoreInteractions(mSuppressFileMock);
+ }
+
+ @Test
+ public void dataChanged_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.dataChanged(PACKAGE_NAME);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void dataChanged_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.dataChanged(PACKAGE_NAME);
+ verify(mBackupManagerServiceMock).dataChanged(PACKAGE_NAME);
+ }
+
+ @Test
+ public void clearBackupData_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.clearBackupData(TRANSPORT_NAME, PACKAGE_NAME);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void clearBackupData_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.clearBackupData(TRANSPORT_NAME, PACKAGE_NAME);
+ verify(mBackupManagerServiceMock).clearBackupData(TRANSPORT_NAME, PACKAGE_NAME);
+ }
+
+ @Test
+ public void agentConnected_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.agentConnected(PACKAGE_NAME, mAgentMock);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void agentConnected_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.agentConnected(PACKAGE_NAME, mAgentMock);
+ verify(mBackupManagerServiceMock).agentConnected(PACKAGE_NAME, mAgentMock);
+ }
+
+ @Test
+ public void agentDisconnected_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.agentDisconnected(PACKAGE_NAME);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void agentDisconnected_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.agentDisconnected(PACKAGE_NAME);
+ verify(mBackupManagerServiceMock).agentDisconnected(PACKAGE_NAME);
+ }
+
+ @Test
+ public void restoreAtInstall_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.restoreAtInstall(PACKAGE_NAME, 123);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void restoreAtInstall_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.restoreAtInstall(PACKAGE_NAME, 123);
+ verify(mBackupManagerServiceMock).restoreAtInstall(PACKAGE_NAME, 123);
+ }
+
+ @Test
+ public void setBackupEnabled_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.setBackupEnabled(true);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void setBackupEnabled_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.setBackupEnabled(true);
+ verify(mBackupManagerServiceMock).setBackupEnabled(true);
+ }
+
+ @Test
+ public void setAutoRestore_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.setAutoRestore(true);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void setAutoRestore_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.setAutoRestore(true);
+ verify(mBackupManagerServiceMock).setAutoRestore(true);
+ }
+
+ @Test
+ public void setBackupProvisioned_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.setBackupProvisioned(true);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void setBackupProvisioned_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.setBackupProvisioned(true);
+ verify(mBackupManagerServiceMock).setBackupProvisioned(true);
+ }
+
+ @Test
+ public void isBackupEnabled_calledBeforeInitialize_ignored() throws RemoteException {
+ assertFalse(mTrampoline.isBackupEnabled());
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void isBackupEnabled_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.isBackupEnabled();
+ verify(mBackupManagerServiceMock).isBackupEnabled();
+ }
+
+ @Test
+ public void setBackupPassword_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.setBackupPassword(CURRENT_PASSWORD, NEW_PASSWORD);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void setBackupPassword_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.setBackupPassword(CURRENT_PASSWORD, NEW_PASSWORD);
+ verify(mBackupManagerServiceMock).setBackupPassword(CURRENT_PASSWORD, NEW_PASSWORD);
+ }
+
+ @Test
+ public void hasBackupPassword_calledBeforeInitialize_ignored() throws RemoteException {
+ assertFalse(mTrampoline.hasBackupPassword());
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void hasBackupPassword_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.hasBackupPassword();
+ verify(mBackupManagerServiceMock).hasBackupPassword();
+ }
+
+ @Test
+ public void backupNow_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.backupNow();
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void backupNow_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.backupNow();
+ verify(mBackupManagerServiceMock).backupNow();
+ }
+
+ @Test
+ public void adbBackup_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.adbBackup(mParcelFileDescriptorMock, true, true, true, true, true, true, true,
+ true,
+ PACKAGE_NAMES);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void adbBackup_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.adbBackup(mParcelFileDescriptorMock, true, true, true, true, true, true, true,
+ true,
+ PACKAGE_NAMES);
+ verify(mBackupManagerServiceMock).adbBackup(mParcelFileDescriptorMock, true, true, true,
+ true,
+ true, true, true, true, PACKAGE_NAMES);
+ }
+
+ @Test
+ public void fullTransportBackup_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.fullTransportBackup(PACKAGE_NAMES);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void fullTransportBackup_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.fullTransportBackup(PACKAGE_NAMES);
+ verify(mBackupManagerServiceMock).fullTransportBackup(PACKAGE_NAMES);
+ }
+
+ @Test
+ public void adbRestore_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.adbRestore(mParcelFileDescriptorMock);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void adbRestore_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.adbRestore(mParcelFileDescriptorMock);
+ verify(mBackupManagerServiceMock).adbRestore(mParcelFileDescriptorMock);
+ }
+
+ @Test
+ public void acknowledgeFullBackupOrRestore_calledBeforeInitialize_ignored()
+ throws RemoteException {
+ mTrampoline.acknowledgeFullBackupOrRestore(123, true, CURRENT_PASSWORD, ENCRYPTION_PASSWORD,
+ mFullBackupRestoreObserverMock);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void acknowledgeFullBackupOrRestore_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.acknowledgeFullBackupOrRestore(123, true, CURRENT_PASSWORD, ENCRYPTION_PASSWORD,
+ mFullBackupRestoreObserverMock);
+ verify(mBackupManagerServiceMock).acknowledgeAdbBackupOrRestore(123, true, CURRENT_PASSWORD,
+ ENCRYPTION_PASSWORD, mFullBackupRestoreObserverMock);
+ }
+
+ @Test
+ public void getCurrentTransport_calledBeforeInitialize_ignored() throws RemoteException {
+ assertNull(mTrampoline.getCurrentTransport());
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void getCurrentTransport_forwarded() throws RemoteException {
+ when(mBackupManagerServiceMock.getCurrentTransport()).thenReturn(TRANSPORT_NAME);
+
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+
+ assertEquals(TRANSPORT_NAME, mTrampoline.getCurrentTransport());
+ verify(mBackupManagerServiceMock).getCurrentTransport();
+ }
+
+ @Test
+ public void listAllTransports_calledBeforeInitialize_ignored() throws RemoteException {
+ assertNull(mTrampoline.listAllTransports());
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void listAllTransports_forwarded() throws RemoteException {
+ when(mBackupManagerServiceMock.listAllTransports()).thenReturn(TRANSPORTS);
+
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ assertEquals(TRANSPORTS, mTrampoline.listAllTransports());
+ verify(mBackupManagerServiceMock).listAllTransports();
+ }
+
+ @Test
+ public void listAllTransportComponents_calledBeforeInitialize_ignored() throws RemoteException {
+ assertNull(mTrampoline.listAllTransportComponents());
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void listAllTransportComponents_forwarded() throws RemoteException {
+ when(mBackupManagerServiceMock.listAllTransportComponents()).thenReturn(
+ TRANSPORT_COMPONENTS);
+
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ assertEquals(TRANSPORT_COMPONENTS, mTrampoline.listAllTransportComponents());
+ verify(mBackupManagerServiceMock).listAllTransportComponents();
+ }
+
+ @Test
+ public void getTransportWhitelist_calledBeforeInitialize_ignored() throws RemoteException {
+ assertNull(mTrampoline.getTransportWhitelist());
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void getTransportWhitelist_forwarded() throws RemoteException {
+ when(mBackupManagerServiceMock.getTransportWhitelist()).thenReturn(TRANSPORTS);
+
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ assertEquals(TRANSPORTS, mTrampoline.getTransportWhitelist());
+ verify(mBackupManagerServiceMock).getTransportWhitelist();
+ }
+
+ @Test
+ public void selectBackupTransport_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.selectBackupTransport(TRANSPORT_NAME);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void selectBackupTransport_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.selectBackupTransport(TRANSPORT_NAME);
+ verify(mBackupManagerServiceMock).selectBackupTransport(TRANSPORT_NAME);
+ }
+
+ @Test
+ public void selectBackupTransportAsync_calledBeforeInitialize_ignored() throws Exception {
+ LinkedBlockingQueue<Integer> q = new LinkedBlockingQueue();
+ mTrampoline.selectBackupTransportAsync(
+ TRANSPORT_COMPONENT_NAME,
+ new ISelectBackupTransportCallback() {
+ @Override
+ public void onSuccess(String transportName) throws RemoteException {
+
+ }
+
+ @Override
+ public void onFailure(int reason) throws RemoteException {
+ q.offer(reason);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ });
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ Integer errorCode = q.poll(5, TimeUnit.SECONDS);
+ assertNotNull(errorCode);
+ assertEquals(BackupManager.ERROR_BACKUP_NOT_ALLOWED, (int) errorCode);
+ }
+
+ @Test
+ public void selectBackupTransportAsync_calledBeforeInitialize_ignored_nullListener()
+ throws Exception {
+ mTrampoline.selectBackupTransportAsync(TRANSPORT_COMPONENT_NAME, null);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ // No crash.
+ }
+
+ @Test
+ public void selectBackupTransportAsync_calledBeforeInitialize_ignored_listenerThrowException()
+ throws Exception {
+ mTrampoline.selectBackupTransportAsync(
+ TRANSPORT_COMPONENT_NAME,
+ new ISelectBackupTransportCallback() {
+ @Override
+ public void onSuccess(String transportName) throws RemoteException {
+
+ }
+
+ @Override
+ public void onFailure(int reason) throws RemoteException {
+ throw new RemoteException("Crash");
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ });
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ // No crash.
+ }
+
+ @Test
+ public void selectBackupTransportAsync_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.selectBackupTransportAsync(TRANSPORT_COMPONENT_NAME, null);
+ verify(mBackupManagerServiceMock).selectBackupTransportAsync(TRANSPORT_COMPONENT_NAME,
+ null);
+ }
+
+ @Test
+ public void getConfigurationIntent_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.getConfigurationIntent(TRANSPORT_NAME);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void getConfigurationIntent_forwarded() throws RemoteException {
+ Intent configurationIntentStub = new Intent();
+ when(mBackupManagerServiceMock.getConfigurationIntent(TRANSPORT_NAME)).thenReturn(
+ configurationIntentStub);
+
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ assertEquals(configurationIntentStub, mTrampoline.getConfigurationIntent(TRANSPORT_NAME));
+ verify(mBackupManagerServiceMock).getConfigurationIntent(TRANSPORT_NAME);
+ }
+
+ @Test
+ public void getDestinationString_calledBeforeInitialize_ignored() throws RemoteException {
+ assertNull(mTrampoline.getDestinationString(TRANSPORT_NAME));
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void getDestinationString_forwarded() throws RemoteException {
+ when(mBackupManagerServiceMock.getDestinationString(TRANSPORT_NAME)).thenReturn(
+ DESTINATION_STRING);
+
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ assertEquals(DESTINATION_STRING, mTrampoline.getDestinationString(TRANSPORT_NAME));
+ verify(mBackupManagerServiceMock).getDestinationString(TRANSPORT_NAME);
+ }
+
+ @Test
+ public void getDataManagementIntent_calledBeforeInitialize_ignored() throws RemoteException {
+ assertNull(mTrampoline.getDataManagementIntent(TRANSPORT_NAME));
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void getDataManagementIntent_forwarded() throws RemoteException {
+ Intent dataManagementIntent = new Intent();
+ when(mBackupManagerServiceMock.getDataManagementIntent(TRANSPORT_NAME)).thenReturn(
+ dataManagementIntent);
+
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ assertEquals(dataManagementIntent, mTrampoline.getDataManagementIntent(TRANSPORT_NAME));
+ verify(mBackupManagerServiceMock).getDataManagementIntent(TRANSPORT_NAME);
+ }
+
+ @Test
+ public void getDataManagementLabel_calledBeforeInitialize_ignored() throws RemoteException {
+ assertNull(mTrampoline.getDataManagementLabel(TRANSPORT_NAME));
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void getDataManagementLabel_forwarded() throws RemoteException {
+ when(mBackupManagerServiceMock.getDataManagementLabel(TRANSPORT_NAME)).thenReturn(
+ DATA_MANAGEMENT_LABEL);
+
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ assertEquals(DATA_MANAGEMENT_LABEL, mTrampoline.getDataManagementLabel(TRANSPORT_NAME));
+ verify(mBackupManagerServiceMock).getDataManagementLabel(TRANSPORT_NAME);
+ }
+
+ @Test
+ public void beginRestoreSession_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.beginRestoreSession(PACKAGE_NAME, TRANSPORT_NAME);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void beginRestoreSession_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.beginRestoreSession(PACKAGE_NAME, TRANSPORT_NAME);
+ verify(mBackupManagerServiceMock).beginRestoreSession(PACKAGE_NAME, TRANSPORT_NAME);
+ }
+
+ @Test
+ public void opComplete_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.opComplete(1, 2);
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void opComplete_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.opComplete(1, 2);
+ verify(mBackupManagerServiceMock).opComplete(1, 2);
+ }
+
+ @Test
+ public void getAvailableRestoreToken_calledBeforeInitialize_ignored() throws RemoteException {
+ assertEquals(0, mTrampoline.getAvailableRestoreToken(PACKAGE_NAME));
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void getAvailableRestoreToken_forwarded() throws RemoteException {
+ when(mBackupManagerServiceMock.getAvailableRestoreToken(PACKAGE_NAME)).thenReturn(123L);
+
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ assertEquals(123, mTrampoline.getAvailableRestoreToken(PACKAGE_NAME));
+ verify(mBackupManagerServiceMock).getAvailableRestoreToken(PACKAGE_NAME);
+ }
+
+ @Test
+ public void isAppEligibleForBackup_calledBeforeInitialize_ignored() throws RemoteException {
+ assertFalse(mTrampoline.isAppEligibleForBackup(PACKAGE_NAME));
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void isAppEligibleForBackup_forwarded() throws RemoteException {
+ when(mBackupManagerServiceMock.isAppEligibleForBackup(PACKAGE_NAME)).thenReturn(true);
+
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ assertTrue(mTrampoline.isAppEligibleForBackup(PACKAGE_NAME));
+ verify(mBackupManagerServiceMock).isAppEligibleForBackup(PACKAGE_NAME);
+ }
+
+ @Test
+ public void requestBackup_calledBeforeInitialize_ignored() throws RemoteException {
+ assertEquals(BackupManager.ERROR_BACKUP_NOT_ALLOWED, mTrampoline.requestBackup(
+ PACKAGE_NAMES, mBackupObserverMock, mBackupManagerMonitorMock, 123));
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void requestBackup_forwarded() throws RemoteException {
+ when(mBackupManagerServiceMock.requestBackup(PACKAGE_NAMES, mBackupObserverMock,
+ mBackupManagerMonitorMock, 123)).thenReturn(456);
+
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ assertEquals(456, mTrampoline.requestBackup(PACKAGE_NAMES, mBackupObserverMock,
+ mBackupManagerMonitorMock, 123));
+ verify(mBackupManagerServiceMock).requestBackup(PACKAGE_NAMES, mBackupObserverMock,
+ mBackupManagerMonitorMock, 123);
+ }
+
+ @Test
+ public void cancelBackups_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.cancelBackups();
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void cancelBackups_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.cancelBackups();
+ verify(mBackupManagerServiceMock).cancelBackups();
+ }
+
+ @Test
+ public void beginFullBackup_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.beginFullBackup(new FullBackupJob());
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void beginFullBackup_forwarded() throws RemoteException {
+ FullBackupJob fullBackupJob = new FullBackupJob();
+ when(mBackupManagerServiceMock.beginFullBackup(fullBackupJob)).thenReturn(true);
+
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ assertTrue(mTrampoline.beginFullBackup(fullBackupJob));
+ verify(mBackupManagerServiceMock).beginFullBackup(fullBackupJob);
+ }
+
+ @Test
+ public void endFullBackup_calledBeforeInitialize_ignored() throws RemoteException {
+ mTrampoline.endFullBackup();
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void endFullBackup_forwarded() throws RemoteException {
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+ mTrampoline.endFullBackup();
+ verify(mBackupManagerServiceMock).endFullBackup();
+ }
+
+ @Test
+ public void dump_callerDoesNotHavePermission_ignored() throws RemoteException {
+ when(mContextMock.checkCallingOrSelfPermission(
+ android.Manifest.permission.DUMP)).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+
+ mTrampoline.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]);
+
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void dump_calledBeforeInitialize_ignored() throws RemoteException {
+ when(mContextMock.checkCallingOrSelfPermission(
+ android.Manifest.permission.DUMP)).thenReturn(
+ PackageManager.PERMISSION_GRANTED);
+
+ mTrampoline.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]);
+
+ verifyNoMoreInteractions(mBackupManagerServiceMock);
+ }
+
+ @Test
+ public void dump_callerHasPermission_forwarded() throws RemoteException {
+ when(mContextMock.checkCallingOrSelfPermission(
+ android.Manifest.permission.DUMP)).thenReturn(
+ PackageManager.PERMISSION_GRANTED);
+
+ mTrampoline.initialize(UserHandle.USER_SYSTEM);
+
+ mTrampoline.dump(mFileDescriptorStub, mPrintWriterMock, null);
+
+ verify(mBackupManagerServiceMock).dump(mFileDescriptorStub, mPrintWriterMock, null);
+ }
+
+ private static class TrampolineTestable extends Trampoline {
+ static boolean sBackupDisabled = false;
+ static boolean sRefactoredServiceEnabled = false;
+ static File sSuppressFile = null;
+ static int sCallingUid = -1;
+ static BackupManagerService sBackupManagerServiceMock = null;
+ static RefactoredBackupManagerService sRefactoredBackupManagerServiceMock = null;
+ private int mCreateServiceCallsCount = 0;
+
+ TrampolineTestable(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected BackupManagerServiceInterface createService() {
+ mCreateServiceCallsCount++;
+ return super.createService();
+ }
+
+ @Override
+ public boolean isBackupDisabled() {
+ return sBackupDisabled;
+ }
+
+ @Override
+ public File getSuppressFile() {
+ return sSuppressFile;
+ }
+
+ @Override
+ protected int binderGetCallingUid() {
+ return sCallingUid;
+ }
+
+ @Override
+ protected boolean isRefactoredServiceEnabled() {
+ return sRefactoredServiceEnabled;
+ }
+
+ @Override
+ protected BackupManagerServiceInterface createRefactoredBackupManagerService() {
+ return sRefactoredBackupManagerServiceMock;
+ }
+
+ @Override
+ protected BackupManagerServiceInterface createBackupManagerService() {
+ return sBackupManagerServiceMock;
+ }
+
+ int getCreateServiceCallsCount() {
+ return mCreateServiceCallsCount;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/restore/PerformAdbRestoreTaskTest.java b/services/tests/servicestests/src/com/android/server/backup/restore/PerformAdbRestoreTaskTest.java
new file mode 100644
index 0000000..05f4c13
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/restore/PerformAdbRestoreTaskTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.restore;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.servicestests.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.io.InputStream;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class PerformAdbRestoreTaskTest {
+ private Context mContext;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = InstrumentationRegistry.getContext();
+ }
+
+ @Test
+ public void parseBackupFileAndReturnTarStream_backupNotEncrypted_returnsNonNull()
+ throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_no_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+
+ assertThat(tarInputStream).isNotNull();
+ }
+
+ @Test
+ public void
+ parseBackupFileAndReturnTarStream_backupEncryptedAndPasswordProvided_returnsNonNull()
+ throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_with_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, "123");
+
+ assertThat(tarInputStream).isNotNull();
+ }
+
+ @Test
+ public void
+ parseBackupFileAndReturnTarStream_backupEncryptedAndPasswordNotProvided_returnsNull()
+ throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_with_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+
+ assertThat(tarInputStream).isNull();
+ }
+
+ @Test
+ public void
+ parseBackupFileAndReturnTarStream_backupEncryptedAndIncorrectPassword_returnsNull()
+ throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_with_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, "1234");
+
+ assertThat(tarInputStream).isNull();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
new file mode 100644
index 0000000..45b107d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/testutils/PackageManagerStub.java
@@ -0,0 +1,1008 @@
+package com.android.server.backup.testutils;
+
+import android.app.PackageInstallObserver;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ChangedPackages;
+import android.content.pm.FeatureInfo;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageInstallObserver;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstantAppInfo;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.KeySet;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VersionedPackage;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.storage.VolumeInfo;
+
+import java.util.List;
+
+/**
+ * Stub for PackageManager to use in tests.
+ */
+public class PackageManagerStub extends PackageManager {
+ public static PackageInfo sPackageInfo;
+
+ @Override
+ public PackageInfo getPackageInfo(String packageName, int flags)
+ throws NameNotFoundException {
+ if (sPackageInfo == null) {
+ throw new NameNotFoundException();
+ }
+
+ return sPackageInfo;
+ }
+
+ @Override
+ public PackageInfo getPackageInfo(VersionedPackage versionedPackage, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public String[] currentToCanonicalPackageNames(String[] names) {
+ return new String[0];
+ }
+
+ @Override
+ public String[] canonicalToCurrentPackageNames(String[] names) {
+ return new String[0];
+ }
+
+ @Override
+ public Intent getLaunchIntentForPackage(String packageName) {
+ return null;
+ }
+
+ @Override
+ public Intent getLeanbackLaunchIntentForPackage(String packageName) {
+ return null;
+ }
+
+ @Override
+ public int[] getPackageGids(String packageName) throws NameNotFoundException {
+ return new int[0];
+ }
+
+ @Override
+ public int[] getPackageGids(String packageName, int flags)
+ throws NameNotFoundException {
+ return new int[0];
+ }
+
+ @Override
+ public int getPackageUid(String packageName, int flags)
+ throws NameNotFoundException {
+ return 0;
+ }
+
+ @Override
+ public int getPackageUidAsUser(String packageName, int userId)
+ throws NameNotFoundException {
+ return 0;
+ }
+
+ @Override
+ public int getPackageUidAsUser(String packageName, int flags, int userId)
+ throws NameNotFoundException {
+ return 0;
+ }
+
+ @Override
+ public PermissionInfo getPermissionInfo(String name, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public List<PermissionInfo> queryPermissionsByGroup(String group, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public boolean isPermissionReviewModeEnabled() {
+ return false;
+ }
+
+ @Override
+ public PermissionGroupInfo getPermissionGroupInfo(String name, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public List<PermissionGroupInfo> getAllPermissionGroups(int flags) {
+ return null;
+ }
+
+ @Override
+ public ApplicationInfo getApplicationInfo(String packageName, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public ApplicationInfo getApplicationInfoAsUser(String packageName, int flags, int userId)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public ActivityInfo getActivityInfo(ComponentName component, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public ActivityInfo getReceiverInfo(ComponentName component, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public ServiceInfo getServiceInfo(ComponentName component, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public ProviderInfo getProviderInfo(ComponentName component, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public List<PackageInfo> getInstalledPackages(int flags) {
+ return null;
+ }
+
+ @Override
+ public List<PackageInfo> getPackagesHoldingPermissions(String[] permissions, int flags) {
+ return null;
+ }
+
+ @Override
+ public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
+ return null;
+ }
+
+ @Override
+ public int checkPermission(String permName, String pkgName) {
+ return 0;
+ }
+
+ @Override
+ public boolean isPermissionRevokedByPolicy(String permName, String pkgName) {
+ return false;
+ }
+
+ @Override
+ public String getPermissionControllerPackageName() {
+ return null;
+ }
+
+ @Override
+ public boolean addPermission(PermissionInfo info) {
+ return false;
+ }
+
+ @Override
+ public boolean addPermissionAsync(PermissionInfo info) {
+ return false;
+ }
+
+ @Override
+ public void removePermission(String name) {
+
+ }
+
+ @Override
+ public void grantRuntimePermission(String packageName, String permissionName,
+ UserHandle user) {
+
+ }
+
+ @Override
+ public void revokeRuntimePermission(String packageName, String permissionName,
+ UserHandle user) {
+
+ }
+
+ @Override
+ public int getPermissionFlags(String permissionName, String packageName, UserHandle user) {
+ return 0;
+ }
+
+ @Override
+ public void updatePermissionFlags(String permissionName, String packageName, int flagMask,
+ int flagValues, UserHandle user) {
+
+ }
+
+ @Override
+ public boolean shouldShowRequestPermissionRationale(String permission) {
+ return false;
+ }
+
+ @Override
+ public int checkSignatures(String pkg1, String pkg2) {
+ return 0;
+ }
+
+ @Override
+ public int checkSignatures(int uid1, int uid2) {
+ return 0;
+ }
+
+ @Override
+ public String[] getPackagesForUid(int uid) {
+ return new String[0];
+ }
+
+ @Override
+ public String getNameForUid(int uid) {
+ return null;
+ }
+
+ @Override
+ public int getUidForSharedUser(String sharedUserName)
+ throws NameNotFoundException {
+ return 0;
+ }
+
+ @Override
+ public List<ApplicationInfo> getInstalledApplications(int flags) {
+ return null;
+ }
+
+ @Override
+ public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) {
+ return null;
+ }
+
+ @Override
+ public List<InstantAppInfo> getInstantApps() {
+ return null;
+ }
+
+ @Override
+ public Drawable getInstantAppIcon(String packageName) {
+ return null;
+ }
+
+ @Override
+ public boolean isInstantApp() {
+ return false;
+ }
+
+ @Override
+ public boolean isInstantApp(String packageName) {
+ return false;
+ }
+
+ @Override
+ public int getInstantAppCookieMaxBytes() {
+ return 0;
+ }
+
+ @Override
+ public int getInstantAppCookieMaxSize() {
+ return 0;
+ }
+
+ @Override
+ public byte[] getInstantAppCookie() {
+ return new byte[0];
+ }
+
+ @Override
+ public void clearInstantAppCookie() {
+
+ }
+
+ @Override
+ public void updateInstantAppCookie(byte[] cookie) {
+
+ }
+
+ @Override
+ public boolean setInstantAppCookie(byte[] cookie) {
+ return false;
+ }
+
+ @Override
+ public String[] getSystemSharedLibraryNames() {
+ return new String[0];
+ }
+
+ @Override
+ public List<SharedLibraryInfo> getSharedLibraries(int flags) {
+ return null;
+ }
+
+ @Override
+ public List<SharedLibraryInfo> getSharedLibrariesAsUser(int flags, int userId) {
+ return null;
+ }
+
+ @Override
+ public String getServicesSystemSharedLibraryPackageName() {
+ return null;
+ }
+
+ @Override
+ public String getSharedSystemSharedLibraryPackageName() {
+ return null;
+ }
+
+ @Override
+ public ChangedPackages getChangedPackages(int sequenceNumber) {
+ return null;
+ }
+
+ @Override
+ public FeatureInfo[] getSystemAvailableFeatures() {
+ return new FeatureInfo[0];
+ }
+
+ @Override
+ public boolean hasSystemFeature(String name) {
+ return false;
+ }
+
+ @Override
+ public boolean hasSystemFeature(String name, int version) {
+ return false;
+ }
+
+ @Override
+ public ResolveInfo resolveActivity(Intent intent, int flags) {
+ return null;
+ }
+
+ @Override
+ public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) {
+ return null;
+ }
+
+ @Override
+ public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
+ return null;
+ }
+
+ @Override
+ public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) {
+ return null;
+ }
+
+ @Override
+ public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller,
+ Intent[] specifics,
+ Intent intent, int flags) {
+ return null;
+ }
+
+ @Override
+ public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
+ return null;
+ }
+
+ @Override
+ public List<ResolveInfo> queryBroadcastReceiversAsUser(Intent intent, int flags,
+ int userId) {
+ return null;
+ }
+
+ @Override
+ public ResolveInfo resolveService(Intent intent, int flags) {
+ return null;
+ }
+
+ @Override
+ public List<ResolveInfo> queryIntentServices(Intent intent, int flags) {
+ return null;
+ }
+
+ @Override
+ public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) {
+ return null;
+ }
+
+ @Override
+ public List<ResolveInfo> queryIntentContentProvidersAsUser(Intent intent, int flags,
+ int userId) {
+ return null;
+ }
+
+ @Override
+ public List<ResolveInfo> queryIntentContentProviders(Intent intent, int flags) {
+ return null;
+ }
+
+ @Override
+ public ProviderInfo resolveContentProvider(String name, int flags) {
+ return null;
+ }
+
+ @Override
+ public ProviderInfo resolveContentProviderAsUser(String name, int flags, int userId) {
+ return null;
+ }
+
+ @Override
+ public List<ProviderInfo> queryContentProviders(String processName, int uid, int flags) {
+ return null;
+ }
+
+ @Override
+ public InstrumentationInfo getInstrumentationInfo(ComponentName className, int flags)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public List<InstrumentationInfo> queryInstrumentation(String targetPackage, int flags) {
+ return null;
+ }
+
+ @Override
+ public Drawable getDrawable(String packageName, int resid, ApplicationInfo appInfo) {
+ return null;
+ }
+
+ @Override
+ public Drawable getActivityIcon(ComponentName activityName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public Drawable getActivityIcon(Intent intent) throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public Drawable getActivityBanner(ComponentName activityName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public Drawable getActivityBanner(Intent intent) throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public Drawable getDefaultActivityIcon() {
+ return null;
+ }
+
+ @Override
+ public Drawable getApplicationIcon(ApplicationInfo info) {
+ return null;
+ }
+
+ @Override
+ public Drawable getApplicationIcon(String packageName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public Drawable getApplicationBanner(ApplicationInfo info) {
+ return null;
+ }
+
+ @Override
+ public Drawable getApplicationBanner(String packageName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public Drawable getActivityLogo(ComponentName activityName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public Drawable getActivityLogo(Intent intent) throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public Drawable getApplicationLogo(ApplicationInfo info) {
+ return null;
+ }
+
+ @Override
+ public Drawable getApplicationLogo(String packageName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public Drawable getUserBadgedIcon(Drawable icon, UserHandle user) {
+ return null;
+ }
+
+ @Override
+ public Drawable getUserBadgedDrawableForDensity(Drawable drawable, UserHandle user,
+ Rect badgeLocation, int badgeDensity) {
+ return null;
+ }
+
+ @Override
+ public Drawable getUserBadgeForDensity(UserHandle user, int density) {
+ return null;
+ }
+
+ @Override
+ public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) {
+ return null;
+ }
+
+ @Override
+ public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) {
+ return null;
+ }
+
+ @Override
+ public CharSequence getText(String packageName, int resid, ApplicationInfo appInfo) {
+ return null;
+ }
+
+ @Override
+ public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) {
+ return null;
+ }
+
+ @Override
+ public CharSequence getApplicationLabel(ApplicationInfo info) {
+ return null;
+ }
+
+ @Override
+ public Resources getResourcesForActivity(ComponentName activityName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public Resources getResourcesForApplication(ApplicationInfo app)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public Resources getResourcesForApplication(String appPackageName)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public Resources getResourcesForApplicationAsUser(String appPackageName, int userId)
+ throws NameNotFoundException {
+ return null;
+ }
+
+ @Override
+ public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags,
+ String installerPackageName) {
+
+ }
+
+ @Override
+ public void installPackage(Uri packageURI, PackageInstallObserver observer, int flags,
+ String installerPackageName) {
+
+ }
+
+ @Override
+ public int installExistingPackage(String packageName)
+ throws NameNotFoundException {
+ return 0;
+ }
+
+ @Override
+ public int installExistingPackage(String packageName, int installReason)
+ throws NameNotFoundException {
+ return 0;
+ }
+
+ @Override
+ public int installExistingPackageAsUser(String packageName, int userId)
+ throws NameNotFoundException {
+ return 0;
+ }
+
+ @Override
+ public void verifyPendingInstall(int id, int verificationCode) {
+
+ }
+
+ @Override
+ public void extendVerificationTimeout(int id, int verificationCodeAtTimeout,
+ long millisecondsToDelay) {
+
+ }
+
+ @Override
+ public void verifyIntentFilter(int verificationId, int verificationCode,
+ List<String> failedDomains) {
+
+ }
+
+ @Override
+ public int getIntentVerificationStatusAsUser(String packageName, int userId) {
+ return 0;
+ }
+
+ @Override
+ public boolean updateIntentVerificationStatusAsUser(String packageName, int status,
+ int userId) {
+ return false;
+ }
+
+ @Override
+ public List<IntentFilterVerificationInfo> getIntentFilterVerifications(String packageName) {
+ return null;
+ }
+
+ @Override
+ public List<IntentFilter> getAllIntentFilters(String packageName) {
+ return null;
+ }
+
+ @Override
+ public String getDefaultBrowserPackageNameAsUser(int userId) {
+ return null;
+ }
+
+ @Override
+ public boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId) {
+ return false;
+ }
+
+ @Override
+ public void setInstallerPackageName(String targetPackage, String installerPackageName) {
+
+ }
+
+ @Override
+ public void setUpdateAvailable(String packageName, boolean updateAvaialble) {
+
+ }
+
+ @Override
+ public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) {
+
+ }
+
+ @Override
+ public void deletePackageAsUser(String packageName, IPackageDeleteObserver observer,
+ int flags,
+ int userId) {
+
+ }
+
+ @Override
+ public String getInstallerPackageName(String packageName) {
+ return null;
+ }
+
+ @Override
+ public void clearApplicationUserData(String packageName, IPackageDataObserver observer) {
+
+ }
+
+ @Override
+ public void deleteApplicationCacheFiles(String packageName, IPackageDataObserver observer) {
+
+ }
+
+ @Override
+ public void deleteApplicationCacheFilesAsUser(String packageName, int userId,
+ IPackageDataObserver observer) {
+
+ }
+
+ @Override
+ public void freeStorageAndNotify(String volumeUuid, long freeStorageSize,
+ IPackageDataObserver observer) {
+
+ }
+
+ @Override
+ public void freeStorage(String volumeUuid, long freeStorageSize, IntentSender pi) {
+
+ }
+
+ @Override
+ public void getPackageSizeInfoAsUser(String packageName, int userId,
+ IPackageStatsObserver observer) {
+
+ }
+
+ @Override
+ public void addPackageToPreferred(String packageName) {
+
+ }
+
+ @Override
+ public void removePackageFromPreferred(String packageName) {
+
+ }
+
+ @Override
+ public List<PackageInfo> getPreferredPackages(int flags) {
+ return null;
+ }
+
+ @Override
+ public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set,
+ ComponentName activity) {
+
+ }
+
+ @Override
+ public void replacePreferredActivity(IntentFilter filter, int match, ComponentName[] set,
+ ComponentName activity) {
+
+ }
+
+ @Override
+ public void clearPackagePreferredActivities(String packageName) {
+
+ }
+
+ @Override
+ public int getPreferredActivities(List<IntentFilter> outFilters,
+ List<ComponentName> outActivities, String packageName) {
+ return 0;
+ }
+
+ @Override
+ public ComponentName getHomeActivities(List<ResolveInfo> outActivities) {
+ return null;
+ }
+
+ @Override
+ public void setComponentEnabledSetting(ComponentName componentName, int newState,
+ int flags) {
+
+ }
+
+ @Override
+ public int getComponentEnabledSetting(ComponentName componentName) {
+ return 0;
+ }
+
+ @Override
+ public void setApplicationEnabledSetting(String packageName, int newState, int flags) {
+
+ }
+
+ @Override
+ public int getApplicationEnabledSetting(String packageName) {
+ return 0;
+ }
+
+ @Override
+ public void flushPackageRestrictionsAsUser(int userId) {
+
+ }
+
+ @Override
+ public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden,
+ UserHandle userHandle) {
+ return false;
+ }
+
+ @Override
+ public boolean getApplicationHiddenSettingAsUser(String packageName,
+ UserHandle userHandle) {
+ return false;
+ }
+
+ @Override
+ public boolean isSafeMode() {
+ return false;
+ }
+
+ @Override
+ public void addOnPermissionsChangeListener(
+ OnPermissionsChangedListener listener) {
+
+ }
+
+ @Override
+ public void removeOnPermissionsChangeListener(
+ OnPermissionsChangedListener listener) {
+
+ }
+
+ @Override
+ public KeySet getKeySetByAlias(String packageName, String alias) {
+ return null;
+ }
+
+ @Override
+ public KeySet getSigningKeySet(String packageName) {
+ return null;
+ }
+
+ @Override
+ public boolean isSignedBy(String packageName, KeySet ks) {
+ return false;
+ }
+
+ @Override
+ public boolean isSignedByExactly(String packageName, KeySet ks) {
+ return false;
+ }
+
+ @Override
+ public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
+ int userId) {
+ return new String[0];
+ }
+
+ @Override
+ public boolean isPackageSuspendedForUser(String packageName, int userId) {
+ return false;
+ }
+
+ @Override
+ public void setApplicationCategoryHint(String packageName, int categoryHint) {
+
+ }
+
+ @Override
+ public int getMoveStatus(int moveId) {
+ return 0;
+ }
+
+ @Override
+ public void registerMoveCallback(MoveCallback callback, Handler handler) {
+
+ }
+
+ @Override
+ public void unregisterMoveCallback(MoveCallback callback) {
+
+ }
+
+ @Override
+ public int movePackage(String packageName, VolumeInfo vol) {
+ return 0;
+ }
+
+ @Override
+ public VolumeInfo getPackageCurrentVolume(ApplicationInfo app) {
+ return null;
+ }
+
+ @Override
+ public List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app) {
+ return null;
+ }
+
+ @Override
+ public int movePrimaryStorage(VolumeInfo vol) {
+ return 0;
+ }
+
+ @Override
+ public VolumeInfo getPrimaryStorageCurrentVolume() {
+ return null;
+ }
+
+ @Override
+ public List<VolumeInfo> getPrimaryStorageCandidateVolumes() {
+ return null;
+ }
+
+ @Override
+ public VerifierDeviceIdentity getVerifierDeviceIdentity() {
+ return null;
+ }
+
+ @Override
+ public boolean isUpgrade() {
+ return false;
+ }
+
+ @Override
+ public PackageInstaller getPackageInstaller() {
+ return null;
+ }
+
+ @Override
+ public void addCrossProfileIntentFilter(IntentFilter filter, int sourceUserId,
+ int targetUserId,
+ int flags) {
+
+ }
+
+ @Override
+ public void clearCrossProfileIntentFilters(int sourceUserId) {
+
+ }
+
+ @Override
+ public Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo) {
+ return null;
+ }
+
+ @Override
+ public Drawable loadUnbadgedItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo) {
+ return null;
+ }
+
+ @Override
+ public boolean isPackageAvailable(String packageName) {
+ return false;
+ }
+
+ @Override
+ public int getInstallReason(String packageName, UserHandle user) {
+ return 0;
+ }
+
+ @Override
+ public boolean canRequestPackageInstalls() {
+ return false;
+ }
+
+ @Override
+ public ComponentName getInstantAppResolverSettingsComponent() {
+ return null;
+ }
+
+ @Override
+ public ComponentName getInstantAppInstallerComponent() {
+ return null;
+ }
+
+ @Override
+ public String getInstantAppAndroidId(String packageName, UserHandle user) {
+ return null;
+ }
+
+ @Override
+ public void registerDexModule(String dexModulePath,
+ DexModuleRegisterCallback callback) {
+
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
new file mode 100644
index 0000000..650681e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.utils;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.Signature;
+import android.os.Process;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.backup.RefactoredBackupManagerService;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AppBackupUtilsTest {
+ private static final String CUSTOM_BACKUP_AGENT_NAME = "custom.backup.agent";
+ private static final String TEST_PACKAGE_NAME = "test_package";
+
+ private final Random mRandom = new Random(1000000009);
+
+ @Test
+ public void appIsEligibleForBackup_backupNotAllowed_returnsFalse() throws Exception {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.flags = 0;
+ applicationInfo.uid = Process.FIRST_APPLICATION_UID;
+ applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
+ applicationInfo.packageName = TEST_PACKAGE_NAME;
+
+ boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo);
+
+ assertThat(isEligible).isFalse();
+ }
+
+ @Test
+ public void appIsEligibleForBackup_systemAppWithoutCustomBackupAgent_returnsFalse()
+ throws Exception {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ applicationInfo.uid = Process.SYSTEM_UID;
+ applicationInfo.backupAgentName = null;
+ applicationInfo.packageName = TEST_PACKAGE_NAME;
+
+ boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo);
+
+ assertThat(isEligible).isFalse();
+ }
+
+ @Test
+ public void appIsEligibleForBackup_sharedStorageBackupPackage_returnsFalse() throws Exception {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ applicationInfo.uid = Process.SYSTEM_UID;
+ applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
+ applicationInfo.packageName = RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
+
+ boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo);
+
+ assertThat(isEligible).isFalse();
+ }
+
+ @Test
+ public void appIsEligibleForBackup_systemAppWithCustomBackupAgent_returnsTrue()
+ throws Exception {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ applicationInfo.uid = Process.SYSTEM_UID;
+ applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
+ applicationInfo.packageName = TEST_PACKAGE_NAME;
+
+ boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo);
+
+ assertThat(isEligible).isTrue();
+ }
+
+ @Test
+ public void appIsEligibleForBackup_nonSystemAppWithoutCustomBackupAgent_returnsTrue()
+ throws Exception {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ applicationInfo.uid = Process.FIRST_APPLICATION_UID;
+ applicationInfo.backupAgentName = null;
+ applicationInfo.packageName = TEST_PACKAGE_NAME;
+
+ boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo);
+
+ assertThat(isEligible).isTrue();
+ }
+
+ @Test
+ public void appIsEligibleForBackup_nonSystemAppWithCustomBackupAgent_returnsTrue()
+ throws Exception {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ applicationInfo.uid = Process.FIRST_APPLICATION_UID;
+ applicationInfo.backupAgentName = CUSTOM_BACKUP_AGENT_NAME;
+ applicationInfo.packageName = TEST_PACKAGE_NAME;
+
+ boolean isEligible = AppBackupUtils.appIsEligibleForBackup(applicationInfo);
+
+ assertThat(isEligible).isTrue();
+ }
+
+ @Test
+ public void appIsStopped_returnsTrue() throws Exception {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.flags |= ApplicationInfo.FLAG_STOPPED;
+
+ boolean isStopped = AppBackupUtils.appIsStopped(applicationInfo);
+
+ assertThat(isStopped).isTrue();
+ }
+
+ @Test
+ public void appIsStopped_returnsFalse() throws Exception {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.flags = ~ApplicationInfo.FLAG_STOPPED;
+
+ boolean isStopped = AppBackupUtils.appIsStopped(applicationInfo);
+
+ assertThat(isStopped).isFalse();
+ }
+
+ @Test
+ public void appGetsFullBackup_noCustomBackupAgent_returnsTrue() throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.backupAgentName = null;
+
+ boolean result = AppBackupUtils.appGetsFullBackup(packageInfo);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void appGetsFullBackup_withCustomBackupAgentAndFullBackupOnlyFlag_returnsTrue()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.backupAgentName = "backup.agent";
+ packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_FULL_BACKUP_ONLY;
+
+ boolean result = AppBackupUtils.appGetsFullBackup(packageInfo);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void appGetsFullBackup_withCustomBackupAgentAndWithoutFullBackupOnlyFlag_returnsFalse()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.backupAgentName = "backup.agent";
+ packageInfo.applicationInfo.flags = ~ApplicationInfo.FLAG_FULL_BACKUP_ONLY;
+
+ boolean result = AppBackupUtils.appGetsFullBackup(packageInfo);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void appIsKeyValueOnly_noCustomBackupAgent_returnsTrue() throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.backupAgentName = null;
+
+ boolean result = AppBackupUtils.appIsKeyValueOnly(packageInfo);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void appIsKeyValueOnly_withCustomBackupAgentAndFullBackupOnlyFlag_returnsTrue()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.backupAgentName = "backup.agent";
+ packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_FULL_BACKUP_ONLY;
+
+ boolean result = AppBackupUtils.appIsKeyValueOnly(packageInfo);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void appIsKeyValueOnly_withCustomBackupAgentAndWithoutFullBackupOnlyFlag_returnsFalse()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.backupAgentName = "backup.agent";
+ packageInfo.applicationInfo.flags = ~ApplicationInfo.FLAG_FULL_BACKUP_ONLY;
+
+ boolean result = AppBackupUtils.appIsKeyValueOnly(packageInfo);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void signaturesMatch_targetIsNull_returnsFalse() throws Exception {
+ boolean result = AppBackupUtils.signaturesMatch(new Signature[0], null);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void signaturesMatch_systemApplication_returnsTrue() throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+
+ boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void signaturesMatch_allowsUnsignedApps_bothSignaturesNull_returnsTrue()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signatures = null;
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ boolean result = AppBackupUtils.signaturesMatch(null, packageInfo);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void signaturesMatch_allowsUnsignedApps_bothSignaturesEmpty_returnsTrue()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signatures = new Signature[0];
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void
+ signaturesMatch_allowsUnsignedApps_storedSignatureNullTargetSignatureEmpty_returnsTrue()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signatures = new Signature[0];
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ boolean result = AppBackupUtils.signaturesMatch(null, packageInfo);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void
+ signaturesMatch_allowsUnsignedApps_storedSignatureEmptyTargetSignatureNull_returnsTrue()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signatures = null;
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void
+ signaturesMatch_disallowsAppsUnsignedOnOnlyOneDevice_storedSignatureIsNull_returnsFalse()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signatures = new Signature[]{generateRandomSignature()};
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ boolean result = AppBackupUtils.signaturesMatch(null, packageInfo);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void
+ signaturesMatch_disallowsAppsUnsignedOnOnlyOneDevice_targetSignatureIsNull_returnsFalse()
+ throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signatures = null;
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ boolean result = AppBackupUtils.signaturesMatch(new Signature[]{generateRandomSignature()},
+ packageInfo);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void signaturesMatch_signaturesMatch_returnsTrue() throws Exception {
+ Signature signature1 = generateRandomSignature();
+ Signature signature2 = generateRandomSignature();
+ Signature signature3 = generateRandomSignature();
+ assertThat(signature1).isNotEqualTo(signature2);
+ assertThat(signature2).isNotEqualTo(signature3);
+ assertThat(signature1).isNotEqualTo(signature3);
+
+ Signature signature1Copy = new Signature(signature1.toByteArray());
+ Signature signature2Copy = new Signature(signature2.toByteArray());
+ Signature signature3Copy = new Signature(signature3.toByteArray());
+ assertThat(signature1Copy).isEqualTo(signature1);
+ assertThat(signature2Copy).isEqualTo(signature2);
+ assertThat(signature3Copy).isEqualTo(signature3);
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signatures = new Signature[]{signature1, signature2, signature3};
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ boolean result = AppBackupUtils.signaturesMatch(
+ new Signature[]{signature3Copy, signature1Copy, signature2Copy}, packageInfo);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void signaturesMatch_extraSignatureInTarget_returnsTrue() throws Exception {
+ Signature signature1 = generateRandomSignature();
+ Signature signature2 = generateRandomSignature();
+ Signature signature3 = generateRandomSignature();
+ assertThat(signature1).isNotEqualTo(signature2);
+ assertThat(signature2).isNotEqualTo(signature3);
+ assertThat(signature1).isNotEqualTo(signature3);
+
+ Signature signature1Copy = new Signature(signature1.toByteArray());
+ Signature signature2Copy = new Signature(signature2.toByteArray());
+ assertThat(signature1Copy).isEqualTo(signature1);
+ assertThat(signature2Copy).isEqualTo(signature2);
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signatures = new Signature[]{signature1, signature2, signature3};
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ boolean result = AppBackupUtils.signaturesMatch(
+ new Signature[]{signature2Copy, signature1Copy}, packageInfo);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void signaturesMatch_extraSignatureInStored_returnsFalse() throws Exception {
+ Signature signature1 = generateRandomSignature();
+ Signature signature2 = generateRandomSignature();
+ Signature signature3 = generateRandomSignature();
+ assertThat(signature1).isNotEqualTo(signature2);
+ assertThat(signature2).isNotEqualTo(signature3);
+ assertThat(signature1).isNotEqualTo(signature3);
+
+ Signature signature1Copy = new Signature(signature1.toByteArray());
+ Signature signature2Copy = new Signature(signature2.toByteArray());
+ assertThat(signature1Copy).isEqualTo(signature1);
+ assertThat(signature2Copy).isEqualTo(signature2);
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signatures = new Signature[]{signature1Copy, signature2Copy};
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ boolean result = AppBackupUtils.signaturesMatch(
+ new Signature[]{signature1, signature2, signature3}, packageInfo);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void signaturesMatch_emptyStoredSignatures_returnsTrue() throws Exception {
+ Signature signature1 = generateRandomSignature();
+ Signature signature2 = generateRandomSignature();
+ Signature signature3 = generateRandomSignature();
+ assertThat(signature1).isNotEqualTo(signature2);
+ assertThat(signature2).isNotEqualTo(signature3);
+ assertThat(signature1).isNotEqualTo(signature3);
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signatures = new Signature[]{signature1, signature2, signature3};
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void signaturesMatch_emptyTargetSignatures_returnsFalse() throws Exception {
+ Signature signature1 = generateRandomSignature();
+ Signature signature2 = generateRandomSignature();
+ Signature signature3 = generateRandomSignature();
+ assertThat(signature1).isNotEqualTo(signature2);
+ assertThat(signature2).isNotEqualTo(signature3);
+ assertThat(signature1).isNotEqualTo(signature3);
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signatures = new Signature[0];
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ boolean result = AppBackupUtils.signaturesMatch(
+ new Signature[]{signature1, signature2, signature3}, packageInfo);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void signaturesMatch_oneNonMatchingSignature_returnsFalse() throws Exception {
+ Signature signature1 = generateRandomSignature();
+ Signature signature2 = generateRandomSignature();
+ Signature signature3 = generateRandomSignature();
+ Signature signature4 = generateRandomSignature();
+ assertThat(signature1).isNotEqualTo(signature2);
+ assertThat(signature2).isNotEqualTo(signature3);
+ assertThat(signature1).isNotEqualTo(signature3);
+ assertThat(signature1).isNotEqualTo(signature4);
+ assertThat(signature2).isNotEqualTo(signature4);
+ assertThat(signature3).isNotEqualTo(signature4);
+
+ Signature signature1Copy = new Signature(signature1.toByteArray());
+ Signature signature2Copy = new Signature(signature2.toByteArray());
+ assertThat(signature1Copy).isEqualTo(signature1);
+ assertThat(signature2Copy).isEqualTo(signature2);
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.signatures = new Signature[]{signature1, signature2, signature3};
+ packageInfo.applicationInfo = new ApplicationInfo();
+
+ boolean result = AppBackupUtils.signaturesMatch(
+ new Signature[]{signature1Copy, signature2Copy, signature4}, packageInfo);
+
+ assertThat(result).isFalse();
+ }
+
+ private Signature generateRandomSignature() {
+ byte[] signatureBytes = new byte[256];
+ mRandom.nextBytes(signatureBytes);
+ return new Signature(signatureBytes);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
new file mode 100644
index 0000000..87c587a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.utils;
+
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY;
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_ID;
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME;
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
+
+import android.app.backup.IBackupManagerMonitor;
+import android.content.pm.PackageInfo;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupManagerMonitorUtilsTest {
+ @Mock private IBackupManagerMonitor mMonitorMock;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void monitorEvent_monitorIsNull_returnsNull() throws Exception {
+ IBackupManagerMonitor result = BackupManagerMonitorUtils.monitorEvent(null, 0, null, 0,
+ null);
+
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void monitorEvent_monitorOnEventThrows_returnsNull() throws Exception {
+ doThrow(new RemoteException()).when(mMonitorMock).onEvent(any(Bundle.class));
+
+ IBackupManagerMonitor result = BackupManagerMonitorUtils.monitorEvent(mMonitorMock, 0, null,
+ 0, null);
+
+ verify(mMonitorMock).onEvent(any(Bundle.class));
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void monitorEvent_packageAndExtrasAreNull_fillsBundleCorrectly() throws Exception {
+ IBackupManagerMonitor result = BackupManagerMonitorUtils.monitorEvent(mMonitorMock, 1, null,
+ 2, null);
+
+ assertThat(result).isEqualTo(mMonitorMock);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mMonitorMock).onEvent(bundleCaptor.capture());
+ Bundle eventBundle = bundleCaptor.getValue();
+ assertThat(eventBundle.size()).isEqualTo(2);
+ assertThat(eventBundle.getInt(EXTRA_LOG_EVENT_ID)).isEqualTo(1);
+ assertThat(eventBundle.getInt(EXTRA_LOG_EVENT_CATEGORY)).isEqualTo(2);
+ }
+
+ @Test
+ public void monitorEvent_packageAndExtrasAreNotNull_fillsBundleCorrectly() throws Exception {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = "test.package";
+ packageInfo.versionCode = 3;
+ Bundle extras = new Bundle();
+ extras.putInt("key1", 4);
+ extras.putString("key2", "value2");
+
+ IBackupManagerMonitor result = BackupManagerMonitorUtils.monitorEvent(mMonitorMock, 1,
+ packageInfo, 2, extras);
+
+ assertThat(result).isEqualTo(mMonitorMock);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mMonitorMock).onEvent(bundleCaptor.capture());
+ Bundle eventBundle = bundleCaptor.getValue();
+ assertThat(eventBundle.size()).isEqualTo(6);
+ assertThat(eventBundle.getInt(EXTRA_LOG_EVENT_ID)).isEqualTo(1);
+ assertThat(eventBundle.getInt(EXTRA_LOG_EVENT_CATEGORY)).isEqualTo(2);
+ assertThat(eventBundle.getString(EXTRA_LOG_EVENT_PACKAGE_NAME)).isEqualTo("test.package");
+ assertThat(eventBundle.getInt(EXTRA_LOG_EVENT_PACKAGE_VERSION)).isEqualTo(3);
+ assertThat(eventBundle.getInt("key1")).isEqualTo(4);
+ assertThat(eventBundle.getString("key2")).isEqualTo("value2");
+ }
+
+ @Test
+ public void putMonitoringExtraString_bundleExists_fillsBundleCorrectly() throws Exception {
+ Bundle bundle = new Bundle();
+
+ Bundle result = BackupManagerMonitorUtils.putMonitoringExtra(bundle, "key", "value");
+
+ assertThat(result).isEqualTo(bundle);
+ assertThat(result.size()).isEqualTo(1);
+ assertThat(result.getString("key")).isEqualTo("value");
+ }
+
+ @Test
+ public void putMonitoringExtraString_bundleDoesNotExist_fillsBundleCorrectly()
+ throws Exception {
+ Bundle result = BackupManagerMonitorUtils.putMonitoringExtra(null, "key", "value");
+
+ assertThat(result).isNotNull();
+ assertThat(result.size()).isEqualTo(1);
+ assertThat(result.getString("key")).isEqualTo("value");
+ }
+
+
+ @Test
+ public void putMonitoringExtraLong_bundleExists_fillsBundleCorrectly() throws Exception {
+ Bundle bundle = new Bundle();
+
+ Bundle result = BackupManagerMonitorUtils.putMonitoringExtra(bundle, "key", 123);
+
+ assertThat(result).isEqualTo(bundle);
+ assertThat(result.size()).isEqualTo(1);
+ assertThat(result.getLong("key")).isEqualTo(123);
+ }
+
+ @Test
+ public void putMonitoringExtraLong_bundleDoesNotExist_fillsBundleCorrectly() throws Exception {
+ Bundle result = BackupManagerMonitorUtils.putMonitoringExtra(null, "key", 123);
+
+ assertThat(result).isNotNull();
+ assertThat(result.size()).isEqualTo(1);
+ assertThat(result.getLong("key")).isEqualTo(123);
+ }
+
+ @Test
+ public void putMonitoringExtraBoolean_bundleExists_fillsBundleCorrectly() throws Exception {
+ Bundle bundle = new Bundle();
+
+ Bundle result = BackupManagerMonitorUtils.putMonitoringExtra(bundle, "key", true);
+
+ assertThat(result).isEqualTo(bundle);
+ assertThat(result.size()).isEqualTo(1);
+ assertThat(result.getBoolean("key")).isTrue();
+ }
+
+ @Test
+ public void putMonitoringExtraBoolean_bundleDoesNotExist_fillsBundleCorrectly()
+ throws Exception {
+ Bundle result = BackupManagerMonitorUtils.putMonitoringExtra(null, "key", true);
+
+ assertThat(result).isNotNull();
+ assertThat(result.size()).isEqualTo(1);
+ assertThat(result.getBoolean("key")).isTrue();
+ }
+
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/BackupObserverUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/BackupObserverUtilsTest.java
new file mode 100644
index 0000000..ebe6133
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/BackupObserverUtilsTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.utils;
+
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
+
+import android.app.backup.BackupProgress;
+import android.app.backup.IBackupObserver;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupObserverUtilsTest {
+ private static final String PACKAGE_NAME = "some.package";
+
+ @Mock
+ private IBackupObserver mBackupObserverMock;
+ private final BackupProgress mBackupProgress = new BackupProgress(0, 0);
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void sendBackupOnUpdate_observerIsNull_doesNotThrow() throws Exception {
+ BackupObserverUtils.sendBackupOnUpdate(null, PACKAGE_NAME, mBackupProgress);
+
+ // Should not throw.
+ }
+
+ @Test
+ public void sendBackupOnUpdate_callsObserver() throws Exception {
+ BackupObserverUtils.sendBackupOnUpdate(mBackupObserverMock, PACKAGE_NAME, mBackupProgress);
+
+ verify(mBackupObserverMock).onUpdate(PACKAGE_NAME, mBackupProgress);
+ }
+
+ @Test
+ public void sendBackupOnUpdate_handlesRemoteException() throws Exception {
+ doThrow(new RemoteException()).when(mBackupObserverMock).onUpdate(PACKAGE_NAME,
+ mBackupProgress);
+
+ BackupObserverUtils.sendBackupOnUpdate(mBackupObserverMock, PACKAGE_NAME, mBackupProgress);
+
+ verify(mBackupObserverMock).onUpdate(PACKAGE_NAME, mBackupProgress);
+ }
+
+ @Test
+ public void sendBackupOnPackageResult_observerIsNull_doesNotThrow() throws Exception {
+ BackupObserverUtils.sendBackupOnPackageResult(null, PACKAGE_NAME, 1);
+
+ // Should not throw.
+ }
+
+ @Test
+ public void sendBackupOnPackageResult_callsObserver() throws Exception {
+ BackupObserverUtils.sendBackupOnPackageResult(mBackupObserverMock, PACKAGE_NAME, 1);
+
+ verify(mBackupObserverMock).onResult(PACKAGE_NAME, 1);
+ }
+
+ @Test
+ public void sendBackupOnPackageResult_handlesRemoteException() throws Exception {
+ doThrow(new RemoteException()).when(mBackupObserverMock).onResult(PACKAGE_NAME, 1);
+
+ BackupObserverUtils.sendBackupOnPackageResult(mBackupObserverMock, PACKAGE_NAME, 1);
+
+ verify(mBackupObserverMock).onResult(PACKAGE_NAME, 1);
+ }
+
+ @Test
+ public void sendBackupFinished_observerIsNull_doesNotThrow() throws Exception {
+ BackupObserverUtils.sendBackupFinished(null, 1);
+
+ // Should not throw.
+ }
+
+ @Test
+ public void sendBackupFinished_callsObserver() throws Exception {
+ BackupObserverUtils.sendBackupFinished(mBackupObserverMock, 1);
+
+ verify(mBackupObserverMock).backupFinished(1);
+ }
+
+ @Test
+ public void sendBackupFinished_handlesRemoteException() throws Exception {
+ doThrow(new RemoteException()).when(mBackupObserverMock).onResult(PACKAGE_NAME, 1);
+
+ BackupObserverUtils.sendBackupFinished(mBackupObserverMock, 1);
+
+ verify(mBackupObserverMock).backupFinished(1);
+ }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/FullBackupRestoreObserverUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/FullBackupRestoreObserverUtilsTest.java
new file mode 100644
index 0000000..2f56598
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/FullBackupRestoreObserverUtilsTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.utils;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
+
+import android.app.backup.IFullBackupRestoreObserver;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class FullBackupRestoreObserverUtilsTest {
+ private static final String PACKAGE_NAME = "some.package";
+ @Mock
+ private IFullBackupRestoreObserver mFullBackupRestoreObserverMock;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void sendStartRestore_observerIsNull_returnsNull() throws Exception {
+ IFullBackupRestoreObserver result = FullBackupRestoreObserverUtils.sendStartRestore(null);
+
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void sendStartRestore_callsObserver() throws Exception {
+ IFullBackupRestoreObserver result = FullBackupRestoreObserverUtils.sendStartRestore(
+ mFullBackupRestoreObserverMock);
+
+ assertThat(result).isEqualTo(mFullBackupRestoreObserverMock);
+ verify(mFullBackupRestoreObserverMock).onStartRestore();
+ }
+
+ @Test
+ public void sendStartRestore_observerThrows_returnsNull() throws Exception {
+ doThrow(new RemoteException()).when(mFullBackupRestoreObserverMock).onStartRestore();
+
+ IFullBackupRestoreObserver result = FullBackupRestoreObserverUtils.sendStartRestore(
+ mFullBackupRestoreObserverMock);
+
+ assertThat(result).isNull();
+ verify(mFullBackupRestoreObserverMock).onStartRestore();
+ }
+
+ @Test
+ public void sendOnRestorePackage_observerIsNull_returnsNull() throws Exception {
+ IFullBackupRestoreObserver result = FullBackupRestoreObserverUtils.sendOnRestorePackage(
+ null, PACKAGE_NAME);
+
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void sendOnRestorePackage_callsObserver() throws Exception {
+ IFullBackupRestoreObserver result = FullBackupRestoreObserverUtils.sendOnRestorePackage(
+ mFullBackupRestoreObserverMock, PACKAGE_NAME);
+
+ assertThat(result).isEqualTo(mFullBackupRestoreObserverMock);
+ verify(mFullBackupRestoreObserverMock).onRestorePackage(PACKAGE_NAME);
+ }
+
+ @Test
+ public void sendOnRestorePackage_observerThrows_returnsNull() throws Exception {
+ doThrow(new RemoteException()).when(mFullBackupRestoreObserverMock).onRestorePackage(
+ PACKAGE_NAME);
+
+ IFullBackupRestoreObserver result = FullBackupRestoreObserverUtils.sendOnRestorePackage(
+ mFullBackupRestoreObserverMock, PACKAGE_NAME);
+
+ assertThat(result).isNull();
+ verify(mFullBackupRestoreObserverMock).onRestorePackage(PACKAGE_NAME);
+ }
+
+ @Test
+ public void sendEndRestore_observerIsNull_returnsNull() throws Exception {
+ IFullBackupRestoreObserver result = FullBackupRestoreObserverUtils.sendEndRestore(null);
+
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void sendEndRestore_callsObserver() throws Exception {
+ IFullBackupRestoreObserver result = FullBackupRestoreObserverUtils.sendEndRestore(
+ mFullBackupRestoreObserverMock);
+
+ assertThat(result).isEqualTo(mFullBackupRestoreObserverMock);
+ verify(mFullBackupRestoreObserverMock).onEndRestore();
+ }
+
+ @Test
+ public void sendEndRestore_observerThrows_returnsNull() throws Exception {
+ doThrow(new RemoteException()).when(mFullBackupRestoreObserverMock).onEndRestore();
+
+ IFullBackupRestoreObserver result = FullBackupRestoreObserverUtils.sendEndRestore(
+ mFullBackupRestoreObserverMock);
+
+ assertThat(result).isNull();
+ verify(mFullBackupRestoreObserverMock).onEndRestore();
+ }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java
new file mode 100644
index 0000000..4e3de64
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/FullBackupUtilsTest.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.utils;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.Presubmit;
+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 org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Random;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class FullBackupUtilsTest {
+ @Mock private ParcelFileDescriptor mParcelFileDescriptorMock;
+ @Mock private OutputStream mOutputStreamMock;
+ private File mTemporaryFile;
+ private ByteArrayOutputStream mByteArrayOutputStream;
+ private ParcelFileDescriptor mTemporaryFileDescriptor;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mTemporaryFile = File.createTempFile("backup-data", ".txt");
+ mByteArrayOutputStream = new ByteArrayOutputStream();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mTemporaryFileDescriptor != null) {
+ mTemporaryFileDescriptor.close();
+ }
+ if (mTemporaryFile != null) {
+ mTemporaryFile.delete();
+ }
+ }
+
+ @Test
+ public void routeSocketDataToOutput_inPipeIsNull_throwsNPE() throws Exception {
+ try {
+ FullBackupUtils.routeSocketDataToOutput(null, mOutputStreamMock);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ @Test
+ public void routeSocketDataToOutput_outNull_throwsNPE() throws Exception {
+ try {
+ FullBackupUtils.routeSocketDataToOutput(mParcelFileDescriptorMock, null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ @Test
+ public void routeSocketDataToOutput_emptyInput_throwsEOFException() throws Exception {
+ DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(mTemporaryFile));
+ outputStream.close();
+
+ mTemporaryFileDescriptor = ParcelFileDescriptor.open(mTemporaryFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ try {
+ FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor,
+ mOutputStreamMock);
+ fail();
+ } catch (EOFException expected) {
+ }
+
+ verifyZeroInteractions(mOutputStreamMock);
+ assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
+ }
+
+ @Test
+ public void routeSocketDataToOutput_incompleteChunkSizeInput_throwsEOFException()
+ throws Exception {
+ DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(mTemporaryFile));
+ outputStream.writeByte(100);
+ outputStream.close();
+
+ mTemporaryFileDescriptor = ParcelFileDescriptor.open(mTemporaryFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ try {
+ FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor,
+ mOutputStreamMock);
+ fail();
+ } catch (EOFException expected) {
+ }
+
+ verifyZeroInteractions(mOutputStreamMock);
+ assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
+ }
+
+ @Test
+ public void routeSocketDataToOutput_validEmptyInput_doesNotWriteAnything() throws Exception {
+ DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(mTemporaryFile));
+ outputStream.writeInt(0);
+ outputStream.close();
+
+ mTemporaryFileDescriptor = ParcelFileDescriptor.open(mTemporaryFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor, mOutputStreamMock);
+
+ verifyZeroInteractions(mOutputStreamMock);
+ assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
+ }
+
+ @Test
+ public void routeSocketDataToOutput_notEnoughData_throwsEOFException() throws Exception {
+ byte[] data = createFakeDataArray(100);
+ DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(mTemporaryFile));
+ outputStream.writeInt(data.length + 1);
+ outputStream.write(data);
+ outputStream.close();
+
+ mTemporaryFileDescriptor = ParcelFileDescriptor.open(mTemporaryFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ try {
+ FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor,
+ mByteArrayOutputStream);
+ fail();
+ } catch (EOFException expected) {
+ }
+
+ verify(mOutputStreamMock, never()).close();
+ assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
+ }
+
+ @Test
+ public void routeSocketDataToOutput_oneSmallChunk_writesOutputCorrectly() throws Exception {
+ byte[] data = createFakeDataArray(100);
+ DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(mTemporaryFile));
+ outputStream.writeInt(data.length);
+ outputStream.write(data);
+ outputStream.writeInt(0);
+ outputStream.close();
+
+ mTemporaryFileDescriptor = ParcelFileDescriptor.open(mTemporaryFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor,
+ mByteArrayOutputStream);
+
+ assertThat(mByteArrayOutputStream.toByteArray()).isEqualTo(data);
+ verify(mOutputStreamMock, never()).close();
+ assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
+ }
+
+ @Test
+ public void routeSocketDataToOutput_oneLargeChunk_writesOutputCorrectly() throws Exception {
+ byte[] data = createFakeDataArray(128000);
+ DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(mTemporaryFile));
+ outputStream.writeInt(data.length);
+ outputStream.write(data);
+ outputStream.writeInt(0);
+ outputStream.close();
+
+ mTemporaryFileDescriptor = ParcelFileDescriptor.open(mTemporaryFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor,
+ mByteArrayOutputStream);
+
+ assertThat(mByteArrayOutputStream.toByteArray()).isEqualTo(data);
+ verify(mOutputStreamMock, never()).close();
+ assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
+ }
+
+ @Test
+ public void routeSocketDataToOutput_twoSmallChunks_writesOutputCorrectly() throws Exception {
+ byte[] data = createFakeDataArray(200);
+ int chunk1Length = 97;
+ int chunk2Length = data.length - chunk1Length;
+
+ DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(mTemporaryFile));
+ outputStream.writeInt(chunk1Length);
+ outputStream.write(data, 0, chunk1Length);
+ outputStream.writeInt(chunk2Length);
+ outputStream.write(data, chunk1Length, chunk2Length);
+ outputStream.writeInt(0);
+ outputStream.close();
+
+ mTemporaryFileDescriptor = ParcelFileDescriptor.open(mTemporaryFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor,
+ mByteArrayOutputStream);
+
+ assertThat(mByteArrayOutputStream.toByteArray()).isEqualTo(data);
+ verify(mOutputStreamMock, never()).close();
+ assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
+ }
+
+ @Test
+ public void routeSocketDataToOutput_twoLargeChunks_writesOutputCorrectly() throws Exception {
+ byte[] data = createFakeDataArray(256000);
+ int chunk1Length = 127313;
+ int chunk2Length = data.length - chunk1Length;
+
+ DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(mTemporaryFile));
+ outputStream.writeInt(chunk1Length);
+ outputStream.write(data, 0, chunk1Length);
+ outputStream.writeInt(chunk2Length);
+ outputStream.write(data, chunk1Length, chunk2Length);
+ outputStream.writeInt(0);
+ outputStream.close();
+
+ mTemporaryFileDescriptor = ParcelFileDescriptor.open(mTemporaryFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ FullBackupUtils.routeSocketDataToOutput(mTemporaryFileDescriptor,
+ mByteArrayOutputStream);
+
+ assertThat(mByteArrayOutputStream.toByteArray()).isEqualTo(data);
+ verify(mOutputStreamMock, never()).close();
+ assertThat(mTemporaryFileDescriptor.getFileDescriptor().valid()).isTrue();
+ }
+
+ private static byte[] createFakeDataArray(int length) {
+ byte[] data = new byte[length];
+ new Random(3742).nextBytes(data);
+ return data;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
new file mode 100644
index 0000000..6b25d12
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
@@ -0,0 +1,533 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.utils;
+
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_ID;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_APK_NOT_INSTALLED;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_RESTORE_ANY_VERSION;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_SYSTEM_APP_NO_AGENT;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSIONS_MATCH;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.internal.verification.VerificationModeFactory.times;
+
+import android.app.backup.IBackupManagerMonitor;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.Signature;
+import android.os.Bundle;
+import android.os.Process;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.servicestests.R;
+import com.android.server.backup.FileMetadata;
+import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.restore.PerformAdbRestoreTask;
+import com.android.server.backup.restore.RestorePolicy;
+import com.android.server.backup.testutils.PackageManagerStub;
+
+import com.google.common.hash.Hashing;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TarBackupReaderTest {
+ private static final String TELEPHONY_PACKAGE_NAME = "com.android.providers.telephony";
+ private static final String TELEPHONY_PACKAGE_SIGNATURE_SHA256 =
+ "301aa3cb081134501c45f1422abc66c24224fd5ded5fdc8f17e697176fd866aa";
+ private static final int TELEPHONY_PACKAGE_VERSION = 25;
+ private static final String TEST_PACKAGE_NAME = "com.android.backup.testing";
+ private static final Signature FAKE_SIGNATURE_1 = new Signature("1234");
+ private static final Signature FAKE_SIGNATURE_2 = new Signature("5678");
+
+ @Mock private BytesReadListener mBytesReadListenerMock;
+ @Mock private IBackupManagerMonitor mBackupManagerMonitorMock;
+
+ private final PackageManagerStub mPackageManagerStub = new PackageManagerStub();
+ private Context mContext;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = InstrumentationRegistry.getContext();
+ }
+
+ @Test
+ public void readTarHeaders_backupEncrypted_correctlyParsesFileMetadata() throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_with_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, "123");
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+ FileMetadata fileMetadata = tarBackupReader.readTarHeaders();
+
+ assertThat(fileMetadata.packageName).isEqualTo(TELEPHONY_PACKAGE_NAME);
+ assertThat(fileMetadata.mode).isEqualTo(0600);
+ assertThat(fileMetadata.size).isEqualTo(2438);
+ assertThat(fileMetadata.domain).isEqualTo(null);
+ assertThat(fileMetadata.path).isEqualTo("_manifest");
+ }
+
+ @Test
+ public void readTarHeaders_backupNotEncrypted_correctlyParsesFileMetadata() throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_no_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+ FileMetadata fileMetadata = tarBackupReader.readTarHeaders();
+
+ assertThat(fileMetadata.packageName).isEqualTo(TELEPHONY_PACKAGE_NAME);
+ assertThat(fileMetadata.mode).isEqualTo(0600);
+ assertThat(fileMetadata.size).isEqualTo(2438);
+ assertThat(fileMetadata.domain).isEqualTo(null);
+ assertThat(fileMetadata.path).isEqualTo("_manifest");
+ }
+
+ @Test
+ public void readTarHeaders_backupNotEncrypted_correctlyReadsPaxHeader() throws Exception {
+ // Files with long names (>100 chars) will force backup to add PAX header.
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_file_with_long_name);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+
+ // Read manifest file.
+ FileMetadata fileMetadata = tarBackupReader.readTarHeaders();
+ Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(
+ fileMetadata);
+ RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
+ mPackageManagerStub, false /* allowApks */, fileMetadata, signatures);
+
+ assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE);
+ assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME);
+ assertThat(fileMetadata.path).isEqualTo(
+ RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME);
+
+ tarBackupReader.skipTarPadding(fileMetadata.size);
+
+ // Read actual file (PAX header will only exist here).
+ fileMetadata = tarBackupReader.readTarHeaders();
+ signatures = tarBackupReader.readAppManifestAndReturnSignatures(
+ fileMetadata);
+ restorePolicy = tarBackupReader.chooseRestorePolicy(
+ mPackageManagerStub, false /* allowApks */, fileMetadata, signatures);
+
+ assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE);
+ assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME);
+ char[] expectedFileNameChars = new char[200];
+ Arrays.fill(expectedFileNameChars, '1');
+ String expectedFileName = new String(expectedFileNameChars);
+ assertThat(fileMetadata.path).isEqualTo(expectedFileName);
+ }
+
+ @Test
+ public void readAppManifest_backupEncrypted_correctlyParsesAppManifest() throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_with_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, "123");
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+ FileMetadata fileMetadata = tarBackupReader.readTarHeaders();
+
+ Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(fileMetadata);
+
+ assertThat(fileMetadata.version).isEqualTo(TELEPHONY_PACKAGE_VERSION);
+ assertThat(fileMetadata.hasApk).isFalse();
+ assertThat(signatures).isNotNull();
+ assertThat(signatures).hasLength(1);
+
+ String signatureSha256 = Hashing.sha256().hashBytes(signatures[0].toByteArray()).toString();
+ assertThat(signatureSha256).isEqualTo(TELEPHONY_PACKAGE_SIGNATURE_SHA256);
+ }
+
+ @Test
+ public void readAppManifest_backupNotEncrypted_correctlyParsesAppManifest() throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_no_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+ FileMetadata fileMetadata = tarBackupReader.readTarHeaders();
+
+ Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(fileMetadata);
+
+ assertThat(fileMetadata.version).isEqualTo(TELEPHONY_PACKAGE_VERSION);
+ assertThat(fileMetadata.hasApk).isFalse();
+ assertThat(signatures).isNotNull();
+ assertThat(signatures).hasLength(1);
+
+ String signatureSha256 = Hashing.sha256().hashBytes(signatures[0].toByteArray()).toString();
+ assertThat(signatureSha256).isEqualTo(TELEPHONY_PACKAGE_SIGNATURE_SHA256);
+ }
+
+ @Test
+ public void chooseRestorePolicy_signaturesIsNull_returnsIgnore() throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_no_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+
+ RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
+ true /* allowApks */, new FileMetadata(), null /* signatures */);
+
+ assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
+ verifyZeroInteractions(mBackupManagerMonitorMock);
+ }
+
+ @Test
+ public void chooseRestorePolicy_packageDoesNotExistAndAllowApksAndHasApk_returnsAcceptIfApk()
+ throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_no_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+ FileMetadata info = new FileMetadata();
+ info.hasApk = true;
+ PackageManagerStub.sPackageInfo = null;
+
+ RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
+ true /* allowApks */, info, new Signature[0] /* signatures */);
+
+ assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mBackupManagerMonitorMock).onEvent(bundleCaptor.capture());
+ assertThat(bundleCaptor.getValue().get(EXTRA_LOG_EVENT_ID)).isEqualTo(
+ LOG_EVENT_ID_APK_NOT_INSTALLED);
+ }
+
+ @Test
+ public void
+ chooseRestorePolicy_packageDoesNotExistAndAllowApksAndDoesNotHaveApk_returnsAcceptIfApkLogsCannotRestore()
+ throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_no_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+ FileMetadata info = new FileMetadata();
+ info.hasApk = false;
+ PackageManagerStub.sPackageInfo = null;
+
+ RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
+ true /* allowApks */, info, new Signature[0] /* signatures */);
+
+ assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mBackupManagerMonitorMock, times(2)).onEvent(bundleCaptor.capture());
+ List<Bundle> eventBundles = bundleCaptor.getAllValues();
+ assertThat(eventBundles).hasSize(2);
+ assertThat(eventBundles.get(0).get(EXTRA_LOG_EVENT_ID)).isEqualTo(
+ LOG_EVENT_ID_APK_NOT_INSTALLED);
+ assertThat(eventBundles.get(1).get(EXTRA_LOG_EVENT_ID)).isEqualTo(
+ LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK);
+ }
+
+ @Test
+ public void chooseRestorePolicy_packageDoesNotExistAndDoesNotAllowApks_returnsIgnore()
+ throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_no_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+ PackageManagerStub.sPackageInfo = null;
+
+ RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
+ false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */);
+
+ assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mBackupManagerMonitorMock).onEvent(bundleCaptor.capture());
+ assertThat(bundleCaptor.getValue().get(EXTRA_LOG_EVENT_ID)).isEqualTo(
+ LOG_EVENT_ID_APK_NOT_INSTALLED);
+ }
+
+ @Test
+ public void chooseRestorePolicy_doesNotAllowsBackup_returnsIgnore() throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_no_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.flags = ~ApplicationInfo.FLAG_ALLOW_BACKUP;
+ PackageManagerStub.sPackageInfo = packageInfo;
+
+ RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
+ false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */);
+
+ assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mBackupManagerMonitorMock).onEvent(bundleCaptor.capture());
+ assertThat(bundleCaptor.getValue().get(EXTRA_LOG_EVENT_ID)).isEqualTo(
+ LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE);
+ }
+
+ @Test
+ public void chooseRestorePolicy_systemAppWithNoAgent_returnsIgnore() throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_no_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ packageInfo.applicationInfo.uid = Process.SYSTEM_UID;
+ packageInfo.applicationInfo.backupAgentName = null;
+ PackageManagerStub.sPackageInfo = packageInfo;
+
+ RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
+ false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */);
+
+ assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mBackupManagerMonitorMock).onEvent(bundleCaptor.capture());
+ assertThat(bundleCaptor.getValue().get(EXTRA_LOG_EVENT_ID)).isEqualTo(
+ LOG_EVENT_ID_SYSTEM_APP_NO_AGENT);
+ }
+
+ @Test
+ public void chooseRestorePolicy_nonSystemAppSignaturesDoNotMatch_returnsIgnore()
+ throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_no_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+ Signature[] signatures = new Signature[]{FAKE_SIGNATURE_1};
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
+ packageInfo.applicationInfo.backupAgentName = null;
+ packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_2};
+ PackageManagerStub.sPackageInfo = packageInfo;
+
+ RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
+ false /* allowApks */, new FileMetadata(), signatures);
+
+ assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mBackupManagerMonitorMock).onEvent(bundleCaptor.capture());
+ assertThat(bundleCaptor.getValue().get(EXTRA_LOG_EVENT_ID)).isEqualTo(
+ LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH);
+ }
+
+ @Test
+ public void chooseRestorePolicy_systemAppWithBackupAgentAndRestoreAnyVersion_returnsAccept()
+ throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_no_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+ Signature[] signatures = new Signature[]{FAKE_SIGNATURE_1};
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.flags |=
+ ApplicationInfo.FLAG_ALLOW_BACKUP | ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
+ packageInfo.applicationInfo.uid = Process.SYSTEM_UID;
+ packageInfo.applicationInfo.backupAgentName = "backup.agent";
+ packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_1};
+ PackageManagerStub.sPackageInfo = packageInfo;
+
+ RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
+ false /* allowApks */, new FileMetadata(), signatures);
+
+ assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mBackupManagerMonitorMock).onEvent(bundleCaptor.capture());
+ assertThat(bundleCaptor.getValue().get(EXTRA_LOG_EVENT_ID)).isEqualTo(
+ LOG_EVENT_ID_RESTORE_ANY_VERSION);
+ }
+
+ @Test
+ public void chooseRestorePolicy_restoreAnyVersion_returnsAccept() throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_no_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+ Signature[] signatures = new Signature[]{FAKE_SIGNATURE_1};
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.flags |=
+ ApplicationInfo.FLAG_ALLOW_BACKUP | ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
+ packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
+ packageInfo.applicationInfo.backupAgentName = null;
+ packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_1};
+ PackageManagerStub.sPackageInfo = packageInfo;
+
+ RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
+ false /* allowApks */, new FileMetadata(), signatures);
+
+ assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mBackupManagerMonitorMock).onEvent(bundleCaptor.capture());
+ assertThat(bundleCaptor.getValue().get(EXTRA_LOG_EVENT_ID)).isEqualTo(
+ LOG_EVENT_ID_RESTORE_ANY_VERSION);
+ }
+
+ @Test
+ public void chooseRestorePolicy_notRestoreAnyVersionButVersionMatch_returnsAccept()
+ throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_no_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+ Signature[] signatures = new Signature[]{FAKE_SIGNATURE_1};
+ FileMetadata info = new FileMetadata();
+ info.version = 1;
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ packageInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
+ packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
+ packageInfo.applicationInfo.backupAgentName = null;
+ packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_1};
+ packageInfo.versionCode = 2;
+ PackageManagerStub.sPackageInfo = packageInfo;
+
+ RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
+ false /* allowApks */, info, signatures);
+
+ assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mBackupManagerMonitorMock).onEvent(bundleCaptor.capture());
+ assertThat(bundleCaptor.getValue().get(EXTRA_LOG_EVENT_ID)).isEqualTo(
+ LOG_EVENT_ID_VERSIONS_MATCH);
+ }
+
+ @Test
+ public void
+ chooseRestorePolicy_notRestoreAnyVersionAndVersionMismatchButAllowApksAndHasApk_returnsAcceptIfApk()
+ throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_no_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+ Signature[] signatures = new Signature[]{FAKE_SIGNATURE_1};
+ FileMetadata info = new FileMetadata();
+ info.version = 2;
+ info.hasApk = true;
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ packageInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
+ packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
+ packageInfo.applicationInfo.backupAgentName = null;
+ packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_1};
+ packageInfo.versionCode = 1;
+ PackageManagerStub.sPackageInfo = packageInfo;
+
+ RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
+ true /* allowApks */, info, signatures);
+
+ assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
+ verifyNoMoreInteractions(mBackupManagerMonitorMock);
+ }
+
+ @Test
+ public void
+ chooseRestorePolicy_notRestoreAnyVersionAndVersionMismatchAndDoesNotAllowApks_returnsIgnore()
+ throws Exception {
+ InputStream inputStream = mContext.getResources().openRawResource(
+ R.raw.backup_telephony_no_password);
+ InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream(
+ inputStream, null);
+ TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream,
+ mBytesReadListenerMock, mBackupManagerMonitorMock);
+ Signature[] signatures = new Signature[]{FAKE_SIGNATURE_1};
+ FileMetadata info = new FileMetadata();
+ info.version = 2;
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ packageInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
+ packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
+ packageInfo.applicationInfo.backupAgentName = null;
+ packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_1};
+ packageInfo.versionCode = 1;
+ PackageManagerStub.sPackageInfo = packageInfo;
+
+ RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
+ false /* allowApks */, info, signatures);
+
+ assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mBackupManagerMonitorMock).onEvent(bundleCaptor.capture());
+ assertThat(bundleCaptor.getValue().get(EXTRA_LOG_EVENT_ID)).isEqualTo(
+ LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER);
+ }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyConstantsTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyConstantsTest.java
index 3819914..175fdd8 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyConstantsTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyConstantsTest.java
@@ -16,6 +16,7 @@
package com.android.server.devicepolicy;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
/**
* Test for {@link DevicePolicyConstants}.
@@ -29,6 +30,7 @@
-w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
*/
+@SmallTest
public class DevicePolicyConstantsTest extends AndroidTestCase {
private static final String TAG = "DevicePolicyConstantsTest";
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index be1d07b..c5fb0bd 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -47,14 +47,14 @@
mContext = getContext();
- when(mContext.packageManager.hasSystemFeature(eq(PackageManager.FEATURE_DEVICE_ADMIN)))
+ when(getServices().packageManager.hasSystemFeature(eq(PackageManager.FEATURE_DEVICE_ADMIN)))
.thenReturn(true);
}
public void testMigration() throws Exception {
- final File user10dir = mMockContext.addUser(10, 0);
- final File user11dir = mMockContext.addUser(11, UserInfo.FLAG_MANAGED_PROFILE);
- mMockContext.addUser(12, 0);
+ final File user10dir = getServices().addUser(10, 0);
+ final File user11dir = getServices().addUser(11, UserInfo.FLAG_MANAGED_PROFILE);
+ getServices().addUser(12, 0);
setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
setUpPackageManagerForAdmin(admin2, UserHandle.getUid(10, 123));
@@ -62,12 +62,12 @@
// Create the legacy owners & policies file.
DpmTestUtils.writeToFile(
- (new File(mContext.dataDir, OwnersTestable.LEGACY_FILE)).getAbsoluteFile(),
+ (new File(getServices().dataDir, OwnersTestable.LEGACY_FILE)).getAbsoluteFile(),
DpmTestUtils.readAsset(mRealTestContext,
"DevicePolicyManagerServiceMigrationTest/legacy_device_owner.xml"));
DpmTestUtils.writeToFile(
- (new File(mContext.systemUserDataDir, "device_policies.xml")).getAbsoluteFile(),
+ (new File(getServices().systemUserDataDir, "device_policies.xml")).getAbsoluteFile(),
DpmTestUtils.readAsset(mRealTestContext,
"DevicePolicyManagerServiceMigrationTest/legacy_device_policies.xml"));
@@ -81,12 +81,12 @@
"DevicePolicyManagerServiceMigrationTest/legacy_device_policies_11.xml"));
// Set up UserManager
- when(mMockContext.userManagerInternal.getBaseUserRestrictions(
+ when(getServices().userManagerInternal.getBaseUserRestrictions(
eq(UserHandle.USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions(
UserManager.DISALLOW_ADD_USER,
UserManager.DISALLOW_RECORD_AUDIO));
- when(mMockContext.userManagerInternal.getBaseUserRestrictions(
+ when(getServices().userManagerInternal.getBaseUserRestrictions(
eq(10))).thenReturn(DpmTestUtils.newRestrictions(
UserManager.DISALLOW_REMOVE_USER,
UserManager.DISALLOW_ADD_USER,
@@ -95,7 +95,7 @@
UserManager.DISALLOW_WALLPAPER,
UserManager.DISALLOW_RECORD_AUDIO));
- when(mMockContext.userManagerInternal.getBaseUserRestrictions(
+ when(getServices().userManagerInternal.getBaseUserRestrictions(
eq(11))).thenReturn(DpmTestUtils.newRestrictions(
UserManager.DISALLOW_REMOVE_USER,
UserManager.DISALLOW_ADD_USER,
@@ -113,7 +113,7 @@
newBaseRestrictions.put(userId, bundle);
return null;
- }).when(mContext.userManagerInternal).setBaseUserRestrictionsByDpmsForMigration(
+ }).when(getServices().userManagerInternal).setBaseUserRestrictionsByDpmsForMigration(
anyInt(), any(Bundle.class));
// Initialize DPM/DPMS and let it migrate the persisted information.
@@ -125,7 +125,7 @@
try {
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
- dpms = new DevicePolicyManagerServiceTestable(mContext, dataDir);
+ dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext);
dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
dpms.systemReady(SystemService.PHASE_BOOT_COMPLETED);
@@ -200,17 +200,17 @@
// Create the legacy owners & policies file.
DpmTestUtils.writeToFile(
- (new File(mContext.dataDir, OwnersTestable.LEGACY_FILE)).getAbsoluteFile(),
+ (new File(getServices().dataDir, OwnersTestable.LEGACY_FILE)).getAbsoluteFile(),
DpmTestUtils.readAsset(mRealTestContext,
"DevicePolicyManagerServiceMigrationTest2/legacy_device_owner.xml"));
DpmTestUtils.writeToFile(
- (new File(mContext.systemUserDataDir, "device_policies.xml")).getAbsoluteFile(),
+ (new File(getServices().systemUserDataDir, "device_policies.xml")).getAbsoluteFile(),
DpmTestUtils.readAsset(mRealTestContext,
"DevicePolicyManagerServiceMigrationTest2/legacy_device_policies.xml"));
// Set up UserManager
- when(mMockContext.userManagerInternal.getBaseUserRestrictions(
+ when(getServices().userManagerInternal.getBaseUserRestrictions(
eq(UserHandle.USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions(
UserManager.DISALLOW_ADD_USER,
UserManager.DISALLOW_RECORD_AUDIO,
@@ -226,7 +226,7 @@
newBaseRestrictions.put(userId, bundle);
return null;
- }).when(mContext.userManagerInternal).setBaseUserRestrictionsByDpmsForMigration(
+ }).when(getServices().userManagerInternal).setBaseUserRestrictionsByDpmsForMigration(
anyInt(), any(Bundle.class));
// Initialize DPM/DPMS and let it migrate the persisted information.
@@ -238,7 +238,7 @@
try {
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
- dpms = new DevicePolicyManagerServiceTestable(mContext, dataDir);
+ dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext);
dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
dpms.systemReady(SystemService.PHASE_BOOT_COMPLETED);
@@ -273,18 +273,18 @@
// Test setting default restrictions for managed profile.
public void testMigration3_managedProfileOwner() throws Exception {
// Create a managed profile user.
- final File user10dir = mMockContext.addUser(10, UserInfo.FLAG_MANAGED_PROFILE);
+ final File user10dir = getServices().addUser(10, UserInfo.FLAG_MANAGED_PROFILE);
// Profile owner package for managed profile user.
setUpPackageManagerForAdmin(admin1, UserHandle.getUid(10, 123));
// Set up fake UserManager to make it look like a managed profile.
- when(mMockContext.userManager.isManagedProfile(eq(10))).thenReturn(true);
+ when(getServices().userManager.isManagedProfile(eq(10))).thenReturn(true);
// Set up fake Settings to make it look like INSTALL_NON_MARKET_APPS was reversed.
- when(mMockContext.settings.settingsSecureGetIntForUser(
+ when(getServices().settings.settingsSecureGetIntForUser(
eq(Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED),
eq(0), eq(10))).thenReturn(1);
// Write policy and owners files.
DpmTestUtils.writeToFile(
- (new File(mContext.systemUserDataDir, "device_policies.xml")).getAbsoluteFile(),
+ (new File(getServices().systemUserDataDir, "device_policies.xml")).getAbsoluteFile(),
DpmTestUtils.readAsset(mRealTestContext,
"DevicePolicyManagerServiceMigrationTest3/system_device_policies.xml"));
DpmTestUtils.writeToFile(
@@ -304,7 +304,7 @@
try {
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
- dpms = new DevicePolicyManagerServiceTestable(mContext, dataDir);
+ dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext);
dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
dpms.systemReady(SystemService.PHASE_BOOT_COMPLETED);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index b870d94..a33153e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -61,11 +61,12 @@
private final File mDeviceOwnerFile;
private final File mUsersDataDir;
- public OwnersTestable(DpmMockContext context) {
- super(context.userManager, context.userManagerInternal, context.packageManagerInternal);
- mLegacyFile = new File(context.dataDir, LEGACY_FILE);
- mDeviceOwnerFile = new File(context.dataDir, DEVICE_OWNER_FILE);
- mUsersDataDir = new File(context.dataDir, "users");
+ public OwnersTestable(MockSystemServices services) {
+ super(services.userManager, services.userManagerInternal,
+ services.packageManagerInternal);
+ mLegacyFile = new File(services.dataDir, LEGACY_FILE);
+ mDeviceOwnerFile = new File(services.dataDir, DEVICE_OWNER_FILE);
+ mUsersDataDir = new File(services.dataDir, "users");
}
@Override
@@ -88,8 +89,8 @@
public final DpmMockContext context;
private final MockInjector mMockInjector;
- public DevicePolicyManagerServiceTestable(DpmMockContext context, File dataDir) {
- this(new MockInjector(context, dataDir));
+ public DevicePolicyManagerServiceTestable(MockSystemServices services, DpmMockContext context) {
+ this(new MockInjector(services, context));
}
private DevicePolicyManagerServiceTestable(MockInjector injector) {
@@ -100,15 +101,13 @@
public void notifyChangeToContentObserver(Uri uri, int userHandle) {
- ContentObserver co = mMockInjector.mContentObservers
- .get(new Pair<Uri, Integer>(uri, userHandle));
+ ContentObserver co = mMockInjector.mContentObservers.get(new Pair<>(uri, userHandle));
if (co != null) {
co.onChange(false, uri, userHandle); // notify synchronously
}
// Notify USER_ALL observer too.
- co = mMockInjector.mContentObservers
- .get(new Pair<Uri, Integer>(uri, UserHandle.USER_ALL));
+ co = mMockInjector.mContentObservers.get(new Pair<>(uri, UserHandle.USER_ALL));
if (co != null) {
co.onChange(false, uri, userHandle); // notify synchronously
}
@@ -118,76 +117,75 @@
private static class MockInjector extends Injector {
public final DpmMockContext context;
-
- public final File dataDir;
+ private final MockSystemServices services;
// Key is a pair of uri and userId
private final Map<Pair<Uri, Integer>, ContentObserver> mContentObservers = new ArrayMap<>();
- private MockInjector(DpmMockContext context, File dataDir) {
+ private MockInjector(MockSystemServices services, DpmMockContext context) {
super(context);
+ this.services = services;
this.context = context;
- this.dataDir = dataDir;
}
@Override
Owners newOwners() {
- return new OwnersTestable(context);
+ return new OwnersTestable(services);
}
@Override
UserManager getUserManager() {
- return context.userManager;
+ return services.userManager;
}
@Override
UserManagerInternal getUserManagerInternal() {
- return context.userManagerInternal;
+ return services.userManagerInternal;
}
@Override
PackageManagerInternal getPackageManagerInternal() {
- return context.packageManagerInternal;
+ return services.packageManagerInternal;
}
@Override
PowerManagerInternal getPowerManagerInternal() {
- return context.powerManagerInternal;
+ return services.powerManagerInternal;
}
@Override
NotificationManager getNotificationManager() {
- return context.notificationManager;
+ return services.notificationManager;
}
@Override
IIpConnectivityMetrics getIIpConnectivityMetrics() {
- return context.iipConnectivityMetrics;
+ return services.iipConnectivityMetrics;
}
@Override
IWindowManager getIWindowManager() {
- return context.iwindowManager;
+ return services.iwindowManager;
}
@Override
IActivityManager getIActivityManager() {
- return context.iactivityManager;
+ return services.iactivityManager;
}
@Override
IPackageManager getIPackageManager() {
- return context.ipackageManager;
+ return services.ipackageManager;
}
@Override
IBackupManager getIBackupManager() {
- return context.ibackupManager;
+ return services.ibackupManager;
}
@Override
IAudioService getIAudioService() {
- return context.iaudioService;
+ return services.iaudioService;
}
@Override
@@ -197,32 +195,32 @@
@Override
LockPatternUtils newLockPatternUtils() {
- return context.lockPatternUtils;
+ return services.lockPatternUtils;
}
@Override
boolean storageManagerIsFileBasedEncryptionEnabled() {
- return context.storageManager.isFileBasedEncryptionEnabled();
+ return services.storageManager.isFileBasedEncryptionEnabled();
}
@Override
boolean storageManagerIsNonDefaultBlockEncrypted() {
- return context.storageManager.isNonDefaultBlockEncrypted();
+ return services.storageManager.isNonDefaultBlockEncrypted();
}
@Override
boolean storageManagerIsEncrypted() {
- return context.storageManager.isEncrypted();
+ return services.storageManager.isEncrypted();
}
@Override
boolean storageManagerIsEncryptable() {
- return context.storageManager.isEncryptable();
+ return services.storageManager.isEncryptable();
}
@Override
String getDevicePolicyFilePathForSystemUser() {
- return context.systemUserDataDir.getAbsolutePath() + "/";
+ return services.systemUserDataDir.getAbsolutePath() + "/";
}
@Override
@@ -257,53 +255,53 @@
@Override
File environmentGetUserSystemDirectory(int userId) {
- return context.environment.getUserSystemDirectory(userId);
+ return services.environment.getUserSystemDirectory(userId);
}
@Override
void powerManagerGoToSleep(long time, int reason, int flags) {
- context.powerManager.goToSleep(time, reason, flags);
+ services.powerManager.goToSleep(time, reason, flags);
}
@Override
void powerManagerReboot(String reason) {
- context.powerManager.reboot(reason);
+ services.powerManager.reboot(reason);
}
@Override
void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force)
throws IOException {
- context.recoverySystem.rebootWipeUserData(shutdown, reason, force);
+ services.recoverySystem.rebootWipeUserData(shutdown, reason, force);
}
@Override
boolean systemPropertiesGetBoolean(String key, boolean def) {
- return context.systemProperties.getBoolean(key, def);
+ return services.systemProperties.getBoolean(key, def);
}
@Override
long systemPropertiesGetLong(String key, long def) {
- return context.systemProperties.getLong(key, def);
+ return services.systemProperties.getLong(key, def);
}
@Override
String systemPropertiesGet(String key, String def) {
- return context.systemProperties.get(key, def);
+ return services.systemProperties.get(key, def);
}
@Override
String systemPropertiesGet(String key) {
- return context.systemProperties.get(key);
+ return services.systemProperties.get(key);
}
@Override
void systemPropertiesSet(String key, String value) {
- context.systemProperties.set(key, value);
+ services.systemProperties.set(key, value);
}
@Override
boolean userManagerIsSplitSystemUser() {
- return context.userManagerForMock.isSplitSystemUser();
+ return services.userManagerForMock.isSplitSystemUser();
}
@Override
@@ -320,87 +318,87 @@
@Override
int settingsSecureGetIntForUser(String name, int def, int userHandle) {
- return context.settings.settingsSecureGetIntForUser(name, def, userHandle);
+ return services.settings.settingsSecureGetIntForUser(name, def, userHandle);
}
@Override
String settingsSecureGetStringForUser(String name, int userHandle) {
- return context.settings.settingsSecureGetStringForUser(name, userHandle);
+ return services.settings.settingsSecureGetStringForUser(name, userHandle);
}
@Override
void settingsSecurePutIntForUser(String name, int value, int userHandle) {
- context.settings.settingsSecurePutIntForUser(name, value, userHandle);
+ services.settings.settingsSecurePutIntForUser(name, value, userHandle);
}
@Override
void settingsSecurePutStringForUser(String name, String value, int userHandle) {
- context.settings.settingsSecurePutStringForUser(name, value, userHandle);
+ services.settings.settingsSecurePutStringForUser(name, value, userHandle);
}
@Override
void settingsGlobalPutStringForUser(String name, String value, int userHandle) {
- context.settings.settingsGlobalPutStringForUser(name, value, userHandle);
+ services.settings.settingsGlobalPutStringForUser(name, value, userHandle);
}
@Override
void settingsSecurePutInt(String name, int value) {
- context.settings.settingsSecurePutInt(name, value);
+ services.settings.settingsSecurePutInt(name, value);
}
@Override
void settingsGlobalPutInt(String name, int value) {
- context.settings.settingsGlobalPutInt(name, value);
+ services.settings.settingsGlobalPutInt(name, value);
}
@Override
void settingsSecurePutString(String name, String value) {
- context.settings.settingsSecurePutString(name, value);
+ services.settings.settingsSecurePutString(name, value);
}
@Override
void settingsGlobalPutString(String name, String value) {
- context.settings.settingsGlobalPutString(name, value);
+ services.settings.settingsGlobalPutString(name, value);
}
@Override
int settingsGlobalGetInt(String name, int def) {
- return context.settings.settingsGlobalGetInt(name, def);
+ return services.settings.settingsGlobalGetInt(name, def);
}
@Override
String settingsGlobalGetString(String name) {
- return context.settings.settingsGlobalGetString(name);
+ return services.settings.settingsGlobalGetString(name);
}
@Override
void securityLogSetLoggingEnabledProperty(boolean enabled) {
- context.settings.securityLogSetLoggingEnabledProperty(enabled);
+ services.settings.securityLogSetLoggingEnabledProperty(enabled);
}
@Override
boolean securityLogGetLoggingEnabledProperty() {
- return context.settings.securityLogGetLoggingEnabledProperty();
+ return services.settings.securityLogGetLoggingEnabledProperty();
}
@Override
boolean securityLogIsLoggingEnabled() {
- return context.settings.securityLogIsLoggingEnabled();
+ return services.settings.securityLogIsLoggingEnabled();
}
@Override
TelephonyManager getTelephonyManager() {
- return context.telephonyManager;
+ return services.telephonyManager;
}
@Override
boolean isBuildDebuggable() {
- return context.buildMock.isDebuggable;
+ return services.buildMock.isDebuggable;
}
@Override
KeyChain.KeyChainConnection keyChainBindAsUser(UserHandle user) {
- return context.keyChainConnection;
+ return services.keyChainConnection;
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index c2b0ea5..6393b0b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -15,41 +15,58 @@
*/
package com.android.server.devicepolicy;
+import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
+import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY;
import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY;
import static android.os.UserManagerInternal.CAMERA_NOT_DISABLED;
+import static com.android.server.testutis.TestUtils.assertExpectException;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.nullable;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+
import android.Manifest.permission;
import android.app.Activity;
import android.app.Notification;
-import android.app.NotificationManager;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.PasswordMetrics;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
-import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.StringParceledListSlice;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.net.IIpConnectivityMetrics;
-import android.net.Uri;
import android.content.pm.UserInfo;
+import android.graphics.Color;
+import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.UserManagerInternal;
import android.provider.Settings;
-import android.security.IKeyChainService;
import android.security.KeyChain;
import android.telephony.TelephonyManager;
import android.test.MoreAsserts;
@@ -77,30 +94,6 @@
import java.util.Set;
import java.util.concurrent.TimeUnit;
-import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
-import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.nullable;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-import static org.mockito.hamcrest.MockitoHamcrest.argThat;
-
/**
* Tests for DevicePolicyManager( and DevicePolicyManagerService).
* You can run them via:
@@ -120,8 +113,15 @@
private static final List<String> OWNER_SETUP_PERMISSIONS = Arrays.asList(
permission.MANAGE_DEVICE_ADMINS, permission.MANAGE_PROFILE_AND_DEVICE_OWNERS,
permission.MANAGE_USERS, permission.INTERACT_ACROSS_USERS_FULL);
+ public static final String NOT_DEVICE_OWNER_MSG = "does not own the device";
+ public static final String ONGOING_CALL_MSG = "ongoing call on the device";
+ // TODO replace all instances of this with explicit {@link #mServiceContext}.
+ @Deprecated
private DpmMockContext mContext;
+
+ private DpmMockContext mServiceContext;
+ private DpmMockContext mAdmin1Context;
public DevicePolicyManager dpm;
public DevicePolicyManagerServiceTestable dpms;
@@ -158,11 +158,13 @@
super.setUp();
mContext = getContext();
- when(mContext.packageManager.hasSystemFeature(eq(PackageManager.FEATURE_DEVICE_ADMIN)))
+ mServiceContext = mContext;
+ mServiceContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ when(getServices().packageManager.hasSystemFeature(eq(PackageManager.FEATURE_DEVICE_ADMIN)))
.thenReturn(true);
// By default, pretend all users are running and unlocked.
- when(mContext.userManager.isUserUnlocked(anyInt())).thenReturn(true);
+ when(getServices().userManager.isUserUnlocked(anyInt())).thenReturn(true);
initializeDpms();
@@ -171,6 +173,11 @@
setUpPackageManagerForAdmin(admin3, DpmMockContext.CALLER_UID);
setUpPackageManagerForAdmin(adminNoPerm, DpmMockContext.CALLER_UID);
+ mAdmin1Context = new DpmMockContext(getServices(), mRealTestContext);
+ mAdmin1Context.packageName = admin1.getPackageName();
+ mAdmin1Context.applicationInfo = new ApplicationInfo();
+ mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_UID;
+
setUpUserManager();
}
@@ -183,18 +190,15 @@
private void initializeDpms() {
// Need clearCallingIdentity() to pass permission checks.
final long ident = mContext.binder.clearCallingIdentity();
- try {
- LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
- dpms = new DevicePolicyManagerServiceTestable(mContext, dataDir);
+ dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext);
+ dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
+ dpms.systemReady(SystemService.PHASE_BOOT_COMPLETED);
- dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
- dpms.systemReady(SystemService.PHASE_BOOT_COMPLETED);
+ dpm = new DevicePolicyManagerTestable(mContext, dpms);
- dpm = new DevicePolicyManagerTestable(mContext, dpms);
- } finally {
- mContext.binder.restoreCallingIdentity(ident);
- }
+ mContext.binder.restoreCallingIdentity(ident);
}
private void setUpUserManager() {
@@ -213,7 +217,7 @@
return null;
}
- }).when(mContext.userManager).setApplicationRestrictions(
+ }).when(getServices().userManager).setApplicationRestrictions(
anyString(), nullable(Bundle.class), any(UserHandle.class));
// UM.getApplicationRestrictions() will read from appRestrictions.
@@ -225,33 +229,36 @@
return appRestrictions.get(Pair.create(pkg, user));
}
- }).when(mContext.userManager).getApplicationRestrictions(
+ }).when(getServices().userManager).getApplicationRestrictions(
anyString(), any(UserHandle.class));
// Add the first secondary user.
- mContext.addUser(DpmMockContext.CALLER_USER_HANDLE, 0);
+ getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0);
}
private void setAsProfileOwner(ComponentName admin) {
- mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
- mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+ final long ident = mServiceContext.binder.clearCallingIdentity();
- // PO needs to be an DA.
- dpm.setActiveAdmin(admin, /* replace =*/ false);
+ mServiceContext.binder.callingUid =
+ UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID);
+ runAsCaller(mServiceContext, dpms, dpm -> {
+ // PO needs to be a DA.
+ dpm.setActiveAdmin(admin, /*replace=*/ false);
+ // Fire!
+ assertTrue(dpm.setProfileOwner(admin, "owner-name", DpmMockContext.CALLER_USER_HANDLE));
+ // Check
+ assertEquals(admin, dpm.getProfileOwnerAsUser(DpmMockContext.CALLER_USER_HANDLE));
+ });
- // Fire!
- assertTrue(dpm.setProfileOwner(admin, "owner-name", DpmMockContext.CALLER_USER_HANDLE));
-
- // Check
- assertEquals(admin, dpm.getProfileOwnerAsUser(DpmMockContext.CALLER_USER_HANDLE));
+ mServiceContext.binder.restoreCallingIdentity(ident);
}
public void testHasNoFeature() throws Exception {
- when(mContext.packageManager.hasSystemFeature(eq(PackageManager.FEATURE_DEVICE_ADMIN)))
+ when(getServices().packageManager.hasSystemFeature(eq(PackageManager.FEATURE_DEVICE_ADMIN)))
.thenReturn(false);
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
- new DevicePolicyManagerServiceTestable(mContext, dataDir);
+ new DevicePolicyManagerServiceTestable(getServices(), mContext);
// If the device has no DPMS feature, it shouldn't register the local service.
assertNull(LocalServices.getService(DevicePolicyManagerInternal.class));
@@ -264,19 +271,14 @@
// 1. Failure cases.
// Caller doesn't have MANAGE_DEVICE_ADMINS.
- try {
- dpm.setActiveAdmin(admin1, false);
- fail("Didn't throw SecurityException");
- } catch (SecurityException expected) {
- }
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setActiveAdmin(admin1, false));
// Caller has MANAGE_DEVICE_ADMINS, but for different user.
mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
- try {
- dpm.setActiveAdmin(admin1, false, DpmMockContext.CALLER_USER_HANDLE + 1);
- fail("Didn't throw SecurityException");
- } catch (SecurityException expected) {
- }
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setActiveAdmin(admin1, false, DpmMockContext.CALLER_USER_HANDLE + 1));
}
/**
@@ -307,7 +309,7 @@
DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED),
MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
- verify(mContext.ipackageManager, times(1)).setApplicationEnabledSetting(
+ verify(getServices().ipackageManager, times(1)).setApplicationEnabledSetting(
eq(admin1.getPackageName()),
eq(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT),
eq(PackageManager.DONT_KILL_APP),
@@ -346,7 +348,7 @@
// Admin2 was already enabled, so setApplicationEnabledSetting() shouldn't have called
// again. (times(1) because it was previously called for admin1)
- verify(mContext.ipackageManager, times(1)).setApplicationEnabledSetting(
+ verify(getServices().ipackageManager, times(1)).setApplicationEnabledSetting(
eq(admin1.getPackageName()),
eq(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT),
eq(PackageManager.DONT_KILL_APP),
@@ -354,11 +356,8 @@
anyString());
// 4. Add the same admin1 again without replace, which should throw.
- try {
- dpm.setActiveAdmin(admin1, /* replace =*/ false);
- fail("Didn't throw");
- } catch (IllegalArgumentException expected) {
- }
+ assertExpectException(IllegalArgumentException.class, /* messageRegex= */ null,
+ () -> dpm.setActiveAdmin(admin1, /* replace =*/ false));
// 5. Add the same admin1 again with replace, which should succeed.
dpm.setActiveAdmin(admin1, /* replace =*/ true);
@@ -385,7 +384,7 @@
final int ANOTHER_USER_ID = 100;
final int ANOTHER_ADMIN_UID = UserHandle.getUid(ANOTHER_USER_ID, 20456);
- mMockContext.addUser(ANOTHER_USER_ID, 0); // Add one more user.
+ getServices().addUser(ANOTHER_USER_ID, 0); // Add one more user.
// Set up pacakge manager for the other user.
setUpPackageManagerForAdmin(admin2, ANOTHER_ADMIN_UID);
@@ -420,11 +419,8 @@
assertTrue(dpm.isAdminActive(admin1));
// Add the same admin1 again without replace, which should throw.
- try {
- dpm.setActiveAdmin(admin1, /* replace =*/ false);
- fail("Didn't throw");
- } catch (IllegalArgumentException expected) {
- }
+ assertExpectException(IllegalArgumentException.class, /* messageRegex= */ null,
+ () -> dpm.setActiveAdmin(admin1, /* replace =*/ false));
}
/**
@@ -436,12 +432,9 @@
// 1. Make sure the caller has proper permissions.
mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
- try {
- dpm.setActiveAdmin(adminNoPerm, /* replace =*/ false);
- fail();
- } catch (IllegalArgumentException expected) {
- assertTrue(expected.getMessage().contains(permission.BIND_DEVICE_ADMIN));
- }
+ assertExpectException(IllegalArgumentException.class,
+ /* messageRegex= */ permission.BIND_DEVICE_ADMIN,
+ () -> dpm.setActiveAdmin(adminNoPerm, /* replace =*/ false));
assertFalse(dpm.isAdminActive(adminNoPerm));
// Change the target API level to MNC. Now it can be set as DA.
@@ -470,11 +463,8 @@
assertFalse(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
// Directly call the DPMS method with a different userid, which should fail.
- try {
- dpms.removeActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE + 1);
- fail("Didn't throw SecurityException");
- } catch (SecurityException expected) {
- }
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpms.removeActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE + 1));
// Try to remove active admin with a different caller userid should fail too, without
// having MANAGE_DEVICE_ADMINS.
@@ -483,11 +473,8 @@
// Change the caller, and call into DPMS directly with a different user-id.
mContext.binder.callingUid = 1234567;
- try {
- dpms.removeActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE);
- fail("Didn't throw SecurityException");
- } catch (SecurityException expected) {
- }
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpms.removeActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
}
/**
@@ -508,20 +495,16 @@
assertFalse(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
// 1. User not unlocked.
- when(mContext.userManager.isUserUnlocked(eq(DpmMockContext.CALLER_USER_HANDLE)))
+ when(getServices().userManager.isUserUnlocked(eq(DpmMockContext.CALLER_USER_HANDLE)))
.thenReturn(false);
- try {
- dpm.removeActiveAdmin(admin1);
- fail("Didn't throw IllegalStateException");
- } catch (IllegalStateException expected) {
- MoreAsserts.assertContainsRegex(
- "User must be running and unlocked", expected.getMessage());
- }
+ assertExpectException(IllegalStateException.class,
+ /* messageRegex= */ "User must be running and unlocked",
+ () -> dpm.removeActiveAdmin(admin1));
assertFalse(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
// 2. User unlocked.
- when(mContext.userManager.isUserUnlocked(eq(DpmMockContext.CALLER_USER_HANDLE)))
+ when(getServices().userManager.isUserUnlocked(eq(DpmMockContext.CALLER_USER_HANDLE)))
.thenReturn(true);
dpm.removeActiveAdmin(admin1);
@@ -656,7 +639,7 @@
// Setup device owner.
mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
mContext.packageName = admin1.getPackageName();
- doReturn(true).when(mContext.lockPatternUtils)
+ doReturn(true).when(getServices().lockPatternUtils)
.isSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID);
setupDeviceOwner();
@@ -687,13 +670,9 @@
// Try to set a profile owner on the same user, which should fail.
setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_SYSTEM_USER_UID);
dpm.setActiveAdmin(admin2, /* refreshing= */ true, UserHandle.USER_SYSTEM);
- try {
- dpm.setProfileOwner(admin2, "owner-name", UserHandle.USER_SYSTEM);
- fail("IllegalStateException not thrown");
- } catch (IllegalStateException expected) {
- assertTrue("Message was: " + expected.getMessage(),
- expected.getMessage().contains("already has a device owner"));
- }
+ assertExpectException(IllegalStateException.class,
+ /* messageRegex= */ "already has a device owner",
+ () -> dpm.setProfileOwner(admin2, "owner-name", UserHandle.USER_SYSTEM));
// DO admin can't be deactivated.
dpm.removeActiveAdmin(admin1);
@@ -739,11 +718,11 @@
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
// Verify internal calls.
- verify(mContext.iactivityManager, times(1)).updateDeviceOwner(
+ verify(getServices().iactivityManager, times(1)).updateDeviceOwner(
eq(admin1.getPackageName()));
// TODO We should check if the caller has called clearCallerIdentity().
- verify(mContext.ibackupManager, times(1)).setBackupServiceActive(
+ verify(getServices().ibackupManager, times(1)).setBackupServiceActive(
eq(UserHandle.USER_SYSTEM), eq(false));
verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
@@ -850,26 +829,14 @@
assertEquals(null, dpm.getDeviceOwnerComponentOnCallingUser());
}
- try {
- dpm.isDeviceOwnerAppOnAnyUser(admin1.getPackageName());
- fail();
- } catch (SecurityException expected) {
- }
- try {
- dpm.getDeviceOwnerComponentOnAnyUser();
- fail();
- } catch (SecurityException expected) {
- }
- try {
- dpm.getDeviceOwnerUserId();
- fail();
- } catch (SecurityException expected) {
- }
- try {
- dpm.getDeviceOwnerNameOnAnyUser();
- fail();
- } catch (SecurityException expected) {
- }
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpm.isDeviceOwnerAppOnAnyUser(admin1.getPackageName()));
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ dpm::getDeviceOwnerComponentOnAnyUser);
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ dpm::getDeviceOwnerUserId);
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ dpm::getDeviceOwnerNameOnAnyUser);
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
// Still no MANAGE_USERS.
@@ -877,26 +844,14 @@
assertFalse(dpm.isDeviceOwnerAppOnCallingUser(admin1.getPackageName()));
assertEquals(null, dpm.getDeviceOwnerComponentOnCallingUser());
- try {
- dpm.isDeviceOwnerAppOnAnyUser(admin1.getPackageName());
- fail();
- } catch (SecurityException expected) {
- }
- try {
- dpm.getDeviceOwnerComponentOnAnyUser();
- fail();
- } catch (SecurityException expected) {
- }
- try {
- dpm.getDeviceOwnerUserId();
- fail();
- } catch (SecurityException expected) {
- }
- try {
- dpm.getDeviceOwnerNameOnAnyUser();
- fail();
- } catch (SecurityException expected) {
- }
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpm.isDeviceOwnerAppOnAnyUser(admin1.getPackageName()));
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ dpm::getDeviceOwnerComponentOnAnyUser);
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ dpm::getDeviceOwnerUserId);
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ dpm::getDeviceOwnerNameOnAnyUser);
// Restore.
mContext.binder.callingUid = origCallingUser;
@@ -916,13 +871,9 @@
// Call from a process on the system user.
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
- try {
- dpm.setDeviceOwner(new ComponentName("a.b.c", ".def"));
- fail("Didn't throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) {
- assertTrue("Message was: " + expected.getMessage(),
- expected.getMessage().contains("Invalid component"));
- }
+ assertExpectException(IllegalArgumentException.class,
+ /* messageRegex= */ "Invalid component",
+ () -> dpm.setDeviceOwner(new ComponentName("a.b.c", ".def")));
}
public void testSetDeviceOwner_failures() throws Exception {
@@ -948,48 +899,43 @@
assertTrue(dpm.setDeviceOwner(admin1, "owner-name"));
// Verify internal calls.
- verify(mContext.iactivityManager, times(1)).updateDeviceOwner(
+ verify(getServices().iactivityManager, times(1)).updateDeviceOwner(
eq(admin1.getPackageName()));
assertEquals(admin1, dpm.getDeviceOwnerComponentOnAnyUser());
dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
- when(mContext.userManager.hasUserRestriction(eq(UserManager.DISALLOW_ADD_USER),
+ when(getServices().userManager.hasUserRestriction(eq(UserManager.DISALLOW_ADD_USER),
MockUtils.checkUserHandle(UserHandle.USER_SYSTEM))).thenReturn(true);
assertTrue(dpm.isAdminActive(admin1));
assertFalse(dpm.isRemovingAdmin(admin1, UserHandle.USER_SYSTEM));
// Set up other mocks.
- when(mContext.userManager.getUserRestrictions()).thenReturn(new Bundle());
+ when(getServices().userManager.getUserRestrictions()).thenReturn(new Bundle());
// Now call clear.
- doReturn(DpmMockContext.CALLER_SYSTEM_USER_UID).when(mContext.packageManager).getPackageUidAsUser(
- eq(admin1.getPackageName()),
- anyInt());
+ doReturn(DpmMockContext.CALLER_SYSTEM_USER_UID).when(getServices().packageManager).
+ getPackageUidAsUser(eq(admin1.getPackageName()), anyInt());
// But first pretend the user is locked. Then it should fail.
- when(mContext.userManager.isUserUnlocked(anyInt())).thenReturn(false);
- try {
- dpm.clearDeviceOwnerApp(admin1.getPackageName());
- fail("Didn't throw IllegalStateException");
- } catch (IllegalStateException expected) {
- MoreAsserts.assertContainsRegex(
- "User must be running and unlocked", expected.getMessage());
- }
+ when(getServices().userManager.isUserUnlocked(anyInt())).thenReturn(false);
+ assertExpectException(IllegalStateException.class,
+ /* messageRegex= */ "User must be running and unlocked",
+ () -> dpm.clearDeviceOwnerApp(admin1.getPackageName()));
- when(mContext.userManager.isUserUnlocked(anyInt())).thenReturn(true);
- reset(mContext.userManagerInternal);
+ when(getServices().userManager.isUserUnlocked(anyInt())).thenReturn(true);
+ reset(getServices().userManagerInternal);
dpm.clearDeviceOwnerApp(admin1.getPackageName());
// Now DO shouldn't be set.
assertNull(dpm.getDeviceOwnerComponentOnAnyUser());
- verify(mContext.userManager).setUserRestriction(eq(UserManager.DISALLOW_ADD_USER),
+ verify(getServices().userManager).setUserRestriction(eq(UserManager.DISALLOW_ADD_USER),
eq(false),
MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
eq(null),
eq(true), eq(CAMERA_NOT_DISABLED));
@@ -1023,7 +969,7 @@
assertTrue(dpm.setDeviceOwner(admin1, "owner-name"));
// Verify internal calls.
- verify(mContext.iactivityManager, times(1)).updateDeviceOwner(
+ verify(getServices().iactivityManager, times(1)).updateDeviceOwner(
eq(admin1.getPackageName()));
assertEquals(admin1, dpm.getDeviceOwnerComponentOnAnyUser());
@@ -1032,15 +978,12 @@
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
// Now call clear.
- doReturn(DpmMockContext.CALLER_UID).when(mContext.packageManager).getPackageUidAsUser(
+ doReturn(DpmMockContext.CALLER_UID).when(getServices().packageManager).getPackageUidAsUser(
eq(admin1.getPackageName()),
anyInt());
- try {
- dpm.clearDeviceOwnerApp(admin1.getPackageName());
- fail("Didn't throw");
- } catch (SecurityException e) {
- assertEquals("clearDeviceOwner can only be called by the device owner", e.getMessage());
- }
+ assertExpectException(SecurityException.class,
+ /* messageRegex =*/ "clearDeviceOwner can only be called by the device owner",
+ () -> dpm.clearDeviceOwnerApp(admin1.getPackageName()));
// DO shouldn't be removed.
assertTrue(dpm.isDeviceManaged());
@@ -1055,14 +998,14 @@
// Try setting DO on the same user, which should fail.
setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID);
- dpm.setActiveAdmin(admin2, /* refreshing= */ true, DpmMockContext.CALLER_USER_HANDLE);
- try {
- dpm.setDeviceOwner(admin2, "owner-name", DpmMockContext.CALLER_USER_HANDLE);
- fail("IllegalStateException not thrown");
- } catch (IllegalStateException expected) {
- assertTrue("Message was: " + expected.getMessage(),
- expected.getMessage().contains("already has a profile owner"));
- }
+ mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ runAsCaller(mServiceContext, dpms, dpm -> {
+ dpm.setActiveAdmin(admin2, /* refreshing= */ true, DpmMockContext.CALLER_USER_HANDLE);
+ assertExpectException(IllegalStateException.class,
+ /* messageRegex= */ "already has a profile owner",
+ () -> dpm.setDeviceOwner(admin2, "owner-name",
+ DpmMockContext.CALLER_USER_HANDLE));
+ });
}
public void testClearProfileOwner() throws Exception {
@@ -1074,18 +1017,14 @@
assertFalse(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
// First try when the user is locked, which should fail.
- when(mContext.userManager.isUserUnlocked(anyInt()))
+ when(getServices().userManager.isUserUnlocked(anyInt()))
.thenReturn(false);
- try {
- dpm.clearProfileOwner(admin1);
- fail("Didn't throw IllegalStateException");
- } catch (IllegalStateException expected) {
- MoreAsserts.assertContainsRegex(
- "User must be running and unlocked", expected.getMessage());
- }
+ assertExpectException(IllegalStateException.class,
+ /* messageRegex= */ "User must be running and unlocked",
+ () -> dpm.clearProfileOwner(admin1));
+
// Clear, really.
- when(mContext.userManager.isUserUnlocked(anyInt()))
- .thenReturn(true);
+ when(getServices().userManager.isUserUnlocked(anyInt())).thenReturn(true);
dpm.clearProfileOwner(admin1);
// Check
@@ -1109,7 +1048,7 @@
final int ANOTHER_USER_ID = 100;
final int ANOTHER_ADMIN_UID = UserHandle.getUid(ANOTHER_USER_ID, 456);
- mMockContext.addUser(ANOTHER_USER_ID, 0); // Add one more user.
+ getServices().addUser(ANOTHER_USER_ID, 0); // Add one more user.
mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
mContext.callerPermissions.add(permission.MANAGE_USERS);
@@ -1118,7 +1057,7 @@
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
- when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true);
+ when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
// Make sure the admin packge is installed to each user.
setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
@@ -1140,7 +1079,7 @@
dpm.setActiveAdmin(admin2, /* replace =*/ false, ANOTHER_USER_ID);
// Set DO on the first non-system user.
- mContext.setUserRunning(DpmMockContext.CALLER_USER_HANDLE, true);
+ getServices().setUserRunning(DpmMockContext.CALLER_USER_HANDLE, true);
assertTrue(dpm.setDeviceOwner(admin2, "owner-name", DpmMockContext.CALLER_USER_HANDLE));
assertEquals(admin2, dpms.getDeviceOwnerComponent(/* callingUserOnly =*/ false));
@@ -1159,7 +1098,7 @@
* finds the right component from a package name upon migration.
*/
public void testDeviceOwnerMigration() throws Exception {
- when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true);
+ when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
checkDeviceOwnerWithMultipleDeviceAdmins();
// Overwrite the device owner setting and clears the clas name.
@@ -1172,7 +1111,7 @@
assertEquals("", dpms.getDeviceOwnerComponent(/* callingUserOnly =*/ false).getClassName());
// Then create a new DPMS to have it load the settings from files.
- when(mContext.userManager.getUserRestrictions(any(UserHandle.class)))
+ when(getServices().userManager.getUserRestrictions(any(UserHandle.class)))
.thenReturn(new Bundle());
initializeDpms();
@@ -1226,8 +1165,7 @@
*/
private int setupPackageInPackageManager(final String packageName, final int appId)
throws Exception {
- return setupPackageInPackageManager(
- packageName, DpmMockContext.CALLER_USER_HANDLE, appId,
+ return setupPackageInPackageManager(packageName, DpmMockContext.CALLER_USER_HANDLE, appId,
ApplicationInfo.FLAG_HAS_CODE);
}
@@ -1240,31 +1178,29 @@
* @param flags flags to set in the ApplicationInfo for this package
* @return the UID of the package as known by the mock package manager
*/
- private int setupPackageInPackageManager(
- final String packageName, int userId, final int appId, int flags)
- throws Exception {
- // Make the PackageManager return the package instead of throwing a NameNotFoundException
+ private int setupPackageInPackageManager(final String packageName, int userId, final int appId,
+ int flags) throws Exception {
+ final int uid = UserHandle.getUid(userId, appId);
+ // Make the PackageManager return the package instead of throwing NameNotFoundException
final PackageInfo pi = new PackageInfo();
pi.applicationInfo = new ApplicationInfo();
pi.applicationInfo.flags = flags;
- doReturn(pi).when(mContext.ipackageManager).getPackageInfo(
+ doReturn(pi).when(getServices().ipackageManager).getPackageInfo(
eq(packageName),
anyInt(),
eq(userId));
- doReturn(pi.applicationInfo).when(mContext.ipackageManager).getApplicationInfo(
+ doReturn(pi.applicationInfo).when(getServices().ipackageManager).getApplicationInfo(
eq(packageName),
anyInt(),
eq(userId));
-
// Setup application UID with the PackageManager
- final int uid = UserHandle.getUid(userId, appId);
- doReturn(uid).when(mContext.packageManager).getPackageUidAsUser(
+ doReturn(uid).when(getServices().packageManager).getPackageUidAsUser(
eq(packageName),
eq(userId));
// Associate packageName to uid
- doReturn(packageName).when(mContext.ipackageManager).getNameForUid(eq(uid));
+ doReturn(packageName).when(getServices().ipackageManager).getNameForUid(eq(uid));
doReturn(new String[]{packageName})
- .when(mContext.ipackageManager).getPackagesForUid(eq(uid));
+ .when(getServices().ipackageManager).getPackagesForUid(eq(uid));
return uid;
}
@@ -1275,7 +1211,7 @@
mContext.applicationInfo = new ApplicationInfo();
mContext.callerPermissions.add(permission.MANAGE_USERS);
mContext.packageName = "com.android.frameworks.servicestests";
- mContext.addPackageContext(user, mContext);
+ getServices().addPackageContext(user, mContext);
when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
StringParceledListSlice oneCert = asSlice(new String[] {"1"});
@@ -1285,20 +1221,20 @@
doReturn(TEST_STRING).when(mContext.resources).getQuantityText(anyInt(), eq(2));
// Given that we have exactly one certificate installed,
- when(mContext.keyChainConnection.getService().getUserCaAliases()).thenReturn(oneCert);
+ when(getServices().keyChainConnection.getService().getUserCaAliases()).thenReturn(oneCert);
// when that certificate is approved,
dpms.approveCaCert(oneCert.getList().get(0), userId, true);
// a notification should not be shown.
- verify(mContext.notificationManager, timeout(1000))
+ verify(getServices().notificationManager, timeout(1000))
.cancelAsUser(anyString(), anyInt(), eq(user));
// Given that we have four certificates installed,
- when(mContext.keyChainConnection.getService().getUserCaAliases()).thenReturn(fourCerts);
+ when(getServices().keyChainConnection.getService().getUserCaAliases()).thenReturn(fourCerts);
// when two of them are approved (one of them approved twice hence no action),
dpms.approveCaCert(fourCerts.getList().get(0), userId, true);
dpms.approveCaCert(fourCerts.getList().get(1), userId, true);
// a notification should be shown saying that there are two certificates left to approve.
- verify(mContext.notificationManager, timeout(1000))
+ verify(getServices().notificationManager, timeout(1000))
.notifyAsUser(anyString(), anyInt(), argThat(
new BaseMatcher<Notification>() {
@Override
@@ -1355,12 +1291,8 @@
mContext.binder.callingUid = RESTRICTIONS_DELEGATE_UID;
mContext.packageName = RESTRICTIONS_DELEGATE;
- // DPMS throws a SecurityException
- try {
- dpm.installCaCert(null, null);
- fail("Didn't throw SecurityException on unauthorized access");
- } catch (SecurityException expected) {
- }
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpm.installCaCert(null, null));
// On calling install certificate APIs from an authorized process
mContext.binder.callingUid = CERT_DELEGATE_UID;
@@ -1382,11 +1314,8 @@
// DPMS does not allow access to ex-delegate
mContext.binder.callingUid = CERT_DELEGATE_UID;
mContext.packageName = CERT_DELEGATE;
- try {
- dpm.installCaCert(null, null);
- fail("Didn't throw SecurityException on unauthorized access");
- } catch (SecurityException expected) {
- }
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpm.installCaCert(null, null));
// But still allows access to other existing delegates
mContext.binder.callingUid = RESTRICTIONS_DELEGATE_UID;
@@ -1403,6 +1332,8 @@
final String nonExistAppRestrictionsManagerPackage = "com.google.app.restrictions.manager2";
final String appRestrictionsManagerPackage = "com.google.app.restrictions.manager";
+ final String nonDelegateExceptionMessageRegex =
+ "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.";
final int appRestrictionsManagerAppId = 20987;
final int appRestrictionsManagerUid = setupPackageInPackageManager(
appRestrictionsManagerPackage, appRestrictionsManagerAppId);
@@ -1412,24 +1343,10 @@
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
mContext.packageName = admin1.getPackageName();
assertFalse(dpm.isCallerApplicationRestrictionsManagingPackage());
- Bundle rest = new Bundle();
+ final Bundle rest = new Bundle();
rest.putString("KEY_STRING", "Foo1");
- try {
- dpm.setApplicationRestrictions(null, "pkg1", rest);
- fail("Didn't throw expected SecurityException");
- } catch (SecurityException expected) {
- MoreAsserts.assertContainsRegex(
- "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
- expected.getMessage());
- }
- try {
- dpm.getApplicationRestrictions(null, "pkg1");
- fail("Didn't throw expected SecurityException");
- } catch (SecurityException expected) {
- MoreAsserts.assertContainsRegex(
- "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
- expected.getMessage());
- }
+ assertExpectException(SecurityException.class, nonDelegateExceptionMessageRegex,
+ () -> dpm.setApplicationRestrictions(null, "pkg1", rest));
// Check via the profile owner that no restrictions were set.
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
@@ -1437,14 +1354,10 @@
assertEquals(0, dpm.getApplicationRestrictions(admin1, "pkg1").size());
// Check the API does not allow setting a non-existent package
- try {
- dpm.setApplicationRestrictionsManagingPackage(admin1,
- nonExistAppRestrictionsManagerPackage);
- fail("Non-existent app set as app restriction manager.");
- } catch (PackageManager.NameNotFoundException expected) {
- MoreAsserts.assertContainsRegex(
- nonExistAppRestrictionsManagerPackage, expected.getMessage());
- }
+ assertExpectException(PackageManager.NameNotFoundException.class,
+ /* messageRegex= */ nonExistAppRestrictionsManagerPackage,
+ () -> dpm.setApplicationRestrictionsManagingPackage(
+ admin1, nonExistAppRestrictionsManagerPackage));
// Let appRestrictionsManagerPackage manage app restrictions
dpm.setApplicationRestrictionsManagingPackage(admin1, appRestrictionsManagerPackage);
@@ -1464,14 +1377,8 @@
mContext.binder.callingUid = UserHandle.getUid(
UserHandle.USER_SYSTEM, appRestrictionsManagerAppId);
assertFalse(dpm.isCallerApplicationRestrictionsManagingPackage());
- try {
- dpm.setApplicationRestrictions(null, "pkg1", rest);
- fail("Didn't throw expected SecurityException");
- } catch (SecurityException expected) {
- MoreAsserts.assertContainsRegex(
- "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
- expected.getMessage());
- }
+ assertExpectException(SecurityException.class, nonDelegateExceptionMessageRegex,
+ () -> dpm.setApplicationRestrictions(null, "pkg1", rest));
// The DPM is still able to manage app restrictions, even if it allowed another app to do it
// too.
@@ -1487,14 +1394,8 @@
mContext.binder.callingUid = appRestrictionsManagerUid;
mContext.packageName = appRestrictionsManagerPackage;
assertFalse(dpm.isCallerApplicationRestrictionsManagingPackage());
- try {
- dpm.setApplicationRestrictions(null, "pkg1", null);
- fail("Didn't throw expected SecurityException");
- } catch (SecurityException expected) {
- MoreAsserts.assertContainsRegex(
- "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
- expected.getMessage());
- }
+ assertExpectException(SecurityException.class, nonDelegateExceptionMessageRegex,
+ () -> dpm.setApplicationRestrictions(null, "pkg1", null));
}
public void testSetUserRestriction_asDo() throws Exception {
@@ -1517,7 +1418,7 @@
UserHandle.USER_SYSTEM));
// Check that the user restrictions that are enabled by default are set. Then unset them.
- String[] defaultRestrictions = UserRestrictionsUtils
+ final String[] defaultRestrictions = UserRestrictionsUtils
.getDefaultEnabledForDeviceOwner().toArray(new String[0]);
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(defaultRestrictions),
@@ -1527,35 +1428,35 @@
DpmTestUtils.newRestrictions(defaultRestrictions),
dpm.getUserRestrictions(admin1)
);
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
MockUtils.checkUserRestrictions(defaultRestrictions),
eq(true) /* isDeviceOwner */,
eq(CAMERA_NOT_DISABLED)
);
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
for (String restriction : defaultRestrictions) {
dpm.clearUserRestriction(admin1, restriction);
}
assertNoDeviceOwnerRestrictions();
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER),
eq(true), eq(CAMERA_NOT_DISABLED));
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS,
UserManager.DISALLOW_ADD_USER),
eq(true), eq(CAMERA_NOT_DISABLED));
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(
@@ -1569,11 +1470,11 @@
);
dpm.clearUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
eq(true), eq(CAMERA_NOT_DISABLED));
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
@@ -1585,71 +1486,71 @@
);
dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
MockUtils.checkUserRestrictions(),
eq(true), eq(CAMERA_NOT_DISABLED));
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
assertNoDeviceOwnerRestrictions();
// DISALLOW_ADJUST_VOLUME and DISALLOW_UNMUTE_MICROPHONE are PO restrictions, but when
// DO sets them, the scope is global.
dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME);
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
dpm.addUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE);
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME,
UserManager.DISALLOW_UNMUTE_MICROPHONE),
eq(true), eq(CAMERA_NOT_DISABLED));
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
dpm.clearUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME);
dpm.clearUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE);
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
// More tests.
dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER),
eq(true), eq(CAMERA_NOT_DISABLED));
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
dpm.addUserRestriction(admin1, UserManager.DISALLOW_FUN);
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
UserManager.DISALLOW_ADD_USER),
eq(true), eq(CAMERA_NOT_DISABLED));
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
dpm.setCameraDisabled(admin1, true);
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
// DISALLOW_CAMERA will be applied to both local and global.
MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
UserManager.DISALLOW_ADD_USER),
eq(true), eq(CAMERA_DISABLED_GLOBALLY));
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
// Set up another DA and let it disable camera. Now DISALLOW_CAMERA will only be applied
// locally.
dpm.setCameraDisabled(admin1, false);
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_SYSTEM_USER_UID);
dpm.setActiveAdmin(admin2, /* replace =*/ false, UserHandle.USER_SYSTEM);
dpm.setCameraDisabled(admin2, true);
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
// DISALLOW_CAMERA will be applied to both local and global. <- TODO: fix this
MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
UserManager.DISALLOW_ADD_USER),
eq(true), eq(CAMERA_DISABLED_LOCALLY));
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
// TODO Make sure restrictions are written to the file.
}
@@ -1663,19 +1564,19 @@
);
dpm.addUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(DpmMockContext.CALLER_USER_HANDLE),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES),
eq(false), eq(CAMERA_NOT_DISABLED));
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(DpmMockContext.CALLER_USER_HANDLE),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
UserManager.DISALLOW_OUTGOING_CALLS),
eq(false), eq(CAMERA_NOT_DISABLED));
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(
@@ -1694,11 +1595,11 @@
);
dpm.clearUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(DpmMockContext.CALLER_USER_HANDLE),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
eq(false), eq(CAMERA_NOT_DISABLED));
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(
@@ -1715,11 +1616,11 @@
);
dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(DpmMockContext.CALLER_USER_HANDLE),
MockUtils.checkUserRestrictions(),
eq(false), eq(CAMERA_NOT_DISABLED));
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(),
@@ -1734,22 +1635,22 @@
// DISALLOW_ADJUST_VOLUME and DISALLOW_UNMUTE_MICROPHONE can be set by PO too, even
// though when DO sets them they'll be applied globally.
dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME);
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
dpm.addUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE);
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(DpmMockContext.CALLER_USER_HANDLE),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME,
UserManager.DISALLOW_UNMUTE_MICROPHONE),
eq(false), eq(CAMERA_NOT_DISABLED));
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
dpm.setCameraDisabled(admin1, true);
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(DpmMockContext.CALLER_USER_HANDLE),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME,
UserManager.DISALLOW_UNMUTE_MICROPHONE),
eq(false), eq(CAMERA_DISABLED_LOCALLY));
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
// TODO Make sure restrictions are written to the file.
}
@@ -1785,13 +1686,13 @@
DpmTestUtils.newRestrictions(defaultRestrictions),
dpm.getUserRestrictions(admin1)
);
- verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
MockUtils.checkUserRestrictions(defaultRestrictions),
eq(true) /* isDeviceOwner */,
eq(CAMERA_NOT_DISABLED)
);
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
for (String restriction : defaultRestrictions) {
dpm.clearUserRestriction(admin1, restriction);
@@ -1800,7 +1701,7 @@
assertNoDeviceOwnerRestrictions();
// Initialize DPMS again and check that the user restriction wasn't enabled again.
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
initializeDpms();
assertTrue(dpm.isDeviceOwnerApp(admin1.getPackageName()));
assertNotNull(dpms.getDeviceOwnerAdminLocked());
@@ -1815,7 +1716,7 @@
UserRestrictionsUtils
.getDefaultEnabledForDeviceOwner().add(newDefaultEnabledRestriction);
try {
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
initializeDpms();
assertTrue(dpm.isDeviceOwnerApp(admin1.getPackageName()));
assertNotNull(dpms.getDeviceOwnerAdminLocked());
@@ -1828,13 +1729,13 @@
DpmTestUtils.newRestrictions(newDefaultEnabledRestriction),
dpm.getUserRestrictions(admin1)
);
- verify(mContext.userManagerInternal, atLeast(1)).setDevicePolicyUserRestrictions(
+ verify(getServices().userManagerInternal, atLeast(1)).setDevicePolicyUserRestrictions(
eq(UserHandle.USER_SYSTEM),
MockUtils.checkUserRestrictions(newDefaultEnabledRestriction),
eq(true) /* isDeviceOwner */,
eq(CAMERA_NOT_DISABLED)
);
- reset(mContext.userManagerInternal);
+ reset(getServices().userManagerInternal);
// Remove the restriction.
dpm.clearUserRestriction(admin1, newDefaultEnabledRestriction);
@@ -1873,33 +1774,21 @@
setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
// Test 1. Caller doesn't have DO or DA.
- try {
- dpm.getWifiMacAddress(admin1);
- fail();
- } catch (SecurityException e) {
- MoreAsserts.assertContainsRegex("No active admin", e.getMessage());
- }
+ assertExpectException(SecurityException.class, /* messageRegex= */ "No active admin",
+ () -> dpm.getWifiMacAddress(admin1));
// DO needs to be an DA.
dpm.setActiveAdmin(admin1, /* replace =*/ false);
assertTrue(dpm.isAdminActive(admin1));
// Test 2. Caller has DA, but not DO.
- try {
- dpm.getWifiMacAddress(admin1);
- fail();
- } catch (SecurityException e) {
- MoreAsserts.assertContainsRegex("does not own the device", e.getMessage());
- }
+ assertExpectException(SecurityException.class, /* messageRegex= */ NOT_DEVICE_OWNER_MSG,
+ () -> dpm.getWifiMacAddress(admin1));
// Test 3. Caller has PO, but not DO.
assertTrue(dpm.setProfileOwner(admin1, null, UserHandle.USER_SYSTEM));
- try {
- dpm.getWifiMacAddress(admin1);
- fail();
- } catch (SecurityException e) {
- MoreAsserts.assertContainsRegex("does not own the device", e.getMessage());
- }
+ assertExpectException(SecurityException.class, /* messageRegex= */ NOT_DEVICE_OWNER_MSG,
+ () -> dpm.getWifiMacAddress(admin1));
// Remove PO.
dpm.clearProfileOwner(admin1);
@@ -1911,13 +1800,13 @@
assertNull(dpm.getWifiMacAddress(admin1));
// 4-2. Returns WifiInfo, but with the default MAC.
- when(mContext.wifiManager.getConnectionInfo()).thenReturn(new WifiInfo());
+ when(getServices().wifiManager.getConnectionInfo()).thenReturn(new WifiInfo());
assertNull(dpm.getWifiMacAddress(admin1));
// 4-3. With a real MAC address.
final WifiInfo wi = new WifiInfo();
wi.setMacAddress("11:22:33:44:55:66");
- when(mContext.wifiManager.getConnectionInfo()).thenReturn(wi);
+ when(getServices().wifiManager.getConnectionInfo()).thenReturn(wi);
assertEquals("11:22:33:44:55:66", dpm.getWifiMacAddress(admin1));
}
@@ -1934,21 +1823,13 @@
// Set admin1 as DA.
dpm.setActiveAdmin(admin1, false);
assertTrue(dpm.isAdminActive(admin1));
- try {
- dpm.reboot(admin1);
- fail("DA calls DPM.reboot(), did not throw expected SecurityException");
- } catch (SecurityException expected) {
- MoreAsserts.assertContainsRegex("does not own the device", expected.getMessage());
- }
+ assertExpectException(SecurityException.class, /* messageRegex= */ NOT_DEVICE_OWNER_MSG,
+ () -> dpm.reboot(admin1));
// Set admin1 as PO.
assertTrue(dpm.setProfileOwner(admin1, null, UserHandle.USER_SYSTEM));
- try {
- dpm.reboot(admin1);
- fail("PO calls DPM.reboot(), did not throw expected SecurityException");
- } catch (SecurityException expected) {
- MoreAsserts.assertContainsRegex("does not own the device", expected.getMessage());
- }
+ assertExpectException(SecurityException.class, /* messageRegex= */ NOT_DEVICE_OWNER_MSG,
+ () -> dpm.reboot(admin1));
// Remove PO and add DO.
dpm.clearProfileOwner(admin1);
@@ -1957,27 +1838,19 @@
// admin1 is DO.
// Set current call state of device to ringing.
- when(mContext.telephonyManager.getCallState())
+ when(getServices().telephonyManager.getCallState())
.thenReturn(TelephonyManager.CALL_STATE_RINGING);
- try {
- dpm.reboot(admin1);
- fail("DPM.reboot() called when receiveing a call, should thrown IllegalStateException");
- } catch (IllegalStateException expected) {
- MoreAsserts.assertContainsRegex("ongoing call on the device", expected.getMessage());
- }
+ assertExpectException(IllegalStateException.class, /* messageRegex= */ ONGOING_CALL_MSG,
+ () -> dpm.reboot(admin1));
// Set current call state of device to dialing/active.
- when(mContext.telephonyManager.getCallState())
+ when(getServices().telephonyManager.getCallState())
.thenReturn(TelephonyManager.CALL_STATE_OFFHOOK);
- try {
- dpm.reboot(admin1);
- fail("DPM.reboot() called when dialing, should thrown IllegalStateException");
- } catch (IllegalStateException expected) {
- MoreAsserts.assertContainsRegex("ongoing call on the device", expected.getMessage());
- }
+ assertExpectException(IllegalStateException.class, /* messageRegex= */ ONGOING_CALL_MSG,
+ () -> dpm.reboot(admin1));
// Set current call state of device to idle.
- when(mContext.telephonyManager.getCallState()).thenReturn(TelephonyManager.CALL_STATE_IDLE);
+ when(getServices().telephonyManager.getCallState()).thenReturn(TelephonyManager.CALL_STATE_IDLE);
dpm.reboot(admin1);
}
@@ -2001,31 +1874,20 @@
// Only system can call the per user versions.
{
- try {
- dpm.getShortSupportMessageForUser(admin1,
- DpmMockContext.CALLER_USER_HANDLE);
- fail("Only system should be able to call getXXXForUser versions");
- } catch (SecurityException expected) {
- MoreAsserts.assertContainsRegex("message for user", expected.getMessage());
- }
- try {
- dpm.getLongSupportMessageForUser(admin1,
- DpmMockContext.CALLER_USER_HANDLE);
- fail("Only system should be able to call getXXXForUser versions");
- } catch (SecurityException expected) {
- MoreAsserts.assertContainsRegex("message for user", expected.getMessage());
- }
+ assertExpectException(SecurityException.class, /* messageRegex= */ "message for user",
+ () -> dpm.getShortSupportMessageForUser(admin1,
+ DpmMockContext.CALLER_USER_HANDLE));
+ assertExpectException(SecurityException.class, /* messageRegex= */ "message for user",
+ () -> dpm.getLongSupportMessageForUser(admin1,
+ DpmMockContext.CALLER_USER_HANDLE));
}
// Can't set message for admin in another uid.
{
mContext.binder.callingUid = DpmMockContext.CALLER_UID + 1;
- try {
- dpm.setShortSupportMessage(admin1, "Some text");
- fail("Admins should only be able to change their own support text.");
- } catch (SecurityException expected) {
- MoreAsserts.assertContainsRegex("is not owned by uid", expected.getMessage());
- }
+ assertExpectException(SecurityException.class,
+ /* messageRegex= */ "is not owned by uid",
+ () -> dpm.setShortSupportMessage(admin1, "Some text"));
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
}
@@ -2086,7 +1948,7 @@
assertNull(intent);
// Existing permission that is not set by device/profile owner returns null
- when(mContext.userManager.hasUserRestriction(
+ when(getServices().userManager.hasUserRestriction(
eq(UserManager.DISALLOW_ADJUST_VOLUME),
eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
.thenReturn(true);
@@ -2094,7 +1956,7 @@
assertNull(intent);
// Permission that is set by device owner returns correct intent
- when(mContext.userManager.getUserRestrictionSource(
+ when(getServices().userManager.getUserRestrictionSource(
eq(UserManager.DISALLOW_ADJUST_VOLUME),
eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
.thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
@@ -2103,8 +1965,7 @@
assertEquals(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS, intent.getAction());
assertEquals(UserHandle.getUserId(DpmMockContext.CALLER_SYSTEM_USER_UID),
intent.getIntExtra(Intent.EXTRA_USER_ID, -1));
- assertEquals(admin1,
- (ComponentName) intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN));
+ assertEquals(admin1, intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN));
assertEquals(UserManager.DISALLOW_ADJUST_VOLUME,
intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION));
@@ -2240,24 +2101,17 @@
public void testSetUserProvisioningState_unprivileged() throws Exception {
setupProfileOwner();
- try {
- dpm.setUserProvisioningState(DevicePolicyManager.STATE_USER_SETUP_FINALIZED,
- DpmMockContext.CALLER_USER_HANDLE);
- fail("Expected SecurityException");
- } catch (SecurityException expected) {
- }
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpm.setUserProvisioningState(DevicePolicyManager.STATE_USER_SETUP_FINALIZED,
+ DpmMockContext.CALLER_USER_HANDLE));
}
public void testSetUserProvisioningState_noManagement() {
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
- try {
- dpm.setUserProvisioningState(DevicePolicyManager.STATE_USER_SETUP_FINALIZED,
- DpmMockContext.CALLER_USER_HANDLE);
- fail("IllegalStateException expected");
- } catch (IllegalStateException e) {
- MoreAsserts.assertContainsRegex("change provisioning state unless a .* owner is set",
- e.getMessage());
- }
+ assertExpectException(IllegalStateException.class,
+ /* messageRegex= */ "change provisioning state unless a .* owner is set",
+ () -> dpm.setUserProvisioningState(DevicePolicyManager.STATE_USER_SETUP_FINALIZED,
+ DpmMockContext.CALLER_USER_HANDLE));
assertEquals(DevicePolicyManager.STATE_USER_UNMANAGED, dpm.getUserProvisioningState());
}
@@ -2323,15 +2177,11 @@
setupProfileOwner();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
- try {
- exerciseUserProvisioningTransitions(DpmMockContext.CALLER_USER_HANDLE,
- DevicePolicyManager.STATE_USER_SETUP_FINALIZED,
- DevicePolicyManager.STATE_USER_UNMANAGED);
- fail("Expected IllegalStateException");
- } catch (IllegalStateException e) {
- MoreAsserts.assertContainsRegex("Cannot move to user provisioning state",
- e.getMessage());
- }
+ assertExpectException(IllegalStateException.class,
+ /* messageRegex= */ "Cannot move to user provisioning state",
+ () -> exerciseUserProvisioningTransitions(DpmMockContext.CALLER_USER_HANDLE,
+ DevicePolicyManager.STATE_USER_SETUP_FINALIZED,
+ DevicePolicyManager.STATE_USER_UNMANAGED));
}
public void testSetUserProvisioningState_illegalTransitionToAnotherInProgressState()
@@ -2339,15 +2189,11 @@
setupProfileOwner();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
- try {
- exerciseUserProvisioningTransitions(DpmMockContext.CALLER_USER_HANDLE,
- DevicePolicyManager.STATE_USER_SETUP_INCOMPLETE,
- DevicePolicyManager.STATE_USER_SETUP_COMPLETE);
- fail("Expected IllegalStateException");
- } catch (IllegalStateException e) {
- MoreAsserts.assertContainsRegex("Cannot move to user provisioning state",
- e.getMessage());
- }
+ assertExpectException(IllegalStateException.class,
+ /* messageRegex= */ "Cannot move to user provisioning state",
+ () -> exerciseUserProvisioningTransitions(DpmMockContext.CALLER_USER_HANDLE,
+ DevicePolicyManager.STATE_USER_SETUP_INCOMPLETE,
+ DevicePolicyManager.STATE_USER_SETUP_COMPLETE));
}
private void exerciseUserProvisioningTransitions(int userId, int... states) {
@@ -2384,52 +2230,52 @@
dpm.setActiveAdmin(admin1, /* replace =*/ false);
dpm.setActiveAdmin(admin2, /* replace =*/ false);
- reset(mMockContext.powerManagerInternal);
- reset(mMockContext.settings);
+ reset(getServices().powerManagerInternal);
+ reset(getServices().settings);
dpm.setMaximumTimeToLock(admin1, 0);
verifyScreenTimeoutCall(null, false);
- reset(mMockContext.powerManagerInternal);
- reset(mMockContext.settings);
+ reset(getServices().powerManagerInternal);
+ reset(getServices().settings);
dpm.setMaximumTimeToLock(admin1, 1);
verifyScreenTimeoutCall(1, true);
- reset(mMockContext.powerManagerInternal);
- reset(mMockContext.settings);
+ reset(getServices().powerManagerInternal);
+ reset(getServices().settings);
dpm.setMaximumTimeToLock(admin2, 10);
verifyScreenTimeoutCall(null, false);
- reset(mMockContext.powerManagerInternal);
- reset(mMockContext.settings);
+ reset(getServices().powerManagerInternal);
+ reset(getServices().settings);
dpm.setMaximumTimeToLock(admin1, 5);
verifyScreenTimeoutCall(5, true);
- reset(mMockContext.powerManagerInternal);
- reset(mMockContext.settings);
+ reset(getServices().powerManagerInternal);
+ reset(getServices().settings);
dpm.setMaximumTimeToLock(admin2, 4);
verifyScreenTimeoutCall(4, true);
- reset(mMockContext.powerManagerInternal);
- reset(mMockContext.settings);
+ reset(getServices().powerManagerInternal);
+ reset(getServices().settings);
dpm.setMaximumTimeToLock(admin1, 0);
- reset(mMockContext.powerManagerInternal);
- reset(mMockContext.settings);
+ reset(getServices().powerManagerInternal);
+ reset(getServices().settings);
dpm.setMaximumTimeToLock(admin2, Integer.MAX_VALUE);
verifyScreenTimeoutCall(Integer.MAX_VALUE, true);
- reset(mMockContext.powerManagerInternal);
- reset(mMockContext.settings);
+ reset(getServices().powerManagerInternal);
+ reset(getServices().settings);
dpm.setMaximumTimeToLock(admin2, Integer.MAX_VALUE + 1);
verifyScreenTimeoutCall(Integer.MAX_VALUE, true);
- reset(mMockContext.powerManagerInternal);
- reset(mMockContext.settings);
+ reset(getServices().powerManagerInternal);
+ reset(getServices().settings);
dpm.setMaximumTimeToLock(admin2, 10);
verifyScreenTimeoutCall(10, true);
- reset(mMockContext.powerManagerInternal);
- reset(mMockContext.settings);
+ reset(getServices().powerManagerInternal);
+ reset(getServices().settings);
// There's no restriction; shold be set to MAX.
dpm.setMaximumTimeToLock(admin2, 0);
@@ -2449,25 +2295,20 @@
// verify that the minimum timeout cannot be modified on user builds (system property is
// not being read)
- mContext.buildMock.isDebuggable = false;
+ getServices().buildMock.isDebuggable = false;
dpm.setRequiredStrongAuthTimeout(admin1, MAX_MINUS_ONE_MINUTE);
assertEquals(dpm.getRequiredStrongAuthTimeout(admin1), MAX_MINUS_ONE_MINUTE);
assertEquals(dpm.getRequiredStrongAuthTimeout(null), MAX_MINUS_ONE_MINUTE);
- verify(mContext.systemProperties, never()).getLong(anyString(), anyLong());
+ verify(getServices().systemProperties, never()).getLong(anyString(), anyLong());
// restore to the debuggable build state
- mContext.buildMock.isDebuggable = true;
+ getServices().buildMock.isDebuggable = true;
// Always return the default (second arg) when getting system property for long type
- when(mContext.systemProperties.getLong(anyString(), anyLong())).thenAnswer(
- new Answer<Long>() {
- @Override
- public Long answer(InvocationOnMock invocation) throws Throwable {
- return (Long) invocation.getArguments()[1];
- }
- }
+ when(getServices().systemProperties.getLong(anyString(), anyLong())).thenAnswer(
+ invocation -> invocation.getArguments()[1]
);
// reset to default (0 means the admin is not participating, so default should be returned)
@@ -2515,20 +2356,17 @@
DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS);
// negative value
- try {
- dpm.setRequiredStrongAuthTimeout(admin1, -ONE_MINUTE);
- fail("Didn't throw IllegalArgumentException");
- } catch (IllegalArgumentException iae) {
- }
+ assertExpectException(IllegalArgumentException.class, /* messageRegex= */ null,
+ () -> dpm.setRequiredStrongAuthTimeout(admin1, -ONE_MINUTE));
}
private void verifyScreenTimeoutCall(Integer expectedTimeout,
boolean shouldStayOnWhilePluggedInBeCleared) {
if (expectedTimeout == null) {
- verify(mMockContext.powerManagerInternal, times(0))
+ verify(getServices().powerManagerInternal, times(0))
.setMaximumScreenOffTimeoutFromDeviceAdmin(anyInt());
} else {
- verify(mMockContext.powerManagerInternal, times(1))
+ verify(getServices().powerManagerInternal, times(1))
.setMaximumScreenOffTimeoutFromDeviceAdmin(eq(expectedTimeout));
}
// TODO Verify calls to settingsGlobalPutInt. Tried but somehow mockito threw
@@ -2536,13 +2374,13 @@
}
private void setup_DeviceAdminFeatureOff() throws Exception {
- when(mContext.packageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN))
+ when(getServices().packageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN))
.thenReturn(false);
- when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
+ when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(false);
initializeDpms();
- when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(false);
- when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true))
+ when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(false);
+ when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true))
.thenReturn(true);
setUserSetupCompleteForUser(false, UserHandle.USER_SYSTEM);
@@ -2575,11 +2413,11 @@
}
private void setup_ManagedProfileFeatureOff() throws Exception {
- when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
+ when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(false);
initializeDpms();
- when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(false);
- when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true))
+ when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(false);
+ when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true))
.thenReturn(true);
setUserSetupCompleteForUser(false, UserHandle.USER_SYSTEM);
@@ -2597,7 +2435,7 @@
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_USER, false);
// Test again when split user is on
- when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true);
+ when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
@@ -2619,7 +2457,7 @@
DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED);
// Test again when split user is on
- when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true);
+ when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
@@ -2632,10 +2470,10 @@
}
private void setup_nonSplitUser_firstBoot_primaryUser() throws Exception {
- when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
+ when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(true);
- when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(false);
- when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true))
+ when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(false);
+ when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true))
.thenReturn(true);
setUserSetupCompleteForUser(false, UserHandle.USER_SYSTEM);
@@ -2670,10 +2508,10 @@
}
private void setup_nonSplitUser_afterDeviceSetup_primaryUser() throws Exception {
- when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
+ when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(true);
- when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(false);
- when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true))
+ when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(false);
+ when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true))
.thenReturn(true);
setUserSetupCompleteForUser(true, UserHandle.USER_SYSTEM);
@@ -2691,9 +2529,9 @@
final int MANAGED_PROFILE_USER_ID = 18;
final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 1308);
addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
- when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM,
+ when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM,
false /* we can't remove a managed profile */)).thenReturn(false);
- when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM,
+ when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM,
true)).thenReturn(true);
}
@@ -2755,11 +2593,11 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
// The DO should be allowed to initiate provisioning if it set the restriction itself, but
// other packages should be forbidden.
- when(mContext.userManager.hasUserRestriction(
+ when(getServices().userManager.hasUserRestriction(
eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
.thenReturn(true);
- when(mContext.userManager.getUserRestrictionSource(
+ when(getServices().userManager.getUserRestrictionSource(
eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
.thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
@@ -2781,11 +2619,11 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
// The DO should not be allowed to initiate provisioning if the restriction is set by
// another entity.
- when(mContext.userManager.hasUserRestriction(
+ when(getServices().userManager.hasUserRestriction(
eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
.thenReturn(true);
- when(mContext.userManager.getUserRestrictionSource(
+ when(getServices().userManager.getUserRestrictionSource(
eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
.thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
@@ -2823,11 +2661,11 @@
setup_nonSplitUser_withDo_primaryUser_ManagedProfile();
mContext.packageName = admin1.getPackageName();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
- when(mContext.userManager.hasUserRestriction(
+ when(getServices().userManager.hasUserRestriction(
eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE),
eq(UserHandle.SYSTEM)))
.thenReturn(true);
- when(mContext.userManager.getUserRestrictionSource(
+ when(getServices().userManager.getUserRestrictionSource(
eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE),
eq(UserHandle.SYSTEM)))
.thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
@@ -2847,10 +2685,10 @@
}
private void setup_splitUser_firstBoot_systemUser() throws Exception {
- when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
+ when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(true);
- when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true);
- when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true))
+ when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
+ when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true))
.thenReturn(false);
setUserSetupCompleteForUser(false, UserHandle.USER_SYSTEM);
@@ -2886,10 +2724,10 @@
}
private void setup_splitUser_afterDeviceSetup_systemUser() throws Exception {
- when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
+ when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(true);
- when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true);
- when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true))
+ when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
+ when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true))
.thenReturn(false);
setUserSetupCompleteForUser(true, UserHandle.USER_SYSTEM);
@@ -2926,10 +2764,10 @@
}
private void setup_splitUser_firstBoot_primaryUser() throws Exception {
- when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
+ when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(true);
- when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true);
- when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
+ when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
+ when(getServices().userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
true)).thenReturn(true);
setUserSetupCompleteForUser(false, DpmMockContext.CALLER_USER_HANDLE);
@@ -2963,10 +2801,10 @@
}
private void setup_splitUser_afterDeviceSetup_primaryUser() throws Exception {
- when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
+ when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(true);
- when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true);
- when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
+ when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
+ when(getServices().userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
true)).thenReturn(true);
setUserSetupCompleteForUser(true, DpmMockContext.CALLER_USER_HANDLE);
@@ -3005,10 +2843,10 @@
private void setup_provisionManagedProfileWithDeviceOwner_systemUser() throws Exception {
setDeviceOwner();
- when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
+ when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(true);
- when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true);
- when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true))
+ when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
+ when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM, true))
.thenReturn(false);
setUserSetupCompleteForUser(true, UserHandle.USER_SYSTEM);
@@ -3035,10 +2873,10 @@
private void setup_provisionManagedProfileWithDeviceOwner_primaryUser() throws Exception {
setDeviceOwner();
- when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
+ when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(true);
- when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true);
- when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
+ when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
+ when(getServices().userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
true)).thenReturn(true);
setUserSetupCompleteForUser(false, DpmMockContext.CALLER_USER_HANDLE);
@@ -3066,16 +2904,16 @@
private void setup_provisionManagedProfileCantRemoveUser_primaryUser() throws Exception {
setDeviceOwner();
- when(mContext.ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
+ when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(true);
- when(mContext.userManagerForMock.isSplitSystemUser()).thenReturn(true);
- when(mContext.userManager.hasUserRestriction(
+ when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
+ when(getServices().userManager.hasUserRestriction(
eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE),
eq(UserHandle.of(DpmMockContext.CALLER_USER_HANDLE))))
.thenReturn(true);
- when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
+ when(getServices().userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
false /* we can't remove a managed profile */)).thenReturn(false);
- when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
+ when(getServices().userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
true)).thenReturn(true);
setUserSetupCompleteForUser(false, DpmMockContext.CALLER_USER_HANDLE);
@@ -3100,32 +2938,23 @@
public void testCheckProvisioningPreCondition_permission() {
// GIVEN the permission MANAGE_PROFILE_AND_DEVICE_OWNERS is not granted
- try {
- dpm.checkProvisioningPreCondition(
- DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, "some.package");
- fail("Didn't throw SecurityException");
- } catch (SecurityException expected) {
- }
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpm.checkProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, "some.package"));
}
public void testForceUpdateUserSetupComplete_permission() {
// GIVEN the permission MANAGE_PROFILE_AND_DEVICE_OWNERS is not granted
- try {
- dpm.forceUpdateUserSetupComplete();
- fail("Didn't throw SecurityException");
- } catch (SecurityException expected) {
- }
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpm.forceUpdateUserSetupComplete());
}
public void testForceUpdateUserSetupComplete_systemUser() {
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
// GIVEN calling from user 20
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
- try {
- dpm.forceUpdateUserSetupComplete();
- fail("Didn't throw SecurityException");
- } catch (SecurityException expected) {
- }
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpm.forceUpdateUserSetupComplete());
}
public void testForceUpdateUserSetupComplete_userbuild() {
@@ -3143,7 +2972,7 @@
dpms.mUserData.put(UserHandle.USER_SYSTEM, userData);
// GIVEN it's user build
- mContext.buildMock.isDebuggable = false;
+ getServices().buildMock.isDebuggable = false;
assertTrue(dpms.hasUserSetupCompleted());
@@ -3168,7 +2997,7 @@
dpms.mUserData.put(UserHandle.USER_SYSTEM, userData);
// GIVEN it's userdebug build
- mContext.buildMock.isDebuggable = true;
+ getServices().buildMock.isDebuggable = true;
assertTrue(dpms.hasUserSetupCompleted());
@@ -3179,12 +3008,13 @@
}
private void clearDeviceOwner() throws Exception {
- final long ident = mContext.binder.clearCallingIdentity();
- mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
- doReturn(DpmMockContext.CALLER_SYSTEM_USER_UID).when(mContext.packageManager)
+ doReturn(DpmMockContext.CALLER_SYSTEM_USER_UID).when(getServices().packageManager)
.getPackageUidAsUser(eq(admin1.getPackageName()), anyInt());
- dpm.clearDeviceOwnerApp(admin1.getPackageName());
- mContext.binder.restoreCallingIdentity(ident);
+
+ mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ runAsCaller(mAdmin1Context, dpms, dpm -> {
+ dpm.clearDeviceOwnerApp(admin1.getPackageName());
+ });
}
public void testGetLastSecurityLogRetrievalTime() throws Exception {
@@ -3193,7 +3023,7 @@
// setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
// feature is disabled because there are non-affiliated secondary users.
- mContext.removeUser(DpmMockContext.CALLER_USER_HANDLE);
+ getServices().removeUser(DpmMockContext.CALLER_USER_HANDLE);
when(mContext.resources.getBoolean(R.bool.config_supportPreRebootSecurityLogs))
.thenReturn(true);
@@ -3202,9 +3032,9 @@
// Enabling logging should not change the timestamp.
dpm.setSecurityLoggingEnabled(admin1, true);
- verify(mContext.settings)
+ verify(getServices().settings)
.securityLogSetLoggingEnabledProperty(true);
- when(mContext.settings.securityLogGetLoggingEnabledProperty())
+ when(getServices().settings.securityLogGetLoggingEnabledProperty())
.thenReturn(true);
assertEquals(-1, dpm.getLastSecurityLogRetrievalTime());
@@ -3268,7 +3098,7 @@
// setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
// feature is disabled because there are non-affiliated secondary users.
- mContext.removeUser(DpmMockContext.CALLER_USER_HANDLE);
+ getServices().removeUser(DpmMockContext.CALLER_USER_HANDLE);
// No bug reports were requested so far.
assertEquals(-1, dpm.getLastBugReportRequestTime());
@@ -3316,8 +3146,8 @@
// setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
// feature is disabled because there are non-affiliated secondary users.
- mContext.removeUser(DpmMockContext.CALLER_USER_HANDLE);
- when(mContext.iipConnectivityMetrics.registerNetdEventCallback(anyObject()))
+ getServices().removeUser(DpmMockContext.CALLER_USER_HANDLE);
+ when(getServices().iipConnectivityMetrics.registerNetdEventCallback(anyObject()))
.thenReturn(true);
// No logs were retrieved so far.
@@ -3388,7 +3218,7 @@
// Add a secondary user, it should never talk with.
final int ANOTHER_USER_ID = 36;
- mContext.addUser(ANOTHER_USER_ID, 0);
+ getServices().addUser(ANOTHER_USER_ID, 0);
// Since the managed profile is not affiliated, they should not be allowed to talk to each
// other.
@@ -3469,7 +3299,7 @@
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
// Lock task packages are updated when loading user data.
- verify(mContext.iactivityManager)
+ verify(getServices().iactivityManager)
.updateLockTaskPackages(eq(UserHandle.USER_SYSTEM), eq(new String[0]));
// Set up a managed profile managed by different package (package name shouldn't matter)
@@ -3478,7 +3308,7 @@
final ComponentName adminDifferentPackage =
new ComponentName("another.package", "whatever.class");
addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2);
- verify(mContext.iactivityManager)
+ verify(getServices().iactivityManager)
.updateLockTaskPackages(eq(MANAGED_PROFILE_USER_ID), eq(new String[0]));
// The DO can still set lock task packages
@@ -3488,22 +3318,16 @@
MoreAsserts.assertEquals(doPackages, dpm.getLockTaskPackages(admin1));
assertTrue(dpm.isLockTaskPermitted("doPackage1"));
assertFalse(dpm.isLockTaskPermitted("anotherPackage"));
- verify(mContext.iactivityManager)
+ verify(getServices().iactivityManager)
.updateLockTaskPackages(eq(UserHandle.USER_SYSTEM), eq(doPackages));
// Managed profile is unaffiliated - shouldn't be able to setLockTaskPackages.
mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
final String[] poPackages = {"poPackage1", "poPackage2"};
- try {
- dpm.setLockTaskPackages(adminDifferentPackage, poPackages);
- fail("Didn't throw expected security exception.");
- } catch (SecurityException expected) {
- }
- try {
- dpm.getLockTaskPackages(adminDifferentPackage);
- fail("Didn't throw expected security exception.");
- } catch (SecurityException expected) {
- }
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpm.setLockTaskPackages(adminDifferentPackage, poPackages));
+ assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+ () -> dpm.getLockTaskPackages(adminDifferentPackage));
assertFalse(dpm.isLockTaskPermitted("doPackage1"));
// Setting same affiliation ids
@@ -3519,14 +3343,14 @@
MoreAsserts.assertEquals(poPackages, dpm.getLockTaskPackages(adminDifferentPackage));
assertTrue(dpm.isLockTaskPermitted("poPackage1"));
assertFalse(dpm.isLockTaskPermitted("doPackage2"));
- verify(mContext.iactivityManager)
+ verify(getServices().iactivityManager)
.updateLockTaskPackages(eq(MANAGED_PROFILE_USER_ID), eq(poPackages));
// Unaffiliate the profile, lock task mode no longer available on the profile.
dpm.setAffiliationIds(adminDifferentPackage, Collections.emptySet());
assertFalse(dpm.isLockTaskPermitted("poPackage1"));
// Lock task packages cleared when loading user data and when the user becomes unaffiliated.
- verify(mContext.iactivityManager, times(2))
+ verify(getServices().iactivityManager, times(2))
.updateLockTaskPackages(eq(MANAGED_PROFILE_USER_ID), eq(new String[0]));
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
@@ -3590,11 +3414,11 @@
mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
// Even if the caller is the managed profile, the current user is the user 0
- when(mContext.iactivityManager.getCurrentUser())
+ when(getServices().iactivityManager.getCurrentUser())
.thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
dpm.wipeData(0);
- verify(mContext.userManagerInternal).removeUserEvenWhenDisallowed(
+ verify(getServices().userManagerInternal).removeUserEvenWhenDisallowed(
MANAGED_PROFILE_USER_ID);
}
@@ -3604,48 +3428,42 @@
addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
// Even if the caller is the managed profile, the current user is the user 0
- when(mContext.iactivityManager.getCurrentUser())
+ when(getServices().iactivityManager.getCurrentUser())
.thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
- when(mContext.userManager.getUserRestrictionSource(
+ when(getServices().userManager.getUserRestrictionSource(
UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
UserHandle.of(MANAGED_PROFILE_USER_ID)))
.thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
- try {
- // The PO is not allowed to remove the profile if the user restriction was set on the
- // profile by the system
- dpm.wipeData(0);
- fail("SecurityException not thrown");
- } catch (SecurityException expected) {
- }
+ // The PO is not allowed to remove the profile if the user restriction was set on the
+ // profile by the system
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.wipeData(0));
}
public void testWipeDataDeviceOwner() throws Exception {
setDeviceOwner();
- when(mContext.userManager.getUserRestrictionSource(
+ when(getServices().userManager.getUserRestrictionSource(
UserManager.DISALLOW_FACTORY_RESET,
UserHandle.SYSTEM))
.thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
dpm.wipeData(0);
- verify(mContext.recoverySystem).rebootWipeUserData(
+ verify(getServices().recoverySystem).rebootWipeUserData(
/*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true));
}
public void testWipeDataDeviceOwnerDisallowed() throws Exception {
setDeviceOwner();
- when(mContext.userManager.getUserRestrictionSource(
+ when(getServices().userManager.getUserRestrictionSource(
UserManager.DISALLOW_FACTORY_RESET,
UserHandle.SYSTEM))
.thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
- try {
- // The DO is not allowed to wipe the device if the user restriction was set
- // by the system
- dpm.wipeData(0);
- fail("SecurityException not thrown");
- } catch (SecurityException expected) {
- }
+ // The DO is not allowed to wipe the device if the user restriction was set
+ // by the system
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.wipeData(0));
}
public void testMaximumFailedPasswordAttemptsReachedManagedProfile() throws Exception {
@@ -3654,10 +3472,10 @@
addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
// Even if the caller is the managed profile, the current user is the user 0
- when(mContext.iactivityManager.getCurrentUser())
+ when(getServices().iactivityManager.getCurrentUser())
.thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
- when(mContext.userManager.getUserRestrictionSource(
+ when(getServices().userManager.getUserRestrictionSource(
UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
UserHandle.of(MANAGED_PROFILE_USER_ID)))
.thenReturn(UserManager.RESTRICTION_SOURCE_PROFILE_OWNER);
@@ -3675,9 +3493,9 @@
// The profile should be wiped even if DISALLOW_REMOVE_MANAGED_PROFILE is enabled, because
// both the user restriction and the policy were set by the PO.
- verify(mContext.userManagerInternal).removeUserEvenWhenDisallowed(
+ verify(getServices().userManagerInternal).removeUserEvenWhenDisallowed(
MANAGED_PROFILE_USER_ID);
- verifyZeroInteractions(mContext.recoverySystem);
+ verifyZeroInteractions(getServices().recoverySystem);
}
public void testMaximumFailedPasswordAttemptsReachedManagedProfileDisallowed()
@@ -3687,10 +3505,10 @@
addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
// Even if the caller is the managed profile, the current user is the user 0
- when(mContext.iactivityManager.getCurrentUser())
+ when(getServices().iactivityManager.getCurrentUser())
.thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
- when(mContext.userManager.getUserRestrictionSource(
+ when(getServices().userManager.getUserRestrictionSource(
UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
UserHandle.of(MANAGED_PROFILE_USER_ID)))
.thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
@@ -3708,14 +3526,14 @@
// DISALLOW_REMOVE_MANAGED_PROFILE was set by the system, not the PO, so the profile is
// not wiped.
- verify(mContext.userManagerInternal, never())
+ verify(getServices().userManagerInternal, never())
.removeUserEvenWhenDisallowed(anyInt());
- verifyZeroInteractions(mContext.recoverySystem);
+ verifyZeroInteractions(getServices().recoverySystem);
}
public void testMaximumFailedPasswordAttemptsReachedDeviceOwner() throws Exception {
setDeviceOwner();
- when(mContext.userManager.getUserRestrictionSource(
+ when(getServices().userManager.getUserRestrictionSource(
UserManager.DISALLOW_FACTORY_RESET,
UserHandle.SYSTEM))
.thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
@@ -3730,13 +3548,13 @@
// The device should be wiped even if DISALLOW_FACTORY_RESET is enabled, because both the
// user restriction and the policy were set by the DO.
- verify(mContext.recoverySystem).rebootWipeUserData(
+ verify(getServices().recoverySystem).rebootWipeUserData(
/*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true));
}
public void testMaximumFailedPasswordAttemptsReachedDeviceOwnerDisallowed() throws Exception {
setDeviceOwner();
- when(mContext.userManager.getUserRestrictionSource(
+ when(getServices().userManager.getUserRestrictionSource(
UserManager.DISALLOW_FACTORY_RESET,
UserHandle.SYSTEM))
.thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
@@ -3750,8 +3568,8 @@
dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
// DISALLOW_FACTORY_RESET was set by the system, not the DO, so the device is not wiped.
- verifyZeroInteractions(mContext.recoverySystem);
- verify(mContext.userManagerInternal, never())
+ verifyZeroInteractions(getServices().recoverySystem);
+ verify(getServices().userManagerInternal, never())
.removeUserEvenWhenDisallowed(anyInt());
}
@@ -3760,18 +3578,18 @@
final String app1 = "com.example.app1";
final String app2 = "com.example.app2";
- when(mContext.ipackageManager.checkPermission(eq(permission), eq(app1), anyInt()))
+ when(getServices().ipackageManager.checkPermission(eq(permission), eq(app1), anyInt()))
.thenReturn(PackageManager.PERMISSION_GRANTED);
- doReturn(PackageManager.FLAG_PERMISSION_POLICY_FIXED).when(mContext.packageManager)
+ doReturn(PackageManager.FLAG_PERMISSION_POLICY_FIXED).when(getServices().packageManager)
.getPermissionFlags(permission, app1, UserHandle.SYSTEM);
- when(mContext.packageManager.getPermissionFlags(permission, app1,
+ when(getServices().packageManager.getPermissionFlags(permission, app1,
UserHandle.of(DpmMockContext.CALLER_USER_HANDLE)))
.thenReturn(PackageManager.FLAG_PERMISSION_POLICY_FIXED);
- when(mContext.ipackageManager.checkPermission(eq(permission), eq(app2), anyInt()))
+ when(getServices().ipackageManager.checkPermission(eq(permission), eq(app2), anyInt()))
.thenReturn(PackageManager.PERMISSION_DENIED);
- doReturn(0).when(mContext.packageManager).getPermissionFlags(permission, app2,
+ doReturn(0).when(getServices().packageManager).getPermissionFlags(permission, app2,
UserHandle.SYSTEM);
- when(mContext.packageManager.getPermissionFlags(permission, app2,
+ when(getServices().packageManager.getPermissionFlags(permission, app2,
UserHandle.of(DpmMockContext.CALLER_USER_HANDLE))).thenReturn(0);
// System can retrieve permission grant state.
@@ -3785,11 +3603,8 @@
// A regular app cannot retrieve permission grant state.
mContext.binder.callingUid = setupPackageInPackageManager(app1, 1);
mContext.packageName = app1;
- try {
- dpm.getPermissionGrantState(null, app1, permission);
- fail("Didn't throw SecurityException");
- } catch (SecurityException expected) {
- }
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.getPermissionGrantState(null, app1, permission));
// Profile owner can retrieve permission grant state.
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
@@ -3805,37 +3620,113 @@
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
// test token validation
- try {
- dpm.setResetPasswordToken(admin1, new byte[31]);
- fail("should not have accepted tokens too short");
- } catch (IllegalArgumentException expected) {
- }
+ assertExpectException(IllegalArgumentException.class, /* messageRegex= */ null,
+ () -> dpm.setResetPasswordToken(admin1, new byte[31]));
+
// test adding a token
final byte[] token = new byte[32];
final long handle = 123456;
final String password = "password";
- when(mContext.lockPatternUtils.addEscrowToken(eq(token), eq(UserHandle.USER_SYSTEM)))
+ when(getServices().lockPatternUtils.addEscrowToken(eq(token), eq(UserHandle.USER_SYSTEM)))
.thenReturn(handle);
assertTrue(dpm.setResetPasswordToken(admin1, token));
// test password activation
- when(mContext.lockPatternUtils.isEscrowTokenActive(eq(handle), eq(UserHandle.USER_SYSTEM)))
+ when(getServices().lockPatternUtils.isEscrowTokenActive(eq(handle), eq(UserHandle.USER_SYSTEM)))
.thenReturn(true);
assertTrue(dpm.isResetPasswordTokenActive(admin1));
// test reset password with token
- when(mContext.lockPatternUtils.setLockCredentialWithToken(eq(password),
+ when(getServices().lockPatternUtils.setLockCredentialWithToken(eq(password),
eq(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), eq(handle), eq(token),
eq(UserHandle.USER_SYSTEM)))
.thenReturn(true);
assertTrue(dpm.resetPasswordWithToken(admin1, password, token, 0));
// test removing a token
- when(mContext.lockPatternUtils.removeEscrowToken(eq(handle), eq(UserHandle.USER_SYSTEM)))
+ when(getServices().lockPatternUtils.removeEscrowToken(eq(handle), eq(UserHandle.USER_SYSTEM)))
.thenReturn(true);
assertTrue(dpm.clearResetPasswordToken(admin1));
}
+ public void testIsActivePasswordSufficient() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ mContext.packageName = admin1.getPackageName();
+ setupDeviceOwner();
+
+ dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
+ dpm.setPasswordMinimumLength(admin1, 8);
+ dpm.setPasswordMinimumLetters(admin1, 6);
+ dpm.setPasswordMinimumLowerCase(admin1, 3);
+ dpm.setPasswordMinimumUpperCase(admin1, 1);
+ dpm.setPasswordMinimumNonLetter(admin1, 1);
+ dpm.setPasswordMinimumNumeric(admin1, 1);
+ dpm.setPasswordMinimumSymbols(admin1, 0);
+
+ PasswordMetrics passwordMetricsNoSymbols = new PasswordMetrics(
+ DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9,
+ 8, 2,
+ 6, 1,
+ 0, 1);
+
+ setActivePasswordState(passwordMetricsNoSymbols);
+ assertTrue(dpm.isActivePasswordSufficient());
+
+ initializeDpms();
+ reset(mContext.spiedContext);
+ assertTrue(dpm.isActivePasswordSufficient());
+
+ // This call simulates the user entering the password for the first time after a reboot.
+ // This causes password metrics to be reloaded into memory. Until this happens,
+ // dpm.isActivePasswordSufficient() will continue to return its last checkpointed value,
+ // even if the DPC changes password requirements so that the password no longer meets the
+ // requirements. This is a known limitation of the current implementation of
+ // isActivePasswordSufficient() - see b/34218769.
+ setActivePasswordState(passwordMetricsNoSymbols);
+ assertTrue(dpm.isActivePasswordSufficient());
+
+ dpm.setPasswordMinimumSymbols(admin1, 1);
+ // This assertion would fail if we had not called setActivePasswordState() again after
+ // initializeDpms() - see previous comment.
+ assertFalse(dpm.isActivePasswordSufficient());
+
+ initializeDpms();
+ reset(mContext.spiedContext);
+ assertFalse(dpm.isActivePasswordSufficient());
+
+ PasswordMetrics passwordMetricsWithSymbols = new PasswordMetrics(
+ DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9,
+ 7, 2,
+ 5, 1,
+ 1, 2);
+
+ setActivePasswordState(passwordMetricsWithSymbols);
+ assertTrue(dpm.isActivePasswordSufficient());
+ }
+
+ private void setActivePasswordState(PasswordMetrics passwordMetrics)
+ throws Exception {
+ final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
+ final long ident = mContext.binder.clearCallingIdentity();
+
+ dpm.setActivePasswordState(passwordMetrics, userHandle);
+ dpm.reportPasswordChanged(userHandle);
+
+ final Intent intent = new Intent(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED);
+ intent.setComponent(admin1);
+ intent.putExtra(Intent.EXTRA_USER, UserHandle.of(mContext.binder.callingUid));
+
+ verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
+ MockUtils.checkIntent(intent),
+ MockUtils.checkUserHandle(userHandle));
+
+ // CertificateMonitor.updateInstalledCertificates is called on the background thread,
+ // let it finish with system uid, otherwise it will throw and crash.
+ flushTasks();
+
+ mContext.binder.restoreCallingIdentity(ident);
+ }
+
public void testIsCurrentInputMethodSetByOwnerForDeviceOwner() throws Exception {
final String currentIme = Settings.Secure.DEFAULT_INPUT_METHOD;
final Uri currentImeUri = Settings.Secure.getUriFor(currentIme);
@@ -3857,12 +3748,12 @@
// Device owner changes IME for first user.
mContext.binder.callingUid = deviceOwnerUid;
- when(mContext.settings.settingsSecureGetStringForUser(currentIme, UserHandle.USER_SYSTEM))
+ when(getServices().settings.settingsSecureGetStringForUser(currentIme, UserHandle.USER_SYSTEM))
.thenReturn("ime1");
dpm.setSecureSetting(admin1, currentIme, "ime2");
- verify(mContext.settings).settingsSecurePutStringForUser(currentIme, "ime2",
+ verify(getServices().settings).settingsSecurePutStringForUser(currentIme, "ime2",
UserHandle.USER_SYSTEM);
- reset(mContext.settings);
+ reset(getServices().settings);
dpms.notifyChangeToContentObserver(currentImeUri, UserHandle.USER_SYSTEM);
mContext.binder.callingUid = firstUserSystemUid;
assertTrue(dpm.isCurrentInputMethodSetByOwner());
@@ -3885,10 +3776,10 @@
// Device owner changes IME for first user again.
mContext.binder.callingUid = deviceOwnerUid;
- when(mContext.settings.settingsSecureGetStringForUser(currentIme, UserHandle.USER_SYSTEM))
+ when(getServices().settings.settingsSecureGetStringForUser(currentIme, UserHandle.USER_SYSTEM))
.thenReturn("ime2");
dpm.setSecureSetting(admin1, currentIme, "ime3");
- verify(mContext.settings).settingsSecurePutStringForUser(currentIme, "ime3",
+ verify(getServices().settings).settingsSecurePutStringForUser(currentIme, "ime3",
UserHandle.USER_SYSTEM);
dpms.notifyChangeToContentObserver(currentImeUri, UserHandle.USER_SYSTEM);
mContext.binder.callingUid = firstUserSystemUid;
@@ -3936,12 +3827,12 @@
// Profile owner changes IME for second user.
mContext.binder.callingUid = profileOwnerUid;
- when(mContext.settings.settingsSecureGetStringForUser(currentIme,
+ when(getServices().settings.settingsSecureGetStringForUser(currentIme,
DpmMockContext.CALLER_USER_HANDLE)).thenReturn("ime1");
dpm.setSecureSetting(admin1, currentIme, "ime2");
- verify(mContext.settings).settingsSecurePutStringForUser(currentIme, "ime2",
+ verify(getServices().settings).settingsSecurePutStringForUser(currentIme, "ime2",
DpmMockContext.CALLER_USER_HANDLE);
- reset(mContext.settings);
+ reset(getServices().settings);
dpms.notifyChangeToContentObserver(currentImeUri, DpmMockContext.CALLER_USER_HANDLE);
mContext.binder.callingUid = firstUserSystemUid;
assertFalse(dpm.isCurrentInputMethodSetByOwner());
@@ -3964,10 +3855,10 @@
// Profile owner changes IME for second user again.
mContext.binder.callingUid = profileOwnerUid;
- when(mContext.settings.settingsSecureGetStringForUser(currentIme,
+ when(getServices().settings.settingsSecureGetStringForUser(currentIme,
DpmMockContext.CALLER_USER_HANDLE)).thenReturn("ime2");
dpm.setSecureSetting(admin1, currentIme, "ime3");
- verify(mContext.settings).settingsSecurePutStringForUser(currentIme, "ime3",
+ verify(getServices().settings).settingsSecurePutStringForUser(currentIme, "ime3",
DpmMockContext.CALLER_USER_HANDLE);
dpms.notifyChangeToContentObserver(currentImeUri, DpmMockContext.CALLER_USER_HANDLE);
mContext.binder.callingUid = firstUserSystemUid;
@@ -4026,7 +3917,7 @@
// Attempt to set to empty list (which means no listener is whitelisted)
mContext.binder.callingUid = adminUid;
assertFalse(dpms.setPermittedCrossProfileNotificationListeners(
- admin1, Collections.<String>emptyList()));
+ admin1, Collections.emptyList()));
assertNull(dpms.getPermittedCrossProfileNotificationListeners(admin1));
mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
@@ -4049,12 +3940,10 @@
assertTrue(dpms.setPermittedCrossProfileNotificationListeners(
admin1, Collections.singletonList(permittedListener)));
- try {
- dpms.isNotificationListenerServicePermitted(
- permittedListener, MANAGED_PROFILE_USER_ID);
- fail("isNotificationListenerServicePermitted should throw if not called from System");
- } catch (SecurityException expected) {
- }
+ // isNotificationListenerServicePermitted should throw if not called from System.
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpms.isNotificationListenerServicePermitted(
+ permittedListener, MANAGED_PROFILE_USER_ID));
mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
assertTrue(dpms.isNotificationListenerServicePermitted(
@@ -4103,7 +3992,7 @@
mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
assertTrue(dpms.setPermittedCrossProfileNotificationListeners(
admin1, Collections.singletonList(permittedListener)));
- List<String> permittedListeners =
+ final List<String> permittedListeners =
dpms.getPermittedCrossProfileNotificationListeners(admin1);
assertEquals(1, permittedListeners.size());
assertEquals(permittedListener, permittedListeners.get(0));
@@ -4120,7 +4009,7 @@
// Setting an empty whitelist - only system listeners allowed
mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
assertTrue(dpms.setPermittedCrossProfileNotificationListeners(
- admin1, Collections.<String>emptyList()));
+ admin1, Collections.emptyList()));
assertEquals(0, dpms.getPermittedCrossProfileNotificationListeners(admin1).size());
mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
@@ -4184,7 +4073,7 @@
// all allowed in primary profile
mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
assertTrue(dpms.setPermittedCrossProfileNotificationListeners(
- admin1, Collections.<String>emptyList()));
+ admin1, Collections.emptyList()));
assertEquals(0, dpms.getPermittedCrossProfileNotificationListeners(admin1).size());
mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
@@ -4199,39 +4088,38 @@
}
public void testGetOwnerInstalledCaCertsForDeviceOwner() throws Exception {
- mContext.packageName = mRealTestContext.getPackageName();
+ mServiceContext.packageName = mRealTestContext.getPackageName();
+ mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setDeviceOwner();
- final DpmMockContext caller = new DpmMockContext(mRealTestContext, "test-caller");
- caller.packageName = admin1.getPackageName();
- caller.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
-
- verifyCanGetOwnerInstalledCaCerts(admin1, caller);
+ verifyCanGetOwnerInstalledCaCerts(admin1, mAdmin1Context);
}
public void testGetOwnerInstalledCaCertsForProfileOwner() throws Exception {
- mContext.packageName = mRealTestContext.getPackageName();
+ mServiceContext.packageName = mRealTestContext.getPackageName();
+ mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_UID;
setAsProfileOwner(admin1);
- final DpmMockContext caller = new DpmMockContext(mRealTestContext, "test-caller");
- caller.packageName = admin1.getPackageName();
- caller.binder.callingUid = DpmMockContext.CALLER_UID;
-
- verifyCanGetOwnerInstalledCaCerts(admin1, caller);
- verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(admin1, caller);
+ verifyCanGetOwnerInstalledCaCerts(admin1, mAdmin1Context);
+ verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(admin1, mAdmin1Context);
}
public void testGetOwnerInstalledCaCertsForDelegate() throws Exception {
- mContext.packageName = mRealTestContext.getPackageName();
+ mServiceContext.packageName = mRealTestContext.getPackageName();
+ mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_UID;
setAsProfileOwner(admin1);
- final String delegate = "com.example.delegate";
- final int delegateUid = setupPackageInPackageManager(delegate, 20988);
- dpm.setCertInstallerPackage(admin1, delegate);
+ final DpmMockContext caller = new DpmMockContext(getServices(), mRealTestContext);
+ caller.packageName = "com.example.delegate";
+ caller.binder.callingUid = setupPackageInPackageManager(caller.packageName,
+ DpmMockContext.CALLER_USER_HANDLE, 20988, ApplicationInfo.FLAG_HAS_CODE);
- final DpmMockContext caller = new DpmMockContext(mRealTestContext, "test-caller");
- caller.packageName = delegate;
- caller.binder.callingUid = delegateUid;
+ // Make caller a delegated cert installer.
+ runAsCaller(mAdmin1Context, dpms,
+ dpm -> dpm.setCertInstallerPackage(admin1, caller.packageName));
verifyCanGetOwnerInstalledCaCerts(null, caller);
verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(null, caller);
@@ -4243,13 +4131,9 @@
final byte[] caCert = TEST_CA.getBytes();
// device admin (used for posting the tls notification)
- final DpmMockContext admin1Context;
+ DpmMockContext admin1Context = mAdmin1Context;
if (admin1.getPackageName().equals(callerContext.getPackageName())) {
admin1Context = callerContext;
- } else {
- admin1Context = new DpmMockContext(mRealTestContext, "test-admin");
- admin1Context.packageName = admin1.getPackageName();
- admin1Context.applicationInfo = new ApplicationInfo();
}
when(admin1Context.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
@@ -4260,21 +4144,21 @@
// system_server
final DpmMockContext serviceContext = mContext;
serviceContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
- serviceContext.addPackageContext(callerUser, admin1Context);
- serviceContext.addPackageContext(callerUser, callerContext);
+ getServices().addPackageContext(callerUser, admin1Context);
+ getServices().addPackageContext(callerUser, callerContext);
// Install a CA cert.
runAsCaller(callerContext, dpms, (dpm) -> {
- when(mContext.keyChainConnection.getService().installCaCertificate(caCert))
+ when(getServices().keyChainConnection.getService().installCaCertificate(caCert))
.thenReturn(alias);
assertTrue(dpm.installCaCert(caller, caCert));
- when(mContext.keyChainConnection.getService().getUserCaAliases())
+ when(getServices().keyChainConnection.getService().getUserCaAliases())
.thenReturn(asSlice(new String[] {alias}));
-
});
- serviceContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED)
- .putExtra(Intent.EXTRA_USER_HANDLE, callerUser.getIdentifier()));
+ getServices().injectBroadcast(mServiceContext, new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED)
+ .putExtra(Intent.EXTRA_USER_HANDLE, callerUser.getIdentifier()),
+ callerUser.getIdentifier());
flushTasks();
final List<String> ownerInstalledCaCerts = new ArrayList<>();
@@ -4282,26 +4166,26 @@
// Device Owner / Profile Owner can find out which CA certs were installed by itself.
runAsCaller(admin1Context, dpms, (dpm) -> {
final List<String> installedCaCerts = dpm.getOwnerInstalledCaCerts(callerUser);
- assertEquals(Arrays.asList(alias), installedCaCerts);
+ assertEquals(Collections.singletonList(alias), installedCaCerts);
ownerInstalledCaCerts.addAll(installedCaCerts);
});
// Restarting the DPMS should not lose information.
initializeDpms();
- runAsCaller(admin1Context, dpms, (dpm) -> {
- assertEquals(ownerInstalledCaCerts, dpm.getOwnerInstalledCaCerts(callerUser));
- });
+ runAsCaller(admin1Context, dpms, (dpm) ->
+ assertEquals(ownerInstalledCaCerts, dpm.getOwnerInstalledCaCerts(callerUser)));
// System can find out which CA certs were installed by the Device Owner / Profile Owner.
runAsCaller(serviceContext, dpms, (dpm) -> {
assertEquals(ownerInstalledCaCerts, dpm.getOwnerInstalledCaCerts(callerUser));
// Remove the CA cert.
- reset(mContext.keyChainConnection.getService());
+ reset(getServices().keyChainConnection.getService());
});
- serviceContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED)
- .putExtra(Intent.EXTRA_USER_HANDLE, callerUser.getIdentifier()));
+ getServices().injectBroadcast(mServiceContext, new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED)
+ .putExtra(Intent.EXTRA_USER_HANDLE, callerUser.getIdentifier()),
+ callerUser.getIdentifier());
flushTasks();
// Verify that the CA cert is no longer reported as installed by the Device Owner / Profile
@@ -4317,13 +4201,9 @@
final byte[] caCert = TEST_CA.getBytes();
// device admin (used for posting the tls notification)
- final DpmMockContext admin1Context;
+ DpmMockContext admin1Context = mAdmin1Context;
if (admin1.getPackageName().equals(callerContext.getPackageName())) {
admin1Context = callerContext;
- } else {
- admin1Context = new DpmMockContext(mRealTestContext, "test-admin");
- admin1Context.packageName = admin1.getPackageName();
- admin1Context.applicationInfo = new ApplicationInfo();
}
when(admin1Context.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
@@ -4334,27 +4214,26 @@
// system_server
final DpmMockContext serviceContext = mContext;
serviceContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
- serviceContext.addPackageContext(callerUser, admin1Context);
- serviceContext.addPackageContext(callerUser, callerContext);
+ getServices().addPackageContext(callerUser, admin1Context);
+ getServices().addPackageContext(callerUser, callerContext);
// Install a CA cert as caller
runAsCaller(callerContext, dpms, (dpm) -> {
- when(mContext.keyChainConnection.getService().installCaCertificate(caCert))
+ when(getServices().keyChainConnection.getService().installCaCertificate(caCert))
.thenReturn(alias);
assertTrue(dpm.installCaCert(callerName, caCert));
});
// Fake the CA cert as having been installed
- when(serviceContext.keyChainConnection.getService().getUserCaAliases())
+ when(getServices().keyChainConnection.getService().getUserCaAliases())
.thenReturn(asSlice(new String[] {alias}));
- serviceContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED)
- .putExtra(Intent.EXTRA_USER_HANDLE, callerUser.getIdentifier()));
+ getServices().injectBroadcast(mServiceContext, new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED)
+ .putExtra(Intent.EXTRA_USER_HANDLE, callerUser.getIdentifier()),
+ callerUser.getIdentifier());
flushTasks();
// Removing the Profile Owner should clear the information on which CA certs were installed
- runAsCaller(admin1Context, dpms, (dpm) -> {
- dpm.clearProfileOwner(admin1);
- });
+ runAsCaller(admin1Context, dpms, dpm -> dpm.clearProfileOwner(admin1));
runAsCaller(serviceContext, dpms, (dpm) -> {
final List<String> ownerInstalledCaCerts = dpm.getOwnerInstalledCaCerts(callerUser);
@@ -4364,7 +4243,7 @@
}
private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
- when(mContext.settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
+ when(getServices().settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
userhandle)).thenReturn(isUserSetupComplete ? 1 : 0);
dpms.notifyChangeToContentObserver(
Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), userhandle);
@@ -4377,8 +4256,8 @@
private void assertProvisioningAllowed(String action, boolean expected, String packageName,
int uid) {
- String previousPackageName = mContext.packageName;
- int previousUid = mMockContext.binder.callingUid;
+ final String previousPackageName = mContext.packageName;
+ final int previousUid = mMockContext.binder.callingUid;
// Call assertProvisioningAllowed with the packageName / uid passed as arguments.
mContext.packageName = packageName;
@@ -4411,7 +4290,7 @@
private void addManagedProfile(
ComponentName admin, int adminUid, ComponentName copyFromAdmin) throws Exception {
final int userId = UserHandle.getUserId(adminUid);
- mContext.addUser(userId, UserInfo.FLAG_MANAGED_PROFILE, UserHandle.USER_SYSTEM);
+ getServices().addUser(userId, UserInfo.FLAG_MANAGED_PROFILE, UserHandle.USER_SYSTEM);
mContext.callerPermissions.addAll(OWNER_SETUP_PERMISSIONS);
setUpPackageManagerForFakeAdmin(admin, adminUid, copyFromAdmin);
dpm.setActiveAdmin(admin, false, userId);
@@ -4432,6 +4311,6 @@
// We can't let exceptions happen on the background thread. Throw them here if they happen
// so they still cause the test to fail despite being suppressed.
- mContext.rethrowBackgroundBroadcastExceptions();
+ getServices().rethrowBackgroundBroadcastExceptions();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 87106ec..408ee9c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -16,61 +16,27 @@
package com.android.server.devicepolicy;
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.AlarmManager;
-import android.app.IActivityManager;
-import android.app.NotificationManager;
-import android.app.backup.IBackupManager;
+import static org.mockito.Mockito.mock;
+
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.content.pm.UserInfo;
import android.content.res.Resources;
-import android.media.IAudioService;
-import android.net.IIpConnectivityMetrics;
-import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
-import android.os.PowerManager.WakeLock;
-import android.os.PowerManagerInternal;
import android.os.UserHandle;
-import android.os.UserManager;
-import android.os.UserManagerInternal;
-import android.security.KeyChain;
-import android.telephony.TelephonyManager;
-import android.test.mock.MockContentResolver;
import android.test.mock.MockContext;
import android.util.ArrayMap;
-import android.util.Pair;
-import android.view.IWindowManager;
-
-import com.android.internal.widget.LockPatternUtils;
import org.junit.Assert;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import java.io.File;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.atomic.AtomicReference;
-
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
/**
* Context used throughout DPMS tests.
@@ -107,9 +73,10 @@
public static final int SYSTEM_PID = 11111;
public static final String ANOTHER_PACKAGE_NAME = "com.another.package.name";
-
public static final int ANOTHER_UID = UserHandle.getUid(UserHandle.USER_SYSTEM, 18434);
+ private final MockSystemServices mMockSystemServices;
+
public static class MockBinder {
public int callingUid = CALLER_UID;
public int callingPid = CALLER_PID;
@@ -144,130 +111,7 @@
}
}
- public static class EnvironmentForMock {
- public File getUserSystemDirectory(int userId) {
- return null;
- }
- }
-
- public static class BuildMock {
- public boolean isDebuggable = true;
- }
-
- public static class PowerManagerForMock {
- public WakeLock newWakeLock(int levelAndFlags, String tag) {
- return null;
- }
-
- public void goToSleep(long time, int reason, int flags) {
- }
-
- public void reboot(String reason) {
- }
- }
-
- public static class RecoverySystemForMock {
- public void rebootWipeUserData(
- boolean shutdown, String reason, boolean force) throws IOException {
- }
- }
-
- public static class SystemPropertiesForMock {
- public boolean getBoolean(String key, boolean def) {
- return false;
- }
-
- public long getLong(String key, long def) {
- return 0;
- }
-
- public String get(String key, String def) {
- return null;
- }
-
- public String get(String key) {
- return null;
- }
-
- public void set(String key, String value) {
- }
- }
-
- public static class UserManagerForMock {
- public boolean isSplitSystemUser() {
- return false;
- }
- }
-
- public static class SettingsForMock {
- public int settingsSecureGetIntForUser(String name, int def, int userHandle) {
- return 0;
- }
-
- public String settingsSecureGetStringForUser(String name, int userHandle) {
- return null;
- }
-
- public void settingsSecurePutIntForUser(String name, int value, int userHandle) {
- }
-
- public void settingsSecurePutStringForUser(String name, String value, int userHandle) {
- }
-
- public void settingsGlobalPutStringForUser(String name, String value, int userHandle) {
- }
-
- public void settingsSecurePutInt(String name, int value) {
- }
-
- public void settingsGlobalPutInt(String name, int value) {
- }
-
- public void settingsSecurePutString(String name, String value) {
- }
-
- public void settingsGlobalPutString(String name, String value) {
- }
-
- public int settingsGlobalGetInt(String name, int value) {
- return 0;
- }
-
- public String settingsGlobalGetString(String name) {
- return "";
- }
-
- public void securityLogSetLoggingEnabledProperty(boolean enabled) {
- }
-
- public boolean securityLogGetLoggingEnabledProperty() {
- return false;
- }
-
- public boolean securityLogIsLoggingEnabled() {
- return false;
- }
- }
-
- public static class StorageManagerForMock {
- public boolean isFileBasedEncryptionEnabled() {
- return false;
- }
-
- public boolean isNonDefaultBlockEncrypted() {
- return false;
- }
-
- public boolean isEncrypted() {
- return false;
- }
-
- public boolean isEncryptable() {
- return false;
- }
- }
-
- public final Context realTestContext;
+ private final Context realTestContext;
/**
* Use this instance to verify unimplemented methods such as {@link #sendBroadcast}.
@@ -276,39 +120,8 @@
*/
public final Context spiedContext;
- public final File dataDir;
- public final File systemUserDataDir;
-
public final MockBinder binder;
- public final EnvironmentForMock environment;
public final Resources resources;
- public final SystemPropertiesForMock systemProperties;
- public final UserManager userManager;
- public final UserManagerInternal userManagerInternal;
- public final PackageManagerInternal packageManagerInternal;
- public final UserManagerForMock userManagerForMock;
- public final PowerManagerForMock powerManager;
- public final PowerManagerInternal powerManagerInternal;
- public final RecoverySystemForMock recoverySystem;
- public final NotificationManager notificationManager;
- public final IIpConnectivityMetrics iipConnectivityMetrics;
- public final IWindowManager iwindowManager;
- public final IActivityManager iactivityManager;
- public final IPackageManager ipackageManager;
- public final IBackupManager ibackupManager;
- public final IAudioService iaudioService;
- public final LockPatternUtils lockPatternUtils;
- public final StorageManagerForMock storageManager;
- public final WifiManager wifiManager;
- public final SettingsForMock settings;
- public final MockContentResolver contentResolver;
- public final TelephonyManager telephonyManager;
- public final AccountManager accountManager;
- public final AlarmManager alarmManager;
- public final KeyChain.KeyChainConnection keyChainConnection;
-
- /** Note this is a partial mock, not a real mock. */
- public final PackageManager packageManager;
/** TODO: Migrate everything to use {@link #permissions} to avoid confusion. */
@Deprecated
@@ -317,246 +130,17 @@
/** Less confusing alias for {@link #callerPermissions}. */
public final List<String> permissions = callerPermissions;
- private final ArrayList<UserInfo> mUserInfos = new ArrayList<>();
-
- public final BuildMock buildMock = new BuildMock();
-
- /** Optional mapping of other user contexts for {@link #createPackageContextAsUser} to return */
- public final Map<Pair<UserHandle, String>, Context> userPackageContexts = new ArrayMap<>();
-
public String packageName = null;
public ApplicationInfo applicationInfo = null;
- // We have to keep track of broadcast receivers registered for a given intent ourselves as the
- // DPM unit tests mock out the package manager and PackageManager.queryBroadcastReceivers() does
- // not work.
- private class BroadcastReceiverRegistration {
- public final BroadcastReceiver receiver;
- public final IntentFilter filter;
- public final Handler scheduler;
-
- // Exceptions thrown in a background thread kill the whole test. Save them instead.
- public final AtomicReference<Exception> backgroundException = new AtomicReference<>();
-
- public BroadcastReceiverRegistration(BroadcastReceiver receiver, IntentFilter filter,
- Handler scheduler) {
- this.receiver = receiver;
- this.filter = filter;
- this.scheduler = scheduler;
- }
-
- public void sendBroadcastIfApplicable(int userId, Intent intent) {
- final BroadcastReceiver.PendingResult result = new BroadcastReceiver.PendingResult(
- 0 /* resultCode */, null /* resultData */, null /* resultExtras */,
- 0 /* type */, false /* ordered */, false /* sticky */, null /* token */, userId,
- 0 /* flags */);
- if (filter.match(null, intent, false, "DpmMockContext") > 0) {
- final Runnable send = () -> {
- receiver.setPendingResult(result);
- receiver.onReceive(DpmMockContext.this, intent);
- };
- if (scheduler != null) {
- scheduler.post(() -> {
- try {
- send.run();
- } catch (Exception e) {
- backgroundException.compareAndSet(null, e);
- }
- });
- } else {
- send.run();
- }
- }
- }
- }
- private List<BroadcastReceiverRegistration> mBroadcastReceivers = new ArrayList<>();
-
- public DpmMockContext(Context realTestContext, String name) {
- this(realTestContext, new File(realTestContext.getCacheDir(), name));
- }
-
- public DpmMockContext(Context context, File dataDir) {
+ public DpmMockContext(MockSystemServices mockSystemServices, Context context) {
+ mMockSystemServices = mockSystemServices;
realTestContext = context;
- this.dataDir = dataDir;
- DpmTestUtils.clearDir(dataDir);
-
binder = new MockBinder();
- environment = mock(EnvironmentForMock.class);
resources = mock(Resources.class);
- systemProperties = mock(SystemPropertiesForMock.class);
- userManager = mock(UserManager.class);
- userManagerInternal = mock(UserManagerInternal.class);
- userManagerForMock = mock(UserManagerForMock.class);
- packageManagerInternal = mock(PackageManagerInternal.class);
- powerManager = mock(PowerManagerForMock.class);
- powerManagerInternal = mock(PowerManagerInternal.class);
- recoverySystem = mock(RecoverySystemForMock.class);
- notificationManager = mock(NotificationManager.class);
- iipConnectivityMetrics = mock(IIpConnectivityMetrics.class);
- iwindowManager = mock(IWindowManager.class);
- iactivityManager = mock(IActivityManager.class);
- ipackageManager = mock(IPackageManager.class);
- ibackupManager = mock(IBackupManager.class);
- iaudioService = mock(IAudioService.class);
- lockPatternUtils = mock(LockPatternUtils.class);
- storageManager = mock(StorageManagerForMock.class);
- wifiManager = mock(WifiManager.class);
- settings = mock(SettingsForMock.class);
- telephonyManager = mock(TelephonyManager.class);
- accountManager = mock(AccountManager.class);
- alarmManager = mock(AlarmManager.class);
- keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS);
-
- // Package manager is huge, so we use a partial mock instead.
- packageManager = spy(context.getPackageManager());
-
spiedContext = mock(Context.class);
-
- contentResolver = new MockContentResolver();
-
- // Add the system user with a fake profile group already set up (this can happen in the real
- // world if a managed profile is added and then removed).
- systemUserDataDir =
- addUser(UserHandle.USER_SYSTEM, UserInfo.FLAG_PRIMARY, UserHandle.USER_SYSTEM);
-
- // System user is always running.
- setUserRunning(UserHandle.USER_SYSTEM, true);
- }
-
- public File addUser(int userId, int flags) {
- return addUser(userId, flags, UserInfo.NO_PROFILE_GROUP_ID);
- }
-
- public File addUser(int userId, int flags, int profileGroupId) {
- // Set up (default) UserInfo for CALLER_USER_HANDLE.
- final UserInfo uh = new UserInfo(userId, "user" + userId, flags);
- uh.profileGroupId = profileGroupId;
- when(userManager.getUserInfo(eq(userId))).thenReturn(uh);
-
- mUserInfos.add(uh);
- when(userManager.getUsers()).thenReturn(mUserInfos);
- when(userManager.getUsers(anyBoolean())).thenReturn(mUserInfos);
- when(userManager.isUserRunning(eq(new UserHandle(userId)))).thenReturn(true);
- when(userManager.getUserInfo(anyInt())).thenAnswer(
- new Answer<UserInfo>() {
- @Override
- public UserInfo answer(InvocationOnMock invocation) throws Throwable {
- final int userId = (int) invocation.getArguments()[0];
- return getUserInfo(userId);
- }
- }
- );
- when(userManager.getProfiles(anyInt())).thenAnswer(
- new Answer<List<UserInfo>>() {
- @Override
- public List<UserInfo> answer(InvocationOnMock invocation) throws Throwable {
- final int userId = (int) invocation.getArguments()[0];
- return getProfiles(userId);
- }
- }
- );
- when(userManager.getProfileIdsWithDisabled(anyInt())).thenAnswer(
- new Answer<int[]>() {
- @Override
- public int[] answer(InvocationOnMock invocation) throws Throwable {
- final int userId = (int) invocation.getArguments()[0];
- List<UserInfo> profiles = getProfiles(userId);
- return profiles.stream()
- .mapToInt(profile -> profile.id)
- .toArray();
- }
- }
- );
- when(accountManager.getAccountsAsUser(anyInt())).thenReturn(new Account[0]);
-
- // Create a data directory.
- final File dir = new File(dataDir, "users/" + userId);
- DpmTestUtils.clearDir(dir);
-
- when(environment.getUserSystemDirectory(eq(userId))).thenReturn(dir);
- return dir;
- }
-
- public void removeUser(int userId) {
- for (int i = 0; i < mUserInfos.size(); i++) {
- if (mUserInfos.get(i).id == userId) {
- mUserInfos.remove(i);
- break;
- }
- }
- when(userManager.getUserInfo(eq(userId))).thenReturn(null);
-
- when(userManager.isUserRunning(eq(new UserHandle(userId)))).thenReturn(false);
- }
-
- private UserInfo getUserInfo(int userId) {
- for (UserInfo ui : mUserInfos) {
- if (ui.id == userId) {
- return ui;
- }
- }
- return null;
- }
-
- private List<UserInfo> getProfiles(int userId) {
- final ArrayList<UserInfo> ret = new ArrayList<UserInfo>();
- UserInfo parent = null;
- for (UserInfo ui : mUserInfos) {
- if (ui.id == userId) {
- parent = ui;
- break;
- }
- }
- if (parent == null) {
- return ret;
- }
- for (UserInfo ui : mUserInfos) {
- if (ui == parent
- || ui.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
- && ui.profileGroupId == parent.profileGroupId) {
- ret.add(ui);
- }
- }
- return ret;
- }
-
- /**
- * Add multiple users at once. They'll all have flag 0.
- */
- public void addUsers(int... userIds) {
- for (int userId : userIds) {
- addUser(userId, 0);
- }
- }
-
- public void setUserRunning(int userId, boolean isRunning) {
- when(userManager.isUserRunning(MockUtils.checkUserHandle(userId)))
- .thenReturn(isRunning);
- }
-
- public void injectBroadcast(final Intent intent) {
- final int userId = UserHandle.getUserId(binder.getCallingUid());
- for (final BroadcastReceiverRegistration receiver : mBroadcastReceivers) {
- receiver.sendBroadcastIfApplicable(userId, intent);
- }
- }
-
- public void rethrowBackgroundBroadcastExceptions() throws Exception {
- for (final BroadcastReceiverRegistration receiver : mBroadcastReceivers) {
- final Exception e = receiver.backgroundException.getAndSet(null);
- if (e != null) {
- throw e;
- }
- }
- }
-
- public void addPackageContext(UserHandle user, Context context) {
- if (context.getPackageName() == null) {
- throw new NullPointerException("getPackageName() == null");
- }
- userPackageContexts.put(new Pair<>(user, context.getPackageName()), context);
}
@Override
@@ -589,15 +173,15 @@
public Object getSystemService(String name) {
switch (name) {
case Context.ALARM_SERVICE:
- return alarmManager;
+ return mMockSystemServices.alarmManager;
case Context.USER_SERVICE:
- return userManager;
+ return mMockSystemServices.userManager;
case Context.POWER_SERVICE:
- return powerManager;
+ return mMockSystemServices.powerManager;
case Context.WIFI_SERVICE:
- return wifiManager;
+ return mMockSystemServices.wifiManager;
case Context.ACCOUNT_SERVICE:
- return accountManager;
+ return mMockSystemServices.accountManager;
}
throw new UnsupportedOperationException();
}
@@ -609,22 +193,21 @@
@Override
public PackageManager getPackageManager() {
- return packageManager;
+ return mMockSystemServices.packageManager;
}
@Override
public void enforceCallingOrSelfPermission(String permission, String message) {
- if (binder.getCallingUid() == SYSTEM_UID) {
+ if (UserHandle.isSameApp(binder.getCallingUid(), SYSTEM_UID)) {
return; // Assume system has all permissions.
}
-
List<String> permissions = binder.callingPermissions.get(binder.getCallingUid());
if (permissions == null) {
// TODO: delete the following line. to do this without breaking any tests, first it's
// necessary to remove all tests that set it directly.
permissions = callerPermissions;
-// throw new UnsupportedOperationException(
-// "Caller UID " + binder.getCallingUid() + " doesn't exist");
+ // throw new UnsupportedOperationException(
+ // "Caller UID " + binder.getCallingUid() + " doesn't exist");
}
if (!permissions.contains(permission)) {
throw new SecurityException("Caller doesn't have " + permission + " : " + message);
@@ -773,44 +356,40 @@
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
- mBroadcastReceivers.add(new BroadcastReceiverRegistration(receiver, filter, null));
+ mMockSystemServices.registerReceiver(receiver, filter, null);
return spiedContext.registerReceiver(receiver, filter);
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
- mBroadcastReceivers.add(new BroadcastReceiverRegistration(receiver, filter, scheduler));
+ mMockSystemServices.registerReceiver(receiver, filter, scheduler);
return spiedContext.registerReceiver(receiver, filter, broadcastPermission, scheduler);
}
@Override
public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
IntentFilter filter, String broadcastPermission, Handler scheduler) {
- mBroadcastReceivers.add(new BroadcastReceiverRegistration(receiver, filter, scheduler));
+ mMockSystemServices.registerReceiver(receiver, filter, scheduler);
return spiedContext.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
scheduler);
}
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
- mBroadcastReceivers.removeIf(r -> r.receiver == receiver);
+ mMockSystemServices.unregisterReceiver(receiver);
spiedContext.unregisterReceiver(receiver);
}
@Override
public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
throws PackageManager.NameNotFoundException {
- final Pair<UserHandle, String> key = new Pair<>(user, packageName);
- if (userPackageContexts.containsKey(key)) {
- return userPackageContexts.get(key);
- }
- throw new UnsupportedOperationException("No package " + packageName + " for user " + user);
+ return mMockSystemServices.createPackageContextAsUser(packageName, flags, user);
}
@Override
public ContentResolver getContentResolver() {
- return contentResolver;
+ return mMockSystemServices.contentResolver;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index 5d68edd..e0ea573 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -16,6 +16,10 @@
package com.android.server.devicepolicy;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
@@ -28,20 +32,14 @@
import android.os.UserHandle;
import android.test.AndroidTestCase;
-import java.io.File;
import java.util.List;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doReturn;
-
public abstract class DpmTestBase extends AndroidTestCase {
public static final String TAG = "DpmTest";
protected Context mRealTestContext;
protected DpmMockContext mMockContext;
-
- public File dataDir;
+ private MockSystemServices mServices;
public ComponentName admin1;
public ComponentName admin2;
@@ -55,8 +53,8 @@
mRealTestContext = super.getContext();
- mMockContext = new DpmMockContext(
- mRealTestContext, new File(mRealTestContext.getCacheDir(), "test-data"));
+ mServices = new MockSystemServices(mRealTestContext, "test-data");
+ mMockContext = new DpmMockContext(mServices, mRealTestContext);
admin1 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin1.class);
admin2 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin2.class);
@@ -71,12 +69,16 @@
return mMockContext;
}
+ public MockSystemServices getServices() {
+ return mServices;
+ }
+
protected interface DpmRunnable {
- public void run(DevicePolicyManager dpm) throws Exception;
+ void run(DevicePolicyManager dpm) throws Exception;
}
/**
- * Simulate an RPC from {@param caller} to the service context ({@link #mContext}).
+ * Simulate an RPC from {@param caller} to the service context ({@link #mMockContext}).
*
* The caller sees its own context. The server also sees its own separate context, with the
* appropriate calling UID and calling permissions fields already set up.
@@ -85,12 +87,15 @@
DpmRunnable action) {
final DpmMockContext serviceContext = mMockContext;
+ // Save calling UID and PID before clearing identity so we don't run into aliasing issues.
+ final int callingUid = caller.binder.callingUid;
+ final int callingPid = caller.binder.callingPid;
+
final long origId = serviceContext.binder.clearCallingIdentity();
try {
- serviceContext.binder.callingUid = caller.binder.callingUid;
- serviceContext.binder.callingPid = caller.binder.callingPid;
- serviceContext.binder.callingPermissions.put(caller.binder.callingUid,
- caller.permissions);
+ serviceContext.binder.callingUid = callingUid;
+ serviceContext.binder.callingPid = callingPid;
+ serviceContext.binder.callingPermissions.put(callingUid, caller.permissions);
action.run(new DevicePolicyManagerTestable(caller, dpms));
} catch (Exception e) {
throw new AssertionError(e);
@@ -99,7 +104,7 @@
}
}
- protected void markPackageAsInstalled(String packageName, ApplicationInfo ai, int userId)
+ private void markPackageAsInstalled(String packageName, ApplicationInfo ai, int userId)
throws Exception {
final PackageInfo pi = DpmTestUtils.cloneParcelable(
mRealTestContext.getPackageManager().getPackageInfo(
@@ -110,12 +115,12 @@
pi.applicationInfo = ai;
}
- doReturn(pi).when(mMockContext.ipackageManager).getPackageInfo(
+ doReturn(pi).when(mServices.ipackageManager).getPackageInfo(
eq(packageName),
eq(0),
eq(userId));
- doReturn(ai.uid).when(mMockContext.packageManager).getPackageUidAsUser(
+ doReturn(ai.uid).when(mServices.packageManager).getPackageUidAsUser(
eq(packageName),
eq(userId));
}
@@ -151,7 +156,7 @@
* @param copyFromAdmin package information for {@code admin} will be built based on this
* component's information.
*/
- protected void setUpPackageManagerForFakeAdmin(ComponentName admin, int packageUid,
+ private void setUpPackageManagerForFakeAdmin(ComponentName admin, int packageUid,
Integer enabledSetting, Integer appTargetSdk, ComponentName copyFromAdmin)
throws Exception {
@@ -171,7 +176,7 @@
ai.packageName = admin.getPackageName();
ai.name = admin.getClassName();
- doReturn(ai).when(mMockContext.ipackageManager).getApplicationInfo(
+ doReturn(ai).when(mServices.ipackageManager).getApplicationInfo(
eq(admin.getPackageName()),
anyInt(),
eq(UserHandle.getUserId(packageUid)));
@@ -198,12 +203,12 @@
// Note we don't set up queryBroadcastReceivers. We don't use it in DPMS.
- doReturn(aci).when(mMockContext.ipackageManager).getReceiverInfo(
+ doReturn(aci).when(mServices.ipackageManager).getReceiverInfo(
eq(admin),
anyInt(),
eq(UserHandle.getUserId(packageUid)));
- doReturn(new String[] {admin.getPackageName()}).when(mMockContext.ipackageManager)
+ doReturn(new String[] {admin.getPackageName()}).when(mServices.ipackageManager)
.getPackagesForUid(eq(packageUid));
// Set up getPackageInfo().
markPackageAsInstalled(admin.getPackageName(), ai, UserHandle.getUserId(packageUid));
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
new file mode 100644
index 0000000..ed8de05
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.devicepolicy;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.AlarmManager;
+import android.app.IActivityManager;
+import android.app.NotificationManager;
+import android.app.backup.IBackupManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
+import android.media.IAudioService;
+import android.net.IIpConnectivityMetrics;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.UserManagerInternal;
+import android.security.KeyChain;
+import android.telephony.TelephonyManager;
+import android.test.mock.MockContentResolver;
+import android.util.ArrayMap;
+import android.util.Pair;
+import android.view.IWindowManager;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * System services mocks and some other data that are shared by all contexts during the test.
+ */
+public class MockSystemServices {
+ public final File systemUserDataDir;
+ public final EnvironmentForMock environment;
+ public final SystemPropertiesForMock systemProperties;
+ public final UserManager userManager;
+ public final UserManagerInternal userManagerInternal;
+ public final PackageManagerInternal packageManagerInternal;
+ public final UserManagerForMock userManagerForMock;
+ public final PowerManagerForMock powerManager;
+ public final PowerManagerInternal powerManagerInternal;
+ public final RecoverySystemForMock recoverySystem;
+ public final NotificationManager notificationManager;
+ public final IIpConnectivityMetrics iipConnectivityMetrics;
+ public final IWindowManager iwindowManager;
+ public final IActivityManager iactivityManager;
+ public final IPackageManager ipackageManager;
+ public final IBackupManager ibackupManager;
+ public final IAudioService iaudioService;
+ public final LockPatternUtils lockPatternUtils;
+ public final StorageManagerForMock storageManager;
+ public final WifiManager wifiManager;
+ public final SettingsForMock settings;
+ public final MockContentResolver contentResolver;
+ public final TelephonyManager telephonyManager;
+ public final AccountManager accountManager;
+ public final AlarmManager alarmManager;
+ public final KeyChain.KeyChainConnection keyChainConnection;
+ /** Note this is a partial mock, not a real mock. */
+ public final PackageManager packageManager;
+ public final BuildMock buildMock = new BuildMock();
+ public final File dataDir;
+
+ public MockSystemServices(Context realContext, String name) {
+ dataDir = new File(realContext.getCacheDir(), name);
+ DpmTestUtils.clearDir(dataDir);
+
+ environment = mock(EnvironmentForMock.class);
+ systemProperties = mock(SystemPropertiesForMock.class);
+ userManager = mock(UserManager.class);
+ userManagerInternal = mock(UserManagerInternal.class);
+ userManagerForMock = mock(UserManagerForMock.class);
+ packageManagerInternal = mock(PackageManagerInternal.class);
+ powerManager = mock(PowerManagerForMock.class);
+ powerManagerInternal = mock(PowerManagerInternal.class);
+ recoverySystem = mock(RecoverySystemForMock.class);
+ notificationManager = mock(NotificationManager.class);
+ iipConnectivityMetrics = mock(IIpConnectivityMetrics.class);
+ iwindowManager = mock(IWindowManager.class);
+ iactivityManager = mock(IActivityManager.class);
+ ipackageManager = mock(IPackageManager.class);
+ ibackupManager = mock(IBackupManager.class);
+ iaudioService = mock(IAudioService.class);
+ lockPatternUtils = mock(LockPatternUtils.class);
+ storageManager = mock(StorageManagerForMock.class);
+ wifiManager = mock(WifiManager.class);
+ settings = mock(SettingsForMock.class);
+ telephonyManager = mock(TelephonyManager.class);
+ accountManager = mock(AccountManager.class);
+ alarmManager = mock(AlarmManager.class);
+ keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS);
+
+ // Package manager is huge, so we use a partial mock instead.
+ packageManager = spy(realContext.getPackageManager());
+
+ contentResolver = new MockContentResolver();
+
+ // Add the system user with a fake profile group already set up (this can happen in the real
+ // world if a managed profile is added and then removed).
+ systemUserDataDir =
+ addUser(UserHandle.USER_SYSTEM, UserInfo.FLAG_PRIMARY, UserHandle.USER_SYSTEM);
+
+ // System user is always running.
+ setUserRunning(UserHandle.USER_SYSTEM, true);
+ }
+
+ /** Optional mapping of other user contexts for {@link #createPackageContextAsUser} to return */
+ private final Map<Pair<UserHandle, String>, Context> userPackageContexts = new ArrayMap<>();
+
+ private final ArrayList<UserInfo> mUserInfos = new ArrayList<>();
+
+ private final List<BroadcastReceiverRegistration> mBroadcastReceivers = new ArrayList<>();
+
+ public void registerReceiver(
+ BroadcastReceiver receiver, IntentFilter filter, Handler scheduler) {
+ mBroadcastReceivers.add(new BroadcastReceiverRegistration(receiver, filter, scheduler));
+ }
+
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ mBroadcastReceivers.removeIf(r -> r.receiver == receiver);
+ }
+
+ public File addUser(int userId, int flags) {
+ return addUser(userId, flags, UserInfo.NO_PROFILE_GROUP_ID);
+ }
+
+ public File addUser(int userId, int flags, int profileGroupId) {
+ // Set up (default) UserInfo for CALLER_USER_HANDLE.
+ final UserInfo uh = new UserInfo(userId, "user" + userId, flags);
+ uh.profileGroupId = profileGroupId;
+ when(userManager.getUserInfo(eq(userId))).thenReturn(uh);
+
+ mUserInfos.add(uh);
+ when(userManager.getUsers()).thenReturn(mUserInfos);
+ when(userManager.getUsers(anyBoolean())).thenReturn(mUserInfos);
+ when(userManager.isUserRunning(eq(new UserHandle(userId)))).thenReturn(true);
+ when(userManager.getUserInfo(anyInt())).thenAnswer(
+ invocation -> {
+ final int userId1 = (int) invocation.getArguments()[0];
+ return getUserInfo(userId1);
+ }
+ );
+ when(userManager.getProfiles(anyInt())).thenAnswer(
+ invocation -> {
+ final int userId12 = (int) invocation.getArguments()[0];
+ return getProfiles(userId12);
+ }
+ );
+ when(userManager.getProfileIdsWithDisabled(anyInt())).thenAnswer(
+ invocation -> {
+ final int userId13 = (int) invocation.getArguments()[0];
+ List<UserInfo> profiles = getProfiles(userId13);
+ return profiles.stream()
+ .mapToInt(profile -> profile.id)
+ .toArray();
+ }
+ );
+ when(accountManager.getAccountsAsUser(anyInt())).thenReturn(new Account[0]);
+
+ // Create a data directory.
+ final File dir = new File(dataDir, "users/" + userId);
+ DpmTestUtils.clearDir(dir);
+
+ when(environment.getUserSystemDirectory(eq(userId))).thenReturn(dir);
+ return dir;
+ }
+
+ public void removeUser(int userId) {
+ for (int i = 0; i < mUserInfos.size(); i++) {
+ if (mUserInfos.get(i).id == userId) {
+ mUserInfos.remove(i);
+ break;
+ }
+ }
+ when(userManager.getUserInfo(eq(userId))).thenReturn(null);
+
+ when(userManager.isUserRunning(eq(new UserHandle(userId)))).thenReturn(false);
+ }
+
+ private UserInfo getUserInfo(int userId) {
+ for (final UserInfo ui : mUserInfos) {
+ if (ui.id == userId) {
+ return ui;
+ }
+ }
+ return null;
+ }
+
+ private List<UserInfo> getProfiles(int userId) {
+ final ArrayList<UserInfo> ret = new ArrayList<>();
+ UserInfo parent = null;
+ for (final UserInfo ui : mUserInfos) {
+ if (ui.id == userId) {
+ parent = ui;
+ break;
+ }
+ }
+ if (parent == null) {
+ return ret;
+ }
+ for (final UserInfo ui : mUserInfos) {
+ if (ui == parent
+ || ui.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
+ && ui.profileGroupId == parent.profileGroupId) {
+ ret.add(ui);
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Add multiple users at once. They'll all have flag 0.
+ */
+ public void addUsers(int... userIds) {
+ for (final int userId : userIds) {
+ addUser(userId, 0);
+ }
+ }
+
+ public void setUserRunning(int userId, boolean isRunning) {
+ when(userManager.isUserRunning(MockUtils.checkUserHandle(userId)))
+ .thenReturn(isRunning);
+ }
+
+ public void injectBroadcast(Context context, final Intent intent, int userId) {
+ //final int userId = UserHandle.getUserId(binder.getCallingUid());
+ for (final BroadcastReceiverRegistration receiver : mBroadcastReceivers) {
+ receiver.sendBroadcastIfApplicable(context, userId, intent);
+ }
+ }
+
+ public void rethrowBackgroundBroadcastExceptions() throws Exception {
+ for (final BroadcastReceiverRegistration receiver : mBroadcastReceivers) {
+ final Exception e = receiver.backgroundException.getAndSet(null);
+ if (e != null) {
+ throw e;
+ }
+ }
+ }
+
+ public void addPackageContext(UserHandle user, Context context) {
+ if (context.getPackageName() == null) {
+ throw new NullPointerException("getPackageName() == null");
+ }
+ userPackageContexts.put(new Pair<>(user, context.getPackageName()), context);
+ }
+
+ public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
+ throws PackageManager.NameNotFoundException {
+ final Pair<UserHandle, String> key = new Pair<>(user, packageName);
+ if (userPackageContexts.containsKey(key)) {
+ return userPackageContexts.get(key);
+ }
+ throw new UnsupportedOperationException("No package " + packageName + " for user " + user);
+ }
+
+
+ public static class EnvironmentForMock {
+ public File getUserSystemDirectory(int userId) {
+ return null;
+ }
+ }
+
+ public static class BuildMock {
+ public boolean isDebuggable = true;
+ }
+
+ public static class PowerManagerForMock {
+ public PowerManager.WakeLock newWakeLock(int levelAndFlags, String tag) {
+ return null;
+ }
+
+ public void goToSleep(long time, int reason, int flags) {
+ }
+
+ public void reboot(String reason) {
+ }
+ }
+
+ public static class RecoverySystemForMock {
+ public void rebootWipeUserData(
+ boolean shutdown, String reason, boolean force) throws IOException {
+ }
+ }
+
+ public static class SystemPropertiesForMock {
+ public boolean getBoolean(String key, boolean def) {
+ return false;
+ }
+
+ public long getLong(String key, long def) {
+ return 0;
+ }
+
+ public String get(String key, String def) {
+ return null;
+ }
+
+ public String get(String key) {
+ return null;
+ }
+
+ public void set(String key, String value) {
+ }
+ }
+
+ public static class UserManagerForMock {
+ public boolean isSplitSystemUser() {
+ return false;
+ }
+ }
+
+ public static class SettingsForMock {
+ public int settingsSecureGetIntForUser(String name, int def, int userHandle) {
+ return 0;
+ }
+
+ public String settingsSecureGetStringForUser(String name, int userHandle) {
+ return null;
+ }
+
+ public void settingsSecurePutIntForUser(String name, int value, int userHandle) {
+ }
+
+ public void settingsSecurePutStringForUser(String name, String value, int userHandle) {
+ }
+
+ public void settingsGlobalPutStringForUser(String name, String value, int userHandle) {
+ }
+
+ public void settingsSecurePutInt(String name, int value) {
+ }
+
+ public void settingsGlobalPutInt(String name, int value) {
+ }
+
+ public void settingsSecurePutString(String name, String value) {
+ }
+
+ public void settingsGlobalPutString(String name, String value) {
+ }
+
+ public int settingsGlobalGetInt(String name, int value) {
+ return 0;
+ }
+
+ public String settingsGlobalGetString(String name) {
+ return "";
+ }
+
+ public void securityLogSetLoggingEnabledProperty(boolean enabled) {
+ }
+
+ public boolean securityLogGetLoggingEnabledProperty() {
+ return false;
+ }
+
+ public boolean securityLogIsLoggingEnabled() {
+ return false;
+ }
+ }
+
+ public static class StorageManagerForMock {
+ public boolean isFileBasedEncryptionEnabled() {
+ return false;
+ }
+
+ public boolean isNonDefaultBlockEncrypted() {
+ return false;
+ }
+
+ public boolean isEncrypted() {
+ return false;
+ }
+
+ public boolean isEncryptable() {
+ return false;
+ }
+ }
+
+ // We have to keep track of broadcast receivers registered for a given intent ourselves as the
+ // DPM unit tests mock out the package manager and PackageManager.queryBroadcastReceivers() does
+ // not work.
+ private static class BroadcastReceiverRegistration {
+ public final BroadcastReceiver receiver;
+ public final IntentFilter filter;
+ public final Handler scheduler;
+
+ // Exceptions thrown in a background thread kill the whole test. Save them instead.
+ public final AtomicReference<Exception> backgroundException = new AtomicReference<>();
+
+ public BroadcastReceiverRegistration(BroadcastReceiver receiver, IntentFilter filter,
+ Handler scheduler) {
+ this.receiver = receiver;
+ this.filter = filter;
+ this.scheduler = scheduler;
+ }
+
+ public void sendBroadcastIfApplicable(Context context, int userId, Intent intent) {
+ final BroadcastReceiver.PendingResult result = new BroadcastReceiver.PendingResult(
+ 0 /* resultCode */, null /* resultData */, null /* resultExtras */,
+ 0 /* type */, false /* ordered */, false /* sticky */, null /* token */, userId,
+ 0 /* flags */);
+ if (filter.match(null, intent, false, "DpmMockContext") > 0) {
+ final Runnable send = () -> {
+ receiver.setPendingResult(result);
+ receiver.onReceive(context, intent);
+ };
+ if (scheduler != null) {
+ scheduler.post(() -> {
+ try {
+ send.run();
+ } catch (Exception e) {
+ backgroundException.compareAndSet(null, e);
+ }
+ });
+ } else {
+ send.run();
+ }
+ }
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 423c4d5..85835f7 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -20,6 +20,7 @@
import android.content.ComponentName;
import android.os.UserHandle;
+import android.test.suitebuilder.annotation.SmallTest;
/**
* Tests for the DeviceOwner object that saves & loads device and policy owner information.
@@ -32,13 +33,14 @@
(mmma frameworks/base/services/tests/servicestests/ for non-ninja build)
*/
+@SmallTest
public class OwnersTest extends DpmTestBase {
public void testUpgrade01() throws Exception {
- getContext().addUsers(10, 11, 20, 21);
+ getServices().addUsers(10, 11, 20, 21);
// First, migrate.
{
- final OwnersTestable owners = new OwnersTestable(getContext());
+ final OwnersTestable owners = new OwnersTestable(getServices());
DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test01/input.xml"));
@@ -70,7 +72,7 @@
// Then re-read and check.
{
- final OwnersTestable owners = new OwnersTestable(getContext());
+ final OwnersTestable owners = new OwnersTestable(getServices());
owners.load();
assertFalse(owners.hasDeviceOwner());
@@ -87,11 +89,11 @@
}
public void testUpgrade02() throws Exception {
- getContext().addUsers(10, 11, 20, 21);
+ getServices().addUsers(10, 11, 20, 21);
// First, migrate.
{
- final OwnersTestable owners = new OwnersTestable(getContext());
+ final OwnersTestable owners = new OwnersTestable(getServices());
DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test02/input.xml"));
@@ -125,7 +127,7 @@
// Then re-read and check.
{
- final OwnersTestable owners = new OwnersTestable(getContext());
+ final OwnersTestable owners = new OwnersTestable(getServices());
owners.load();
assertTrue(owners.hasDeviceOwner());
@@ -145,11 +147,11 @@
}
public void testUpgrade03() throws Exception {
- getContext().addUsers(10, 11, 20, 21);
+ getServices().addUsers(10, 11, 20, 21);
// First, migrate.
{
- final OwnersTestable owners = new OwnersTestable(getContext());
+ final OwnersTestable owners = new OwnersTestable(getServices());
DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test03/input.xml"));
@@ -191,7 +193,7 @@
// Then re-read and check.
{
- final OwnersTestable owners = new OwnersTestable(getContext());
+ final OwnersTestable owners = new OwnersTestable(getServices());
owners.load();
assertFalse(owners.hasDeviceOwner());
@@ -223,11 +225,11 @@
* and {@link Owners#setProfileOwnerUserRestrictionsMigrated(int)}.
*/
public void testUpgrade04() throws Exception {
- getContext().addUsers(10, 11, 20, 21);
+ getServices().addUsers(10, 11, 20, 21);
// First, migrate.
{
- final OwnersTestable owners = new OwnersTestable(getContext());
+ final OwnersTestable owners = new OwnersTestable(getServices());
DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test04/input.xml"));
@@ -273,7 +275,7 @@
// Then re-read and check.
{
- final OwnersTestable owners = new OwnersTestable(getContext());
+ final OwnersTestable owners = new OwnersTestable(getServices());
owners.load();
assertTrue(owners.hasDeviceOwner());
@@ -306,7 +308,7 @@
}
{
- final OwnersTestable owners = new OwnersTestable(getContext());
+ final OwnersTestable owners = new OwnersTestable(getServices());
owners.load();
assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
@@ -319,7 +321,7 @@
}
{
- final OwnersTestable owners = new OwnersTestable(getContext());
+ final OwnersTestable owners = new OwnersTestable(getServices());
owners.load();
assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration());
@@ -333,11 +335,11 @@
}
public void testUpgrade05() throws Exception {
- getContext().addUsers(10, 11, 20, 21);
+ getServices().addUsers(10, 11, 20, 21);
// First, migrate.
{
- final OwnersTestable owners = new OwnersTestable(getContext());
+ final OwnersTestable owners = new OwnersTestable(getServices());
DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test05/input.xml"));
@@ -370,7 +372,7 @@
// Then re-read and check.
{
- final OwnersTestable owners = new OwnersTestable(getContext());
+ final OwnersTestable owners = new OwnersTestable(getServices());
owners.load();
assertFalse(owners.hasDeviceOwner());
@@ -389,11 +391,11 @@
}
public void testUpgrade06() throws Exception {
- getContext().addUsers(10, 11, 20, 21);
+ getServices().addUsers(10, 11, 20, 21);
// First, migrate.
{
- final OwnersTestable owners = new OwnersTestable(getContext());
+ final OwnersTestable owners = new OwnersTestable(getServices());
DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test06/input.xml"));
@@ -425,7 +427,7 @@
// Then re-read and check.
{
- final OwnersTestable owners = new OwnersTestable(getContext());
+ final OwnersTestable owners = new OwnersTestable(getServices());
owners.load();
assertFalse(owners.hasDeviceOwner());
@@ -444,9 +446,9 @@
}
public void testRemoveExistingFiles() throws Exception {
- getContext().addUsers(10, 11, 20, 21);
+ getServices().addUsers(10, 11, 20, 21);
- final OwnersTestable owners = new OwnersTestable(getContext());
+ final OwnersTestable owners = new OwnersTestable(getServices());
// First, migrate to create new-style config files.
DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(),
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
new file mode 100644
index 0000000..84cca0e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.locksettings;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.IActivityManager;
+import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.pm.UserInfo;
+import android.os.FileUtils;
+import android.os.IProgressListener;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.security.KeyStore;
+import android.test.AndroidTestCase;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+
+public class BaseLockSettingsServiceTests extends AndroidTestCase {
+ protected static final int PRIMARY_USER_ID = 0;
+ protected static final int MANAGED_PROFILE_USER_ID = 12;
+ protected static final int TURNED_OFF_PROFILE_USER_ID = 17;
+ protected static final int SECONDARY_USER_ID = 20;
+
+ private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER_ID, null, null,
+ UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
+ private static final UserInfo SECONDARY_USER_INFO = new UserInfo(SECONDARY_USER_ID, null, null,
+ UserInfo.FLAG_INITIALIZED);
+
+ private ArrayList<UserInfo> mPrimaryUserProfiles = new ArrayList<>();
+
+ LockSettingsService mService;
+
+ MockLockSettingsContext mContext;
+ LockSettingsStorageTestable mStorage;
+
+ LockPatternUtils mLockPatternUtils;
+ MockGateKeeperService mGateKeeperService;
+ NotificationManager mNotificationManager;
+ UserManager mUserManager;
+ MockStorageManager mStorageManager;
+ IActivityManager mActivityManager;
+ DevicePolicyManager mDevicePolicyManager;
+ KeyStore mKeyStore;
+ MockSyntheticPasswordManager mSpManager;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mLockPatternUtils = mock(LockPatternUtils.class);
+ mGateKeeperService = new MockGateKeeperService();
+ mNotificationManager = mock(NotificationManager.class);
+ mUserManager = mock(UserManager.class);
+ mStorageManager = new MockStorageManager();
+ mActivityManager = mock(IActivityManager.class);
+ mDevicePolicyManager = mock(DevicePolicyManager.class);
+
+ mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager,
+ mDevicePolicyManager, mock(StorageManager.class));
+ mStorage = new LockSettingsStorageTestable(mContext,
+ new File(getContext().getFilesDir(), "locksettings"));
+ File storageDir = mStorage.mStorageDir;
+ if (storageDir.exists()) {
+ FileUtils.deleteContents(storageDir);
+ } else {
+ storageDir.mkdirs();
+ }
+
+ mSpManager = new MockSyntheticPasswordManager(mStorage, mGateKeeperService, mUserManager);
+ mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils,
+ mStorage, mGateKeeperService, mKeyStore, mStorageManager, mActivityManager,
+ mSpManager);
+ when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
+ mPrimaryUserProfiles.add(PRIMARY_USER_INFO);
+ installChildProfile(MANAGED_PROFILE_USER_ID);
+ installAndTurnOffChildProfile(TURNED_OFF_PROFILE_USER_ID);
+ when(mUserManager.getUsers(anyBoolean())).thenReturn(mPrimaryUserProfiles);
+ when(mUserManager.getProfiles(eq(PRIMARY_USER_ID))).thenReturn(mPrimaryUserProfiles);
+ when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(SECONDARY_USER_INFO);
+
+ when(mActivityManager.unlockUser(anyInt(), any(), any(), any())).thenAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ mStorageManager.unlockUser((int)args[0], (byte[])args[2],
+ (IProgressListener) args[3]);
+ return true;
+ }
+ });
+
+ when(mLockPatternUtils.getLockSettings()).thenReturn(mService);
+
+ // Adding a fake Device Owner app which will enable escrow token support in LSS.
+ when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(
+ new ComponentName("com.dummy.package", ".FakeDeviceOwner"));
+ }
+
+ private UserInfo installChildProfile(int profileId) {
+ final UserInfo userInfo = new UserInfo(
+ profileId, null, null, UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE);
+ mPrimaryUserProfiles.add(userInfo);
+ when(mUserManager.getUserInfo(eq(profileId))).thenReturn(userInfo);
+ when(mUserManager.getProfileParent(eq(profileId))).thenReturn(PRIMARY_USER_INFO);
+ when(mUserManager.isUserRunning(eq(profileId))).thenReturn(true);
+ when(mUserManager.isUserUnlocked(eq(profileId))).thenReturn(true);
+ return userInfo;
+ }
+
+ private UserInfo installAndTurnOffChildProfile(int profileId) {
+ final UserInfo userInfo = installChildProfile(profileId);
+ userInfo.flags |= UserInfo.FLAG_QUIET_MODE;
+ when(mUserManager.isUserRunning(eq(profileId))).thenReturn(false);
+ when(mUserManager.isUserUnlocked(eq(profileId))).thenReturn(false);
+ return userInfo;
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mStorage.closeDatabase();
+ File db = getContext().getDatabasePath("locksettings.db");
+ assertTrue(!db.exists() || db.delete());
+
+ File storageDir = mStorage.mStorageDir;
+ assertTrue(FileUtils.deleteContents(storageDir));
+ }
+
+ protected static void assertArrayEquals(byte[] expected, byte[] actual) {
+ assertTrue(Arrays.equals(expected, actual));
+ }
+
+ protected static void assertArrayNotSame(byte[] expected, byte[] actual) {
+ assertFalse(Arrays.equals(expected, actual));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
new file mode 100644
index 0000000..3a4aa2d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.locksettings;
+
+import static org.mockito.Mockito.mock;
+
+import android.app.IActivityManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.storage.IStorageManager;
+import android.security.KeyStore;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import java.io.FileNotFoundException;
+
+public class LockSettingsServiceTestable extends LockSettingsService {
+
+ private static class MockInjector extends LockSettingsService.Injector {
+
+ private LockSettingsStorage mLockSettingsStorage;
+ private KeyStore mKeyStore;
+ private IActivityManager mActivityManager;
+ private LockPatternUtils mLockPatternUtils;
+ private IStorageManager mStorageManager;
+ private SyntheticPasswordManager mSpManager;
+
+ public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
+ IActivityManager activityManager, LockPatternUtils lockPatternUtils,
+ IStorageManager storageManager, SyntheticPasswordManager spManager) {
+ super(context);
+ mLockSettingsStorage = storage;
+ mKeyStore = keyStore;
+ mActivityManager = activityManager;
+ mLockPatternUtils = lockPatternUtils;
+ mStorageManager = storageManager;
+ mSpManager = spManager;
+ }
+
+ @Override
+ public Handler getHandler() {
+ return mock(Handler.class);
+ }
+
+ @Override
+ public LockSettingsStorage getStorage() {
+ return mLockSettingsStorage;
+ }
+
+ @Override
+ public LockSettingsStrongAuth getStrongAuth() {
+ return mock(LockSettingsStrongAuth.class);
+ }
+
+ @Override
+ public SynchronizedStrongAuthTracker getStrongAuthTracker() {
+ return mock(SynchronizedStrongAuthTracker.class);
+ }
+
+ @Override
+ public IActivityManager getActivityManager() {
+ return mActivityManager;
+ }
+
+ @Override
+ public LockPatternUtils getLockPatternUtils() {
+ return mLockPatternUtils;
+ }
+
+ @Override
+ public KeyStore getKeyStore() {
+ return mKeyStore;
+ }
+
+ @Override
+ public IStorageManager getStorageManager() {
+ return mStorageManager;
+ }
+
+ @Override
+ public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) {
+ return mSpManager;
+ }
+
+ @Override
+ public int binderGetCallingUid() {
+ return Process.SYSTEM_UID;
+ }
+
+
+ }
+
+ protected LockSettingsServiceTestable(Context context, LockPatternUtils lockPatternUtils,
+ LockSettingsStorage storage, MockGateKeeperService gatekeeper, KeyStore keystore,
+ IStorageManager storageManager, IActivityManager mActivityManager,
+ SyntheticPasswordManager spManager) {
+ super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils,
+ storageManager, spManager));
+ mGateKeeperService = gatekeeper;
+ }
+
+ @Override
+ protected void tieProfileLockToParent(int userId, String password) {
+ mStorage.writeChildProfileLock(userId, password.getBytes());
+ }
+
+ @Override
+ protected String getDecryptedPasswordForTiedProfile(int userId) throws FileNotFoundException,
+ KeyPermanentlyInvalidatedException {
+ byte[] storedData = mStorage.readChildProfileLock(userId);
+ if (storedData == null) {
+ throw new FileNotFoundException("Child profile lock file not found");
+ }
+ try {
+ if (mGateKeeperService.getSecureUserId(userId) == 0) {
+ throw new KeyPermanentlyInvalidatedException();
+ }
+ } catch (RemoteException e) {
+ // shouldn't happen.
+ }
+ return new String(storedData);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
new file mode 100644
index 0000000..cb32492
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.locksettings;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+
+import android.os.RemoteException;
+import android.service.gatekeeper.GateKeeperResponse;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
+import com.android.server.locksettings.MockGateKeeperService.VerifyHandle;
+
+/**
+ * runtest frameworks-services -c com.android.server.locksettings.LockSettingsServiceTests
+ */
+public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testCreatePasswordPrimaryUser() throws RemoteException {
+ testCreateCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD,
+ PASSWORD_QUALITY_ALPHABETIC);
+ }
+
+ public void testCreatePatternPrimaryUser() throws RemoteException {
+ testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN,
+ PASSWORD_QUALITY_SOMETHING);
+ }
+
+ public void testChangePasswordPrimaryUser() throws RemoteException {
+ testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN,
+ "asdfghjk", CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
+ }
+
+ public void testChangePatternPrimaryUser() throws RemoteException {
+ testChangeCredentials(PRIMARY_USER_ID, "!£$%^&*(())", CREDENTIAL_TYPE_PASSWORD,
+ "1596321", CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING);
+ }
+
+ public void testChangePasswordFailPrimaryUser() throws RemoteException {
+ final long sid = 1234;
+ final String FAILED_MESSAGE = "Failed to enroll password";
+ initializeStorageWithCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
+
+ try {
+ mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, "badpwd",
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ fail("Did not fail when enrolling using incorrect credential");
+ } catch (RemoteException expected) {
+ assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
+ }
+ try {
+ mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
+ fail("Did not fail when enrolling using incorrect credential");
+ } catch (RemoteException expected) {
+ assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
+ }
+ assertVerifyCredentials(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
+ }
+
+ public void testClearPasswordPrimaryUser() throws RemoteException {
+ final String PASSWORD = "password";
+ initializeStorageWithCredential(PRIMARY_USER_ID, PASSWORD, CREDENTIAL_TYPE_PASSWORD, 1234);
+ mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, PASSWORD,
+ PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
+ assertFalse(mService.havePassword(PRIMARY_USER_ID));
+ assertFalse(mService.havePattern(PRIMARY_USER_ID));
+ assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ }
+
+ public void testManagedProfileUnifiedChallenge() throws RemoteException {
+ final String UnifiedPassword = "testManagedProfileUnifiedChallenge-pwd";
+ mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID);
+ mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+ final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
+ final long turnedOffProfileSid =
+ mGateKeeperService.getSecureUserId(TURNED_OFF_PROFILE_USER_ID);
+ assertTrue(primarySid != 0);
+ assertTrue(profileSid != 0);
+ assertTrue(profileSid != primarySid);
+ assertTrue(turnedOffProfileSid != 0);
+ assertTrue(turnedOffProfileSid != primarySid);
+ assertTrue(turnedOffProfileSid != profileSid);
+
+ // clear auth token and wait for verify challenge from primary user to re-generate it.
+ mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID);
+ mGateKeeperService.clearAuthToken(TURNED_OFF_PROFILE_USER_ID);
+ // verify credential
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ .getResponseCode());
+
+ // Verify that we have a new auth token for the profile
+ assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+
+ // Verify that profile which aren't running (e.g. turn off work) don't get unlocked
+ assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
+
+ /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
+ * credential as part of verifyCredential() before the new credential is committed in
+ * StorageManager. So we relax the check in our mock StorageManager to allow that.
+ */
+ mStorageManager.setIgnoreBadUnlock(true);
+ // Change primary password and verify that profile SID remains
+ mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ UnifiedPassword, PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ mStorageManager.setIgnoreBadUnlock(false);
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
+
+ // Clear unified challenge
+ mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, UnifiedPassword,
+ PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
+ assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ assertEquals(0, mGateKeeperService.getSecureUserId(TURNED_OFF_PROFILE_USER_ID));
+ }
+
+ public void testManagedProfileSeparateChallenge() throws RemoteException {
+ final String primaryPassword = "testManagedProfileSeparateChallenge-primary";
+ final String profilePassword = "testManagedProfileSeparateChallenge-profile";
+ mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID);
+ /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
+ * credential as part of verifyCredential() before the new credential is committed in
+ * StorageManager. So we relax the check in our mock StorageManager to allow that.
+ */
+ mStorageManager.setIgnoreBadUnlock(true);
+ mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_COMPLEX, MANAGED_PROFILE_USER_ID);
+ mStorageManager.setIgnoreBadUnlock(false);
+
+ final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
+ assertTrue(primarySid != 0);
+ assertTrue(profileSid != 0);
+ assertTrue(profileSid != primarySid);
+
+ // clear auth token and make sure verify challenge from primary user does not regenerate it.
+ mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID);
+ // verify primary credential
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+ .getResponseCode());
+ assertNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
+
+ // verify profile credential
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
+ MANAGED_PROFILE_USER_ID).getResponseCode());
+ assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+
+ // Change primary credential and make sure we don't affect profile
+ mStorageManager.setIgnoreBadUnlock(true);
+ mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ primaryPassword, PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ mStorageManager.setIgnoreBadUnlock(false);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+ profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
+ MANAGED_PROFILE_USER_ID).getResponseCode());
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ }
+
+ private void testCreateCredential(int userId, String credential, int type, int quality)
+ throws RemoteException {
+ mService.setLockCredential(credential, type, null, quality, userId);
+ assertVerifyCredentials(userId, credential, type, -1);
+ }
+
+ private void testChangeCredentials(int userId, String newCredential, int newType,
+ String oldCredential, int oldType, int quality) throws RemoteException {
+ final long sid = 1234;
+ initializeStorageWithCredential(userId, oldCredential, oldType, sid);
+ mService.setLockCredential(newCredential, newType, oldCredential, quality, userId);
+ assertVerifyCredentials(userId, newCredential, newType, sid);
+ }
+
+ private void assertVerifyCredentials(int userId, String credential, int type, long sid)
+ throws RemoteException{
+ final long challenge = 54321;
+ VerifyCredentialResponse response = mService.verifyCredential(credential, type, challenge,
+ userId);
+
+ assertEquals(GateKeeperResponse.RESPONSE_OK, response.getResponseCode());
+ if (sid != -1) assertEquals(sid, mGateKeeperService.getSecureUserId(userId));
+ final int incorrectType;
+ if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
+ assertTrue(mService.havePassword(userId));
+ assertFalse(mService.havePattern(userId));
+ incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+ } else if (type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN){
+ assertFalse(mService.havePassword(userId));
+ assertTrue(mService.havePattern(userId));
+ incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+ } else {
+ assertFalse(mService.havePassword(userId));
+ assertFalse(mService.havePassword(userId));
+ incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+ }
+ // check for bad type
+ assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential(credential,
+ incorrectType, challenge, userId).getResponseCode());
+ // check for bad credential
+ assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential("0" + credential,
+ type, challenge, userId).getResponseCode());
+ }
+
+ private void initializeStorageWithCredential(int userId, String credential, int type, long sid) {
+ byte[] oldHash = new VerifyHandle(credential.getBytes(), sid).toBytes();
+ if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
+ mStorage.writeCredentialHash(CredentialHash.create(oldHash,
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), userId);
+ } else {
+ mStorage.writeCredentialHash(CredentialHash.create(oldHash,
+ LockPatternUtils.CREDENTIAL_TYPE_PATTERN), userId);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
new file mode 100644
index 0000000..424c08c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.locksettings;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+
+import static com.android.internal.widget.LockPatternUtils.stringToPattern;
+
+import static junit.framework.Assert.*;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import static java.io.FileDescriptor.*;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.FileDescriptor;
+
+/**
+ * Test class for {@link LockSettingsShellCommand}.
+ *
+ * runtest frameworks-services -c com.android.server.locksettings.LockSettingsShellCommandTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LockSettingsShellCommandTest {
+
+ private LockSettingsShellCommand mCommand;
+
+ private @Mock LockPatternUtils mLockPatternUtils;
+ private int mUserId;
+ private final Binder mBinder = new Binder();
+ private final ShellCallback mShellCallback = new ShellCallback();
+ private final ResultReceiver mResultReceiver = new ResultReceiver(
+ new Handler(Looper.getMainLooper()));
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ final Context context = InstrumentationRegistry.getTargetContext();
+ mUserId = ActivityManager.getCurrentUser();
+ mCommand = new LockSettingsShellCommand(context, mLockPatternUtils);
+ }
+
+ @Test
+ public void testWrongPassword() throws Exception {
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.checkPassword("1234", mUserId)).thenReturn(false);
+ assertEquals(-1, mCommand.exec(mBinder, in, out, err,
+ new String[] { "set-pin", "--old", "1234" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils, never()).saveLockPassword(any(), any(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void testChangePin() throws Exception {
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.checkPassword("1234", mUserId)).thenReturn(true);
+ assertEquals(0, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-pin", "--old", "1234", "4321" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).saveLockPassword("4321", "1234", PASSWORD_QUALITY_NUMERIC,
+ mUserId);
+ }
+
+ @Test
+ public void testChangePassword() throws Exception {
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.checkPassword("1234", mUserId)).thenReturn(true);
+ assertEquals(0, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-password", "--old", "1234", "4321" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).saveLockPassword("4321", "1234", PASSWORD_QUALITY_ALPHABETIC,
+ mUserId);
+ }
+
+ @Test
+ public void testChangePattern() throws Exception {
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.checkPattern(stringToPattern("1234"), mUserId)).thenReturn(true);
+ assertEquals(0, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-pattern", "--old", "1234", "4321" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).saveLockPattern(stringToPattern("4321"), "1234", mUserId);
+ }
+
+ @Test
+ public void testClear() throws Exception {
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.checkPattern(stringToPattern("1234"), mUserId)).thenReturn(true);
+ assertEquals(0, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "clear", "--old", "1234" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).clearLock("1234", mUserId);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
new file mode 100644
index 0000000..4bdd1c5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.locksettings;
+
+import android.content.Context;
+
+import java.io.File;
+
+public class LockSettingsStorageTestable extends LockSettingsStorage {
+
+ public File mStorageDir;
+
+ public LockSettingsStorageTestable(Context context, File storageDir) {
+ super(context);
+ mStorageDir = storageDir;
+ }
+
+ @Override
+ String getLockPatternFilename(int userId) {
+ return makeDirs(mStorageDir,
+ super.getLockPatternFilename(userId)).getAbsolutePath();
+ }
+
+ @Override
+ String getLockPasswordFilename(int userId) {
+ return makeDirs(mStorageDir,
+ super.getLockPasswordFilename(userId)).getAbsolutePath();
+ }
+
+ @Override
+ String getChildProfileLockFile(int userId) {
+ return makeDirs(mStorageDir,
+ super.getChildProfileLockFile(userId)).getAbsolutePath();
+ }
+
+ @Override
+ protected File getSyntheticPasswordDirectoryForUser(int userId) {
+ return makeDirs(mStorageDir, super.getSyntheticPasswordDirectoryForUser(
+ userId).getAbsolutePath());
+ }
+
+ private File makeDirs(File baseDir, String filePath) {
+ File path = new File(filePath);
+ if (path.getParent() == null) {
+ return new File(baseDir, filePath);
+ } else {
+ File mappedDir = new File(baseDir, path.getParent());
+ mappedDir.mkdirs();
+ return new File(mappedDir, path.getName());
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
new file mode 100644
index 0000000..a0578c9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2014 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.locksettings;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.pm.UserInfo;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.FileUtils;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.test.AndroidTestCase;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * runtest frameworks-services -c com.android.server.locksettings.LockSettingsStorageTests
+ */
+public class LockSettingsStorageTests extends AndroidTestCase {
+ private static final int SOME_USER_ID = 1034;
+ private final byte[] PASSWORD_0 = "thepassword0".getBytes();
+ private final byte[] PASSWORD_1 = "password1".getBytes();
+ private final byte[] PATTERN_0 = "123654".getBytes();
+ private final byte[] PATTERN_1 = "147852369".getBytes();
+
+ public static final byte[] PAYLOAD = new byte[] {1, 2, -1, -2, 33};
+
+ LockSettingsStorage mStorage;
+ File mStorageDir;
+
+ private File mDb;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mStorageDir = new File(getContext().getFilesDir(), "locksettings");
+ mDb = getContext().getDatabasePath("locksettings.db");
+
+ assertTrue(mStorageDir.exists() || mStorageDir.mkdirs());
+ assertTrue(FileUtils.deleteContents(mStorageDir));
+ assertTrue(!mDb.exists() || mDb.delete());
+
+ final UserManager mockUserManager = mock(UserManager.class);
+ // User 2 is a profile of user 1.
+ when(mockUserManager.getProfileParent(eq(2))).thenReturn(new UserInfo(1, "name", 0));
+ // User 3 is a profile of user 0.
+ when(mockUserManager.getProfileParent(eq(3))).thenReturn(new UserInfo(0, "name", 0));
+
+ MockLockSettingsContext context = new MockLockSettingsContext(getContext(), mockUserManager,
+ mock(NotificationManager.class), mock(DevicePolicyManager.class),
+ mock(StorageManager.class));
+ mStorage = new LockSettingsStorageTestable(context,
+ new File(getContext().getFilesDir(), "locksettings"));
+ mStorage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() {
+ @Override
+ public void initialize(SQLiteDatabase db) {
+ mStorage.writeKeyValue(db, "initializedKey", "initialValue", 0);
+ }
+ });
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mStorage.closeDatabase();
+ }
+
+ public void testKeyValue_InitializeWorked() {
+ assertEquals("initialValue", mStorage.readKeyValue("initializedKey", "default", 0));
+ mStorage.clearCache();
+ assertEquals("initialValue", mStorage.readKeyValue("initializedKey", "default", 0));
+ }
+
+ public void testKeyValue_WriteThenRead() {
+ mStorage.writeKeyValue("key", "value", 0);
+ assertEquals("value", mStorage.readKeyValue("key", "default", 0));
+ mStorage.clearCache();
+ assertEquals("value", mStorage.readKeyValue("key", "default", 0));
+ }
+
+ public void testKeyValue_DefaultValue() {
+ assertEquals("default", mStorage.readKeyValue("unititialized key", "default", 0));
+ assertEquals("default2", mStorage.readKeyValue("unititialized key", "default2", 0));
+ }
+
+ public void testKeyValue_Concurrency() {
+ final Object monitor = new Object();
+ List<Thread> threads = new ArrayList<>();
+ for (int i = 0; i < 100; i++) {
+ final int threadId = i;
+ threads.add(new Thread() {
+ @Override
+ public void run() {
+ synchronized (monitor) {
+ try {
+ monitor.wait();
+ } catch (InterruptedException e) {
+ return;
+ }
+ mStorage.writeKeyValue("key", "1 from thread " + threadId, 0);
+ mStorage.readKeyValue("key", "default", 0);
+ mStorage.writeKeyValue("key", "2 from thread " + threadId, 0);
+ mStorage.readKeyValue("key", "default", 0);
+ mStorage.writeKeyValue("key", "3 from thread " + threadId, 0);
+ mStorage.readKeyValue("key", "default", 0);
+ mStorage.writeKeyValue("key", "4 from thread " + threadId, 0);
+ mStorage.readKeyValue("key", "default", 0);
+ mStorage.writeKeyValue("key", "5 from thread " + threadId, 0);
+ mStorage.readKeyValue("key", "default", 0);
+ }
+ }
+ });
+ threads.get(i).start();
+ }
+ mStorage.writeKeyValue("key", "initalValue", 0);
+ synchronized (monitor) {
+ monitor.notifyAll();
+ }
+ for (int i = 0; i < threads.size(); i++) {
+ try {
+ threads.get(i).join();
+ } catch (InterruptedException e) {
+ }
+ }
+ assertEquals('5', mStorage.readKeyValue("key", "default", 0).charAt(0));
+ mStorage.clearCache();
+ assertEquals('5', mStorage.readKeyValue("key", "default", 0).charAt(0));
+ }
+
+ public void testKeyValue_CacheStarvedWriter() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ List<Thread> threads = new ArrayList<>();
+ for (int i = 0; i < 100; i++) {
+ final int threadId = i;
+ threads.add(new Thread() {
+ @Override
+ public void run() {
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ return;
+ }
+ if (threadId == 50) {
+ mStorage.writeKeyValue("starvedWriterKey", "value", 0);
+ } else {
+ mStorage.readKeyValue("starvedWriterKey", "default", 0);
+ }
+ }
+ });
+ threads.get(i).start();
+ }
+ latch.countDown();
+ for (int i = 0; i < threads.size(); i++) {
+ try {
+ threads.get(i).join();
+ } catch (InterruptedException e) {
+ }
+ }
+ String cached = mStorage.readKeyValue("key", "default", 0);
+ mStorage.clearCache();
+ String storage = mStorage.readKeyValue("key", "default", 0);
+ assertEquals("Cached value didn't match stored value", storage, cached);
+ }
+
+ public void testRemoveUser() {
+ mStorage.writeKeyValue("key", "value", 0);
+ writePasswordBytes(PASSWORD_0, 0);
+ writePatternBytes(PATTERN_0, 0);
+
+ mStorage.writeKeyValue("key", "value", 1);
+ writePasswordBytes(PASSWORD_1, 1);
+ writePatternBytes(PATTERN_1, 1);
+
+ mStorage.removeUser(0);
+
+ assertEquals("value", mStorage.readKeyValue("key", "default", 1));
+ assertEquals("default", mStorage.readKeyValue("key", "default", 0));
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_NONE, mStorage.readCredentialHash(0).type);
+ assertPatternBytes(PATTERN_1, 1);
+ }
+
+ public void testCredential_Default() {
+ assertEquals(mStorage.readCredentialHash(0).type, LockPatternUtils.CREDENTIAL_TYPE_NONE);
+ }
+
+ public void testPassword_Write() {
+ writePasswordBytes(PASSWORD_0, 0);
+
+ assertPasswordBytes(PASSWORD_0, 0);
+ mStorage.clearCache();
+ assertPasswordBytes(PASSWORD_0, 0);
+ }
+
+ public void testPassword_WriteProfileWritesParent() {
+ writePasswordBytes(PASSWORD_0, 1);
+ writePasswordBytes(PASSWORD_1, 2);
+
+ assertPasswordBytes(PASSWORD_0, 1);
+ assertPasswordBytes(PASSWORD_1, 2);
+ mStorage.clearCache();
+ assertPasswordBytes(PASSWORD_0, 1);
+ assertPasswordBytes(PASSWORD_1, 2);
+ }
+
+ public void testLockType_WriteProfileWritesParent() {
+ writePasswordBytes(PASSWORD_0, 10);
+ writePatternBytes(PATTERN_0, 20);
+
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ mStorage.readCredentialHash(10).type);
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+ mStorage.readCredentialHash(20).type);
+ mStorage.clearCache();
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ mStorage.readCredentialHash(10).type);
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+ mStorage.readCredentialHash(20).type);
+ }
+
+ public void testPassword_WriteParentWritesProfile() {
+ writePasswordBytes(PASSWORD_0, 2);
+ writePasswordBytes(PASSWORD_1, 1);
+
+ assertPasswordBytes(PASSWORD_1, 1);
+ assertPasswordBytes(PASSWORD_0, 2);
+ mStorage.clearCache();
+ assertPasswordBytes(PASSWORD_1, 1);
+ assertPasswordBytes(PASSWORD_0, 2);
+ }
+
+ public void testProfileLock_ReadWriteChildProfileLock() {
+ assertFalse(mStorage.hasChildProfileLock(20));
+ mStorage.writeChildProfileLock(20, PASSWORD_0);
+ assertArrayEquals(PASSWORD_0, mStorage.readChildProfileLock(20));
+ assertTrue(mStorage.hasChildProfileLock(20));
+ mStorage.clearCache();
+ assertArrayEquals(PASSWORD_0, mStorage.readChildProfileLock(20));
+ assertTrue(mStorage.hasChildProfileLock(20));
+ }
+
+ public void testPattern_Write() {
+ writePatternBytes(PATTERN_0, 0);
+
+ assertPatternBytes(PATTERN_0, 0);
+ mStorage.clearCache();
+ assertPatternBytes(PATTERN_0, 0);
+ }
+
+ public void testPattern_WriteProfileWritesParent() {
+ writePatternBytes(PATTERN_0, 1);
+ writePatternBytes(PATTERN_1, 2);
+
+ assertPatternBytes(PATTERN_0, 1);
+ assertPatternBytes(PATTERN_1, 2);
+ mStorage.clearCache();
+ assertPatternBytes(PATTERN_0, 1);
+ assertPatternBytes(PATTERN_1, 2);
+ }
+
+ public void testPattern_WriteParentWritesProfile() {
+ writePatternBytes(PATTERN_1, 2);
+ writePatternBytes(PATTERN_0, 1);
+
+ assertPatternBytes(PATTERN_0, 1);
+ assertPatternBytes(PATTERN_1, 2);
+ mStorage.clearCache();
+ assertPatternBytes(PATTERN_0, 1);
+ assertPatternBytes(PATTERN_1, 2);
+ }
+
+ public void testPrefetch() {
+ mStorage.writeKeyValue("key", "toBeFetched", 0);
+ writePatternBytes(PATTERN_0, 0);
+
+ mStorage.clearCache();
+ mStorage.prefetchUser(0);
+
+ assertEquals("toBeFetched", mStorage.readKeyValue("key", "default", 0));
+ assertPatternBytes(PATTERN_0, 0);
+ }
+
+ public void testFileLocation_Owner() {
+ LockSettingsStorage storage = new LockSettingsStorage(getContext());
+
+ assertEquals("/data/system/gesture.key", storage.getLegacyLockPatternFilename(0));
+ assertEquals("/data/system/password.key", storage.getLegacyLockPasswordFilename(0));
+ assertEquals("/data/system/gatekeeper.pattern.key", storage.getLockPatternFilename(0));
+ assertEquals("/data/system/gatekeeper.password.key", storage.getLockPasswordFilename(0));
+ }
+
+ public void testFileLocation_SecondaryUser() {
+ LockSettingsStorage storage = new LockSettingsStorage(getContext());
+
+ assertEquals("/data/system/users/1/gatekeeper.pattern.key", storage.getLockPatternFilename(1));
+ assertEquals("/data/system/users/1/gatekeeper.password.key", storage.getLockPasswordFilename(1));
+ }
+
+ public void testFileLocation_ProfileToSecondary() {
+ LockSettingsStorage storage = new LockSettingsStorage(getContext());
+
+ assertEquals("/data/system/users/2/gatekeeper.pattern.key", storage.getLockPatternFilename(2));
+ assertEquals("/data/system/users/2/gatekeeper.password.key", storage.getLockPasswordFilename(2));
+ }
+
+ public void testFileLocation_ProfileToOwner() {
+ LockSettingsStorage storage = new LockSettingsStorage(getContext());
+
+ assertEquals("/data/system/users/3/gatekeeper.pattern.key", storage.getLockPatternFilename(3));
+ assertEquals("/data/system/users/3/gatekeeper.password.key", storage.getLockPasswordFilename(3));
+ }
+
+ public void testSyntheticPasswordState() {
+ final byte[] data = {1,2,3,4};
+ mStorage.writeSyntheticPasswordState(10, 1234L, "state", data);
+ assertArrayEquals(data, mStorage.readSyntheticPasswordState(10, 1234L, "state"));
+ assertEquals(null, mStorage.readSyntheticPasswordState(0, 1234L, "state"));
+
+ mStorage.deleteSyntheticPasswordState(10, 1234L, "state");
+ assertEquals(null, mStorage.readSyntheticPasswordState(10, 1234L, "state"));
+ }
+
+ public void testPersistentData_serializeUnserialize() {
+ byte[] serialized = PersistentData.toBytes(PersistentData.TYPE_GATEKEEPER, SOME_USER_ID,
+ DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, PAYLOAD);
+ PersistentData deserialized = PersistentData.fromBytes(serialized);
+
+ assertEquals(PersistentData.TYPE_GATEKEEPER, deserialized.type);
+ assertEquals(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, deserialized.qualityForUi);
+ assertArrayEquals(PAYLOAD, deserialized.payload);
+ }
+
+ public void testPersistentData_unserializeNull() {
+ PersistentData deserialized = PersistentData.fromBytes(null);
+ assertSame(PersistentData.NONE, deserialized);
+ }
+
+ public void testPersistentData_unserializeEmptyArray() {
+ PersistentData deserialized = PersistentData.fromBytes(new byte[0]);
+ assertSame(PersistentData.NONE, deserialized);
+ }
+
+ public void testPersistentData_unserialize_version1() {
+ // This test ensures that we can read serialized VERSION_1 PersistentData even if we change
+ // the wire format in the future.
+ byte[] serializedVersion1 = new byte[] {
+ 1, /* PersistentData.VERSION_1 */
+ 2, /* PersistentData.TYPE_SP */
+ 0x00, 0x00, 0x04, 0x0A, /* SOME_USER_ID */
+ 0x00, 0x03, 0x00, 0x00, /* PASSWORD_NUMERIC_COMPLEX */
+ 1, 2, -1, -2, 33, /* PAYLOAD */
+ };
+ PersistentData deserialized = PersistentData.fromBytes(serializedVersion1);
+ assertEquals(PersistentData.TYPE_SP, deserialized.type);
+ assertEquals(SOME_USER_ID, deserialized.userId);
+ assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX,
+ deserialized.qualityForUi);
+ assertArrayEquals(PAYLOAD, deserialized.payload);
+
+ // Make sure the constants we use on the wire do not change.
+ assertEquals(0, PersistentData.TYPE_NONE);
+ assertEquals(1, PersistentData.TYPE_GATEKEEPER);
+ assertEquals(2, PersistentData.TYPE_SP);
+ assertEquals(3, PersistentData.TYPE_SP_WEAVER);
+ }
+
+ public void testCredentialHash_serializeUnserialize() {
+ byte[] serialized = CredentialHash.create(
+ PAYLOAD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD).toBytes();
+ CredentialHash deserialized = CredentialHash.fromBytes(serialized);
+
+ assertEquals(CredentialHash.VERSION_GATEKEEPER, deserialized.version);
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, deserialized.type);
+ assertArrayEquals(PAYLOAD, deserialized.hash);
+ assertFalse(deserialized.isBaseZeroPattern);
+ }
+
+ public void testCredentialHash_unserialize_versionGatekeeper() {
+ // This test ensures that we can read serialized VERSION_GATEKEEPER CredentialHashes
+ // even if we change the wire format in the future.
+ byte[] serialized = new byte[] {
+ 1, /* VERSION_GATEKEEPER */
+ 2, /* CREDENTIAL_TYPE_PASSWORD */
+ 0, 0, 0, 5, /* hash length */
+ 1, 2, -1, -2, 33, /* hash */
+ };
+ CredentialHash deserialized = CredentialHash.fromBytes(serialized);
+
+ assertEquals(CredentialHash.VERSION_GATEKEEPER, deserialized.version);
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, deserialized.type);
+ assertArrayEquals(PAYLOAD, deserialized.hash);
+ assertFalse(deserialized.isBaseZeroPattern);
+
+ // Make sure the constants we use on the wire do not change.
+ assertEquals(-1, LockPatternUtils.CREDENTIAL_TYPE_NONE);
+ assertEquals(1, LockPatternUtils.CREDENTIAL_TYPE_PATTERN);
+ assertEquals(2, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
+ }
+
+ private static void assertArrayEquals(byte[] expected, byte[] actual) {
+ if (!Arrays.equals(expected, actual)) {
+ fail("expected:<" + Arrays.toString(expected) +
+ "> but was:<" + Arrays.toString(actual) + ">");
+ }
+ }
+
+ private void writePasswordBytes(byte[] password, int userId) {
+ mStorage.writeCredentialHash(CredentialHash.create(
+ password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), userId);
+ }
+
+ private void writePatternBytes(byte[] pattern, int userId) {
+ mStorage.writeCredentialHash(CredentialHash.create(
+ pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN), userId);
+ }
+
+ private void assertPasswordBytes(byte[] password, int userId) {
+ CredentialHash cred = mStorage.readCredentialHash(userId);
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, cred.type);
+ assertArrayEquals(password, cred.hash);
+ }
+
+ private void assertPatternBytes(byte[] pattern, int userId) {
+ CredentialHash cred = mStorage.readCredentialHash(userId);
+ assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN, cred.type);
+ assertArrayEquals(pattern, cred.hash);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockGateKeeperService.java b/services/tests/servicestests/src/com/android/server/locksettings/MockGateKeeperService.java
new file mode 100644
index 0000000..b89c1d1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockGateKeeperService.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.locksettings;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.service.gatekeeper.GateKeeperResponse;
+import android.service.gatekeeper.IGateKeeperService;
+import android.util.ArrayMap;
+
+import junit.framework.AssertionFailedError;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Random;
+
+public class MockGateKeeperService implements IGateKeeperService {
+ static class VerifyHandle {
+ public byte[] password;
+ public long sid;
+
+ public VerifyHandle(byte[] password, long sid) {
+ this.password = password;
+ this.sid = sid;
+ }
+
+ public VerifyHandle(byte[] handle) {
+ ByteBuffer buffer = ByteBuffer.allocate(handle.length);
+ buffer.put(handle, 0, handle.length);
+ buffer.flip();
+ int version = buffer.get();
+ sid = buffer.getLong();
+ password = new byte[buffer.remaining()];
+ buffer.get(password);
+ }
+
+ public byte[] toBytes() {
+ ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + password.length);
+ buffer.put((byte)0);
+ buffer.putLong(sid);
+ buffer.put(password);
+ return buffer.array();
+ }
+ }
+
+ static class AuthToken {
+ public long challenge;
+ public long sid;
+
+ public AuthToken(long challenge, long sid) {
+ this.challenge = challenge;
+ this.sid = sid;
+ }
+
+ public AuthToken(byte[] handle) {
+ ByteBuffer buffer = ByteBuffer.allocate(handle.length);
+ buffer.put(handle, 0, handle.length);
+ buffer.flip();
+ int version = buffer.get();
+ challenge = buffer.getLong();
+ sid = buffer.getLong();
+ }
+
+ public byte[] toBytes() {
+ ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + Long.BYTES);
+ buffer.put((byte)0);
+ buffer.putLong(challenge);
+ buffer.putLong(sid);
+ return buffer.array();
+ }
+ }
+
+ private ArrayMap<Integer, Long> sidMap = new ArrayMap<>();
+ private ArrayMap<Integer, AuthToken> authTokenMap = new ArrayMap<>();
+
+ private ArrayMap<Integer, byte[]> handleMap = new ArrayMap<>();
+
+ @Override
+ public GateKeeperResponse enroll(int uid, byte[] currentPasswordHandle, byte[] currentPassword,
+ byte[] desiredPassword) throws android.os.RemoteException {
+
+ if (currentPasswordHandle != null) {
+ VerifyHandle handle = new VerifyHandle(currentPasswordHandle);
+ if (Arrays.equals(currentPassword, handle.password)) {
+ // Trusted enroll
+ VerifyHandle newHandle = new VerifyHandle(desiredPassword, handle.sid);
+ refreshSid(uid, handle.sid, false);
+ handleMap.put(uid, newHandle.toBytes());
+ return GateKeeperResponse.createOkResponse(newHandle.toBytes(), false);
+ } else {
+ return null;
+ }
+ } else {
+ // Untrusted enroll
+ long newSid = new Random().nextLong();
+ VerifyHandle newHandle = new VerifyHandle(desiredPassword, newSid);
+ refreshSid(uid, newSid, true);
+ handleMap.put(uid, newHandle.toBytes());
+ return GateKeeperResponse.createOkResponse(newHandle.toBytes(), false);
+ }
+ }
+
+ @Override
+ public GateKeeperResponse verify(int uid, byte[] enrolledPasswordHandle,
+ byte[] providedPassword) throws android.os.RemoteException {
+ return verifyChallenge(uid, 0, enrolledPasswordHandle, providedPassword);
+ }
+
+ @Override
+ public GateKeeperResponse verifyChallenge(int uid, long challenge,
+ byte[] enrolledPasswordHandle, byte[] providedPassword) throws RemoteException {
+
+ VerifyHandle handle = new VerifyHandle(enrolledPasswordHandle);
+ if (Arrays.equals(handle.password, providedPassword)) {
+ byte[] knownHandle = handleMap.get(uid);
+ if (knownHandle != null) {
+ if (!Arrays.equals(knownHandle, enrolledPasswordHandle)) {
+ throw new AssertionFailedError("Got correct but obsolete handle");
+ }
+ }
+ refreshSid(uid, handle.sid, false);
+ AuthToken token = new AuthToken(challenge, handle.sid);
+ refreshAuthToken(uid, token);
+ return GateKeeperResponse.createOkResponse(token.toBytes(), false);
+ } else {
+ return GateKeeperResponse.createGenericResponse(GateKeeperResponse.RESPONSE_ERROR);
+ }
+ }
+
+ private void refreshAuthToken(int uid, AuthToken token) {
+ authTokenMap.put(uid, token);
+ }
+
+ public AuthToken getAuthToken(int uid) {
+ return authTokenMap.get(uid);
+ }
+
+ public AuthToken getAuthTokenForSid(long sid) {
+ for(AuthToken token : authTokenMap.values()) {
+ if (token.sid == sid) {
+ return token;
+ }
+ }
+ return null;
+ }
+
+ public void clearAuthToken(int uid) {
+ authTokenMap.remove(uid);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clearSecureUserId(int userId) throws RemoteException {
+ sidMap.remove(userId);
+ }
+
+ @Override
+ public void reportDeviceSetupComplete() throws RemoteException {
+ }
+
+ @Override
+ public long getSecureUserId(int userId) throws RemoteException {
+ if (sidMap.containsKey(userId)) {
+ return sidMap.get(userId);
+ } else {
+ return 0L;
+ }
+ }
+
+ private void refreshSid(int uid, long sid, boolean force) {
+ if (!sidMap.containsKey(uid) || force) {
+ sidMap.put(uid, sid);
+ } else{
+ if (sidMap.get(uid) != sid) {
+ throw new AssertionFailedError("Inconsistent SID");
+ }
+ }
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
new file mode 100644
index 0000000..8da33a8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.locksettings;
+
+import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+
+public class MockLockSettingsContext extends ContextWrapper {
+
+ private UserManager mUserManager;
+ private NotificationManager mNotificationManager;
+ private DevicePolicyManager mDevicePolicyManager;
+ private StorageManager mStorageManager;
+
+ public MockLockSettingsContext(Context base, UserManager userManager,
+ NotificationManager notificationManager, DevicePolicyManager devicePolicyManager,
+ StorageManager storageManager) {
+ super(base);
+ mUserManager = userManager;
+ mNotificationManager = notificationManager;
+ mDevicePolicyManager = devicePolicyManager;
+ mStorageManager = storageManager;
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (USER_SERVICE.equals(name)) {
+ return mUserManager;
+ } else if (NOTIFICATION_SERVICE.equals(name)) {
+ return mNotificationManager;
+ } else if (DEVICE_POLICY_SERVICE.equals(name)) {
+ return mDevicePolicyManager;
+ } else if (STORAGE_SERVICE.equals(name)) {
+ return mStorageManager;
+ } else {
+ throw new RuntimeException("System service not mocked: " + name);
+ }
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ // Skip permission checks for unit tests.
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockStorageManager.java b/services/tests/servicestests/src/com/android/server/locksettings/MockStorageManager.java
new file mode 100644
index 0000000..89e18b4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockStorageManager.java
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.locksettings;
+
+import android.content.pm.IPackageMoveObserver;
+import android.os.IBinder;
+import android.os.IProgressListener;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.storage.DiskInfo;
+import android.os.storage.IObbActionListener;
+import android.os.storage.IStorageEventListener;
+import android.os.storage.IStorageManager;
+import android.os.storage.IStorageShutdownObserver;
+import android.os.storage.StorageVolume;
+import android.os.storage.VolumeInfo;
+import android.os.storage.VolumeRecord;
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import com.android.internal.os.AppFuseMount;
+
+import junit.framework.AssertionFailedError;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class MockStorageManager implements IStorageManager {
+
+ private ArrayMap<Integer, ArrayList<Pair<byte[], byte[]>>> mAuth = new ArrayMap<>();
+ private boolean mIgnoreBadUnlock;
+
+ @Override
+ public void addUserKeyAuth(int userId, int serialNumber, byte[] token, byte[] secret)
+ throws RemoteException {
+ getUserAuth(userId).add(new Pair<>(token, secret));
+ }
+
+ @Override
+ public void fixateNewestUserKeyAuth(int userId) throws RemoteException {
+ ArrayList<Pair<byte[], byte[]>> auths = mAuth.get(userId);
+ Pair<byte[], byte[]> latest = auths.get(auths.size() - 1);
+ auths.clear();
+ auths.add(latest);
+ }
+
+ private ArrayList<Pair<byte[], byte[]>> getUserAuth(int userId) {
+ if (!mAuth.containsKey(userId)) {
+ ArrayList<Pair<byte[], byte[]>> auths = new ArrayList<Pair<byte[], byte[]>>();
+ auths.add(new Pair(null, null));
+ mAuth.put(userId, auths);
+ }
+ return mAuth.get(userId);
+ }
+
+ public byte[] getUserUnlockToken(int userId) {
+ ArrayList<Pair<byte[], byte[]>> auths = getUserAuth(userId);
+ if (auths.size() != 1) {
+ throw new AssertionFailedError("More than one secret exists");
+ }
+ return auths.get(0).second;
+ }
+
+ public void unlockUser(int userId, byte[] secret, IProgressListener listener)
+ throws RemoteException {
+ listener.onStarted(userId, null);
+ listener.onFinished(userId, null);
+ ArrayList<Pair<byte[], byte[]>> auths = getUserAuth(userId);
+ if (secret != null) {
+ if (auths.size() > 1) {
+ throw new AssertionFailedError("More than one secret exists");
+ }
+ Pair<byte[], byte[]> auth = auths.get(0);
+ if ((!mIgnoreBadUnlock) && auth.second != null && !Arrays.equals(secret, auth.second)) {
+ throw new AssertionFailedError("Invalid secret to unlock user");
+ }
+ } else {
+ if (auths != null && auths.size() > 0) {
+ throw new AssertionFailedError("Cannot unlock encrypted user with empty token");
+ }
+ }
+ }
+
+ public void setIgnoreBadUnlock(boolean ignore) {
+ mIgnoreBadUnlock = ignore;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void registerListener(IStorageEventListener listener) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unregisterListener(IStorageEventListener listener) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isUsbMassStorageConnected() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setUsbMassStorageEnabled(boolean enable) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isUsbMassStorageEnabled() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int mountVolume(String mountPoint) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unmountVolume(String mountPoint, boolean force, boolean removeEncryption)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public int formatVolume(String mountPoint) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int[] getStorageUsers(String path) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getVolumeState(String mountPoint) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int createSecureContainer(String id, int sizeMb, String fstype, String key, int ownerUid,
+ boolean external) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int finalizeSecureContainer(String id) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int destroySecureContainer(String id, boolean force) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int unmountSecureContainer(String id, boolean force) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isSecureContainerMounted(String id) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int renameSecureContainer(String oldId, String newId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getSecureContainerPath(String id) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String[] getSecureContainerList() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void shutdown(IStorageShutdownObserver observer) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void finishMediaUpdate() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void mountObb(String rawPath, String canonicalPath, String key, IObbActionListener token,
+ int nonce) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isObbMounted(String rawPath) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getMountedObbPath(String rawPath) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isExternalStorageEmulated() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int decryptStorage(String password) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int encryptStorage(int type, String password) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int changeEncryptionPassword(int type, String password) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public StorageVolume[] getVolumeList(int uid, String packageName, int flags)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getSecureContainerFilesystemPath(String cid) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getEncryptionState() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int verifyEncryptionPassword(String password) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int fixPermissionsSecureContainer(String id, int gid, String filename)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int mkdirs(String callingPkg, String path) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getPasswordType() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPassword() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clearPassword() throws RemoteException {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public void setField(String field, String contents) throws RemoteException {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public String getField(String field) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int resizeSecureContainer(String id, int sizeMb, String key) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long lastMaintenance() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void runMaintenance() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void waitForAsecScan() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DiskInfo[] getDisks() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public VolumeInfo[] getVolumes(int flags) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public VolumeRecord[] getVolumeRecords(int flags) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void mount(String volId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unmount(String volId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void format(String volId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void partitionPublic(String diskId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void partitionPrivate(String diskId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void partitionMixed(String diskId, int ratio) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setVolumeNickname(String fsUuid, String nickname) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setVolumeUserFlags(String fsUuid, int flags, int mask) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void forgetVolume(String fsUuid) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void forgetAllVolumes() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPrimaryStorageUuid() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long benchmark(String volId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setDebugFlags(int flags, int mask) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void createUserKey(int userId, int serialNumber, boolean ephemeral)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void destroyUserKey(int userId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void lockUserKey(int userId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isUserKeyUnlocked(int userId) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void destroyUserStorage(String volumeUuid, int userId, int flags)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isConvertibleToFBE() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void fstrim(int flags) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public AppFuseMount mountProxyFileDescriptorBridge() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getCacheQuotaBytes(String volumeUuid, int uid) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getCacheSizeBytes(String volumeUuid, int uid) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getAllocatableBytes(String path, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void allocateBytes(String path, long bytes, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void secdiscard(String path) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
new file mode 100644
index 0000000..d7468c2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.locksettings;
+
+import android.hardware.weaver.V1_0.IWeaver;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.util.ArrayMap;
+
+import junit.framework.AssertionFailedError;
+
+import java.nio.ByteBuffer;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+public class MockSyntheticPasswordManager extends SyntheticPasswordManager {
+
+ private MockGateKeeperService mGateKeeper;
+ private IWeaver mWeaverService;
+
+ public MockSyntheticPasswordManager(LockSettingsStorage storage,
+ MockGateKeeperService gatekeeper, UserManager userManager) {
+ super(storage, userManager);
+ mGateKeeper = gatekeeper;
+ }
+
+ private ArrayMap<String, byte[]> mBlobs = new ArrayMap<>();
+
+ @Override
+ protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] applicationId) {
+ if (mBlobs.containsKey(blobKeyName) && !Arrays.equals(mBlobs.get(blobKeyName), blob)) {
+ throw new AssertionFailedError("blobKeyName content is overwritten: " + blobKeyName);
+ }
+ ByteBuffer buffer = ByteBuffer.allocate(blob.length);
+ buffer.put(blob, 0, blob.length);
+ buffer.flip();
+ int len;
+ len = buffer.getInt();
+ byte[] data = new byte[len];
+ buffer.get(data);
+ len = buffer.getInt();
+ byte[] appId = new byte[len];
+ buffer.get(appId);
+ long sid = buffer.getLong();
+ if (!Arrays.equals(appId, applicationId)) {
+ throw new AssertionFailedError("Invalid application id");
+ }
+ if (sid != 0 && mGateKeeper.getAuthTokenForSid(sid) == null) {
+ throw new AssertionFailedError("No valid auth token");
+ }
+ return data;
+ }
+
+ @Override
+ protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] applicationId, long sid) {
+ ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + data.length + Integer.BYTES
+ + applicationId.length + Long.BYTES);
+ buffer.putInt(data.length);
+ buffer.put(data);
+ buffer.putInt(applicationId.length);
+ buffer.put(applicationId);
+ buffer.putLong(sid);
+ byte[] result = buffer.array();
+ mBlobs.put(blobKeyName, result);
+ return result;
+ }
+
+ @Override
+ protected void destroySPBlobKey(String keyAlias) {
+ }
+
+ @Override
+ protected long sidFromPasswordHandle(byte[] handle) {
+ return new MockGateKeeperService.VerifyHandle(handle).sid;
+ }
+
+ @Override
+ protected byte[] scrypt(String password, byte[] salt, int N, int r, int p, int outLen) {
+ try {
+ PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10, outLen * 8);
+ SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+ return f.generateSecret(spec).getEncoded();
+ } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ @Override
+ protected IWeaver getWeaverService() throws RemoteException {
+ return mWeaverService;
+ }
+
+ public void enableWeaver() {
+ mWeaverService = new MockWeaverService();
+ initWeaverService();
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockWeaverService.java b/services/tests/servicestests/src/com/android/server/locksettings/MockWeaverService.java
new file mode 100644
index 0000000..34831cd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockWeaverService.java
@@ -0,0 +1,108 @@
+package com.android.server.locksettings;
+
+import android.hardware.weaver.V1_0.IWeaver;
+import android.hardware.weaver.V1_0.WeaverConfig;
+import android.hardware.weaver.V1_0.WeaverReadResponse;
+import android.hardware.weaver.V1_0.WeaverStatus;
+import android.hidl.base.V1_0.DebugInfo;
+import android.os.IHwBinder;
+import android.os.IHwBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.util.Pair;
+
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class MockWeaverService implements IWeaver {
+
+ private static final int MAX_SLOTS = 8;
+ private static final int KEY_LENGTH = 256 / 8;
+ private static final int VALUE_LENGTH = 256 / 8;
+
+ private Pair<ArrayList<Byte>, ArrayList<Byte>>[] slots = new Pair[MAX_SLOTS];
+ @Override
+ public void getConfig(getConfigCallback cb) throws RemoteException {
+ WeaverConfig config = new WeaverConfig();
+ config.keySize = KEY_LENGTH;
+ config.valueSize = VALUE_LENGTH;
+ config.slots = MAX_SLOTS;
+ cb.onValues(WeaverStatus.OK, config);
+ }
+
+ @Override
+ public int write(int slotId, ArrayList<Byte> key, ArrayList<Byte> value)
+ throws RemoteException {
+ if (slotId < 0 || slotId >= MAX_SLOTS) {
+ throw new RuntimeException("Invalid slot id");
+ }
+ slots[slotId] = Pair.create((ArrayList<Byte>) key.clone(), (ArrayList<Byte>) value.clone());
+ return WeaverStatus.OK;
+ }
+
+ @Override
+ public void read(int slotId, ArrayList<Byte> key, readCallback cb) throws RemoteException {
+ if (slotId < 0 || slotId >= MAX_SLOTS) {
+ throw new RuntimeException("Invalid slot id");
+ }
+
+ WeaverReadResponse response = new WeaverReadResponse();
+ if (key.equals(slots[slotId].first)) {
+ response.value.addAll(slots[slotId].second);
+ cb.onValues(WeaverStatus.OK, response);
+ } else {
+ cb.onValues(WeaverStatus.FAILED, response);
+ }
+ }
+
+ @Override
+ public IHwBinder asBinder() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ArrayList<String> interfaceChain() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String interfaceDescriptor() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setHALInstrumentation() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean linkToDeath(DeathRecipient recipient, long cookie) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void ping() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DebugInfo getDebugInfo() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void notifySyspropsChanged() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean unlinkToDeath(DeathRecipient recipient) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ArrayList<byte[]> getHashChain() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
new file mode 100644
index 0000000..ba4ff33
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.locksettings;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
+import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
+
+import android.app.admin.DevicePolicyManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
+import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
+import com.android.server.locksettings.SyntheticPasswordManager.PasswordData;
+
+
+/**
+ * runtest frameworks-services -c com.android.server.locksettings.SyntheticPasswordTests
+ */
+public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
+
+ public static final byte[] PAYLOAD = new byte[] {1, 2, -1, -2, 55};
+ public static final byte[] PAYLOAD2 = new byte[] {2, 3, -2, -3, 44, 1};
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testPasswordBasedSyntheticPassword() throws RemoteException {
+ final int USER_ID = 10;
+ final String PASSWORD = "user-password";
+ final String BADPASSWORD = "bad-password";
+ MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mStorage,
+ mGateKeeperService, mUserManager);
+ AuthenticationToken authToken = manager.newSyntheticPasswordAndSid(mGateKeeperService, null,
+ null, USER_ID);
+ long handle = manager.createPasswordBasedSyntheticPassword(mGateKeeperService, PASSWORD,
+ LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, authToken, PASSWORD_QUALITY_ALPHABETIC,
+ USER_ID);
+
+ AuthenticationResult result = manager.unwrapPasswordBasedSyntheticPassword(mGateKeeperService, handle, PASSWORD, USER_ID);
+ assertEquals(result.authToken.deriveKeyStorePassword(), authToken.deriveKeyStorePassword());
+
+ result = manager.unwrapPasswordBasedSyntheticPassword(mGateKeeperService, handle, BADPASSWORD, USER_ID);
+ assertNull(result.authToken);
+ }
+
+ private void disableSyntheticPassword(int userId) throws RemoteException {
+ mService.setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM);
+ }
+
+ private void enableSyntheticPassword(int userId) throws RemoteException {
+ mService.setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1, UserHandle.USER_SYSTEM);
+ }
+
+ private boolean hasSyntheticPassword(int userId) throws RemoteException {
+ return mService.getLong(SYNTHETIC_PASSWORD_HANDLE_KEY, 0, userId) != 0;
+ }
+
+ public void testPasswordMigration() throws RemoteException {
+ final String PASSWORD = "testPasswordMigration-password";
+
+ disableSyntheticPassword(PRIMARY_USER_ID);
+ mService.setLockCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+ enableSyntheticPassword(PRIMARY_USER_ID);
+ // Performs migration
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
+
+ // SP-based verification
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ }
+
+ private void initializeCredentialUnderSP(String password, int userId) throws RemoteException {
+ enableSyntheticPassword(userId);
+ int quality = password != null ? PASSWORD_QUALITY_ALPHABETIC
+ : PASSWORD_QUALITY_UNSPECIFIED;
+ int type = password != null ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+ : LockPatternUtils.CREDENTIAL_TYPE_NONE;
+ mService.setLockCredential(password, type, null, quality, userId);
+ }
+
+ public void testSyntheticPasswordChangeCredential() throws RemoteException {
+ final String PASSWORD = "testSyntheticPasswordChangeCredential-password";
+ final String NEWPASSWORD = "testSyntheticPasswordChangeCredential-newpassword";
+
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, PASSWORD,
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ mGateKeeperService.clearSecureUserId(PRIMARY_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ }
+
+ public void testSyntheticPasswordVerifyCredential() throws RemoteException {
+ final String PASSWORD = "testSyntheticPasswordVerifyCredential-password";
+ final String BADPASSWORD = "testSyntheticPasswordVerifyCredential-badpassword";
+
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+ mService.verifyCredential(BADPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ }
+
+ public void testSyntheticPasswordClearCredential() throws RemoteException {
+ final String PASSWORD = "testSyntheticPasswordClearCredential-password";
+ final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
+
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ // clear password
+ mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, PASSWORD,
+ PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
+ assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+
+ // set a new password
+ mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertNotSame(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ }
+
+ public void testSyntheticPasswordClearCredentialUntrusted() throws RemoteException {
+ final String PASSWORD = "testSyntheticPasswordClearCredential-password";
+ final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
+
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ // clear password
+ mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, null,
+ PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
+ assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+
+ // set a new password
+ mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertNotSame(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ }
+
+ public void testSyntheticPasswordChangeCredentialUntrusted() throws RemoteException {
+ final String PASSWORD = "testSyntheticPasswordClearCredential-password";
+ final String NEWPASSWORD = "testSyntheticPasswordClearCredential-newpassword";
+
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ // Untrusted change password
+ mService.setLockCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ assertNotSame(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertNotSame(sid ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+
+ // Verify the password
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ }
+
+
+ public void testManagedProfileUnifiedChallengeMigration() throws RemoteException {
+ final String UnifiedPassword = "testManagedProfileUnifiedChallengeMigration-pwd";
+ disableSyntheticPassword(PRIMARY_USER_ID);
+ disableSyntheticPassword(MANAGED_PROFILE_USER_ID);
+ mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+ final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
+ final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+ final byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
+ assertTrue(primarySid != 0);
+ assertTrue(profileSid != 0);
+ assertTrue(profileSid != primarySid);
+
+ // do migration
+ enableSyntheticPassword(PRIMARY_USER_ID);
+ enableSyntheticPassword(MANAGED_PROFILE_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+
+ // verify
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ assertArrayNotSame(profileStorageKey, mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID));
+ assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
+ assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID));
+ }
+
+ public void testManagedProfileSeparateChallengeMigration() throws RemoteException {
+ final String primaryPassword = "testManagedProfileSeparateChallengeMigration-primary";
+ final String profilePassword = "testManagedProfileSeparateChallengeMigration-profile";
+ mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_ALPHABETIC, MANAGED_PROFILE_USER_ID);
+ final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+ final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
+ final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+ final byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
+ assertTrue(primarySid != 0);
+ assertTrue(profileSid != 0);
+ assertTrue(profileSid != primarySid);
+
+ // do migration
+ enableSyntheticPassword(PRIMARY_USER_ID);
+ enableSyntheticPassword(MANAGED_PROFILE_USER_ID);
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, MANAGED_PROFILE_USER_ID).getResponseCode());
+
+ // verify
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, MANAGED_PROFILE_USER_ID).getResponseCode());
+ assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+ assertArrayNotSame(primaryStorageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ assertArrayNotSame(profileStorageKey, mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID));
+ assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
+ assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID));
+ }
+
+ public void testTokenBasedResetPassword() throws RemoteException {
+ final String PASSWORD = "password";
+ final String PATTERN = "123654";
+ final String TOKEN = "some-high-entropy-secure-token";
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+
+ long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
+ assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
+ assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+ handle, TOKEN.getBytes(), PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode());
+ assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ }
+
+ public void testTokenBasedClearPassword() throws RemoteException {
+ final String PASSWORD = "password";
+ final String PATTERN = "123654";
+ final String TOKEN = "some-high-entropy-secure-token";
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+
+ long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
+ assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
+ assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.setLockCredentialWithToken(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, handle,
+ TOKEN.getBytes(), PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
+ mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+ handle, TOKEN.getBytes(), PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode());
+ assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ }
+
+ public void testTokenBasedResetPasswordAfterCredentialChanges() throws RemoteException {
+ final String PASSWORD = "password";
+ final String PATTERN = "123654";
+ final String NEWPASSWORD = "password";
+ final String TOKEN = "some-high-entropy-secure-token";
+ initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID);
+ final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+
+ long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
+ assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode();
+ assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+ mService.setLockCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, PASSWORD,
+ PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
+
+ mService.setLockCredentialWithToken(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+ handle, TOKEN.getBytes(), PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+ assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
+ }
+
+ public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration() throws RemoteException {
+ final String TOKEN = "some-high-entropy-secure-token";
+ enableSyntheticPassword(PRIMARY_USER_ID);
+ long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
+ assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+ assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
+ }
+
+ public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNoMigration() throws RemoteException {
+ final String TOKEN = "some-high-entropy-secure-token";
+ initializeCredentialUnderSP(null, PRIMARY_USER_ID);
+ long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
+ assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+ assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
+ }
+
+ public void testEscrowTokenActivatedLaterWithUserPasswordNeedsMigration() throws RemoteException {
+ final String TOKEN = "some-high-entropy-secure-token";
+ final String PASSWORD = "password";
+ // Set up pre-SP user password
+ disableSyntheticPassword(PRIMARY_USER_ID);
+ mService.setLockCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+ PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+ enableSyntheticPassword(PRIMARY_USER_ID);
+
+ long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID);
+ // Token not activated immediately since user password exists
+ assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+ // Activate token (password gets migrated to SP at the same time)
+ assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+ mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
+ PRIMARY_USER_ID).getResponseCode());
+ // Verify token is activated
+ assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+ }
+
+ public void testPasswordData_serializeDeserialize() {
+ PasswordData data = new PasswordData();
+ data.scryptN = 11;
+ data.scryptR = 22;
+ data.scryptP = 33;
+ data.passwordType = CREDENTIAL_TYPE_PASSWORD;
+ data.salt = PAYLOAD;
+ data.passwordHandle = PAYLOAD2;
+
+ PasswordData deserialized = PasswordData.fromBytes(data.toBytes());
+
+ assertEquals(11, deserialized.scryptN);
+ assertEquals(22, deserialized.scryptR);
+ assertEquals(33, deserialized.scryptP);
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, deserialized.passwordType);
+ assertArrayEquals(PAYLOAD, deserialized.salt);
+ assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
+ }
+
+ public void testPasswordData_deserialize() {
+ // Test that we can deserialize existing PasswordData and don't inadvertently change the
+ // wire format.
+ byte[] serialized = new byte[] {
+ 0, 0, 0, 2, /* CREDENTIAL_TYPE_PASSWORD */
+ 11, /* scryptN */
+ 22, /* scryptR */
+ 33, /* scryptP */
+ 0, 0, 0, 5, /* salt.length */
+ 1, 2, -1, -2, 55, /* salt */
+ 0, 0, 0, 6, /* passwordHandle.length */
+ 2, 3, -2, -3, 44, 1, /* passwordHandle */
+ };
+ PasswordData deserialized = PasswordData.fromBytes(serialized);
+
+ assertEquals(11, deserialized.scryptN);
+ assertEquals(22, deserialized.scryptR);
+ assertEquals(33, deserialized.scryptP);
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, deserialized.passwordType);
+ assertArrayEquals(PAYLOAD, deserialized.salt);
+ assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
+ }
+
+ // b/34600579
+ //TODO: add non-migration work profile case, and unify/un-unify transition.
+ //TODO: test token after user resets password
+ //TODO: test token based reset after unified work challenge
+ //TODO: test clear password after unified work challenge
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
new file mode 100644
index 0000000..5e56704
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
@@ -0,0 +1,11 @@
+package com.android.server.locksettings;
+
+public class WeaverBasedSyntheticPasswordTests extends SyntheticPasswordTests {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mSpManager.enableWeaver();
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java
deleted file mode 100644
index bb8f9d1..0000000
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsAccessTest.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2015 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.net;
-
-import static org.mockito.Mockito.when;
-
-import android.Manifest;
-import android.Manifest.permission;
-import android.app.AppOpsManager;
-import android.app.admin.DeviceAdminInfo;
-import android.app.admin.DevicePolicyManagerInternal;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.telephony.TelephonyManager;
-
-import com.android.server.LocalServices;
-
-import junit.framework.TestCase;
-
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-public class NetworkStatsAccessTest extends TestCase {
- private static final String TEST_PKG = "com.example.test";
- private static final int TEST_UID = 12345;
-
- @Mock private Context mContext;
- @Mock private DevicePolicyManagerInternal mDpmi;
- @Mock private TelephonyManager mTm;
- @Mock private AppOpsManager mAppOps;
-
- // Hold the real service so we can restore it when tearing down the test.
- private DevicePolicyManagerInternal mSystemDpmi;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- MockitoAnnotations.initMocks(this);
-
- mSystemDpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
- LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
- LocalServices.addService(DevicePolicyManagerInternal.class, mDpmi);
-
- when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTm);
- when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOps);
- }
-
- @Override
- public void tearDown() throws Exception {
- LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
- LocalServices.addService(DevicePolicyManagerInternal.class, mSystemDpmi);
- super.tearDown();
- }
-
- public void testCheckAccessLevel_hasCarrierPrivileges() throws Exception {
- setHasCarrierPrivileges(true);
- setIsDeviceOwner(false);
- setIsProfileOwner(false);
- setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
- setHasReadHistoryPermission(false);
- assertEquals(NetworkStatsAccess.Level.DEVICE,
- NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
- }
-
- public void testCheckAccessLevel_isDeviceOwner() throws Exception {
- setHasCarrierPrivileges(false);
- setIsDeviceOwner(true);
- setIsProfileOwner(false);
- setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
- setHasReadHistoryPermission(false);
- assertEquals(NetworkStatsAccess.Level.DEVICE,
- NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
- }
-
- public void testCheckAccessLevel_isProfileOwner() throws Exception {
- setHasCarrierPrivileges(false);
- setIsDeviceOwner(false);
- setIsProfileOwner(true);
- setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
- setHasReadHistoryPermission(false);
- assertEquals(NetworkStatsAccess.Level.USER,
- NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
- }
-
- public void testCheckAccessLevel_hasAppOpsBitAllowed() throws Exception {
- setHasCarrierPrivileges(false);
- setIsDeviceOwner(false);
- setIsProfileOwner(true);
- setHasAppOpsPermission(AppOpsManager.MODE_ALLOWED, false);
- setHasReadHistoryPermission(false);
- assertEquals(NetworkStatsAccess.Level.USER,
- NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
- }
-
- public void testCheckAccessLevel_hasAppOpsBitDefault_grantedPermission() throws Exception {
- setHasCarrierPrivileges(false);
- setIsDeviceOwner(false);
- setIsProfileOwner(true);
- setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, true);
- setHasReadHistoryPermission(false);
- assertEquals(NetworkStatsAccess.Level.USER,
- NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
- }
-
- public void testCheckAccessLevel_hasReadHistoryPermission() throws Exception {
- setHasCarrierPrivileges(false);
- setIsDeviceOwner(false);
- setIsProfileOwner(true);
- setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
- setHasReadHistoryPermission(true);
- assertEquals(NetworkStatsAccess.Level.USER,
- NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
- }
-
- public void testCheckAccessLevel_deniedAppOpsBit() throws Exception {
- setHasCarrierPrivileges(false);
- setIsDeviceOwner(false);
- setIsProfileOwner(false);
- setHasAppOpsPermission(AppOpsManager.MODE_ERRORED, true);
- setHasReadHistoryPermission(false);
- assertEquals(NetworkStatsAccess.Level.DEFAULT,
- NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
- }
-
- public void testCheckAccessLevel_deniedAppOpsBit_deniedPermission() throws Exception {
- setHasCarrierPrivileges(false);
- setIsDeviceOwner(false);
- setIsProfileOwner(false);
- setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
- setHasReadHistoryPermission(false);
- assertEquals(NetworkStatsAccess.Level.DEFAULT,
- NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
- }
-
- private void setHasCarrierPrivileges(boolean hasPrivileges) {
- when(mTm.checkCarrierPrivilegesForPackage(TEST_PKG)).thenReturn(
- hasPrivileges ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
- : TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
- }
-
- private void setIsDeviceOwner(boolean isOwner) {
- when(mDpmi.isActiveAdminWithPolicy(TEST_UID, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER))
- .thenReturn(isOwner);
- }
-
- private void setIsProfileOwner(boolean isOwner) {
- when(mDpmi.isActiveAdminWithPolicy(TEST_UID, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER))
- .thenReturn(isOwner);
- }
-
- private void setHasAppOpsPermission(int appOpsMode, boolean hasPermission) {
- when(mAppOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS, TEST_UID, TEST_PKG))
- .thenReturn(appOpsMode);
- when(mContext.checkCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS)).thenReturn(
- hasPermission ? PackageManager.PERMISSION_GRANTED
- : PackageManager.PERMISSION_DENIED);
- }
-
- private void setHasReadHistoryPermission(boolean hasPermission) {
- when(mContext.checkCallingOrSelfPermission(permission.READ_NETWORK_USAGE_HISTORY))
- .thenReturn(hasPermission ? PackageManager.PERMISSION_GRANTED
- : PackageManager.PERMISSION_DENIED);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
deleted file mode 100644
index 9f53c87..0000000
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2012 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.net;
-
-import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.TAG_NONE;
-import static android.net.NetworkStats.UID_ALL;
-import static android.net.NetworkTemplate.buildTemplateMobileAll;
-import static android.text.format.DateUtils.HOUR_IN_MILLIS;
-import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
-
-import android.content.res.Resources;
-import android.net.NetworkIdentity;
-import android.net.NetworkStats;
-import android.net.NetworkTemplate;
-import android.os.Process;
-import android.os.UserHandle;
-import android.telephony.TelephonyManager;
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
-import android.test.suitebuilder.annotation.MediumTest;
-
-import com.android.frameworks.servicestests.R;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
-/**
- * Tests for {@link NetworkStatsCollection}.
- */
-@MediumTest
-public class NetworkStatsCollectionTest extends AndroidTestCase {
-
- private static final String TEST_FILE = "test.bin";
- private static final String TEST_IMSI = "310260000000000";
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
-
- // ignore any device overlay while testing
- NetworkTemplate.forceAllNetworkTypes();
- }
-
- public void testReadLegacyNetwork() throws Exception {
- final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
- stageFile(R.raw.netstats_v1, testFile);
-
- final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
- collection.readLegacyNetwork(testFile);
-
- // verify that history read correctly
- assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
- 636016770L, 709306L, 88038768L, 518836L, NetworkStatsAccess.Level.DEVICE);
-
- // now export into a unified format
- final ByteArrayOutputStream bos = new ByteArrayOutputStream();
- collection.write(new DataOutputStream(bos));
-
- // clear structure completely
- collection.reset();
- assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
- 0L, 0L, 0L, 0L, NetworkStatsAccess.Level.DEVICE);
-
- // and read back into structure, verifying that totals are same
- collection.read(new ByteArrayInputStream(bos.toByteArray()));
- assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
- 636016770L, 709306L, 88038768L, 518836L, NetworkStatsAccess.Level.DEVICE);
- }
-
- public void testReadLegacyUid() throws Exception {
- final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
- stageFile(R.raw.netstats_uid_v4, testFile);
-
- final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
- collection.readLegacyUid(testFile, false);
-
- // verify that history read correctly
- assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
- 637076152L, 711413L, 88343717L, 521022L, NetworkStatsAccess.Level.DEVICE);
-
- // now export into a unified format
- final ByteArrayOutputStream bos = new ByteArrayOutputStream();
- collection.write(new DataOutputStream(bos));
-
- // clear structure completely
- collection.reset();
- assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
- 0L, 0L, 0L, 0L, NetworkStatsAccess.Level.DEVICE);
-
- // and read back into structure, verifying that totals are same
- collection.read(new ByteArrayInputStream(bos.toByteArray()));
- assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
- 637076152L, 711413L, 88343717L, 521022L, NetworkStatsAccess.Level.DEVICE);
- }
-
- public void testReadLegacyUidTags() throws Exception {
- final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
- stageFile(R.raw.netstats_uid_v4, testFile);
-
- final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
- collection.readLegacyUid(testFile, true);
-
- // verify that history read correctly
- assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
- 77017831L, 100995L, 35436758L, 92344L);
-
- // now export into a unified format
- final ByteArrayOutputStream bos = new ByteArrayOutputStream();
- collection.write(new DataOutputStream(bos));
-
- // clear structure completely
- collection.reset();
- assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
- 0L, 0L, 0L, 0L);
-
- // and read back into structure, verifying that totals are same
- collection.read(new ByteArrayInputStream(bos.toByteArray()));
- assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
- 77017831L, 100995L, 35436758L, 92344L);
- }
-
- public void testStartEndAtomicBuckets() throws Exception {
- final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS);
-
- // record empty data straddling between buckets
- final NetworkStats.Entry entry = new NetworkStats.Entry();
- entry.rxBytes = 32;
- collection.recordData(null, UID_ALL, SET_DEFAULT, TAG_NONE, 30 * MINUTE_IN_MILLIS,
- 90 * MINUTE_IN_MILLIS, entry);
-
- // assert that we report boundary in atomic buckets
- assertEquals(0, collection.getStartMillis());
- assertEquals(2 * HOUR_IN_MILLIS, collection.getEndMillis());
- }
-
- public void testAccessLevels() throws Exception {
- final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS);
- final NetworkStats.Entry entry = new NetworkStats.Entry();
- final NetworkIdentitySet identSet = new NetworkIdentitySet();
- identSet.add(new NetworkIdentity(TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TEST_IMSI, null, false, true));
-
- int myUid = Process.myUid();
- int otherUidInSameUser = Process.myUid() + 1;
- int uidInDifferentUser = Process.myUid() + UserHandle.PER_USER_RANGE;
-
- // Record one entry for the current UID.
- entry.rxBytes = 32;
- collection.recordData(identSet, myUid, SET_DEFAULT, TAG_NONE, 0, 60 * MINUTE_IN_MILLIS,
- entry);
-
- // Record one entry for another UID in this user.
- entry.rxBytes = 64;
- collection.recordData(identSet, otherUidInSameUser, SET_DEFAULT, TAG_NONE, 0,
- 60 * MINUTE_IN_MILLIS, entry);
-
- // Record one entry for the system UID.
- entry.rxBytes = 128;
- collection.recordData(identSet, Process.SYSTEM_UID, SET_DEFAULT, TAG_NONE, 0,
- 60 * MINUTE_IN_MILLIS, entry);
-
- // Record one entry for a UID in a different user.
- entry.rxBytes = 256;
- collection.recordData(identSet, uidInDifferentUser, SET_DEFAULT, TAG_NONE, 0,
- 60 * MINUTE_IN_MILLIS, entry);
-
- // Verify the set of relevant UIDs for each access level.
- MoreAsserts.assertEquals(new int[] { myUid },
- collection.getRelevantUids(NetworkStatsAccess.Level.DEFAULT));
- MoreAsserts.assertEquals(new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser },
- collection.getRelevantUids(NetworkStatsAccess.Level.USER));
- MoreAsserts.assertEquals(
- new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser, uidInDifferentUser },
- collection.getRelevantUids(NetworkStatsAccess.Level.DEVICE));
-
- // Verify security check in getHistory.
- assertNotNull(collection.getHistory(buildTemplateMobileAll(TEST_IMSI), myUid, SET_DEFAULT,
- TAG_NONE, 0, NetworkStatsAccess.Level.DEFAULT));
- try {
- collection.getHistory(buildTemplateMobileAll(TEST_IMSI), otherUidInSameUser,
- SET_DEFAULT, TAG_NONE, 0, NetworkStatsAccess.Level.DEFAULT);
- fail("Should have thrown SecurityException for accessing different UID");
- } catch (SecurityException e) {
- // expected
- }
-
- // Verify appropriate aggregation in getSummary.
- assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32, 0, 0, 0,
- NetworkStatsAccess.Level.DEFAULT);
- assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32 + 64 + 128, 0, 0, 0,
- NetworkStatsAccess.Level.USER);
- assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32 + 64 + 128 + 256, 0, 0,
- 0, NetworkStatsAccess.Level.DEVICE);
- }
-
- /**
- * Copy a {@link Resources#openRawResource(int)} into {@link File} for
- * testing purposes.
- */
- private void stageFile(int rawId, File file) throws Exception {
- new File(file.getParent()).mkdirs();
- InputStream in = null;
- OutputStream out = null;
- try {
- in = getContext().getResources().openRawResource(rawId);
- out = new FileOutputStream(file);
- Streams.copy(in, out);
- } finally {
- IoUtils.closeQuietly(in);
- IoUtils.closeQuietly(out);
- }
- }
-
- private static void assertSummaryTotal(NetworkStatsCollection collection,
- NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets,
- @NetworkStatsAccess.Level int accessLevel) {
- final NetworkStats.Entry entry = collection.getSummary(
- template, Long.MIN_VALUE, Long.MAX_VALUE, accessLevel)
- .getTotal(null);
- assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
- }
-
- private static void assertSummaryTotalIncludingTags(NetworkStatsCollection collection,
- NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
- final NetworkStats.Entry entry = collection.getSummary(
- template, Long.MIN_VALUE, Long.MAX_VALUE, NetworkStatsAccess.Level.DEVICE)
- .getTotalIncludingTags(null);
- assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
- }
-
- private static void assertEntry(
- NetworkStats.Entry entry, long rxBytes, long rxPackets, long txBytes, long txPackets) {
- assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
- assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
- assertEquals("unexpected txBytes", txBytes, entry.txBytes);
- assertEquals("unexpected txPackets", txPackets, entry.txPackets);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
deleted file mode 100644
index 5eee7b9..0000000
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsObserversTest.java
+++ /dev/null
@@ -1,479 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.net;
-
-import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.isA;
-import static org.mockito.Mockito.when;
-
-import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.METERED_NO;
-import static android.net.NetworkStats.ROAMING_NO;
-import static android.net.NetworkStats.TAG_NONE;
-import static android.net.NetworkTemplate.buildTemplateMobileAll;
-import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
-import static android.net.TrafficStats.MB_IN_BYTES;
-import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
-
-import android.app.usage.NetworkStatsManager;
-import android.net.DataUsageRequest;
-import android.net.NetworkIdentity;
-import android.net.NetworkStats;
-import android.net.NetworkTemplate;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Process;
-
-import android.os.ConditionVariable;
-import android.os.Looper;
-import android.os.Messenger;
-import android.os.Message;
-import android.os.UserHandle;
-import android.telephony.TelephonyManager;
-import android.util.ArrayMap;
-
-import com.android.internal.net.VpnInfo;
-import com.android.server.net.NetworkStatsService;
-import com.android.server.net.NetworkStatsServiceTest.IdleableHandlerThread;
-import com.android.server.net.NetworkStatsServiceTest.LatchedHandler;
-
-import java.util.ArrayList;
-import java.util.Objects;
-import java.util.List;
-
-import junit.framework.TestCase;
-
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link NetworkStatsObservers}.
- */
-public class NetworkStatsObserversTest extends TestCase {
- private static final String TEST_IFACE = "test0";
- private static final String TEST_IFACE2 = "test1";
- private static final long TEST_START = 1194220800000L;
-
- private static final String IMSI_1 = "310004";
- private static final String IMSI_2 = "310260";
- private static final String TEST_SSID = "AndroidAP";
-
- private static NetworkTemplate sTemplateWifi = buildTemplateWifiWildcard();
- private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
- private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
-
- private static final int UID_RED = UserHandle.PER_USER_RANGE + 1;
- private static final int UID_BLUE = UserHandle.PER_USER_RANGE + 2;
- private static final int UID_GREEN = UserHandle.PER_USER_RANGE + 3;
- private static final int UID_ANOTHER_USER = 2 * UserHandle.PER_USER_RANGE + 4;
-
- private static final long WAIT_TIMEOUT = 500; // 1/2 sec
- private static final long THRESHOLD_BYTES = 2 * MB_IN_BYTES;
- private static final long BASE_BYTES = 7 * MB_IN_BYTES;
- private static final int INVALID_TYPE = -1;
-
- private static final VpnInfo[] VPN_INFO = new VpnInfo[0];
-
- private long mElapsedRealtime;
-
- private IdleableHandlerThread mObserverHandlerThread;
- private Handler mObserverNoopHandler;
-
- private LatchedHandler mHandler;
- private ConditionVariable mCv;
-
- private NetworkStatsObservers mStatsObservers;
- private Messenger mMessenger;
- private ArrayMap<String, NetworkIdentitySet> mActiveIfaces;
- private ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
-
- @Mock private IBinder mockBinder;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- MockitoAnnotations.initMocks(this);
-
- mObserverHandlerThread = new IdleableHandlerThread("HandlerThread");
- mObserverHandlerThread.start();
- final Looper observerLooper = mObserverHandlerThread.getLooper();
- mStatsObservers = new NetworkStatsObservers() {
- @Override
- protected Looper getHandlerLooperLocked() {
- return observerLooper;
- }
- };
-
- mCv = new ConditionVariable();
- mHandler = new LatchedHandler(Looper.getMainLooper(), mCv);
- mMessenger = new Messenger(mHandler);
-
- mActiveIfaces = new ArrayMap<>();
- mActiveUidIfaces = new ArrayMap<>();
- }
-
- public void testRegister_thresholdTooLow_setsDefaultThreshold() throws Exception {
- long thresholdTooLowBytes = 1L;
- DataUsageRequest inputRequest = new DataUsageRequest(
- DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdTooLowBytes);
-
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
- assertTrue(request.requestId > 0);
- assertTrue(Objects.equals(sTemplateWifi, request.template));
- assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
- }
-
- public void testRegister_highThreshold_accepted() throws Exception {
- long highThresholdBytes = 2 * THRESHOLD_BYTES;
- DataUsageRequest inputRequest = new DataUsageRequest(
- DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, highThresholdBytes);
-
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
- assertTrue(request.requestId > 0);
- assertTrue(Objects.equals(sTemplateWifi, request.template));
- assertEquals(highThresholdBytes, request.thresholdInBytes);
- }
-
- public void testRegister_twoRequests_twoIds() throws Exception {
- DataUsageRequest inputRequest = new DataUsageRequest(
- DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, THRESHOLD_BYTES);
-
- DataUsageRequest request1 = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
- assertTrue(request1.requestId > 0);
- assertTrue(Objects.equals(sTemplateWifi, request1.template));
- assertEquals(THRESHOLD_BYTES, request1.thresholdInBytes);
-
- DataUsageRequest request2 = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
- assertTrue(request2.requestId > request1.requestId);
- assertTrue(Objects.equals(sTemplateWifi, request2.template));
- assertEquals(THRESHOLD_BYTES, request2.thresholdInBytes);
- }
-
- public void testUnregister_unknownRequest_noop() throws Exception {
- DataUsageRequest unknownRequest = new DataUsageRequest(
- 123456 /* id */, sTemplateWifi, THRESHOLD_BYTES);
-
- mStatsObservers.unregister(unknownRequest, UID_RED);
- }
-
- public void testUnregister_knownRequest_releasesCaller() throws Exception {
- DataUsageRequest inputRequest = new DataUsageRequest(
- DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
-
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
- assertTrue(request.requestId > 0);
- assertTrue(Objects.equals(sTemplateImsi1, request.template));
- assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
- Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
-
- mStatsObservers.unregister(request, Process.SYSTEM_UID);
- waitForObserverToIdle();
-
- Mockito.verify(mockBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
- }
-
- public void testUnregister_knownRequest_invalidUid_doesNotUnregister() throws Exception {
- DataUsageRequest inputRequest = new DataUsageRequest(
- DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
-
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
- UID_RED, NetworkStatsAccess.Level.DEVICE);
- assertTrue(request.requestId > 0);
- assertTrue(Objects.equals(sTemplateImsi1, request.template));
- assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
- Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
-
- mStatsObservers.unregister(request, UID_BLUE);
- waitForObserverToIdle();
-
- Mockito.verifyZeroInteractions(mockBinder);
- }
-
- public void testUpdateStats_initialSample_doesNotNotify() throws Exception {
- DataUsageRequest inputRequest = new DataUsageRequest(
- DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
-
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
- assertTrue(request.requestId > 0);
- assertTrue(Objects.equals(sTemplateImsi1, request.template));
- assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
-
- NetworkIdentitySet identSet = new NetworkIdentitySet();
- identSet.add(new NetworkIdentity(
- TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
- mActiveIfaces.put(TEST_IFACE, identSet);
-
- // Baseline
- NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
- .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
- NetworkStats uidSnapshot = null;
-
- mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
- waitForObserverToIdle();
-
- assertTrue(mCv.block(WAIT_TIMEOUT));
- assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
- }
-
- public void testUpdateStats_belowThreshold_doesNotNotify() throws Exception {
- DataUsageRequest inputRequest = new DataUsageRequest(
- DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
-
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
- assertTrue(request.requestId > 0);
- assertTrue(Objects.equals(sTemplateImsi1, request.template));
- assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
-
- NetworkIdentitySet identSet = new NetworkIdentitySet();
- identSet.add(new NetworkIdentity(
- TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
- mActiveIfaces.put(TEST_IFACE, identSet);
-
- // Baseline
- NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
- .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
- NetworkStats uidSnapshot = null;
- mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
-
- // Delta
- xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
- .addIfaceValues(TEST_IFACE, BASE_BYTES + 1024L, 10L, BASE_BYTES + 2048L, 20L);
- mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
- waitForObserverToIdle();
-
- assertTrue(mCv.block(WAIT_TIMEOUT));
- mCv.block(WAIT_TIMEOUT);
- assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
- }
-
- public void testUpdateStats_deviceAccess_notifies() throws Exception {
- DataUsageRequest inputRequest = new DataUsageRequest(
- DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
-
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
- assertTrue(request.requestId > 0);
- assertTrue(Objects.equals(sTemplateImsi1, request.template));
- assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
-
- NetworkIdentitySet identSet = new NetworkIdentitySet();
- identSet.add(new NetworkIdentity(
- TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
- mActiveIfaces.put(TEST_IFACE, identSet);
-
- // Baseline
- NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
- .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
- NetworkStats uidSnapshot = null;
- mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
-
- // Delta
- xtSnapshot = new NetworkStats(TEST_START + MINUTE_IN_MILLIS, 1 /* initialSize */)
- .addIfaceValues(TEST_IFACE, BASE_BYTES + THRESHOLD_BYTES, 12L,
- BASE_BYTES + THRESHOLD_BYTES, 22L);
- mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
- waitForObserverToIdle();
-
- assertTrue(mCv.block(WAIT_TIMEOUT));
- assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
- }
-
- public void testUpdateStats_defaultAccess_notifiesSameUid() throws Exception {
- DataUsageRequest inputRequest = new DataUsageRequest(
- DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
-
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
- UID_RED, NetworkStatsAccess.Level.DEFAULT);
- assertTrue(request.requestId > 0);
- assertTrue(Objects.equals(sTemplateImsi1, request.template));
- assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
-
- NetworkIdentitySet identSet = new NetworkIdentitySet();
- identSet.add(new NetworkIdentity(
- TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
- mActiveUidIfaces.put(TEST_IFACE, identSet);
-
- // Baseline
- NetworkStats xtSnapshot = null;
- NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
- mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
-
- // Delta
- uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
- mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
- waitForObserverToIdle();
-
- assertTrue(mCv.block(WAIT_TIMEOUT));
- assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
- }
-
- public void testUpdateStats_defaultAccess_usageOtherUid_doesNotNotify() throws Exception {
- DataUsageRequest inputRequest = new DataUsageRequest(
- DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
-
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
- UID_BLUE, NetworkStatsAccess.Level.DEFAULT);
- assertTrue(request.requestId > 0);
- assertTrue(Objects.equals(sTemplateImsi1, request.template));
- assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
-
- NetworkIdentitySet identSet = new NetworkIdentitySet();
- identSet.add(new NetworkIdentity(
- TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
- mActiveUidIfaces.put(TEST_IFACE, identSet);
-
- // Baseline
- NetworkStats xtSnapshot = null;
- NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
- mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
-
- // Delta
- uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
- mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
- waitForObserverToIdle();
-
- assertTrue(mCv.block(WAIT_TIMEOUT));
- assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
- }
-
- public void testUpdateStats_userAccess_usageSameUser_notifies() throws Exception {
- DataUsageRequest inputRequest = new DataUsageRequest(
- DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
-
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
- UID_BLUE, NetworkStatsAccess.Level.USER);
- assertTrue(request.requestId > 0);
- assertTrue(Objects.equals(sTemplateImsi1, request.template));
- assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
-
- NetworkIdentitySet identSet = new NetworkIdentitySet();
- identSet.add(new NetworkIdentity(
- TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
- mActiveUidIfaces.put(TEST_IFACE, identSet);
-
- // Baseline
- NetworkStats xtSnapshot = null;
- NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
- mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
-
- // Delta
- uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
- mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
- waitForObserverToIdle();
-
- assertTrue(mCv.block(WAIT_TIMEOUT));
- assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
- }
-
- public void testUpdateStats_userAccess_usageAnotherUser_doesNotNotify() throws Exception {
- DataUsageRequest inputRequest = new DataUsageRequest(
- DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
-
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
- UID_RED, NetworkStatsAccess.Level.USER);
- assertTrue(request.requestId > 0);
- assertTrue(Objects.equals(sTemplateImsi1, request.template));
- assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
-
- NetworkIdentitySet identSet = new NetworkIdentitySet();
- identSet.add(new NetworkIdentity(
- TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
- mActiveUidIfaces.put(TEST_IFACE, identSet);
-
- // Baseline
- NetworkStats xtSnapshot = null;
- NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
- .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
- ROAMING_NO, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
- mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
-
- // Delta
- uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
- .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
- ROAMING_NO, BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES,
- 2L, 0L);
- mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
- waitForObserverToIdle();
-
- assertTrue(mCv.block(WAIT_TIMEOUT));
- assertEquals(INVALID_TYPE, mHandler.mLastMessageType);
- }
-
- private void waitForObserverToIdle() {
- // Send dummy message to make sure that any previous message has been handled
- mHandler.sendMessage(mHandler.obtainMessage(-1));
- mObserverHandlerThread.waitForIdle(WAIT_TIMEOUT);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
deleted file mode 100644
index 728eb73..0000000
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsServiceTest.java
+++ /dev/null
@@ -1,1263 +0,0 @@
-/*
- * Copyright (C) 2011 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.net;
-
-import static android.content.Intent.ACTION_UID_REMOVED;
-import static android.content.Intent.EXTRA_UID;
-import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.net.ConnectivityManager.TYPE_WIMAX;
-import static android.net.NetworkStats.IFACE_ALL;
-import static android.net.NetworkStats.METERED_ALL;
-import static android.net.NetworkStats.METERED_NO;
-import static android.net.NetworkStats.METERED_YES;
-import static android.net.NetworkStats.ROAMING_ALL;
-import static android.net.NetworkStats.ROAMING_NO;
-import static android.net.NetworkStats.ROAMING_YES;
-import static android.net.NetworkStats.SET_ALL;
-import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.SET_FOREGROUND;
-import static android.net.NetworkStats.TAG_NONE;
-import static android.net.NetworkStats.UID_ALL;
-import static android.net.NetworkStatsHistory.FIELD_ALL;
-import static android.net.NetworkTemplate.buildTemplateMobileAll;
-import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
-import static android.net.TrafficStats.MB_IN_BYTES;
-import static android.net.TrafficStats.UID_REMOVED;
-import static android.net.TrafficStats.UID_TETHERING;
-import static android.text.format.DateUtils.DAY_IN_MILLIS;
-import static android.text.format.DateUtils.HOUR_IN_MILLIS;
-import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
-import static android.text.format.DateUtils.WEEK_IN_MILLIS;
-
-import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.verify;
-
-import android.app.AlarmManager;
-import android.app.usage.NetworkStatsManager;
-import android.content.Context;
-import android.content.Intent;
-import android.net.DataUsageRequest;
-import android.net.IConnectivityManager;
-import android.net.INetworkManagementEventObserver;
-import android.net.INetworkStatsSession;
-import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkState;
-import android.net.NetworkStats;
-import android.net.NetworkStatsHistory;
-import android.net.NetworkTemplate;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.INetworkManagementService;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Messenger;
-import android.os.MessageQueue;
-import android.os.MessageQueue.IdleHandler;
-import android.os.Message;
-import android.os.PowerManager;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-import android.telephony.TelephonyManager;
-import android.test.AndroidTestCase;
-import android.util.TrustedTime;
-
-import com.android.internal.net.VpnInfo;
-import com.android.internal.util.test.BroadcastInterceptingContext;
-import com.android.server.net.NetworkStatsService;
-import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
-import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
-
-import libcore.io.IoUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Objects;
-import java.util.List;
-
-/**
- * Tests for {@link NetworkStatsService}.
- *
- * TODO: This test used to be really brittle because it used Easymock - it uses Mockito now, but
- * still uses the Easymock structure, which could be simplified.
- */
-@RunWith(AndroidJUnit4.class)
-public class NetworkStatsServiceTest {
- private static final String TAG = "NetworkStatsServiceTest";
-
- private static final String TEST_IFACE = "test0";
- private static final String TEST_IFACE2 = "test1";
- private static final long TEST_START = 1194220800000L;
-
- private static final String IMSI_1 = "310004";
- private static final String IMSI_2 = "310260";
- private static final String TEST_SSID = "AndroidAP";
-
- private static NetworkTemplate sTemplateWifi = buildTemplateWifiWildcard();
- private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
- private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
-
- private static final int UID_RED = 1001;
- private static final int UID_BLUE = 1002;
- private static final int UID_GREEN = 1003;
-
- private static final long WAIT_TIMEOUT = 2 * 1000; // 2 secs
- private static final int INVALID_TYPE = -1;
-
- private long mElapsedRealtime;
-
- private BroadcastInterceptingContext mServiceContext;
- private File mStatsDir;
-
- private @Mock INetworkManagementService mNetManager;
- private @Mock TrustedTime mTime;
- private @Mock NetworkStatsSettings mSettings;
- private @Mock IConnectivityManager mConnManager;
- private @Mock IBinder mBinder;
- private @Mock AlarmManager mAlarmManager;
- private IdleableHandlerThread mHandlerThread;
- private Handler mHandler;
-
- private NetworkStatsService mService;
- private INetworkStatsSession mSession;
- private INetworkManagementEventObserver mNetworkObserver;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- final Context context = InstrumentationRegistry.getContext();
-
- mServiceContext = new BroadcastInterceptingContext(context);
- mStatsDir = context.getFilesDir();
- if (mStatsDir.exists()) {
- IoUtils.deleteContents(mStatsDir);
- }
-
- PowerManager powerManager = (PowerManager) mServiceContext.getSystemService(
- Context.POWER_SERVICE);
- PowerManager.WakeLock wakeLock =
- powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
-
- mService = new NetworkStatsService(
- mServiceContext, mNetManager, mAlarmManager, wakeLock, mTime,
- TelephonyManager.getDefault(), mSettings, new NetworkStatsObservers(),
- mStatsDir, getBaseDir(mStatsDir));
- mHandlerThread = new IdleableHandlerThread("HandlerThread");
- mHandlerThread.start();
- Handler.Callback callback = new NetworkStatsService.HandlerCallback(mService);
- mHandler = new Handler(mHandlerThread.getLooper(), callback);
- mService.setHandler(mHandler, callback);
- mService.bindConnectivityManager(mConnManager);
-
- mElapsedRealtime = 0L;
-
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectSystemReady();
-
- mService.systemReady();
- mSession = mService.openSession();
- assertNotNull("openSession() failed", mSession);
-
-
- // catch INetworkManagementEventObserver during systemReady()
- ArgumentCaptor<INetworkManagementEventObserver> networkObserver =
- ArgumentCaptor.forClass(INetworkManagementEventObserver.class);
- verify(mNetManager).registerObserver(networkObserver.capture());
- mNetworkObserver = networkObserver.getValue();
-
- }
-
- @After
- public void tearDown() throws Exception {
- IoUtils.deleteContents(mStatsDir);
-
- mServiceContext = null;
- mStatsDir = null;
-
- mNetManager = null;
- mTime = null;
- mSettings = null;
- mConnManager = null;
-
- mSession.close();
- mService = null;
- }
-
- @Test
- public void testNetworkStatsWifi() throws Exception {
- // pretend that wifi network comes online; service should ask about full
- // network state, and poll any existing interfaces before updating.
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkState(buildWifiState());
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
-
- mService.forceUpdateIfaces();
-
- // verify service has empty history for wifi
- assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
-
-
- // modify some number on wifi, and trigger poll event
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 1024L, 1L, 2048L, 2L));
- expectNetworkStatsUidDetail(buildEmptyStats());
- forcePollAndWaitForIdle();
-
- // verify service recorded history
- assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
-
-
- // and bump forward again, with counters going higher. this is
- // important, since polling should correctly subtract last snapshot.
- incrementCurrentTime(DAY_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 4096L, 4L, 8192L, 8L));
- expectNetworkStatsUidDetail(buildEmptyStats());
- forcePollAndWaitForIdle();
-
- // verify service recorded history
- assertNetworkTotal(sTemplateWifi, 4096L, 4L, 8192L, 8L, 0);
-
- }
-
- @Test
- public void testStatsRebootPersist() throws Exception {
- assertStatsFilesExist(false);
-
- // pretend that wifi network comes online; service should ask about full
- // network state, and poll any existing interfaces before updating.
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkState(buildWifiState());
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
-
- mService.forceUpdateIfaces();
-
- // verify service has empty history for wifi
- assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
-
-
- // modify some number on wifi, and trigger poll event
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 1024L, 8L, 2048L, 16L));
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
- mService.setUidForeground(UID_RED, false);
- mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
- mService.setUidForeground(UID_RED, true);
- mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
-
- forcePollAndWaitForIdle();
-
- // verify service recorded history
- assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0);
- assertUidTotal(sTemplateWifi, UID_RED, 1024L, 8L, 512L, 4L, 10);
- assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, METERED_NO, ROAMING_NO, 512L, 4L, 256L,
- 2L, 4);
- assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, METERED_NO, ROAMING_NO, 512L, 4L,
- 256L, 2L, 6);
- assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 1L, 128L, 1L, 0);
-
-
- // graceful shutdown system, which should trigger persist of stats, and
- // clear any values in memory.
- expectCurrentTime();
- expectDefaultSettings();
- mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
- assertStatsFilesExist(true);
-
- // boot through serviceReady() again
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectSystemReady();
-
- mService.systemReady();
-
- // after systemReady(), we should have historical stats loaded again
- assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0);
- assertUidTotal(sTemplateWifi, UID_RED, 1024L, 8L, 512L, 4L, 10);
- assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, METERED_NO, ROAMING_NO, 512L, 4L, 256L,
- 2L, 4);
- assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, METERED_NO, ROAMING_NO, 512L, 4L,
- 256L, 2L, 6);
- assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 1L, 128L, 1L, 0);
-
- }
-
- // TODO: simulate reboot to test bucket resize
- @Test
- @Ignore
- public void testStatsBucketResize() throws Exception {
- NetworkStatsHistory history = null;
-
- assertStatsFilesExist(false);
-
- // pretend that wifi network comes online; service should ask about full
- // network state, and poll any existing interfaces before updating.
- expectCurrentTime();
- expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
- expectNetworkState(buildWifiState());
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
-
- mService.forceUpdateIfaces();
-
-
- // modify some number on wifi, and trigger poll event
- incrementCurrentTime(2 * HOUR_IN_MILLIS);
- expectCurrentTime();
- expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 512L, 4L, 512L, 4L));
- expectNetworkStatsUidDetail(buildEmptyStats());
- forcePollAndWaitForIdle();
-
- // verify service recorded history
- history = mSession.getHistoryForNetwork(sTemplateWifi, FIELD_ALL);
- assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0);
- assertEquals(HOUR_IN_MILLIS, history.getBucketDuration());
- assertEquals(2, history.size());
-
-
- // now change bucket duration setting and trigger another poll with
- // exact same values, which should resize existing buckets.
- expectCurrentTime();
- expectSettings(0L, 30 * MINUTE_IN_MILLIS, WEEK_IN_MILLIS);
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
- forcePollAndWaitForIdle();
-
- // verify identical stats, but spread across 4 buckets now
- history = mSession.getHistoryForNetwork(sTemplateWifi, FIELD_ALL);
- assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0);
- assertEquals(30 * MINUTE_IN_MILLIS, history.getBucketDuration());
- assertEquals(4, history.size());
-
- }
-
- @Test
- public void testUidStatsAcrossNetworks() throws Exception {
- // pretend first mobile network comes online
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkState(buildMobile3gState(IMSI_1));
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
-
- mService.forceUpdateIfaces();
-
-
- // create some traffic on first network
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
- mService.incrementOperationCount(UID_RED, 0xF00D, 10);
-
- forcePollAndWaitForIdle();
-
- // verify service recorded history
- assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
- assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
- assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 512L, 4L, 10);
- assertUidTotal(sTemplateImsi1, UID_BLUE, 512L, 4L, 0L, 0L, 0);
-
-
- // now switch networks; this also tests that we're okay with interfaces
- // disappearing, to verify we don't count backwards.
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkState(buildMobile3gState(IMSI_2));
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
- expectBandwidthControlCheck();
-
- mService.forceUpdateIfaces();
- forcePollAndWaitForIdle();
-
-
- // create traffic on second network
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 2176L, 17L, 1536L, 12L));
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L));
- mService.incrementOperationCount(UID_BLUE, 0xFAAD, 10);
-
- forcePollAndWaitForIdle();
-
- // verify original history still intact
- assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
- assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 512L, 4L, 10);
- assertUidTotal(sTemplateImsi1, UID_BLUE, 512L, 4L, 0L, 0L, 0);
-
- // and verify new history also recorded under different template, which
- // verifies that we didn't cross the streams.
- assertNetworkTotal(sTemplateImsi2, 128L, 1L, 1024L, 8L, 0);
- assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
- assertUidTotal(sTemplateImsi2, UID_BLUE, 128L, 1L, 1024L, 8L, 10);
-
- }
-
- @Test
- public void testUidRemovedIsMoved() throws Exception {
- // pretend that network comes online
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkState(buildWifiState());
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
-
- mService.forceUpdateIfaces();
-
-
- // create some traffic
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L));
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
- .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
- mService.incrementOperationCount(UID_RED, 0xFAAD, 10);
-
- forcePollAndWaitForIdle();
-
- // verify service recorded history
- assertNetworkTotal(sTemplateWifi, 4128L, 258L, 544L, 34L, 0);
- assertUidTotal(sTemplateWifi, UID_RED, 16L, 1L, 16L, 1L, 10);
- assertUidTotal(sTemplateWifi, UID_BLUE, 4096L, 258L, 512L, 32L, 0);
- assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 1L, 16L, 1L, 0);
-
-
- // now pretend two UIDs are uninstalled, which should migrate stats to
- // special "removed" bucket.
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L));
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
- .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
- final Intent intent = new Intent(ACTION_UID_REMOVED);
- intent.putExtra(EXTRA_UID, UID_BLUE);
- mServiceContext.sendBroadcast(intent);
- intent.putExtra(EXTRA_UID, UID_RED);
- mServiceContext.sendBroadcast(intent);
-
- // existing uid and total should remain unchanged; but removed UID
- // should be gone completely.
- assertNetworkTotal(sTemplateWifi, 4128L, 258L, 544L, 34L, 0);
- assertUidTotal(sTemplateWifi, UID_RED, 0L, 0L, 0L, 0L, 0);
- assertUidTotal(sTemplateWifi, UID_BLUE, 0L, 0L, 0L, 0L, 0);
- assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 1L, 16L, 1L, 0);
- assertUidTotal(sTemplateWifi, UID_REMOVED, 4112L, 259L, 528L, 33L, 10);
-
- }
-
- @Test
- public void testUid3g4gCombinedByTemplate() throws Exception {
- // pretend that network comes online
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkState(buildMobile3gState(IMSI_1));
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
-
- mService.forceUpdateIfaces();
-
-
- // create some traffic
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
- mService.incrementOperationCount(UID_RED, 0xF00D, 5);
-
- forcePollAndWaitForIdle();
-
- // verify service recorded history
- assertUidTotal(sTemplateImsi1, UID_RED, 1024L, 8L, 1024L, 8L, 5);
-
-
- // now switch over to 4g network
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkState(buildMobile4gState(TEST_IFACE2));
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
- expectBandwidthControlCheck();
-
- mService.forceUpdateIfaces();
- forcePollAndWaitForIdle();
-
-
- // create traffic on second network
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
- .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
- .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L));
- mService.incrementOperationCount(UID_RED, 0xFAAD, 5);
-
- forcePollAndWaitForIdle();
-
- // verify that ALL_MOBILE template combines both
- assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 1280L, 10L, 10);
- }
-
- @Test
- public void testSummaryForAllUid() throws Exception {
- // pretend that network comes online
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkState(buildWifiState());
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
-
- mService.forceUpdateIfaces();
-
-
- // create some traffic for two apps
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L));
- mService.incrementOperationCount(UID_RED, 0xF00D, 1);
-
- forcePollAndWaitForIdle();
-
- // verify service recorded history
- assertUidTotal(sTemplateWifi, UID_RED, 50L, 5L, 50L, 5L, 1);
- assertUidTotal(sTemplateWifi, UID_BLUE, 1024L, 8L, 512L, 4L, 0);
-
-
- // now create more traffic in next hour, but only for one app
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0L));
- forcePollAndWaitForIdle();
-
- // first verify entire history present
- NetworkStats stats = mSession.getSummaryForAllUid(
- sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
- assertEquals(3, stats.size());
- assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 50L,
- 5L, 50L, 5L, 1);
- assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 10L,
- 1L, 10L, 1L, 1);
- assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- 2048L, 16L, 1024L, 8L, 0);
-
- // now verify that recent history only contains one uid
- final long currentTime = currentTimeMillis();
- stats = mSession.getSummaryForAllUid(
- sTemplateWifi, currentTime - HOUR_IN_MILLIS, currentTime, true);
- assertEquals(1, stats.size());
- assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- 1024L, 8L, 512L, 4L, 0);
- }
-
- @Test
- public void testForegroundBackground() throws Exception {
- // pretend that network comes online
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkState(buildWifiState());
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
-
- mService.forceUpdateIfaces();
-
-
- // create some initial traffic
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L));
- mService.incrementOperationCount(UID_RED, 0xF00D, 1);
-
- forcePollAndWaitForIdle();
-
- // verify service recorded history
- assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1);
-
-
- // now switch to foreground
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L));
- mService.setUidForeground(UID_RED, true);
- mService.incrementOperationCount(UID_RED, 0xFAAD, 1);
-
- forcePollAndWaitForIdle();
-
- // test that we combined correctly
- assertUidTotal(sTemplateWifi, UID_RED, 160L, 4L, 160L, 4L, 2);
-
- // verify entire history present
- final NetworkStats stats = mSession.getSummaryForAllUid(
- sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
- assertEquals(4, stats.size());
- assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 128L,
- 2L, 128L, 2L, 1);
- assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 64L,
- 1L, 64L, 1L, 1);
- assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
- 32L, 2L, 32L, 2L, 1);
- assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, 0xFAAD, METERED_NO, ROAMING_NO, 1L,
- 1L, 1L, 1L, 1);
- }
-
- @Test
- public void testMetered() throws Exception {
- // pretend that network comes online
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkState(buildWifiState(true /* isMetered */));
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
-
- mService.forceUpdateIfaces();
-
-
- // create some initial traffic
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- // Note that all traffic from NetworkManagementService is tagged as METERED_NO and
- // ROAMING_NO, because metered and roaming isn't tracked at that layer. We layer it
- // on top by inspecting the iface properties.
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 128L,
- 2L, 128L, 2L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 64L,
- 1L, 64L, 1L, 0L));
- mService.incrementOperationCount(UID_RED, 0xF00D, 1);
-
- forcePollAndWaitForIdle();
-
- // verify service recorded history
- assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1);
- // verify entire history present
- final NetworkStats stats = mSession.getSummaryForAllUid(
- sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
- assertEquals(2, stats.size());
- assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
- 128L, 2L, 128L, 2L, 1);
- assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, 64L,
- 1L, 64L, 1L, 1);
- }
-
- @Test
- public void testRoaming() throws Exception {
- // pretend that network comes online
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkState(buildMobile3gState(IMSI_1, true /* isRoaming */));
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
-
- mService.forceUpdateIfaces();
-
-
- // Create some traffic
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- // Note that all traffic from NetworkManagementService is tagged as METERED_NO and
- // ROAMING_NO, because metered and roaming isn't tracked at that layer. We layer it
- // on top by inspecting the iface properties.
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO,
- 128L, 2L, 128L, 2L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_NO, 64L,
- 1L, 64L, 1L, 0L));
- forcePollAndWaitForIdle();
-
- // verify service recorded history
- assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 0);
-
- // verify entire history present
- final NetworkStats stats = mSession.getSummaryForAllUid(
- sTemplateImsi1, Long.MIN_VALUE, Long.MAX_VALUE, true);
- assertEquals(2, stats.size());
- assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_YES,
- 128L, 2L, 128L, 2L, 0);
- assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_YES, 64L,
- 1L, 64L, 1L, 0);
- }
-
- @Test
- public void testTethering() throws Exception {
- // pretend first mobile network comes online
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkState(buildMobile3gState(IMSI_1));
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
-
- mService.forceUpdateIfaces();
-
-
- // create some tethering traffic
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
-
- final NetworkStats uidStats = new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L);
- final String[] tetherIfacePairs = new String[] { TEST_IFACE, "wlan0" };
- final NetworkStats tetherStats = new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L,
- 0L);
-
- expectNetworkStatsUidDetail(uidStats, tetherIfacePairs, tetherStats);
- forcePollAndWaitForIdle();
-
- // verify service recorded history
- assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
- assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 0);
- assertUidTotal(sTemplateImsi1, UID_TETHERING, 1920L, 14L, 384L, 2L, 0);
-
- }
-
- @Test
- public void testRegisterUsageCallback() throws Exception {
- // pretend that wifi network comes online; service should ask about full
- // network state, and poll any existing interfaces before updating.
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkState(buildWifiState());
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
-
- mService.forceUpdateIfaces();
-
- // verify service has empty history for wifi
- assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
- String callingPackage = "the.calling.package";
- long thresholdInBytes = 1L; // very small; should be overriden by framework
- DataUsageRequest inputRequest = new DataUsageRequest(
- DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdInBytes);
-
- // Create a messenger that waits for callback activity
- ConditionVariable cv = new ConditionVariable(false);
- LatchedHandler latchedHandler = new LatchedHandler(Looper.getMainLooper(), cv);
- Messenger messenger = new Messenger(latchedHandler);
-
- // Force poll
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
-
-
-
- // Register and verify request and that binder was called
- DataUsageRequest request =
- mService.registerUsageCallback(callingPackage, inputRequest,
- messenger, mBinder);
- assertTrue(request.requestId > 0);
- assertTrue(Objects.equals(sTemplateWifi, request.template));
- long minThresholdInBytes = 2 * 1024 * 1024; // 2 MB
- assertEquals(minThresholdInBytes, request.thresholdInBytes);
-
- // Send dummy message to make sure that any previous message has been handled
- mHandler.sendMessage(mHandler.obtainMessage(-1));
- mHandlerThread.waitForIdle(WAIT_TIMEOUT);
-
-
-
- // Make sure that the caller binder gets connected
- verify(mBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
-
-
- // modify some number on wifi, and trigger poll event
- // not enough traffic to call data usage callback
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 1024L, 1L, 2048L, 2L));
- expectNetworkStatsUidDetail(buildEmptyStats());
- forcePollAndWaitForIdle();
-
- // verify service recorded history
- assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
-
- // make sure callback has not being called
- assertEquals(INVALID_TYPE, latchedHandler.mLastMessageType);
-
- // and bump forward again, with counters going higher. this is
- // important, since it will trigger the data usage callback
- incrementCurrentTime(DAY_IN_MILLIS);
- expectCurrentTime();
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 4096000L, 4L, 8192000L, 8L));
- expectNetworkStatsUidDetail(buildEmptyStats());
- forcePollAndWaitForIdle();
-
- // verify service recorded history
- assertNetworkTotal(sTemplateWifi, 4096000L, 4L, 8192000L, 8L, 0);
-
-
- // Wait for the caller to ack receipt of CALLBACK_LIMIT_REACHED
- assertTrue(cv.block(WAIT_TIMEOUT));
- assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, latchedHandler.mLastMessageType);
- cv.close();
-
- // Allow binder to disconnect
- when(mBinder.unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt())).thenReturn(true);
-
- // Unregister request
- mService.unregisterUsageRequest(request);
-
- // Wait for the caller to ack receipt of CALLBACK_RELEASED
- assertTrue(cv.block(WAIT_TIMEOUT));
- assertEquals(NetworkStatsManager.CALLBACK_RELEASED, latchedHandler.mLastMessageType);
-
- // Make sure that the caller binder gets disconnected
- verify(mBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
- }
-
- @Test
- public void testUnregisterUsageCallback_unknown_noop() throws Exception {
- String callingPackage = "the.calling.package";
- long thresholdInBytes = 10 * 1024 * 1024; // 10 MB
- DataUsageRequest unknownRequest = new DataUsageRequest(
- 2 /* requestId */, sTemplateImsi1, thresholdInBytes);
-
- mService.unregisterUsageRequest(unknownRequest);
- }
-
- private static File getBaseDir(File statsDir) {
- File baseDir = new File(statsDir, "netstats");
- baseDir.mkdirs();
- return baseDir;
- }
-
- private void assertNetworkTotal(NetworkTemplate template, long rxBytes, long rxPackets,
- long txBytes, long txPackets, int operations) throws Exception {
- assertNetworkTotal(template, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, rxPackets, txBytes,
- txPackets, operations);
- }
-
- private void assertNetworkTotal(NetworkTemplate template, long start, long end, long rxBytes,
- long rxPackets, long txBytes, long txPackets, int operations) throws Exception {
- // verify history API
- final NetworkStatsHistory history = mSession.getHistoryForNetwork(template, FIELD_ALL);
- assertValues(history, start, end, rxBytes, rxPackets, txBytes, txPackets, operations);
-
- // verify summary API
- final NetworkStats stats = mSession.getSummaryForNetwork(template, start, end);
- assertValues(stats, IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO,
- rxBytes, rxPackets, txBytes, txPackets, operations);
- }
-
- private void assertUidTotal(NetworkTemplate template, int uid, long rxBytes, long rxPackets,
- long txBytes, long txPackets, int operations) throws Exception {
- assertUidTotal(template, uid, SET_ALL, METERED_ALL, ROAMING_ALL, rxBytes, rxPackets,
- txBytes, txPackets, operations);
- }
-
- private void assertUidTotal(NetworkTemplate template, int uid, int set, int metered,
- int roaming, long rxBytes, long rxPackets, long txBytes, long txPackets, int operations)
- throws Exception {
- // verify history API
- final NetworkStatsHistory history = mSession.getHistoryForUid(
- template, uid, set, TAG_NONE, FIELD_ALL);
- assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, rxPackets, txBytes,
- txPackets, operations);
-
- // verify summary API
- final NetworkStats stats = mSession.getSummaryForAllUid(
- template, Long.MIN_VALUE, Long.MAX_VALUE, false);
- assertValues(stats, IFACE_ALL, uid, set, TAG_NONE, metered, roaming, rxBytes, rxPackets,
- txBytes, txPackets, operations);
- }
-
- private void expectSystemReady() throws Exception {
- expectNetworkStatsSummary(buildEmptyStats());
- expectBandwidthControlCheck();
- }
-
- private void expectNetworkState(NetworkState... state) throws Exception {
- when(mConnManager.getAllNetworkState()).thenReturn(state);
-
- final LinkProperties linkProp = state.length > 0 ? state[0].linkProperties : null;
- when(mConnManager.getActiveLinkProperties()).thenReturn(linkProp);
- }
-
- private void expectNetworkStatsSummary(NetworkStats summary) throws Exception {
- when(mConnManager.getAllVpnInfo()).thenReturn(new VpnInfo[0]);
-
- expectNetworkStatsSummaryDev(summary);
- expectNetworkStatsSummaryXt(summary);
- }
-
- private void expectNetworkStatsSummaryDev(NetworkStats summary) throws Exception {
- when(mNetManager.getNetworkStatsSummaryDev()).thenReturn(summary);
- }
-
- private void expectNetworkStatsSummaryXt(NetworkStats summary) throws Exception {
- when(mNetManager.getNetworkStatsSummaryXt()).thenReturn(summary);
- }
-
- private void expectNetworkStatsUidDetail(NetworkStats detail) throws Exception {
- expectNetworkStatsUidDetail(detail, new String[0], new NetworkStats(0L, 0));
- }
-
- private void expectNetworkStatsUidDetail(
- NetworkStats detail, String[] tetherIfacePairs, NetworkStats tetherStats)
- throws Exception {
- when(mNetManager.getNetworkStatsUidDetail(UID_ALL)).thenReturn(detail);
-
- // also include tethering details, since they are folded into UID
- when(mNetManager.getNetworkStatsTethering()).thenReturn(tetherStats);
- }
-
- private void expectDefaultSettings() throws Exception {
- expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
- }
-
- private void expectSettings(long persistBytes, long bucketDuration, long deleteAge)
- throws Exception {
- when(mSettings.getPollInterval()).thenReturn(HOUR_IN_MILLIS);
- when(mSettings.getTimeCacheMaxAge()).thenReturn(DAY_IN_MILLIS);
- when(mSettings.getSampleEnabled()).thenReturn(true);
-
- final Config config = new Config(bucketDuration, deleteAge, deleteAge);
- when(mSettings.getDevConfig()).thenReturn(config);
- when(mSettings.getXtConfig()).thenReturn(config);
- when(mSettings.getUidConfig()).thenReturn(config);
- when(mSettings.getUidTagConfig()).thenReturn(config);
-
- when(mSettings.getGlobalAlertBytes(anyLong())).thenReturn(MB_IN_BYTES);
- when(mSettings.getDevPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
- when(mSettings.getXtPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
- when(mSettings.getUidPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
- when(mSettings.getUidTagPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
- }
-
- private void expectCurrentTime() throws Exception {
- when(mTime.forceRefresh()).thenReturn(false);
- when(mTime.hasCache()).thenReturn(true);
- when(mTime.currentTimeMillis()).thenReturn(currentTimeMillis());
- when(mTime.getCacheAge()).thenReturn(0L);
- when(mTime.getCacheCertainty()).thenReturn(0L);
- }
-
- private void expectBandwidthControlCheck() throws Exception {
- when(mNetManager.isBandwidthControlEnabled()).thenReturn(true);
- }
-
- private void assertStatsFilesExist(boolean exist) {
- final File basePath = new File(mStatsDir, "netstats");
- if (exist) {
- assertTrue(basePath.list().length > 0);
- } else {
- assertTrue(basePath.list().length == 0);
- }
- }
-
- private static void assertValues(NetworkStats stats, String iface, int uid, int set,
- int tag, int metered, int roaming, long rxBytes, long rxPackets, long txBytes,
- long txPackets, int operations) {
- final NetworkStats.Entry entry = new NetworkStats.Entry();
- List<Integer> sets = new ArrayList<>();
- if (set == SET_DEFAULT || set == SET_ALL) {
- sets.add(SET_DEFAULT);
- }
- if (set == SET_FOREGROUND || set == SET_ALL) {
- sets.add(SET_FOREGROUND);
- }
-
- List<Integer> roamings = new ArrayList<>();
- if (roaming == ROAMING_NO || roaming == ROAMING_ALL) {
- roamings.add(ROAMING_NO);
- }
- if (roaming == ROAMING_YES || roaming == ROAMING_ALL) {
- roamings.add(ROAMING_YES);
- }
-
- List<Integer> meterings = new ArrayList<>();
- if (metered == METERED_NO || metered == METERED_ALL) {
- meterings.add(METERED_NO);
- }
- if (metered == METERED_YES || metered == METERED_ALL) {
- meterings.add(METERED_YES);
- }
-
- for (int s : sets) {
- for (int r : roamings) {
- for (int m : meterings) {
- final int i = stats.findIndex(iface, uid, s, tag, m, r);
- if (i != -1) {
- entry.add(stats.getValues(i, null));
- }
- }
- }
- }
-
- assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
- assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
- assertEquals("unexpected txBytes", txBytes, entry.txBytes);
- assertEquals("unexpected txPackets", txPackets, entry.txPackets);
- assertEquals("unexpected operations", operations, entry.operations);
- }
-
- private static void assertValues(NetworkStatsHistory stats, long start, long end, long rxBytes,
- long rxPackets, long txBytes, long txPackets, int operations) {
- final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null);
- assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
- assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
- assertEquals("unexpected txBytes", txBytes, entry.txBytes);
- assertEquals("unexpected txPackets", txPackets, entry.txPackets);
- assertEquals("unexpected operations", operations, entry.operations);
- }
-
- private static NetworkState buildWifiState() {
- return buildWifiState(false);
- }
-
- private static NetworkState buildWifiState(boolean isMetered) {
- final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0, null, null);
- info.setDetailedState(DetailedState.CONNECTED, null, null);
- final LinkProperties prop = new LinkProperties();
- prop.setInterfaceName(TEST_IFACE);
- final NetworkCapabilities capabilities = new NetworkCapabilities();
- if (!isMetered) {
- capabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
- }
- return new NetworkState(info, prop, capabilities, null, null, TEST_SSID);
- }
-
- private static NetworkState buildMobile3gState(String subscriberId) {
- return buildMobile3gState(subscriberId, false /* isRoaming */);
- }
-
- private static NetworkState buildMobile3gState(String subscriberId, boolean isRoaming) {
- final NetworkInfo info = new NetworkInfo(
- TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UMTS, null, null);
- info.setDetailedState(DetailedState.CONNECTED, null, null);
- info.setRoaming(isRoaming);
- final LinkProperties prop = new LinkProperties();
- prop.setInterfaceName(TEST_IFACE);
- final NetworkCapabilities capabilities = new NetworkCapabilities();
- return new NetworkState(info, prop, capabilities, null, subscriberId, null);
- }
-
- private static NetworkState buildMobile4gState(String iface) {
- final NetworkInfo info = new NetworkInfo(TYPE_WIMAX, 0, null, null);
- info.setDetailedState(DetailedState.CONNECTED, null, null);
- final LinkProperties prop = new LinkProperties();
- prop.setInterfaceName(iface);
- final NetworkCapabilities capabilities = new NetworkCapabilities();
- return new NetworkState(info, prop, capabilities, null, null, null);
- }
-
- private NetworkStats buildEmptyStats() {
- return new NetworkStats(getElapsedRealtime(), 0);
- }
-
- private long getElapsedRealtime() {
- return mElapsedRealtime;
- }
-
- private long startTimeMillis() {
- return TEST_START;
- }
-
- private long currentTimeMillis() {
- return startTimeMillis() + mElapsedRealtime;
- }
-
- private void incrementCurrentTime(long duration) {
- mElapsedRealtime += duration;
- }
-
- private void forcePollAndWaitForIdle() {
- mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
- // Send dummy message to make sure that any previous message has been handled
- mHandler.sendMessage(mHandler.obtainMessage(-1));
- mHandlerThread.waitForIdle(WAIT_TIMEOUT);
- }
-
- static class LatchedHandler extends Handler {
- private final ConditionVariable mCv;
- int mLastMessageType = INVALID_TYPE;
-
- LatchedHandler(Looper looper, ConditionVariable cv) {
- super(looper);
- mCv = cv;
- }
-
- @Override
- public void handleMessage(Message msg) {
- mLastMessageType = msg.what;
- mCv.open();
- super.handleMessage(msg);
- }
- }
-
- /**
- * A subclass of HandlerThread that allows callers to wait for it to become idle. waitForIdle
- * will return immediately if the handler is already idle.
- */
- static class IdleableHandlerThread extends HandlerThread {
- private IdleHandler mIdleHandler;
-
- public IdleableHandlerThread(String name) {
- super(name);
- }
-
- public void waitForIdle(long timeoutMs) {
- final ConditionVariable cv = new ConditionVariable();
- final MessageQueue queue = getLooper().getQueue();
-
- synchronized (queue) {
- if (queue.isIdle()) {
- return;
- }
-
- assertNull("BUG: only one idle handler allowed", mIdleHandler);
- mIdleHandler = new IdleHandler() {
- public boolean queueIdle() {
- cv.open();
- mIdleHandler = null;
- return false; // Remove the handler.
- }
- };
- queue.addIdleHandler(mIdleHandler);
- }
-
- if (!cv.block(timeoutMs)) {
- fail("HandlerThread " + getName() + " did not become idle after " + timeoutMs
- + " ms");
- queue.removeIdleHandler(mIdleHandler);
- }
- }
- }
-
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index f1d5927..3c64582 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -1266,16 +1266,16 @@
if (force || !ENABLE_DUMP) return;
Log.v(TAG, "Dumping ShortcutService: " + message);
- for (String line : dumpsys(null).split("\n")) {
+ for (String line : dumpsys("-u").split("\n")) {
Log.v(TAG, line);
}
}
protected String dumpCheckin() {
- return dumpsys(new String[]{"--checkin"});
+ return dumpsys("--checkin");
}
- private String dumpsys(String[] args) {
+ protected String dumpsys(String... args) {
final ArrayList<String> origPermissions = new ArrayList<>(mCallerPermissions);
mCallerPermissions.add(android.Manifest.permission.DUMP);
try {
@@ -2139,4 +2139,15 @@
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(getCallingPackage(), getCallingUserId()));
}
+
+ protected void assertFileNotExists(String path) {
+ final File f = new File(mInjectedFilePathRoot, path);
+ assertFalse("File shouldn't exist: " + f.getAbsolutePath(), f.exists());
+ }
+
+ protected void assertFileExistsWithContent(String path) {
+ final File f = new File(mInjectedFilePathRoot, path);
+ assertTrue("File should exist: " + f.getAbsolutePath(), f.exists());
+ assertTrue("File should be larger than 0b: " + f.getAbsolutePath(), f.length() > 0);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index f4944f9..951f226 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -4732,8 +4732,23 @@
* - Bitmaps
*/
public void testBackupAndRestore() {
+
+ assertFileNotExists("user-0/shortcut_dump/restore-0-start.txt");
+ assertFileNotExists("user-0/shortcut_dump/restore-1-payload.xml");
+ assertFileNotExists("user-0/shortcut_dump/restore-2.txt");
+ assertFileNotExists("user-0/shortcut_dump/restore-3.txt");
+ assertFileNotExists("user-0/shortcut_dump/restore-4.txt");
+ assertFileNotExists("user-0/shortcut_dump/restore-5-finish.txt");
+
prepareForBackupTest();
+ assertFileExistsWithContent("user-0/shortcut_dump/restore-0-start.txt");
+ assertFileExistsWithContent("user-0/shortcut_dump/restore-1-payload.xml");
+ assertFileExistsWithContent("user-0/shortcut_dump/restore-2.txt");
+ assertFileExistsWithContent("user-0/shortcut_dump/restore-3.txt");
+ assertFileExistsWithContent("user-0/shortcut_dump/restore-4.txt");
+ assertFileExistsWithContent("user-0/shortcut_dump/restore-5-finish.txt");
+
checkBackupAndRestore_success();
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
index 25f9100..da641b9 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
@@ -15,6 +15,7 @@
*/
package com.android.server.pm;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.array;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertContains;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertSuccess;
@@ -40,7 +41,7 @@
import java.util.concurrent.atomic.AtomicInteger;
/**
- * Unit test for "cmd shortcut"
+ * Unit test for "cmd shortcut" and "dumpsys shortcut".
*
* Launcher related commands are tested in
*/
@@ -336,4 +337,29 @@
.areAllNotPinned();
});
}
+
+ public void testDumpsysArgs() {
+ checkDumpsysArgs(null, true, false, false);
+ checkDumpsysArgs(array("-u"), true, true, false);
+ checkDumpsysArgs(array("--uid"), true, true, false);
+
+ checkDumpsysArgs(array("-f"), true, false, true);
+ checkDumpsysArgs(array("--files"), true, false, true);
+
+ checkDumpsysArgs(array("-a"), true, true, true);
+ checkDumpsysArgs(array("--all"), true, true, true);
+
+ checkDumpsysArgs(array("-a", "-n"), false, true, true);
+ checkDumpsysArgs(array("-a", "--no-main"), false, true, true);
+ }
+
+ private void checkDumpsysArgs(String[] args, boolean expectMain, boolean expectUid,
+ boolean expectDumpFiles) {
+ String dump = dumpsys(args);
+
+ assertEquals(expectMain, dump.contains("Icon format: PNG"));
+ assertEquals(expectUid, dump.contains("SHORTCUT MANAGER UID STATES"));
+ assertEquals(expectDumpFiles, dump.contains("SHORTCUT MANAGER FILES"));
+ }
+
}
diff --git a/services/tests/servicestests/src/com/android/server/retaildemo/PreloadAppsInstallerTest.java b/services/tests/servicestests/src/com/android/server/retaildemo/PreloadAppsInstallerTest.java
index 6706969..cf27a50 100644
--- a/services/tests/servicestests/src/com/android/server/retaildemo/PreloadAppsInstallerTest.java
+++ b/services/tests/servicestests/src/com/android/server/retaildemo/PreloadAppsInstallerTest.java
@@ -39,7 +39,9 @@
import com.android.internal.util.test.FakeSettingsProvider;
import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -65,6 +67,12 @@
private PreloadAppsInstaller mInstaller;
+ @BeforeClass
+ @AfterClass
+ public static void clearSettingsProvider() {
+ FakeSettingsProvider.clearSettingsProvider();
+ }
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
diff --git a/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java b/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
index 2e13d29..a93f6439 100644
--- a/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
@@ -71,7 +71,9 @@
import com.android.server.retaildemo.RetailDemoModeService.Injector;
import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -112,6 +114,12 @@
private RetailDemoModeService mService;
private TestInjector mInjector;
+ @BeforeClass
+ @AfterClass
+ public static void clearSettingsProvider() {
+ FakeSettingsProvider.clearSettingsProvider();
+ }
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
diff --git a/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java b/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java
index d2a4484..8828988 100644
--- a/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java
@@ -23,12 +23,14 @@
private TestUtils() {
}
+ public interface ExceptionRunnable {
+ void run() throws Exception;
+ }
+
public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
- String expectedExceptionMessageRegex, Runnable r) {
+ String expectedExceptionMessageRegex, ExceptionRunnable r) {
try {
r.run();
- Assert.fail("Expected exception type " + expectedExceptionType.getName()
- + " was not thrown");
} catch (Throwable e) {
Assert.assertTrue(
"Expected exception type was " + expectedExceptionType.getName()
@@ -37,6 +39,9 @@
if (expectedExceptionMessageRegex != null) {
MoreAsserts.assertContainsRegex(expectedExceptionMessageRegex, e.getMessage());
}
+ return; // Pass.
}
+ Assert.fail("Expected exception type " + expectedExceptionType.getName()
+ + " was not thrown");
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/CheckTokenTest.java b/services/tests/servicestests/src/com/android/server/timezone/CheckTokenTest.java
new file mode 100644
index 0000000..9603a06
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/CheckTokenTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import org.junit.Test;
+
+import android.support.test.filters.SmallTest;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+@SmallTest
+public class CheckTokenTest {
+
+ @Test
+ public void toByteArray() throws Exception {
+ PackageVersions packageVersions =
+ new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+ CheckToken originalToken = new CheckToken(1 /* optimisticLockId */, packageVersions);
+ assertEquals(originalToken, CheckToken.fromByteArray(originalToken.toByteArray()));
+ }
+
+ @Test
+ public void fromByteArray() {
+ PackageVersions packageVersions =
+ new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+ CheckToken token = new CheckToken(1, packageVersions);
+ byte[] validTokenBytes = token.toByteArray();
+ byte[] shortTokenBytes = new byte[validTokenBytes.length - 1];
+ System.arraycopy(validTokenBytes, 0, shortTokenBytes, 0, shortTokenBytes.length);
+
+ try {
+ CheckToken.fromByteArray(shortTokenBytes);
+ fail();
+ } catch (IOException expected) {}
+ }
+
+ @Test
+ public void equals() {
+ PackageVersions packageVersions1 =
+ new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+ PackageVersions packageVersions2 =
+ new PackageVersions(2 /* updateAppVersion */, 2 /* dataAppVersion */);
+ assertFalse(packageVersions1.equals(packageVersions2));
+
+ CheckToken baseline = new CheckToken(1, packageVersions1);
+ assertEquals(baseline, baseline);
+
+ CheckToken deepEqual = new CheckToken(1, packageVersions1);
+ assertEquals(baseline, deepEqual);
+
+ CheckToken differentOptimisticLockId = new CheckToken(2, packageVersions1);
+ assertFalse(differentOptimisticLockId.equals(baseline));
+
+ CheckToken differentPackageVersions = new CheckToken(1, packageVersions2);
+ assertFalse(differentPackageVersions.equals(baseline));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java
new file mode 100644
index 0000000..e085270
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+@SmallTest
+public class PackageStatusStorageTest {
+ private static final PackageVersions VALID_PACKAGE_VERSIONS = new PackageVersions(1, 2);
+
+ private PackageStatusStorage mPackageStatusStorage;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+
+ // Using the instrumentation context means the database is created in a test app-specific
+ // directory.
+ mPackageStatusStorage = new PackageStatusStorage(context);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mPackageStatusStorage.deleteDatabaseForTests();
+ }
+
+ @Test
+ public void getPackageStatus_initialState() {
+ assertNull(mPackageStatusStorage.getPackageStatus());
+ }
+
+ @Test
+ public void resetCheckState() {
+ // Assert initial state.
+ assertNull(mPackageStatusStorage.getPackageStatus());
+
+ CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+
+ // There should now be a state.
+ assertNotNull(mPackageStatusStorage.getPackageStatus());
+
+ // Now clear the state.
+ mPackageStatusStorage.resetCheckState();
+
+ // After reset, there should be no package state again.
+ assertNull(mPackageStatusStorage.getPackageStatus());
+
+ CheckToken token2 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+
+ // Token after a reset should still be distinct.
+ assertFalse(token1.equals(token2));
+
+ // Now clear the state again.
+ mPackageStatusStorage.resetCheckState();
+
+ // After reset, there should be no package state again.
+ assertNull(mPackageStatusStorage.getPackageStatus());
+
+ CheckToken token3 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+
+ // A CheckToken generated after a reset should still be distinct.
+ assertFalse(token2.equals(token3));
+ }
+
+ @Test
+ public void generateCheckToken_missingRowBehavior() {
+ // Assert initial state.
+ assertNull(mPackageStatusStorage.getPackageStatus());
+
+ CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+ assertNotNull(token1);
+
+ // There should now be state.
+ assertNotNull(mPackageStatusStorage.getPackageStatus());
+
+ // Corrupt the table by removing the one row.
+ mPackageStatusStorage.deleteRowForTests();
+
+ // Check that generateCheckToken recovers.
+ assertNotNull(mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS));
+ }
+
+ @Test
+ public void getPackageStatus_missingRowBehavior() {
+ // Assert initial state.
+ assertNull(mPackageStatusStorage.getPackageStatus());
+
+ CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+ assertNotNull(token1);
+
+ // There should now be a state.
+ assertNotNull(mPackageStatusStorage.getPackageStatus());
+
+ // Corrupt the table by removing the one row.
+ mPackageStatusStorage.deleteRowForTests();
+
+ assertNull(mPackageStatusStorage.getPackageStatus());
+ }
+
+ @Test
+ public void markChecked_missingRowBehavior() {
+ // Assert initial state.
+ CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+ assertNotNull(token1);
+
+ // There should now be a state.
+ assertNotNull(mPackageStatusStorage.getPackageStatus());
+
+ // Corrupt the table by removing the one row.
+ mPackageStatusStorage.deleteRowForTests();
+
+ // The missing row should mean token1 is now considered invalid, so we should get a false.
+ assertFalse(mPackageStatusStorage.markChecked(token1, true /* succeeded */));
+
+ // The storage should have recovered and we should be able to carry on like before.
+ CheckToken token2 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+ assertTrue(mPackageStatusStorage.markChecked(token2, true /* succeeded */));
+ }
+
+ @Test
+ public void checkToken_tokenIsUnique() {
+ PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
+ PackageStatus expectedPackageStatus =
+ new PackageStatus(PackageStatus.CHECK_STARTED, packageVersions);
+
+ CheckToken token1 = mPackageStatusStorage.generateCheckToken(packageVersions);
+ assertEquals(packageVersions, token1.mPackageVersions);
+
+ PackageStatus actualPackageStatus1 = mPackageStatusStorage.getPackageStatus();
+ assertEquals(expectedPackageStatus, actualPackageStatus1);
+
+ CheckToken token2 = mPackageStatusStorage.generateCheckToken(packageVersions);
+ assertEquals(packageVersions, token1.mPackageVersions);
+ assertFalse(token1.mOptimisticLockId == token2.mOptimisticLockId);
+ assertFalse(token1.equals(token2));
+ }
+
+ @Test
+ public void markChecked_checkSucceeded() {
+ PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
+
+ CheckToken token = mPackageStatusStorage.generateCheckToken(packageVersions);
+ boolean writeOk = mPackageStatusStorage.markChecked(token, true /* succeeded */);
+ assertTrue(writeOk);
+
+ PackageStatus expectedPackageStatus =
+ new PackageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+ assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
+ }
+
+ @Test
+ public void markChecked_checkFailed() {
+ PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
+
+ CheckToken token = mPackageStatusStorage.generateCheckToken(packageVersions);
+ boolean writeOk = mPackageStatusStorage.markChecked(token, false /* succeeded */);
+ assertTrue(writeOk);
+
+ PackageStatus expectedPackageStatus =
+ new PackageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, packageVersions);
+ assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
+ }
+
+ @Test
+ public void markChecked_optimisticLocking_multipleToken() {
+ PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
+ CheckToken token1 = mPackageStatusStorage.generateCheckToken(packageVersions);
+ CheckToken token2 = mPackageStatusStorage.generateCheckToken(packageVersions);
+
+ PackageStatus packageStatusBeforeChecked = mPackageStatusStorage.getPackageStatus();
+
+ boolean writeOk1 = mPackageStatusStorage.markChecked(token1, true /* succeeded */);
+ // Generation of token2 should mean that token1 is no longer valid.
+ assertFalse(writeOk1);
+ assertEquals(packageStatusBeforeChecked, mPackageStatusStorage.getPackageStatus());
+
+ boolean writeOk2 = mPackageStatusStorage.markChecked(token2, true /* succeeded */);
+ // token2 should still be valid, and the attempt with token1 should have had no effect.
+ assertTrue(writeOk2);
+ PackageStatus expectedPackageStatus =
+ new PackageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+ assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
+ }
+
+ @Test
+ public void markChecked_optimisticLocking_repeatedTokenUse() {
+ PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
+ CheckToken token = mPackageStatusStorage.generateCheckToken(packageVersions);
+
+ boolean writeOk1 = mPackageStatusStorage.markChecked(token, true /* succeeded */);
+ assertTrue(writeOk1);
+
+ PackageStatus expectedPackageStatus =
+ new PackageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+ assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
+
+ // token cannot be reused.
+ boolean writeOk2 = mPackageStatusStorage.markChecked(token, true /* succeeded */);
+ assertFalse(writeOk2);
+ assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageStatusTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusTest.java
new file mode 100644
index 0000000..c0ae81e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import org.junit.Test;
+
+import android.support.test.filters.SmallTest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+@SmallTest
+public class PackageStatusTest {
+
+ @Test
+ public void equals() {
+ PackageVersions packageVersions1 =
+ new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+ PackageVersions packageVersions2 =
+ new PackageVersions(2 /* updateAppVersion */, 1 /* dataAppVersion */);
+ assertFalse(packageVersions1.equals(packageVersions2));
+
+ PackageStatus baseline =
+ new PackageStatus(PackageStatus.CHECK_STARTED, packageVersions1);
+ assertEquals(baseline, baseline);
+
+ PackageStatus deepEqual =
+ new PackageStatus(PackageStatus.CHECK_STARTED, packageVersions1);
+ assertEquals(baseline, deepEqual);
+
+ PackageStatus differentStatus =
+ new PackageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions1);
+ assertFalse(differentStatus.equals(baseline));
+
+ PackageStatus differentPackageVersions =
+ new PackageStatus(PackageStatus.CHECK_STARTED, packageVersions2);
+ assertFalse(differentPackageVersions.equals(baseline));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
new file mode 100644
index 0000000..45b0af3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
@@ -0,0 +1,1471 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import android.app.timezone.RulesUpdaterContract;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.TimeZoneRulesDataContract;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+
+/**
+ * White box interaction / unit testing of the {@link PackageTracker}.
+ */
+@SmallTest
+public class PackageTrackerTest {
+ private static final String UPDATE_APP_PACKAGE_NAME = "updateAppPackageName";
+ private static final String DATA_APP_PACKAGE_NAME = "dataAppPackageName";
+ private static final PackageVersions INITIAL_APP_PACKAGE_VERSIONS =
+ new PackageVersions(2 /* updateAppVersion */, 2 /* dataAppVersion */);
+
+ private ConfigHelper mMockConfigHelper;
+ private PackageManagerHelper mMockPackageManagerHelper;
+
+ private FakeClockHelper mFakeClock;
+ private FakeIntentHelper mFakeIntentHelper;
+ private PackageStatusStorage mPackageStatusStorage;
+ private PackageTracker mPackageTracker;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+
+ mFakeClock = new FakeClockHelper();
+
+ // Read-only interfaces so are easy to mock.
+ mMockConfigHelper = mock(ConfigHelper.class);
+ mMockPackageManagerHelper = mock(PackageManagerHelper.class);
+
+ // Using the instrumentation context means the database is created in a test app-specific
+ // directory. We can use the real thing for this test.
+ mPackageStatusStorage = new PackageStatusStorage(context);
+
+ // For other interactions with the Android framework we create a fake object.
+ mFakeIntentHelper = new FakeIntentHelper();
+
+ // Create the PackageTracker to use in tests.
+ mPackageTracker = new PackageTracker(
+ mFakeClock,
+ mMockConfigHelper,
+ mMockPackageManagerHelper,
+ mPackageStatusStorage,
+ mFakeIntentHelper);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mPackageStatusStorage != null) {
+ mPackageStatusStorage.deleteDatabaseForTests();
+ }
+ }
+
+ @Test
+ public void trackingDisabled_intentHelperNotUsed() {
+ // Set up device configuration.
+ configureTrackingDisabled();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check the IntentHelper was not initialized.
+ mFakeIntentHelper.assertNotInitialized();
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ }
+
+ @Test
+ public void trackingDisabled_triggerUpdateIfNeededNotAllowed() {
+ // Set up device configuration.
+ configureTrackingDisabled();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+ try {
+ // This call should also not be allowed and will throw an exception if tracking is
+ // disabled.
+ mPackageTracker.triggerUpdateIfNeeded(true);
+ fail();
+ } catch (IllegalStateException expected) {}
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ }
+
+ @Test
+ public void trackingDisabled_unsolicitedResultsIgnored_withoutToken() {
+ // Set up device configuration.
+ configureTrackingDisabled();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+ // Receiving a check result when tracking is disabled should cause the storage to be
+ // reset.
+ mPackageTracker.recordCheckResult(null /* checkToken */, true /* success */);
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+ // Assert the storage was reset.
+ checkPackageStorageStatusIsInitialOrReset();
+ }
+
+ @Test
+ public void trackingDisabled_unsolicitedResultsIgnored_withToken() {
+ // Set up device configuration.
+ configureTrackingDisabled();
+
+ // Set the storage into an arbitrary state so we can detect a reset.
+ mPackageStatusStorage.generateCheckToken(INITIAL_APP_PACKAGE_VERSIONS);
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+ // Receiving a check result when tracking is disabled should cause the storage to be reset.
+ mPackageTracker.recordCheckResult(createArbitraryCheckToken(), true /* success */);
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+ // Assert the storage was reset.
+ checkPackageStorageStatusIsInitialOrReset();
+ }
+
+ @Test
+ public void trackingEnabled_updateAppConfigMissing() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureUpdateAppPackageNameMissing();
+ configureDataAppPackageOk(DATA_APP_PACKAGE_NAME);
+
+ try {
+ // Initialize the tracker.
+ mPackageTracker.start();
+ fail();
+ } catch (RuntimeException expected) {}
+
+ mFakeIntentHelper.assertNotInitialized();
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ }
+
+ // TODO(nfuller): Uncomment or delete when it's clear what will happen with http://b/35995024
+ // @Test
+ // public void trackingEnabled_updateAppNotPrivileged() throws Exception {
+ // // Set up device configuration.
+ // configureTrackingEnabled();
+ // configureReliabilityConfigSettingsOk();
+ // configureUpdateAppPackageNotPrivileged(UPDATE_APP_PACKAGE_NAME);
+ // configureDataAppPackageOk(DATA_APP_PACKAGE_NAME);
+ //
+ // try {
+ // // Initialize the tracker.
+ // mPackageTracker.start();
+ // fail();
+ // } catch (RuntimeException expected) {}
+ //
+ // mFakeIntentHelper.assertNotInitialized();
+ //
+ // // Check reliability triggering state.
+ // mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ // }
+
+ @Test
+ public void trackingEnabled_dataAppConfigMissing() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureUpdateAppPackageOk(UPDATE_APP_PACKAGE_NAME);
+ configureDataAppPackageNameMissing();
+
+ try {
+ // Initialize the tracker.
+ mPackageTracker.start();
+ fail();
+ } catch (RuntimeException expected) {}
+
+ mFakeIntentHelper.assertNotInitialized();
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ }
+
+ // TODO(nfuller): Uncomment or delete when it's clear what will happen with http://b/35995024
+ // @Test
+ // public void trackingEnabled_dataAppNotPrivileged() throws Exception {
+ // // Set up device configuration.
+ // configureTrackingEnabled();
+ // configureReliabilityConfigSettingsOk();
+ // configureUpdateAppPackageOk(UPDATE_APP_PACKAGE_NAME);
+ // configureDataAppPackageNotPrivileged(DATA_APP_PACKAGE_NAME);
+ //
+ // try {
+ // // Initialize the tracker.
+ // mPackageTracker.start();
+ // fail();
+ // } catch (RuntimeException expected) {}
+ //
+ // mFakeIntentHelper.assertNotInitialized();
+ //
+ // // Check reliability triggering state.
+ // mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ // }
+
+ @Test
+ public void trackingEnabled_packageUpdate_badUpdateAppManifestEntry() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Configure a bad manifest for the update app. Should effectively turn off tracking.
+ PackageVersions packageVersions =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ configureUpdateAppManifestBad(UPDATE_APP_PACKAGE_NAME);
+ configureDataAppManifestOk(DATA_APP_PACKAGE_NAME);
+ configureUpdateAppPackageVersion(
+ UPDATE_APP_PACKAGE_NAME, packageVersions.mUpdateAppVersion);
+ configureDataAppPackageVersion(DATA_APP_PACKAGE_NAME, packageVersions.mDataAppVersion);
+ // Simulate a tracked package being updated.
+ mFakeIntentHelper.simulatePackageUpdatedEvent();
+
+ // Assert the PackageTracker did not attempt to trigger an update.
+ mFakeIntentHelper.assertUpdateNotTriggered();
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+ // Assert the storage was not touched.
+ checkPackageStorageStatusIsInitialOrReset();
+ }
+
+ @Test
+ public void trackingEnabled_packageUpdate_badDataAppManifestEntry() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Configure a bad manifest for the data app. Should effectively turn off tracking.
+ PackageVersions packageVersions =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ configureUpdateAppManifestOk(UPDATE_APP_PACKAGE_NAME);
+ configureDataAppManifestBad(DATA_APP_PACKAGE_NAME);
+ configureUpdateAppPackageVersion(
+ UPDATE_APP_PACKAGE_NAME, packageVersions.mUpdateAppVersion);
+ configureDataAppPackageVersion(DATA_APP_PACKAGE_NAME, packageVersions.mDataAppVersion);
+ mFakeIntentHelper.simulatePackageUpdatedEvent();
+
+ // Assert the PackageTracker did not attempt to trigger an update.
+ mFakeIntentHelper.assertUpdateNotTriggered();
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+ // Assert the storage was not touched.
+ checkPackageStorageStatusIsInitialOrReset();
+ }
+
+ @Test
+ public void trackingEnabled_packageUpdate_responseWithToken_success() throws Exception {
+ trackingEnabled_packageUpdate_responseWithToken(true);
+ }
+
+ @Test
+ public void trackingEnabled_packageUpdate_responseWithToken_failed() throws Exception {
+ trackingEnabled_packageUpdate_responseWithToken(false);
+ }
+
+ private void trackingEnabled_packageUpdate_responseWithToken(boolean success) throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Simulate a tracked package being updated.
+ PackageVersions packageVersions =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions);
+
+ // Get the token that was passed to the intent helper, and pass it back.
+ CheckToken token = mFakeIntentHelper.captureAndResetLastToken();
+ mPackageTracker.recordCheckResult(token, success);
+
+ // Check storage and reliability triggering state.
+ if (success) {
+ checkUpdateCheckSuccessful(packageVersions);
+ } else {
+ checkUpdateCheckFailed(packageVersions);
+ }
+ }
+
+ @Test
+ public void trackingEnabled_packageUpdate_responseWithoutTokenCausesStorageReset_success()
+ throws Exception {
+ trackingEnabled_packageUpdate_responseWithoutTokenCausesStorageReset(true);
+ }
+
+ @Test
+ public void trackingEnabled_packageUpdate_responseWithoutTokenCausesStorageReset_failed()
+ throws Exception {
+ trackingEnabled_packageUpdate_responseWithoutTokenCausesStorageReset(false);
+ }
+
+ private void trackingEnabled_packageUpdate_responseWithoutTokenCausesStorageReset(
+ boolean success) throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Set up installed app versions / manifests.
+ PackageVersions packageVersions =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions);
+
+ // Ignore the token that was given to the intent helper, just pass null.
+ mPackageTracker.recordCheckResult(null /* checkToken */, success);
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+ // Assert the storage was reset.
+ checkPackageStorageStatusIsInitialOrReset();
+ }
+
+ /**
+ * Two package updates triggered for the same package versions. The second is triggered while
+ * the first is still happening.
+ */
+ @Test
+ public void trackingEnabled_packageUpdate_twoChecksNoPackageChange_secondWhileFirstInProgress()
+ throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Simulate package installation.
+ PackageVersions packageVersions =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions);
+
+ // Get the first token.
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions, token1.mPackageVersions);
+
+ // Now attempt to generate another check while the first is in progress and without having
+ // updated the package versions. The PackageTracker should trigger again for safety.
+ simulatePackageInstallation(packageVersions);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions);
+
+ CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions, token2.mPackageVersions);
+ assertEquals(token1.mPackageVersions, token2.mPackageVersions);
+ assertTrue(token1.mOptimisticLockId != token2.mOptimisticLockId);
+ }
+
+ /**
+ * Two package updates triggered for the same package versions. The second happens after
+ * the first has succeeded.
+ */
+ @Test
+ public void trackingEnabled_packageUpdate_twoChecksNoPackageChange_sequential()
+ throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Simulate package installation.
+ PackageVersions packageVersions =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions);
+
+ // Get the token.
+ CheckToken token = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions, token.mPackageVersions);
+
+ // Simulate a successful check.
+ mPackageTracker.recordCheckResult(token, true /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions);
+
+ // Now attempt to generate another check, but without having updated the package. The
+ // PackageTracker should be smart enough to recognize there's nothing to do here.
+ simulatePackageInstallation(packageVersions);
+
+ // Assert the PackageTracker did not attempt to trigger an update.
+ mFakeIntentHelper.assertUpdateNotTriggered();
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions);
+ }
+
+ /**
+ * Two package updates triggered for the same package versions. The second is triggered after
+ * the first has failed.
+ */
+ @Test
+ public void trackingEnabled_packageUpdate_afterFailure() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Simulate package installation.
+ PackageVersions packageVersions =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions);
+
+ // Get the first token.
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions, token1.mPackageVersions);
+
+ // Simulate an *unsuccessful* check.
+ mPackageTracker.recordCheckResult(token1, false /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckFailed(packageVersions);
+
+ // Now generate another check, but without having updated the package. The
+ // PackageTracker should recognize the last check failed and trigger again.
+ simulatePackageInstallation(packageVersions);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions);
+
+ // Get the second token.
+ CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+
+ // Assert some things about the tokens.
+ assertEquals(packageVersions, token2.mPackageVersions);
+ assertTrue(token1.mOptimisticLockId != token2.mOptimisticLockId);
+
+ // For completeness, now simulate this check was successful.
+ mPackageTracker.recordCheckResult(token2, true /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions);
+ }
+
+ /**
+ * Two package updates triggered for different package versions. The second is triggered while
+ * the first is still happening.
+ */
+ @Test
+ public void trackingEnabled_packageUpdate_twoChecksWithPackageChange_firstCheckInProcess()
+ throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Simulate package installation.
+ PackageVersions packageVersions1 =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions1);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions1);
+
+ // Get the first token.
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions1, token1.mPackageVersions);
+
+ // Simulate a tracked package being updated a second time (before the response for the
+ // first has been received).
+ PackageVersions packageVersions2 =
+ new PackageVersions(3 /* updateAppPackageVersion */, 4 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions2);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions2);
+
+ // Get the second token.
+ CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions2, token2.mPackageVersions);
+
+ // token1 should be invalid because the token2 was generated.
+ mPackageTracker.recordCheckResult(token1, true /* success */);
+
+ // Reliability triggering should still be enabled.
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+ // Check the expected storage state.
+ checkPackageStorageStatus(PackageStatus.CHECK_STARTED, packageVersions2);
+
+ // token2 should still be accepted.
+ mPackageTracker.recordCheckResult(token2, true /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions2);
+ }
+
+ /**
+ * Two package updates triggered for different package versions. The second is triggered after
+ * the first has completed successfully.
+ */
+ @Test
+ public void trackingEnabled_packageUpdate_twoChecksWithPackageChange_sequential()
+ throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Simulate package installation.
+ PackageVersions packageVersions1 =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions1);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions1);
+
+ // Get the first token.
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions1, token1.mPackageVersions);
+
+ // token1 should be accepted.
+ mPackageTracker.recordCheckResult(token1, true /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions1);
+
+ // Simulate a tracked package being updated a second time.
+ PackageVersions packageVersions2 =
+ new PackageVersions(3 /* updateAppPackageVersion */, 4 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions2);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions2);
+
+ // Get the second token.
+ CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions2, token2.mPackageVersions);
+
+ // token2 should still be accepted.
+ mPackageTracker.recordCheckResult(token2, true /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions2);
+ }
+
+ /**
+ * Replaying the same token twice.
+ */
+ @Test
+ public void trackingEnabled_packageUpdate_sameTokenReplayFails() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Simulate package installation.
+ PackageVersions packageVersions1 =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions1);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions1);
+
+ // Get the first token.
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions1, token1.mPackageVersions);
+
+ // token1 should be accepted.
+ mPackageTracker.recordCheckResult(token1, true /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions1);
+
+ // Apply token1 again.
+ mPackageTracker.recordCheckResult(token1, true /* success */);
+
+ // Check the expected storage state. No real way to tell if it has been updated, but
+ // we can check the final state is still what it should be.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions1);
+
+ // Under the covers we expect it to fail to update because the storage should recognize that
+ // the token is no longer valid.
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+ // Peek inside the package tracker to make sure it is tracking failure counts properly.
+ assertEquals(1, mPackageTracker.getCheckFailureCountForTests());
+ }
+
+ @Test
+ public void trackingEnabled_reliabilityTrigger_firstTime_initialStorage() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ PackageVersions packageVersions = configureValidApplications();
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did trigger an update.
+ checkUpdateCheckTriggered(packageVersions);
+
+ // Confirm the token was correct.
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions, token1.mPackageVersions);
+
+ // token1 should be accepted.
+ mPackageTracker.recordCheckResult(token1, true /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions);
+ }
+
+ @Test
+ public void trackingEnabled_reliabilityTrigger_afterRebootNoTriggerNeeded() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ PackageVersions packageVersions = configureValidApplications();
+
+ // Force the storage into a state we want.
+ mPackageStatusStorage.forceCheckStateForTests(
+ PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did not attempt to trigger an update.
+ mFakeIntentHelper.assertUpdateNotTriggered();
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions);
+ }
+
+ /**
+ * Simulates the device starting where the storage records do not match the installed app
+ * versions. The reliability trigger should cause the package tracker to perform a check.
+ */
+ @Test
+ public void trackingEnabled_reliabilityTrigger_afterRebootTriggerNeededBecausePreviousFailed()
+ throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+
+ PackageVersions oldPackageVersions = new PackageVersions(1, 1);
+ PackageVersions currentPackageVersions = new PackageVersions(2, 2);
+
+ // Simulate there being a newer version installed than the one recorded in storage.
+ configureValidApplications(currentPackageVersions);
+
+ // Force the storage into a state we want.
+ mPackageStatusStorage.forceCheckStateForTests(
+ PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did trigger an update.
+ checkUpdateCheckTriggered(currentPackageVersions);
+
+ // Simulate the update check completing successfully.
+ CheckToken checkToken = mFakeIntentHelper.captureAndResetLastToken();
+ mPackageTracker.recordCheckResult(checkToken, true /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(currentPackageVersions);
+ }
+
+ /**
+ * Simulates persistent failures of the reliability check. It should stop after the configured
+ * number of checks.
+ */
+ @Test
+ public void trackingEnabled_reliabilityTrigger_repeatedFailures() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+
+ int retriesAllowed = 3;
+ int checkDelayMillis = 5 * 60 * 1000;
+ configureReliabilityConfigSettings(retriesAllowed, checkDelayMillis);
+
+ PackageVersions oldPackageVersions = new PackageVersions(1, 1);
+ PackageVersions currentPackageVersions = new PackageVersions(2, 2);
+
+ // Simulate there being a newer version installed than the one recorded in storage.
+ configureValidApplications(currentPackageVersions);
+
+ // Force the storage into a state we want.
+ mPackageStatusStorage.forceCheckStateForTests(
+ PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+ for (int i = 0; i < retriesAllowed + 1; i++) {
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did trigger an update.
+ checkUpdateCheckTriggered(currentPackageVersions);
+
+ // Check the PackageTracker failure count before calling recordCheckResult.
+ assertEquals(i, mPackageTracker.getCheckFailureCountForTests());
+
+ // Simulate a check failure.
+ CheckToken checkToken = mFakeIntentHelper.captureAndResetLastToken();
+ mPackageTracker.recordCheckResult(checkToken, false /* success */);
+
+ // Peek inside the package tracker to make sure it is tracking failure counts properly.
+ assertEquals(i + 1, mPackageTracker.getCheckFailureCountForTests());
+
+ // Confirm nothing has changed.
+ mFakeIntentHelper.assertUpdateNotTriggered();
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE,
+ currentPackageVersions);
+
+ // Check reliability triggering is in the correct state.
+ if (i <= retriesAllowed) {
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+ } else {
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ }
+ }
+ }
+
+ @Test
+ public void trackingEnabled_reliabilityTrigger_failureCountIsReset() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+
+ int retriesAllowed = 3;
+ int checkDelayMillis = 5 * 60 * 1000;
+ configureReliabilityConfigSettings(retriesAllowed, checkDelayMillis);
+
+ PackageVersions oldPackageVersions = new PackageVersions(1, 1);
+ PackageVersions currentPackageVersions = new PackageVersions(2, 2);
+
+ // Simulate there being a newer version installed than the one recorded in storage.
+ configureValidApplications(currentPackageVersions);
+
+ // Force the storage into a state we want.
+ mPackageStatusStorage.forceCheckStateForTests(
+ PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+ // Fail (retries - 1) times.
+ for (int i = 0; i < retriesAllowed - 1; i++) {
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did trigger an update.
+ checkUpdateCheckTriggered(currentPackageVersions);
+
+ // Check the PackageTracker failure count before calling recordCheckResult.
+ assertEquals(i, mPackageTracker.getCheckFailureCountForTests());
+
+ // Simulate a check failure.
+ CheckToken checkToken = mFakeIntentHelper.captureAndResetLastToken();
+ mPackageTracker.recordCheckResult(checkToken, false /* success */);
+
+ // Peek inside the package tracker to make sure it is tracking failure counts properly.
+ assertEquals(i + 1, mPackageTracker.getCheckFailureCountForTests());
+
+ // Confirm nothing has changed.
+ mFakeIntentHelper.assertUpdateNotTriggered();
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE,
+ currentPackageVersions);
+
+ // Check reliability triggering is still enabled.
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+ }
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did trigger an update.
+ checkUpdateCheckTriggered(currentPackageVersions);
+
+ // Check the PackageTracker failure count before calling recordCheckResult.
+ assertEquals(retriesAllowed - 1, mPackageTracker.getCheckFailureCountForTests());
+
+ // On the last possible try, succeed.
+ CheckToken checkToken = mFakeIntentHelper.captureAndResetLastToken();
+ mPackageTracker.recordCheckResult(checkToken, true /* success */);
+
+ checkUpdateCheckSuccessful(currentPackageVersions);
+ }
+
+ /**
+ * Simulates reliability triggers happening too close together. Package tracker should ignore
+ * the ones it doesn't need.
+ */
+ @Test
+ public void trackingEnabled_reliabilityTrigger_tooSoon() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+
+ int retriesAllowed = 5;
+ int checkDelayMillis = 5 * 60 * 1000;
+ configureReliabilityConfigSettings(retriesAllowed, checkDelayMillis);
+
+ PackageVersions oldPackageVersions = new PackageVersions(1, 1);
+ PackageVersions currentPackageVersions = new PackageVersions(2, 2);
+
+ // Simulate there being a newer version installed than the one recorded in storage.
+ configureValidApplications(currentPackageVersions);
+
+ // Force the storage into a state we want.
+ mPackageStatusStorage.forceCheckStateForTests(
+ PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did trigger an update.
+ checkUpdateCheckTriggered(currentPackageVersions);
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+
+ // Increment the clock, but not enough.
+ mFakeClock.incrementClock(checkDelayMillis - 1);
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did not trigger an update.
+ mFakeIntentHelper.assertUpdateNotTriggered();
+ checkPackageStorageStatus(PackageStatus.CHECK_STARTED, currentPackageVersions);
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+ // Increment the clock slightly more. Should now consider the response overdue.
+ mFakeClock.incrementClock(2);
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Triggering should have happened.
+ checkUpdateCheckTriggered(currentPackageVersions);
+ CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+
+ // Check a new token was generated.
+ assertFalse(token1.equals(token2));
+ }
+
+ /**
+ * Tests what happens when a package update doesn't complete and a reliability trigger cleans
+ * up for it.
+ */
+ @Test
+ public void trackingEnabled_reliabilityTrigger_afterPackageUpdateDidNotComplete()
+ throws Exception {
+
+ // Set up device configuration.
+ configureTrackingEnabled();
+
+ int retriesAllowed = 5;
+ int checkDelayMillis = 5 * 60 * 1000;
+ configureReliabilityConfigSettings(retriesAllowed, checkDelayMillis);
+
+ PackageVersions currentPackageVersions = new PackageVersions(1, 1);
+ PackageVersions newPackageVersions = new PackageVersions(2, 2);
+
+ // Simulate there being a newer version installed than the one recorded in storage.
+ configureValidApplications(currentPackageVersions);
+
+ // Force the storage into a state we want.
+ mPackageStatusStorage.forceCheckStateForTests(
+ PackageStatus.CHECK_COMPLETED_SUCCESS, currentPackageVersions);
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Simulate a reliability trigger.
+ simulatePackageInstallation(newPackageVersions);
+
+ // Assert the PackageTracker did trigger an update.
+ checkUpdateCheckTriggered(newPackageVersions);
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+
+ // Increment the clock, but not enough.
+ mFakeClock.incrementClock(checkDelayMillis + 1);
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker triggered an update.
+ checkUpdateCheckTriggered(newPackageVersions);
+ CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+
+ // Check a new token was generated.
+ assertFalse(token1.equals(token2));
+
+ // Simulate the reliability check completing.
+ mPackageTracker.recordCheckResult(token2, true /* success */);
+
+ // Check everything is now as it should be.
+ checkUpdateCheckSuccessful(newPackageVersions);
+ }
+
+ /**
+ * Simulates a reliability trigger happening too soon after a package update trigger occurred.
+ */
+ @Test
+ public void trackingEnabled_reliabilityTriggerAfterUpdate_tooSoon() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+
+ int retriesAllowed = 5;
+ int checkDelayMillis = 5 * 60 * 1000;
+ configureReliabilityConfigSettings(retriesAllowed, checkDelayMillis);
+
+ PackageVersions currentPackageVersions = new PackageVersions(1, 1);
+ PackageVersions newPackageVersions = new PackageVersions(2, 2);
+
+ // Simulate there being a newer version installed than the one recorded in storage.
+ configureValidApplications(currentPackageVersions);
+
+ // Force the storage into a state we want.
+ mPackageStatusStorage.forceCheckStateForTests(
+ PackageStatus.CHECK_COMPLETED_SUCCESS, currentPackageVersions);
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, currentPackageVersions);
+
+ // Simulate a package update trigger.
+ simulatePackageInstallation(newPackageVersions);
+
+ // Assert the PackageTracker did trigger an update.
+ checkUpdateCheckTriggered(newPackageVersions);
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+
+ // Increment the clock, but not enough.
+ mFakeClock.incrementClock(checkDelayMillis - 1);
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did not trigger an update.
+ mFakeIntentHelper.assertUpdateNotTriggered();
+ checkPackageStorageStatus(PackageStatus.CHECK_STARTED, newPackageVersions);
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+ // Increment the clock slightly more. Should now consider the response overdue.
+ mFakeClock.incrementClock(2);
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Triggering should have happened.
+ checkUpdateCheckTriggered(newPackageVersions);
+ CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+
+ // Check a new token was generated.
+ assertFalse(token1.equals(token2));
+ }
+
+ private void simulatePackageInstallation(PackageVersions packageVersions) throws Exception {
+ configureApplicationsValidManifests(packageVersions);
+
+ // Simulate a tracked package being updated.
+ mFakeIntentHelper.simulatePackageUpdatedEvent();
+ }
+
+ /**
+ * Checks an update check was triggered, reliability triggering is therefore enabled and the
+ * storage state reflects that there is a check in progress.
+ */
+ private void checkUpdateCheckTriggered(PackageVersions packageVersions) {
+ // Assert the PackageTracker attempted to trigger an update.
+ mFakeIntentHelper.assertUpdateTriggered();
+
+ // If an update check was triggered reliability triggering should always be enabled to
+ // ensure that it can be completed if it fails.
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+ // Check the expected storage state.
+ checkPackageStorageStatus(PackageStatus.CHECK_STARTED, packageVersions);
+ }
+
+ private void checkUpdateCheckFailed(PackageVersions packageVersions) {
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+ // Assert the storage was updated.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, packageVersions);
+ }
+
+ private void checkUpdateCheckSuccessful(PackageVersions packageVersions) {
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+ // Assert the storage was updated.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+
+ // Peek inside the package tracker to make sure it is tracking failure counts properly.
+ assertEquals(0, mPackageTracker.getCheckFailureCountForTests());
+ }
+
+ private PackageVersions configureValidApplications() throws Exception {
+ configureValidApplications(INITIAL_APP_PACKAGE_VERSIONS);
+ return INITIAL_APP_PACKAGE_VERSIONS;
+ }
+
+ private void configureValidApplications(PackageVersions versions) throws Exception {
+ configureUpdateAppPackageOk(UPDATE_APP_PACKAGE_NAME);
+ configureDataAppPackageOk(DATA_APP_PACKAGE_NAME);
+ configureApplicationsValidManifests(versions);
+ }
+
+ private void configureApplicationsValidManifests(PackageVersions versions) throws Exception {
+ configureUpdateAppManifestOk(UPDATE_APP_PACKAGE_NAME);
+ configureDataAppManifestOk(DATA_APP_PACKAGE_NAME);
+ configureUpdateAppPackageVersion(UPDATE_APP_PACKAGE_NAME, versions.mUpdateAppVersion);
+ configureDataAppPackageVersion(DATA_APP_PACKAGE_NAME, versions.mDataAppVersion);
+ }
+
+ private void configureUpdateAppPackageVersion(String updateAppPackageName,
+ int updataAppPackageVersion) throws Exception {
+ when(mMockPackageManagerHelper.getInstalledPackageVersion(updateAppPackageName))
+ .thenReturn(updataAppPackageVersion);
+ }
+
+ private void configureDataAppPackageVersion(String dataAppPackageName,
+ int dataAppPackageVersion) throws Exception {
+ when(mMockPackageManagerHelper.getInstalledPackageVersion(dataAppPackageName))
+ .thenReturn(dataAppPackageVersion);
+ }
+
+ private void configureUpdateAppManifestOk(String updateAppPackageName) throws Exception {
+ Intent expectedIntent = RulesUpdaterContract.createUpdaterIntent(updateAppPackageName);
+ when(mMockPackageManagerHelper.receiverRegistered(
+ filterEquals(expectedIntent),
+ eq(RulesUpdaterContract.TRIGGER_TIME_ZONE_RULES_CHECK_PERMISSION)))
+ .thenReturn(true);
+ when(mMockPackageManagerHelper.usesPermission(
+ updateAppPackageName, RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION))
+ .thenReturn(true);
+ }
+
+ private void configureUpdateAppManifestBad(String updateAppPackageName) throws Exception {
+ Intent expectedIntent = RulesUpdaterContract.createUpdaterIntent(updateAppPackageName);
+ when(mMockPackageManagerHelper.receiverRegistered(
+ filterEquals(expectedIntent),
+ eq(RulesUpdaterContract.TRIGGER_TIME_ZONE_RULES_CHECK_PERMISSION)))
+ .thenReturn(false);
+ // Has permission, but that shouldn't matter if the check above is false.
+ when(mMockPackageManagerHelper.usesPermission(
+ updateAppPackageName, RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION))
+ .thenReturn(true);
+ }
+
+ private void configureDataAppManifestOk(String dataAppPackageName) throws Exception {
+ when(mMockPackageManagerHelper.contentProviderRegistered(
+ TimeZoneRulesDataContract.AUTHORITY, dataAppPackageName))
+ .thenReturn(true);
+ }
+
+ private void configureDataAppManifestBad(String dataAppPackageName) throws Exception {
+ // Simulate the data app not exposing the content provider we require.
+ when(mMockPackageManagerHelper.contentProviderRegistered(
+ TimeZoneRulesDataContract.AUTHORITY, dataAppPackageName))
+ .thenReturn(false);
+ }
+
+ private void configureTrackingEnabled() {
+ when(mMockConfigHelper.isTrackingEnabled()).thenReturn(true);
+ }
+
+ private void configureTrackingDisabled() {
+ when(mMockConfigHelper.isTrackingEnabled()).thenReturn(false);
+ }
+
+ private void configureReliabilityConfigSettings(int retriesAllowed, int checkDelayMillis) {
+ when(mMockConfigHelper.getFailedCheckRetryCount()).thenReturn(retriesAllowed);
+ when(mMockConfigHelper.getCheckTimeAllowedMillis()).thenReturn(checkDelayMillis);
+ }
+
+ private void configureReliabilityConfigSettingsOk() {
+ configureReliabilityConfigSettings(5, 5 * 60 * 1000);
+ }
+
+ private void configureUpdateAppPackageOk(String updateAppPackageName) throws Exception {
+ when(mMockConfigHelper.getUpdateAppPackageName()).thenReturn(updateAppPackageName);
+ when(mMockPackageManagerHelper.isPrivilegedApp(updateAppPackageName)).thenReturn(true);
+ }
+
+ private void configureUpdateAppPackageNotPrivileged(String updateAppPackageName)
+ throws Exception {
+ when(mMockConfigHelper.getUpdateAppPackageName()).thenReturn(updateAppPackageName);
+ when(mMockPackageManagerHelper.isPrivilegedApp(updateAppPackageName)).thenReturn(false);
+ }
+
+ private void configureUpdateAppPackageNameMissing() {
+ when(mMockConfigHelper.getUpdateAppPackageName()).thenReturn(null);
+ }
+
+ private void configureDataAppPackageOk(String dataAppPackageName) throws Exception {
+ when(mMockConfigHelper.getDataAppPackageName()).thenReturn(dataAppPackageName);
+ when(mMockPackageManagerHelper.isPrivilegedApp(dataAppPackageName)).thenReturn(true);
+ }
+
+ private void configureDataAppPackageNotPrivileged(String dataAppPackageName)
+ throws Exception {
+ when(mMockConfigHelper.getUpdateAppPackageName()).thenReturn(dataAppPackageName);
+ when(mMockPackageManagerHelper.isPrivilegedApp(dataAppPackageName)).thenReturn(false);
+ }
+
+ private void configureDataAppPackageNameMissing() {
+ when(mMockConfigHelper.getDataAppPackageName()).thenThrow(new RuntimeException());
+ }
+
+ private void checkIntentHelperInitializedAndReliabilityTrackingEnabled() {
+ // Verify that calling start initialized the IntentHelper as well.
+ mFakeIntentHelper.assertInitialized(UPDATE_APP_PACKAGE_NAME, DATA_APP_PACKAGE_NAME);
+
+ // Assert that reliability tracking is always enabled after initialization.
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+ }
+
+ private void checkPackageStorageStatus(
+ int expectedCheckStatus, PackageVersions expectedPackageVersions) {
+ PackageStatus packageStatus = mPackageStatusStorage.getPackageStatus();
+ assertEquals(expectedCheckStatus, packageStatus.mCheckStatus);
+ assertEquals(expectedPackageVersions, packageStatus.mVersions);
+ }
+
+ private void checkPackageStorageStatusIsInitialOrReset() {
+ assertNull(mPackageStatusStorage.getPackageStatus());
+ }
+
+ private static CheckToken createArbitraryCheckToken() {
+ return new CheckToken(1, INITIAL_APP_PACKAGE_VERSIONS);
+ }
+
+ /**
+ * A fake IntentHelper implementation for use in tests.
+ */
+ private static class FakeIntentHelper implements IntentHelper {
+
+ private Listener mListener;
+ private String mUpdateAppPackageName;
+ private String mDataAppPackageName;
+
+ private CheckToken mLastToken;
+
+ private boolean mReliabilityTriggeringEnabled;
+
+ @Override
+ public void initialize(String updateAppPackageName, String dataAppPackageName,
+ Listener listener) {
+ assertNotNull(updateAppPackageName);
+ assertNotNull(dataAppPackageName);
+ assertNotNull(listener);
+ mListener = listener;
+ mUpdateAppPackageName = updateAppPackageName;
+ mDataAppPackageName = dataAppPackageName;
+ }
+
+ public void assertInitialized(
+ String expectedUpdateAppPackageName, String expectedDataAppPackageName) {
+ assertNotNull(mListener);
+ assertEquals(expectedUpdateAppPackageName, mUpdateAppPackageName);
+ assertEquals(expectedDataAppPackageName, mDataAppPackageName);
+ }
+
+ public void assertNotInitialized() {
+ assertNull(mListener);
+ }
+
+ @Override
+ public void sendTriggerUpdateCheck(CheckToken checkToken) {
+ if (mLastToken != null) {
+ fail("lastToken already set");
+ }
+ mLastToken = checkToken;
+ }
+
+ @Override
+ public void enableReliabilityTriggering() {
+ mReliabilityTriggeringEnabled = true;
+ }
+
+ @Override
+ public void disableReliabilityTriggering() {
+ mReliabilityTriggeringEnabled = false;
+ }
+
+ public void assertReliabilityTriggeringEnabled() {
+ assertTrue(mReliabilityTriggeringEnabled);
+ }
+
+ public void assertReliabilityTriggeringDisabled() {
+ assertFalse(mReliabilityTriggeringEnabled);
+ }
+
+ public void assertUpdateTriggered() {
+ assertNotNull(mLastToken);
+ }
+
+ public void assertUpdateNotTriggered() {
+ assertNull(mLastToken);
+ }
+
+ public CheckToken captureAndResetLastToken() {
+ CheckToken toReturn = mLastToken;
+ assertNotNull("No update triggered", toReturn);
+ mLastToken = null;
+ return toReturn;
+ }
+
+ public void simulatePackageUpdatedEvent() {
+ mListener.triggerUpdateIfNeeded(true);
+ }
+
+ public void simulateReliabilityTrigger() {
+ mListener.triggerUpdateIfNeeded(false);
+ }
+ }
+
+ private static class FakeClockHelper implements ClockHelper {
+
+ private long currentTime = 1000;
+
+ @Override
+ public long currentTimestamp() {
+ return currentTime;
+ }
+
+ public void incrementClock(long millis) {
+ currentTime += millis;
+ }
+ }
+
+ /**
+ * Registers a mockito parameter matcher that uses {@link Intent#filterEquals(Intent)}. to
+ * check the parameter against the intent supplied.
+ */
+ private static Intent filterEquals(final Intent expected) {
+ final Matcher<Intent> m = new BaseMatcher<Intent>() {
+ @Override
+ public boolean matches(Object actual) {
+ return actual != null && expected.filterEquals((Intent) actual);
+ }
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(expected.toString());
+ }
+ };
+ return argThat(m);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageVersionsTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageVersionsTest.java
new file mode 100644
index 0000000..a470f8f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageVersionsTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import org.junit.Test;
+
+import android.support.test.filters.SmallTest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+@SmallTest
+public class PackageVersionsTest {
+
+ @Test
+ public void equals() {
+ PackageVersions baseline =
+ new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+ assertEquals(baseline, baseline);
+
+ PackageVersions deepEqual =
+ new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+ assertEquals(baseline, deepEqual);
+
+ PackageVersions differentUpdateAppVersion =
+ new PackageVersions(2 /* updateAppVersion */, 1 /* dataAppVersion */);
+ assertFalse(baseline.equals(differentUpdateAppVersion));
+
+ PackageVersions differentDataAppVersion =
+ new PackageVersions(1 /* updateAppVersion */, 2 /* dataAppVersion */);
+ assertFalse(baseline.equals(differentDataAppVersion));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
new file mode 100644
index 0000000..a7f4c99
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
@@ -0,0 +1,924 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import android.app.timezone.Callback;
+import android.app.timezone.DistroRulesVersion;
+import android.app.timezone.ICallback;
+import android.app.timezone.RulesManager;
+import android.app.timezone.RulesState;
+import android.os.ParcelFileDescriptor;
+
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import javax.annotation.Nullable;
+import libcore.tzdata.shared2.DistroVersion;
+import libcore.tzdata.shared2.StagedDistroOperation;
+import libcore.tzdata.update2.TimeZoneDistroInstaller;
+
+import static com.android.server.timezone.RulesManagerService.REQUIRED_UPDATER_PERMISSION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+/**
+ * White box interaction / unit testing of the {@link RulesManagerService}.
+ */
+public class RulesManagerServiceTest {
+
+ private RulesManagerService mRulesManagerService;
+
+ private FakeExecutor mFakeExecutor;
+ private PermissionHelper mMockPermissionHelper;
+ private FileDescriptorHelper mMockFileDescriptorHelper;
+ private PackageTracker mMockPackageTracker;
+ private TimeZoneDistroInstaller mMockTimeZoneDistroInstaller;
+
+ @Before
+ public void setUp() {
+ mFakeExecutor = new FakeExecutor();
+
+ mMockFileDescriptorHelper = mock(FileDescriptorHelper.class);
+ mMockPackageTracker = mock(PackageTracker.class);
+ mMockPermissionHelper = mock(PermissionHelper.class);
+ mMockTimeZoneDistroInstaller = mock(TimeZoneDistroInstaller.class);
+
+ mRulesManagerService = new RulesManagerService(
+ mMockPermissionHelper,
+ mFakeExecutor,
+ mMockFileDescriptorHelper,
+ mMockPackageTracker,
+ mMockTimeZoneDistroInstaller);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void getRulesState_noCallerPermission() throws Exception {
+ configureCallerDoesNotHavePermission();
+ mRulesManagerService.getRulesState();
+ }
+
+ @Test(expected = SecurityException.class)
+ public void requestInstall_noCallerPermission() throws Exception {
+ configureCallerDoesNotHavePermission();
+ mRulesManagerService.requestInstall(null, null, null);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void requestUninstall_noCallerPermission() throws Exception {
+ configureCallerDoesNotHavePermission();
+ mRulesManagerService.requestUninstall(null, null);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void requestNothing_noCallerPermission() throws Exception {
+ configureCallerDoesNotHavePermission();
+ mRulesManagerService.requestNothing(null, true);
+ }
+
+ @Test
+ public void getRulesState_systemRulesError() throws Exception {
+ configureDeviceCannotReadSystemRulesVersion();
+
+ assertNull(mRulesManagerService.getRulesState());
+ }
+
+ @Test
+ public void getRulesState_stagedInstall() throws Exception {
+ configureCallerHasPermission();
+
+ configureDeviceSystemRulesVersion("2016a");
+
+ DistroVersion stagedDistroVersion = new DistroVersion(
+ DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+ DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ "2016c",
+ 3);
+ configureStagedInstall(stagedDistroVersion);
+
+ DistroVersion installedDistroVersion = new DistroVersion(
+ DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+ DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ "2016b",
+ 4);
+ configureInstalledDistroVersion(installedDistroVersion);
+
+ DistroRulesVersion stagedDistroRulesVersion = new DistroRulesVersion(
+ stagedDistroVersion.rulesVersion, stagedDistroVersion.revision);
+ DistroRulesVersion installedDistroRulesVersion = new DistroRulesVersion(
+ installedDistroVersion.rulesVersion, installedDistroVersion.revision);
+ RulesState expectedRuleState = new RulesState(
+ "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+ false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, stagedDistroRulesVersion,
+ RulesState.DISTRO_STATUS_INSTALLED, installedDistroRulesVersion);
+ assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+ }
+
+ @Test
+ public void getRulesState_nothingStaged() throws Exception {
+ configureCallerHasPermission();
+
+ configureDeviceSystemRulesVersion("2016a");
+
+ configureNoStagedOperation();
+
+ DistroVersion installedDistroVersion = new DistroVersion(
+ DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+ DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ "2016b",
+ 4);
+ configureInstalledDistroVersion(installedDistroVersion);
+
+ DistroRulesVersion installedDistroRulesVersion = new DistroRulesVersion(
+ installedDistroVersion.rulesVersion, installedDistroVersion.revision);
+ RulesState expectedRuleState = new RulesState(
+ "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+ false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_INSTALLED, installedDistroRulesVersion);
+ assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+ }
+
+ @Test
+ public void getRulesState_uninstallStaged() throws Exception {
+ configureCallerHasPermission();
+
+ configureDeviceSystemRulesVersion("2016a");
+
+ configureStagedUninstall();
+
+ DistroVersion installedDistroVersion = new DistroVersion(
+ DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+ DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ "2016b",
+ 4);
+ configureInstalledDistroVersion(installedDistroVersion);
+
+ DistroRulesVersion installedDistroRulesVersion = new DistroRulesVersion(
+ installedDistroVersion.rulesVersion, installedDistroVersion.revision);
+ RulesState expectedRuleState = new RulesState(
+ "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+ false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_UNINSTALL, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_INSTALLED, installedDistroRulesVersion);
+ assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+ }
+
+ @Test
+ public void getRulesState_installedRulesError() throws Exception {
+ configureCallerHasPermission();
+
+ String systemRulesVersion = "2016a";
+ configureDeviceSystemRulesVersion(systemRulesVersion);
+
+ configureStagedUninstall();
+ configureDeviceCannotReadInstalledDistroVersion();
+
+ RulesState expectedRuleState = new RulesState(
+ "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+ false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_UNINSTALL, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
+ assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+ }
+
+ @Test
+ public void getRulesState_stagedRulesError() throws Exception {
+ configureCallerHasPermission();
+
+ String systemRulesVersion = "2016a";
+ configureDeviceSystemRulesVersion(systemRulesVersion);
+
+ configureDeviceCannotReadStagedDistroOperation();
+
+ DistroVersion installedDistroVersion = new DistroVersion(
+ DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+ DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ "2016b",
+ 4);
+ configureInstalledDistroVersion(installedDistroVersion);
+
+ DistroRulesVersion installedDistroRulesVersion = new DistroRulesVersion(
+ installedDistroVersion.rulesVersion, installedDistroVersion.revision);
+ RulesState expectedRuleState = new RulesState(
+ "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+ false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_INSTALLED, installedDistroRulesVersion);
+ assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+ }
+
+ @Test
+ public void getRulesState_noInstalledRules() throws Exception {
+ configureCallerHasPermission();
+
+ String systemRulesVersion = "2016a";
+ configureDeviceSystemRulesVersion(systemRulesVersion);
+ configureNoStagedOperation();
+ configureInstalledDistroVersion(null);
+
+ RulesState expectedRuleState = new RulesState(
+ systemRulesVersion, RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+ false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
+ assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+ }
+
+ @Test
+ public void getRulesState_operationInProgress() throws Exception {
+ configureCallerHasPermission();
+
+ String systemRulesVersion = "2016a";
+ String installedRulesVersion = "2016b";
+ int revision = 3;
+
+ configureDeviceSystemRulesVersion(systemRulesVersion);
+
+ DistroVersion installedDistroVersion = new DistroVersion(
+ DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+ DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ installedRulesVersion,
+ revision);
+ configureInstalledDistroVersion(installedDistroVersion);
+
+ byte[] expectedContent = createArbitraryBytes(1000);
+ ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+ configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+ // Start an async operation so there is one in progress. The mFakeExecutor won't actually
+ // execute it.
+ byte[] tokenBytes = createArbitraryTokenBytes();
+ ICallback callback = new StubbedCallback();
+
+ mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback);
+
+ RulesState expectedRuleState = new RulesState(
+ systemRulesVersion, RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+ true /* operationInProgress */,
+ RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
+ assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+ }
+
+ @Test
+ public void requestInstall_operationInProgress() throws Exception {
+ configureCallerHasPermission();
+
+ byte[] expectedContent = createArbitraryBytes(1000);
+ ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+ configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+ byte[] tokenBytes = createArbitraryTokenBytes();
+ ICallback callback = new StubbedCallback();
+
+ // First request should succeed.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback));
+
+ // Something async should be enqueued. Clear it but do not execute it so we can detect the
+ // second request does nothing.
+ mFakeExecutor.getAndResetLastCommand();
+
+ // Second request should fail.
+ assertEquals(RulesManager.ERROR_OPERATION_IN_PROGRESS,
+ mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback));
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+ }
+
+ @Test
+ public void requestInstall_badToken() throws Exception {
+ configureCallerHasPermission();
+
+ byte[] expectedContent = createArbitraryBytes(1000);
+ ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+ configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+ byte[] badTokenBytes = new byte[2];
+ ICallback callback = new StubbedCallback();
+
+ try {
+ mRulesManagerService.requestInstall(parcelFileDescriptor, badTokenBytes, callback);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+ }
+
+ @Test
+ public void requestInstall_nullParcelFileDescriptor() throws Exception {
+ configureCallerHasPermission();
+
+ ParcelFileDescriptor parcelFileDescriptor = null;
+ byte[] tokenBytes = createArbitraryTokenBytes();
+ ICallback callback = new StubbedCallback();
+
+ try {
+ mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback);
+ fail();
+ } catch (NullPointerException expected) {}
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+ }
+
+ @Test
+ public void requestInstall_nullCallback() throws Exception {
+ configureCallerHasPermission();
+
+ ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+ byte[] tokenBytes = createArbitraryTokenBytes();
+ ICallback callback = null;
+
+ try {
+ mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback);
+ fail();
+ } catch (NullPointerException expected) {}
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+ }
+
+ @Test
+ public void requestInstall_asyncSuccess() throws Exception {
+ configureCallerHasPermission();
+
+ ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+ byte[] expectedContent = createArbitraryBytes(1000);
+ configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+ CheckToken token = createArbitraryToken();
+ byte[] tokenBytes = token.toByteArray();
+
+ TestCallback callback = new TestCallback();
+
+ // Request the install.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback));
+
+ // Assert nothing has happened yet.
+ callback.assertNoResultReceived();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+
+ // Set up the installer.
+ configureStageInstallExpectation(expectedContent, TimeZoneDistroInstaller.INSTALL_SUCCESS);
+
+ // Simulate the async execution.
+ mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+ // Verify the expected calls were made to other components.
+ verifyStageInstallCalled(expectedContent);
+ verifyPackageTrackerCalled(token, true /* success */);
+
+ // Check the callback was called.
+ callback.assertResultReceived(Callback.SUCCESS);
+ }
+
+ @Test
+ public void requestInstall_nullTokenBytes() throws Exception {
+ configureCallerHasPermission();
+
+ ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+ byte[] expectedContent = createArbitraryBytes(1000);
+ configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+ TestCallback callback = new TestCallback();
+
+ // Request the install.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestInstall(
+ parcelFileDescriptor, null /* tokenBytes */, callback));
+
+ // Assert nothing has happened yet.
+ verifyNoInstallerCallsMade();
+ callback.assertNoResultReceived();
+
+ // Set up the installer.
+ configureStageInstallExpectation(expectedContent, TimeZoneDistroInstaller.INSTALL_SUCCESS);
+
+ // Simulate the async execution.
+ mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+ // Verify the expected calls were made to other components.
+ verifyStageInstallCalled(expectedContent);
+ verifyPackageTrackerCalled(null /* expectedToken */, true /* success */);
+
+ // Check the callback was received.
+ callback.assertResultReceived(Callback.SUCCESS);
+ }
+
+ @Test
+ public void requestInstall_asyncInstallFail() throws Exception {
+ configureCallerHasPermission();
+
+ byte[] expectedContent = createArbitraryBytes(1000);
+ ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+ configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+ CheckToken token = createArbitraryToken();
+ byte[] tokenBytes = token.toByteArray();
+
+ TestCallback callback = new TestCallback();
+
+ // Request the install.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback));
+
+ // Assert nothing has happened yet.
+ verifyNoInstallerCallsMade();
+ callback.assertNoResultReceived();
+
+ // Set up the installer.
+ configureStageInstallExpectation(
+ expectedContent, TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR);
+
+ // Simulate the async execution.
+ mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+ // Verify the expected calls were made to other components.
+ verifyStageInstallCalled(expectedContent);
+
+ // Validation failure is treated like a successful check: repeating it won't improve things.
+ boolean expectedSuccess = true;
+ verifyPackageTrackerCalled(token, expectedSuccess);
+
+ // Check the callback was received.
+ callback.assertResultReceived(Callback.ERROR_INSTALL_VALIDATION_ERROR);
+ }
+
+ @Test
+ public void requestInstall_asyncParcelFileDescriptorReadFail() throws Exception {
+ configureCallerHasPermission();
+
+ ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+ configureParcelFileDescriptorReadFailure(parcelFileDescriptor);
+
+ CheckToken token = createArbitraryToken();
+ byte[] tokenBytes = token.toByteArray();
+
+ TestCallback callback = new TestCallback();
+
+ // Request the install.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback));
+
+ // Simulate the async execution.
+ mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+ // Verify nothing else happened.
+ verifyNoInstallerCallsMade();
+
+ // A failure to read the ParcelFileDescriptor is treated as a failure. It might be the
+ // result of a file system error. This is a fairly arbitrary choice.
+ verifyPackageTrackerCalled(token, false /* success */);
+
+ verifyNoPackageTrackerCallsMade();
+
+ // Check the callback was received.
+ callback.assertResultReceived(Callback.ERROR_UNKNOWN_FAILURE);
+ }
+
+ @Test
+ public void requestUninstall_operationInProgress() throws Exception {
+ configureCallerHasPermission();
+
+ byte[] tokenBytes = createArbitraryTokenBytes();
+ ICallback callback = new StubbedCallback();
+
+ // First request should succeed.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestUninstall(tokenBytes, callback));
+
+ // Something async should be enqueued. Clear it but do not execute it so we can detect the
+ // second request does nothing.
+ mFakeExecutor.getAndResetLastCommand();
+
+ // Second request should fail.
+ assertEquals(RulesManager.ERROR_OPERATION_IN_PROGRESS,
+ mRulesManagerService.requestUninstall(tokenBytes, callback));
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+ }
+
+ @Test
+ public void requestUninstall_badToken() throws Exception {
+ configureCallerHasPermission();
+
+ byte[] badTokenBytes = new byte[2];
+ ICallback callback = new StubbedCallback();
+
+ try {
+ mRulesManagerService.requestUninstall(badTokenBytes, callback);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+ }
+
+ @Test
+ public void requestUninstall_nullCallback() throws Exception {
+ configureCallerHasPermission();
+
+ byte[] tokenBytes = createArbitraryTokenBytes();
+ ICallback callback = null;
+
+ try {
+ mRulesManagerService.requestUninstall(tokenBytes, callback);
+ fail();
+ } catch (NullPointerException expected) {}
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+ }
+
+ @Test
+ public void requestUninstall_asyncSuccess() throws Exception {
+ configureCallerHasPermission();
+
+ CheckToken token = createArbitraryToken();
+ byte[] tokenBytes = token.toByteArray();
+
+ TestCallback callback = new TestCallback();
+
+ // Request the uninstall.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestUninstall(tokenBytes, callback));
+
+ // Assert nothing has happened yet.
+ callback.assertNoResultReceived();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+
+ // Set up the installer.
+ configureStageUninstallExpectation(true /* success */);
+
+ // Simulate the async execution.
+ mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+ // Verify the expected calls were made to other components.
+ verifyStageUninstallCalled();
+ verifyPackageTrackerCalled(token, true /* success */);
+
+ // Check the callback was called.
+ callback.assertResultReceived(Callback.SUCCESS);
+ }
+
+ @Test
+ public void requestUninstall_nullTokenBytes() throws Exception {
+ configureCallerHasPermission();
+
+ TestCallback callback = new TestCallback();
+
+ // Request the uninstall.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestUninstall(null /* tokenBytes */, callback));
+
+ // Assert nothing has happened yet.
+ verifyNoInstallerCallsMade();
+ callback.assertNoResultReceived();
+
+ // Set up the installer.
+ configureStageUninstallExpectation(true /* success */);
+
+ // Simulate the async execution.
+ mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+ // Verify the expected calls were made to other components.
+ verifyStageUninstallCalled();
+ verifyPackageTrackerCalled(null /* expectedToken */, true /* success */);
+
+ // Check the callback was received.
+ callback.assertResultReceived(Callback.SUCCESS);
+ }
+
+ @Test
+ public void requestUninstall_asyncUninstallFail() throws Exception {
+ configureCallerHasPermission();
+
+ CheckToken token = createArbitraryToken();
+ byte[] tokenBytes = token.toByteArray();
+
+ TestCallback callback = new TestCallback();
+
+ // Request the uninstall.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestUninstall(tokenBytes, callback));
+
+ // Assert nothing has happened yet.
+ verifyNoInstallerCallsMade();
+ callback.assertNoResultReceived();
+
+ // Set up the installer.
+ configureStageUninstallExpectation(false /* success */);
+
+ // Simulate the async execution.
+ mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+ // Verify the expected calls were made to other components.
+ verifyStageUninstallCalled();
+ verifyPackageTrackerCalled(token, false /* success */);
+
+ // Check the callback was received.
+ callback.assertResultReceived(Callback.ERROR_UNKNOWN_FAILURE);
+ }
+
+ @Test
+ public void requestNothing_operationInProgressOk() throws Exception {
+ configureCallerHasPermission();
+
+ // Set up a parallel operation.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestUninstall(null, new StubbedCallback()));
+ // Something async should be enqueued. Clear it but do not execute it to simulate it still
+ // being in progress.
+ mFakeExecutor.getAndResetLastCommand();
+
+ CheckToken token = createArbitraryToken();
+ byte[] tokenBytes = token.toByteArray();
+
+ // Make the call.
+ mRulesManagerService.requestNothing(tokenBytes, true /* success */);
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+
+ // Verify the expected calls were made to other components.
+ verifyPackageTrackerCalled(token, true /* success */);
+ verifyNoInstallerCallsMade();
+ }
+
+ @Test
+ public void requestNothing_badToken() throws Exception {
+ configureCallerHasPermission();
+
+ byte[] badTokenBytes = new byte[2];
+
+ try {
+ mRulesManagerService.requestNothing(badTokenBytes, true /* success */);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+
+ // Assert no other calls were made.
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+ }
+
+ @Test
+ public void requestNothing() throws Exception {
+ configureCallerHasPermission();
+
+ CheckToken token = createArbitraryToken();
+ byte[] tokenBytes = token.toByteArray();
+
+ // Make the call.
+ mRulesManagerService.requestNothing(tokenBytes, false /* success */);
+
+ // Assert everything required was done.
+ verifyNoInstallerCallsMade();
+ verifyPackageTrackerCalled(token, false /* success */);
+ }
+
+ @Test
+ public void requestNothing_nullTokenBytes() throws Exception {
+ configureCallerHasPermission();
+
+ // Make the call.
+ mRulesManagerService.requestNothing(null /* tokenBytes */, true /* success */);
+
+ // Assert everything required was done.
+ verifyNoInstallerCallsMade();
+ verifyPackageTrackerCalled(null /* token */, true /* success */);
+ }
+
+ private void verifyNoPackageTrackerCallsMade() {
+ verifyNoMoreInteractions(mMockPackageTracker);
+ reset(mMockPackageTracker);
+ }
+
+ private void verifyPackageTrackerCalled(
+ CheckToken expectedCheckToken, boolean expectedSuccess) {
+ verify(mMockPackageTracker).recordCheckResult(expectedCheckToken, expectedSuccess);
+ reset(mMockPackageTracker);
+ }
+
+ private void configureCallerHasPermission() throws Exception {
+ doNothing()
+ .when(mMockPermissionHelper)
+ .enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+ }
+
+ private void configureCallerDoesNotHavePermission() {
+ doThrow(new SecurityException("Simulated permission failure"))
+ .when(mMockPermissionHelper)
+ .enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+ }
+
+ private void configureParcelFileDescriptorReadSuccess(ParcelFileDescriptor parcelFileDescriptor,
+ byte[] content) throws Exception {
+ when(mMockFileDescriptorHelper.readFully(parcelFileDescriptor)).thenReturn(content);
+ }
+
+ private void configureParcelFileDescriptorReadFailure(ParcelFileDescriptor parcelFileDescriptor)
+ throws Exception {
+ when(mMockFileDescriptorHelper.readFully(parcelFileDescriptor))
+ .thenThrow(new IOException("Simulated failure"));
+ }
+
+ private void configureStageInstallExpectation(byte[] expectedContent, int resultCode)
+ throws Exception {
+ when(mMockTimeZoneDistroInstaller.stageInstallWithErrorCode(eq(expectedContent)))
+ .thenReturn(resultCode);
+ }
+
+ private void configureStageUninstallExpectation(boolean success) throws Exception {
+ doReturn(success).when(mMockTimeZoneDistroInstaller).stageUninstall();
+ }
+
+ private void verifyStageInstallCalled(byte[] expectedContent) throws Exception {
+ verify(mMockTimeZoneDistroInstaller).stageInstallWithErrorCode(eq(expectedContent));
+ verifyNoMoreInteractions(mMockTimeZoneDistroInstaller);
+ reset(mMockTimeZoneDistroInstaller);
+ }
+
+ private void verifyStageUninstallCalled() throws Exception {
+ verify(mMockTimeZoneDistroInstaller).stageUninstall();
+ verifyNoMoreInteractions(mMockTimeZoneDistroInstaller);
+ reset(mMockTimeZoneDistroInstaller);
+ }
+
+ private void verifyNoInstallerCallsMade() {
+ verifyNoMoreInteractions(mMockTimeZoneDistroInstaller);
+ reset(mMockTimeZoneDistroInstaller);
+ }
+
+ private static byte[] createArbitraryBytes(int length) {
+ byte[] bytes = new byte[length];
+ for (int i = 0; i < length; i++) {
+ bytes[i] = (byte) i;
+ }
+ return bytes;
+ }
+
+ private byte[] createArbitraryTokenBytes() {
+ return createArbitraryToken().toByteArray();
+ }
+
+ private CheckToken createArbitraryToken() {
+ return new CheckToken(1, new PackageVersions(1, 1));
+ }
+
+ private ParcelFileDescriptor createFakeParcelFileDescriptor() {
+ return new ParcelFileDescriptor((ParcelFileDescriptor) null);
+ }
+
+ private void configureDeviceSystemRulesVersion(String systemRulesVersion) throws Exception {
+ when(mMockTimeZoneDistroInstaller.getSystemRulesVersion()).thenReturn(systemRulesVersion);
+ }
+
+ private void configureInstalledDistroVersion(@Nullable DistroVersion installedDistroVersion)
+ throws Exception {
+ when(mMockTimeZoneDistroInstaller.getInstalledDistroVersion())
+ .thenReturn(installedDistroVersion);
+ }
+
+ private void configureStagedInstall(DistroVersion stagedDistroVersion) throws Exception {
+ when(mMockTimeZoneDistroInstaller.getStagedDistroOperation())
+ .thenReturn(StagedDistroOperation.install(stagedDistroVersion));
+ }
+
+ private void configureStagedUninstall() throws Exception {
+ when(mMockTimeZoneDistroInstaller.getStagedDistroOperation())
+ .thenReturn(StagedDistroOperation.uninstall());
+ }
+
+ private void configureNoStagedOperation() throws Exception {
+ when(mMockTimeZoneDistroInstaller.getStagedDistroOperation()).thenReturn(null);
+ }
+
+ private void configureDeviceCannotReadStagedDistroOperation() throws Exception {
+ when(mMockTimeZoneDistroInstaller.getStagedDistroOperation())
+ .thenThrow(new IOException("Simulated failure"));
+ }
+
+ private void configureDeviceCannotReadSystemRulesVersion() throws Exception {
+ when(mMockTimeZoneDistroInstaller.getSystemRulesVersion())
+ .thenThrow(new IOException("Simulated failure"));
+ }
+
+ private void configureDeviceCannotReadInstalledDistroVersion() throws Exception {
+ when(mMockTimeZoneDistroInstaller.getInstalledDistroVersion())
+ .thenThrow(new IOException("Simulated failure"));
+ }
+
+ private static class FakeExecutor implements Executor {
+
+ private Runnable mLastCommand;
+
+ @Override
+ public void execute(Runnable command) {
+ assertNull(mLastCommand);
+ assertNotNull(command);
+ mLastCommand = command;
+ }
+
+ public Runnable getAndResetLastCommand() {
+ assertNotNull(mLastCommand);
+ Runnable toReturn = mLastCommand;
+ mLastCommand = null;
+ return toReturn;
+ }
+
+ public void simulateAsyncExecutionOfLastCommand() {
+ Runnable toRun = getAndResetLastCommand();
+ toRun.run();
+ }
+
+ public void assertNothingQueued() {
+ assertNull(mLastCommand);
+ }
+ }
+
+ private static class TestCallback extends ICallback.Stub {
+
+ private boolean mOnFinishedCalled;
+ private int mLastError;
+
+ @Override
+ public void onFinished(int error) {
+ assertFalse(mOnFinishedCalled);
+ mOnFinishedCalled = true;
+ mLastError = error;
+ }
+
+ public void assertResultReceived(int expectedResult) {
+ assertTrue(mOnFinishedCalled);
+ assertEquals(expectedResult, mLastError);
+ }
+
+ public void assertNoResultReceived() {
+ assertFalse(mOnFinishedCalled);
+ }
+ }
+
+ private static class StubbedCallback extends ICallback.Stub {
+ @Override
+ public void onFinished(int error) {
+ fail("Unexpected call");
+ }
+ }
+}
diff --git a/services/tests/shortcutmanagerutils/Android.mk b/services/tests/shortcutmanagerutils/Android.mk
index dc97599..c7657f6 100644
--- a/services/tests/shortcutmanagerutils/Android.mk
+++ b/services/tests/shortcutmanagerutils/Android.mk
@@ -20,7 +20,8 @@
$(call all-java-files-under, src)
LOCAL_JAVA_LIBRARIES := \
- mockito-target
+ mockito-target \
+ legacy-android-test
LOCAL_MODULE_TAGS := optional
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index 922f08d..926a606 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -339,6 +339,10 @@
return ret;
}
+ public static <T> T[] array(T... array) {
+ return array;
+ }
+
public static <T> List<T> list(T... array) {
return Arrays.asList(array);
}
diff --git a/services/usb/Android.mk b/services/usb/Android.mk
index 57bf57d..55bfccf 100644
--- a/services/usb/Android.mk
+++ b/services/usb/Android.mk
@@ -10,6 +10,7 @@
LOCAL_JAVA_LIBRARIES := services.core \
android.hidl.manager-V1.0-java
-LOCAL_STATIC_JAVA_LIBRARIES := android.hardware.usb-V1.0-java
+LOCAL_STATIC_JAVA_LIBRARIES := android.hardware.usb-V1.0-java \
+android.hardware.usb-V1.1-java
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 03d8241..d315b18 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -189,10 +189,10 @@
AudioSystem.DEVICE_OUT_USB_DEVICE);
if (DEBUG) {
Slog.i(TAG, "pre-call device:0x" + Integer.toHexString(device) +
- " addr:" + address + " name:" + audioDevice.mDeviceName);
+ " addr:" + address + " name:" + audioDevice.getDeviceName());
}
mAudioService.setWiredDeviceConnectionState(
- device, state, address, audioDevice.mDeviceName, TAG);
+ device, state, address, audioDevice.getDeviceName(), TAG);
}
// Capture Device
@@ -201,7 +201,7 @@
AudioSystem.DEVICE_IN_USB_ACCESSORY :
AudioSystem.DEVICE_IN_USB_DEVICE);
mAudioService.setWiredDeviceConnectionState(
- device, state, address, audioDevice.mDeviceName, TAG);
+ device, state, address, audioDevice.getDeviceName(), TAG);
}
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState");
@@ -329,10 +329,9 @@
UsbAudioDevice audioDevice =
new UsbAudioDevice(card, device, hasPlayback, hasCapture, deviceClass);
AlsaCardsParser.AlsaCardRecord cardRecord = mCardsParser.getCardRecordFor(card);
- audioDevice.mDeviceName = cardRecord.mCardName;
- audioDevice.mDeviceDescription = cardRecord.mCardDescription;
+ audioDevice.setDeviceNameAndDescription(cardRecord.mCardName, cardRecord.mCardDescription);
- notifyDeviceState(audioDevice, true);
+ notifyDeviceState(audioDevice, true /*enabled*/);
return audioDevice;
}
@@ -442,7 +441,7 @@
Slog.i(TAG, "USB Audio Device Removed: " + audioDevice);
if (audioDevice != null) {
if (audioDevice.mHasPlayback || audioDevice.mHasCapture) {
- notifyDeviceState(audioDevice, false);
+ notifyDeviceState(audioDevice, false /*enabled*/);
// if there any external devices left, select one of them
selectDefaultDevice();
@@ -461,9 +460,9 @@
if (enabled) {
mAccessoryAudioDevice = new UsbAudioDevice(card, device, true, false,
UsbAudioDevice.kAudioDeviceClass_External);
- notifyDeviceState(mAccessoryAudioDevice, true);
+ notifyDeviceState(mAccessoryAudioDevice, true /*enabled*/);
} else if (mAccessoryAudioDevice != null) {
- notifyDeviceState(mAccessoryAudioDevice, false);
+ notifyDeviceState(mAccessoryAudioDevice, false /*enabled*/);
mAccessoryAudioDevice = null;
}
}
@@ -508,6 +507,7 @@
//
// Logging
//
+ // called by UsbService.dump
public void dump(IndentingPrintWriter pw) {
pw.println("USB Audio Devices:");
for (UsbDevice device : mAudioDevices.keySet()) {
diff --git a/services/usb/java/com/android/server/usb/UsbAudioDevice.java b/services/usb/java/com/android/server/usb/UsbAudioDevice.java
index 70d1fc6..4b17dfe 100644
--- a/services/usb/java/com/android/server/usb/UsbAudioDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbAudioDevice.java
@@ -36,8 +36,8 @@
// This member is a combination of the above bit-flags
public final int mDeviceClass;
- public String mDeviceName = "";
- public String mDeviceDescription = "";
+ private String mDeviceName = "";
+ private String mDeviceDescription = "";
public UsbAudioDevice(int card, int device,
boolean hasPlayback, boolean hasCapture, int deviceClass) {
@@ -59,8 +59,19 @@
return sb.toString();
}
- public String toShortString() {
+ // called by logDevices
+ String toShortString() {
return "[card:" + mCard + " device:" + mDevice + " " + mDeviceName + "]";
}
+
+ String getDeviceName() {
+ return mDeviceName;
+ }
+
+ void setDeviceNameAndDescription(String deviceName, String deviceDescription) {
+ mDeviceName = deviceName;
+ mDeviceDescription = deviceDescription;
+ }
+
}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index ca74688..42272fd 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -30,6 +30,10 @@
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbConfiguration;
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
@@ -61,6 +65,8 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Scanner;
@@ -125,9 +131,11 @@
private static final int MSG_BOOT_COMPLETED = 4;
private static final int MSG_USER_SWITCHED = 5;
private static final int MSG_UPDATE_USER_RESTRICTIONS = 6;
- private static final int MSG_UPDATE_HOST_STATE = 7;
+ private static final int MSG_UPDATE_PORT_STATE = 7;
private static final int MSG_ACCESSORY_MODE_ENTER_TIMEOUT = 8;
private static final int MSG_UPDATE_CHARGING_STATE = 9;
+ private static final int MSG_UPDATE_HOST_STATE = 10;
+ private static final int MSG_LOCALE_CHANGED = 11;
private static final int AUDIO_MODE_SOURCE = 1;
@@ -168,6 +176,22 @@
private final UsbSettingsManager mSettingsManager;
private Intent mBroadcastedIntent;
private boolean mPendingBootBroadcast;
+ private static Set<Integer> sBlackListedInterfaces;
+
+ static {
+ sBlackListedInterfaces = new HashSet<Integer>();
+ sBlackListedInterfaces.add(UsbConstants.USB_CLASS_AUDIO);
+ sBlackListedInterfaces.add(UsbConstants.USB_CLASS_COMM);
+ sBlackListedInterfaces.add(UsbConstants.USB_CLASS_HID);
+ sBlackListedInterfaces.add(UsbConstants.USB_CLASS_PRINTER);
+ sBlackListedInterfaces.add(UsbConstants.USB_CLASS_MASS_STORAGE);
+ sBlackListedInterfaces.add(UsbConstants.USB_CLASS_HUB);
+ sBlackListedInterfaces.add(UsbConstants.USB_CLASS_CDC_DATA);
+ sBlackListedInterfaces.add(UsbConstants.USB_CLASS_CSCID);
+ sBlackListedInterfaces.add(UsbConstants.USB_CLASS_CONTENT_SEC);
+ sBlackListedInterfaces.add(UsbConstants.USB_CLASS_VIDEO);
+ sBlackListedInterfaces.add(UsbConstants.USB_CLASS_WIRELESS_CONTROLLER);
+ };
private class AdbSettingsObserver extends ContentObserver {
public AdbSettingsObserver() {
@@ -201,7 +225,7 @@
}
};
- private final BroadcastReceiver mHostReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mPortReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
UsbPort port = intent.getParcelableExtra(UsbManager.EXTRA_PORT);
@@ -219,6 +243,26 @@
}
};
+ private final BroadcastReceiver mHostReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Iterator devices = ((UsbManager) context.getSystemService(Context.USB_SERVICE))
+ .getDeviceList().entrySet().iterator();
+ if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
+ mHandler.sendMessage(MSG_UPDATE_HOST_STATE, devices, true);
+ } else {
+ mHandler.sendMessage(MSG_UPDATE_HOST_STATE, devices, false);
+ }
+ }
+ };
+
+ private final BroadcastReceiver mLanguageChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mHandler.sendEmptyMessage(MSG_LOCALE_CHANGED);
+ }
+ };
+
public UsbDeviceManager(Context context, UsbAlsaManager alsaManager,
UsbSettingsManager settingsManager) {
mContext = context;
@@ -243,10 +287,18 @@
if (secureAdbEnabled && !dataEncrypted) {
mDebuggingManager = new UsbDebuggingManager(context);
}
- mContext.registerReceiver(mHostReceiver,
+ mContext.registerReceiver(mPortReceiver,
new IntentFilter(UsbManager.ACTION_USB_PORT_CHANGED));
mContext.registerReceiver(mChargingReceiver,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+
+ IntentFilter filter =
+ new IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+ mContext.registerReceiver(mHostReceiver, filter);
+
+ mContext.registerReceiver(mLanguageChangedReceiver,
+ new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
}
private UsbProfileGroupSettingsManager getCurrentSettings() {
@@ -371,6 +423,8 @@
private boolean mSinkPower;
private boolean mConfigured;
private boolean mUsbDataUnlocked;
+ private boolean mAudioAccessoryConnected;
+ private boolean mAudioAccessorySupported;
private String mCurrentFunctions;
private boolean mCurrentFunctionsApplied;
private UsbAccessory mCurrentAccessory;
@@ -379,6 +433,8 @@
private int mCurrentUser = UserHandle.USER_NULL;
private boolean mUsbCharging;
private String mCurrentOemFunctions;
+ private boolean mHideUsbNotification;
+ private boolean mSupportsAllCombinations;
public UsbHandler(Looper looper) {
super(looper);
@@ -481,21 +537,16 @@
}
public void updateHostState(UsbPort port, UsbPortStatus status) {
- boolean hostConnected = status.getCurrentDataRole() == UsbPort.DATA_ROLE_HOST;
- boolean sourcePower = status.getCurrentPowerRole() == UsbPort.POWER_ROLE_SOURCE;
- boolean sinkPower = status.getCurrentPowerRole() == UsbPort.POWER_ROLE_SINK;
-
if (DEBUG) {
Slog.i(TAG, "updateHostState " + port + " status=" + status);
}
SomeArgs args = SomeArgs.obtain();
- args.argi1 = hostConnected ? 1 : 0;
- args.argi2 = sourcePower ? 1 : 0;
- args.argi3 = sinkPower ? 1 : 0;
+ args.arg1 = port;
+ args.arg2 = status;
- removeMessages(MSG_UPDATE_HOST_STATE);
- Message msg = obtainMessage(MSG_UPDATE_HOST_STATE, args);
+ removeMessages(MSG_UPDATE_PORT_STATE);
+ Message msg = obtainMessage(MSG_UPDATE_PORT_STATE, args);
// debounce rapid transitions of connect/disconnect on type-c ports
sendMessageDelayed(msg, UPDATE_DELAY);
}
@@ -540,7 +591,7 @@
}
setEnabledFunctions(oldFunctions, true, mUsbDataUnlocked);
- updateAdbNotification();
+ updateAdbNotification(false);
}
if (mDebuggingManager != null) {
@@ -560,7 +611,7 @@
if (usbDataUnlocked != mUsbDataUnlocked) {
mUsbDataUnlocked = usbDataUnlocked;
- updateUsbNotification();
+ updateUsbNotification(false);
forceRestart = true;
}
@@ -765,7 +816,7 @@
}
// send broadcast intent only if the USB state has changed
- if (!isUsbStateChanged(intent)) {
+ if (!isUsbStateChanged(intent) && !configChanged) {
if (DEBUG) {
Slog.d(TAG, "skip broadcasting " + intent + " extras: " + intent.getExtras());
}
@@ -840,8 +891,8 @@
mConnected = (msg.arg1 == 1);
mConfigured = (msg.arg2 == 1);
- updateUsbNotification();
- updateAdbNotification();
+ updateUsbNotification(false);
+ updateAdbNotification(false);
if (mBootCompleted) {
updateUsbStateBroadcastIfNeeded(false);
}
@@ -859,14 +910,31 @@
mPendingBootBroadcast = true;
}
break;
- case MSG_UPDATE_HOST_STATE:
+ case MSG_UPDATE_PORT_STATE:
SomeArgs args = (SomeArgs) msg.obj;
boolean prevHostConnected = mHostConnected;
- mHostConnected = (args.argi1 == 1);
- mSourcePower = (args.argi2 == 1);
- mSinkPower = (args.argi3 == 1);
+ UsbPort port = (UsbPort) args.arg1;
+ UsbPortStatus status = (UsbPortStatus) args.arg2;
+ mHostConnected = status.getCurrentDataRole() == UsbPort.DATA_ROLE_HOST;
+ mSourcePower = status.getCurrentPowerRole() == UsbPort.POWER_ROLE_SOURCE;
+ mSinkPower = status.getCurrentPowerRole() == UsbPort.POWER_ROLE_SINK;
+ mAudioAccessoryConnected =
+ (status.getCurrentMode() == UsbPort.MODE_AUDIO_ACCESSORY);
+ mAudioAccessorySupported = port.isModeSupported(UsbPort.MODE_AUDIO_ACCESSORY);
+ // Ideally we want to see if PR_SWAP and DR_SWAP is supported.
+ // But, this should be suffice, since, all four combinations are only supported
+ // when PR_SWAP and DR_SWAP are supported.
+ mSupportsAllCombinations = status.isRoleCombinationSupported(
+ UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_HOST)
+ && status.isRoleCombinationSupported(UsbPort.POWER_ROLE_SINK,
+ UsbPort.DATA_ROLE_HOST)
+ && status.isRoleCombinationSupported(UsbPort.POWER_ROLE_SOURCE,
+ UsbPort.DATA_ROLE_DEVICE)
+ && status.isRoleCombinationSupported(UsbPort.POWER_ROLE_SINK,
+ UsbPort.DATA_ROLE_HOST);
+
args.recycle();
- updateUsbNotification();
+ updateUsbNotification(false);
if (mBootCompleted) {
if (mHostConnected || prevHostConnected) {
updateUsbStateBroadcastIfNeeded(false);
@@ -877,7 +945,44 @@
break;
case MSG_UPDATE_CHARGING_STATE:
mUsbCharging = (msg.arg1 == 1);
- updateUsbNotification();
+ updateUsbNotification(false);
+ break;
+ case MSG_UPDATE_HOST_STATE:
+ Iterator devices = (Iterator) msg.obj;
+ boolean connected = (msg.arg1 == 1);
+
+ if (DEBUG) {
+ Slog.i(TAG, "HOST_STATE connected:" + connected);
+ }
+
+ if ((mHideUsbNotification && connected)
+ || (!mHideUsbNotification && !connected)) {
+ break;
+ }
+
+ mHideUsbNotification = false;
+ while (devices.hasNext()) {
+ Map.Entry pair = (Map.Entry) devices.next();
+ if (DEBUG) {
+ Slog.i(TAG, pair.getKey() + " = " + pair.getValue());
+ }
+ UsbDevice device = (UsbDevice) pair.getValue();
+ int configurationCount = device.getConfigurationCount() - 1;
+ while (configurationCount >= 0) {
+ UsbConfiguration config = device.getConfiguration(configurationCount);
+ configurationCount--;
+ int interfaceCount = config.getInterfaceCount() - 1;
+ while (interfaceCount >= 0) {
+ UsbInterface intrface = config.getInterface(interfaceCount);
+ interfaceCount--;
+ if (sBlackListedInterfaces.contains(intrface.getInterfaceClass())) {
+ mHideUsbNotification = true;
+ break;
+ }
+ }
+ }
+ }
+ updateUsbNotification(false);
break;
case MSG_ENABLE_ADB:
setAdbEnabled(msg.arg1 == 1);
@@ -895,10 +1000,14 @@
mCurrentFunctions, forceRestart, mUsbDataUnlocked && !forceRestart);
break;
case MSG_SYSTEM_READY:
- updateUsbNotification();
- updateAdbNotification();
+ updateUsbNotification(false);
+ updateAdbNotification(false);
updateUsbFunctions();
break;
+ case MSG_LOCALE_CHANGED:
+ updateAdbNotification(true);
+ updateUsbNotification(true);
+ break;
case MSG_BOOT_COMPLETED:
mBootCompleted = true;
if (mPendingBootBroadcast) {
@@ -949,15 +1058,21 @@
return mCurrentAccessory;
}
- private void updateUsbNotification() {
+ private void updateUsbNotification(boolean force) {
if (mNotificationManager == null || !mUseUsbNotification
- || ("0".equals(SystemProperties.get("persist.charging.notify")))) {
+ || ("0".equals(SystemProperties.get("persist.charging.notify")))
+ // Dont show the notification when connected to a USB peripheral
+ // and the link does not support PR_SWAP and DR_SWAP
+ || (mHideUsbNotification && !mSupportsAllCombinations)) {
return;
}
int id = 0;
int titleRes = 0;
Resources r = mContext.getResources();
- if (mConnected) {
+ if (mAudioAccessoryConnected && !mAudioAccessorySupported) {
+ titleRes = com.android.internal.R.string.usb_unsupported_audio_accessory_title;
+ id = SystemMessage.NOTE_USB_AUDIO_ACCESSORY_NOT_SUPPORTED;
+ } else if (mConnected) {
if (!mUsbDataUnlocked) {
if (mSourcePower) {
titleRes = com.android.internal.R.string.usb_supplying_notification_title;
@@ -996,7 +1111,7 @@
titleRes = com.android.internal.R.string.usb_charging_notification_title;
id = SystemMessage.NOTE_USB_CHARGING;
}
- if (id != mUsbNotificationId) {
+ if (id != mUsbNotificationId || force) {
// clear notification if title needs changing
if (mUsbNotificationId != 0) {
mNotificationManager.cancelAsUser(null, mUsbNotificationId,
@@ -1004,18 +1119,43 @@
mUsbNotificationId = 0;
}
if (id != 0) {
- CharSequence message = r.getText(
- com.android.internal.R.string.usb_notification_message);
+ CharSequence message;
CharSequence title = r.getText(titleRes);
+ PendingIntent pi;
+ String channel;
- Intent intent = Intent.makeRestartActivityTask(
- new ComponentName("com.android.settings",
- "com.android.settings.deviceinfo.UsbModeChooserActivity"));
- PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
- intent, 0, null, UserHandle.CURRENT);
+ if (titleRes
+ != com.android.internal.R.string
+ .usb_unsupported_audio_accessory_title) {
+ Intent intent = Intent.makeRestartActivityTask(
+ new ComponentName("com.android.settings",
+ "com.android.settings.deviceinfo.UsbModeChooserActivity"));
+ pi = PendingIntent.getActivityAsUser(mContext, 0,
+ intent, 0, null, UserHandle.CURRENT);
+ channel = SystemNotificationChannels.USB;
+ message = r.getText(
+ com.android.internal.R.string.usb_notification_message);
+ } else {
+ final Intent intent = new Intent();
+ intent.setClassName("com.android.settings",
+ "com.android.settings.HelpTrampoline");
+ intent.putExtra(Intent.EXTRA_TEXT,
+ "help_url_audio_accessory_not_supported");
+
+ if (mContext.getPackageManager().resolveActivity(intent, 0) != null) {
+ pi = PendingIntent.getActivity(mContext, 0, intent, 0);
+ } else {
+ pi = null;
+ }
+
+ channel = SystemNotificationChannels.ALERTS;
+ message = r.getText(
+ com.android.internal.R.string
+ .usb_unsupported_audio_accessory_message);
+ }
Notification notification =
- new Notification.Builder(mContext, SystemNotificationChannels.USB)
+ new Notification.Builder(mContext, channel)
.setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
.setWhen(0)
.setOngoing(true)
@@ -1029,6 +1169,7 @@
.setContentIntent(pi)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.build();
+
mNotificationManager.notifyAsUser(null, id, notification,
UserHandle.ALL);
mUsbNotificationId = id;
@@ -1036,13 +1177,19 @@
}
}
- private void updateAdbNotification() {
+ private void updateAdbNotification(boolean force) {
if (mNotificationManager == null) return;
final int id = SystemMessage.NOTE_ADB_ACTIVE;
final int titleRes = com.android.internal.R.string.adb_active_notification_title;
+
if (mAdbEnabled && mConnected) {
if ("0".equals(SystemProperties.get("persist.adb.notify"))) return;
+ if (force && mAdbNotificationShown) {
+ mAdbNotificationShown = false;
+ mNotificationManager.cancelAsUser(null, id, UserHandle.ALL);
+ }
+
if (!mAdbNotificationShown) {
Resources r = mContext.getResources();
CharSequence title = r.getText(titleRes);
@@ -1085,10 +1232,13 @@
private String getDefaultFunctions() {
String func = SystemProperties.get(getPersistProp(true),
UsbManager.USB_FUNCTION_NONE);
- if (UsbManager.USB_FUNCTION_NONE.equals(func)) {
- func = UsbManager.USB_FUNCTION_MTP;
+ // if ADB is enabled, reset functions to ADB
+ // else enable MTP as usual.
+ if (UsbManager.containsFunction(func, UsbManager.USB_FUNCTION_ADB)) {
+ return UsbManager.USB_FUNCTION_ADB;
+ } else {
+ return UsbManager.USB_FUNCTION_MTP;
}
- return func;
}
public void dump(IndentingPrintWriter pw) {
@@ -1104,6 +1254,9 @@
pw.println(" mSourcePower: " + mSourcePower);
pw.println(" mSinkPower: " + mSinkPower);
pw.println(" mUsbCharging: " + mUsbCharging);
+ pw.println(" mHideUsbNotification: " + mHideUsbNotification);
+ pw.println(" mAudioAccessoryConnected: " + mAudioAccessoryConnected);
+
try {
pw.println(" Kernel state: "
+ FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim());
@@ -1211,8 +1364,7 @@
newFunction = UsbManager.addFunction(overrideFunctions.second,
UsbManager.USB_FUNCTION_ADB);
} else {
- newFunction = UsbManager.addFunction(UsbManager.USB_FUNCTION_NONE,
- UsbManager.USB_FUNCTION_ADB);
+ newFunction = overrideFunctions.second;
}
Slog.d(TAG, "OEM USB override persisting: " + newFunction + "in prop: "
+ UsbDeviceManager.getPersistProp(false));
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 2729795..7e2496c 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -22,11 +22,12 @@
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
import android.hardware.usb.V1_0.IUsb;
-import android.hardware.usb.V1_0.IUsbCallback;
import android.hardware.usb.V1_0.PortRole;
import android.hardware.usb.V1_0.PortRoleType;
import android.hardware.usb.V1_0.PortStatus;
import android.hardware.usb.V1_0.Status;
+import android.hardware.usb.V1_1.IUsbCallback;
+import android.hardware.usb.V1_1.PortStatus_1_1;
import android.hidl.manager.V1_0.IServiceManager;
import android.hidl.manager.V1_0.IServiceNotification;
import android.os.Bundle;
@@ -454,6 +455,39 @@
return;
}
+
+ public void notifyPortStatusChange_1_1(ArrayList<PortStatus_1_1> currentPortStatus,
+ int retval) {
+ if (!portManager.mSystemReady) {
+ return;
+ }
+
+ if (retval != Status.SUCCESS) {
+ logAndPrint(Log.ERROR, pw, "port status enquiry failed");
+ return;
+ }
+
+ ArrayList<RawPortInfo> newPortInfo = new ArrayList<RawPortInfo>();
+
+ for (PortStatus_1_1 current : currentPortStatus) {
+ RawPortInfo temp = new RawPortInfo(current.status.portName,
+ current.supportedModes, current.currentMode,
+ current.status.canChangeMode, current.status.currentPowerRole,
+ current.status.canChangePowerRole,
+ current.status.currentDataRole, current.status.canChangeDataRole);
+ newPortInfo.add(temp);
+ logAndPrint(Log.INFO, pw, "ClientCallback: " + current.status.portName);
+ }
+
+ Message message = portManager.mHandler.obtainMessage();
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList(PORT_INFO, newPortInfo);
+ message.what = MSG_UPDATE_PORTS;
+ message.setData(bundle);
+ portManager.mHandler.sendMessage(message);
+ return;
+ }
+
public void notifyRoleSwitchStatus(String portName, PortRole role, int retval) {
if (retval == Status.SUCCESS) {
logAndPrint(Log.INFO, pw, portName + " role switch successful");
@@ -568,7 +602,7 @@
IndentingPrintWriter pw) {
// Only allow mode switch capability for dual role ports.
// Validate that the current mode matches the supported modes we expect.
- if (supportedModes != UsbPort.MODE_DUAL) {
+ if ((supportedModes & UsbPort.MODE_DUAL) != UsbPort.MODE_DUAL) {
canChangeMode = false;
if (currentMode != 0 && currentMode != supportedModes) {
logAndPrint(Log.WARN, pw, "Ignoring inconsistent current mode from USB "
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 1aa952cd..cb44471 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -300,7 +300,11 @@
// Load the model if it is not loaded.
if (!modelData.isModelLoaded()) {
- // Load the model
+ // Before we try and load this model, we should first make sure that any other
+ // models that don't have an active recognition/dead callback are unloaded. Since
+ // there is a finite limit on the number of models that the hardware may be able to
+ // have loaded, we want to make sure there's room for our model.
+ stopAndUnloadDeadModelsLocked();
int[] handle = new int[] { INVALID_VALUE };
int status = mModule.loadSoundModel(soundModel, handle);
if (status != SoundTrigger.STATUS_OK) {
@@ -899,7 +903,29 @@
}
}
+ /**
+ * Stops and unloads a sound model, and removes any reference to the model if successful.
+ *
+ * @param modelData The model data to remove.
+ * @param exception Optional exception to print in logcat. May be null.
+ */
private void forceStopAndUnloadModel(ModelData modelData, Exception exception) {
+ forceStopAndUnloadModel(modelData, exception, null /* modelDataIterator */);
+ }
+
+ /**
+ * Stops and unloads a sound model, and removes any reference to the model if successful.
+ *
+ * @param modelData The model data to remove.
+ * @param exception Optional exception to print in logcat. May be null.
+ * @param modelDataIterator If this function is to be used while iterating over the
+ * mModelDataMap, you can provide the iterator for the current model data to be used to
+ * remove the modelData from the map. This avoids generating a
+ * ConcurrentModificationException, since this function will try and remove the model
+ * data from the mModelDataMap when it can successfully unload the model.
+ */
+ private void forceStopAndUnloadModel(ModelData modelData, Exception exception,
+ Iterator modelDataIterator) {
if (exception != null) {
Slog.e(TAG, "forceStopAndUnloadModel", exception);
}
@@ -916,7 +942,11 @@
Slog.d(TAG, "Unloading previously loaded dangling model " + modelData.getHandle());
if (mModule.unloadSoundModel(modelData.getHandle()) == STATUS_OK) {
// Remove the model data from existence.
- mModelDataMap.remove(modelData.getModelId());
+ if (modelDataIterator != null) {
+ modelDataIterator.remove();
+ } else {
+ mModelDataMap.remove(modelData.getModelId());
+ }
Iterator it = mKeyphraseUuidMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
@@ -931,6 +961,23 @@
}
}
+ private void stopAndUnloadDeadModelsLocked() {
+ Iterator it = mModelDataMap.entrySet().iterator();
+ while (it.hasNext()) {
+ ModelData modelData = (ModelData) ((Map.Entry) it.next()).getValue();
+ if (!modelData.isModelLoaded()) {
+ continue;
+ }
+ if (modelData.getCallback() == null
+ || (modelData.getCallback().asBinder() != null
+ && !modelData.getCallback().asBinder().pingBinder())) {
+ // No one is listening on this model, so we might as well evict it.
+ Slog.w(TAG, "Removing model " + modelData.getHandle() + " that has no clients");
+ forceStopAndUnloadModel(modelData, null /* exception */, it);
+ }
+ }
+ }
+
private ModelData getOrCreateGenericModelDataLocked(UUID modelId) {
ModelData modelData = mModelDataMap.get(modelId);
if (modelData == null) {
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index f80ee73..c147578 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -132,6 +132,73 @@
public static final String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS =
"android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS";
+ /**
+ * Call event sent from a {@link Call} via {@link #sendCallEvent(String, Bundle)} to inform
+ * Telecom that the user has requested that the current {@link Call} should be handed over
+ * to another {@link ConnectionService}.
+ * <p>
+ * The caller must specify the {@link #EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE} to indicate to
+ * Telecom which {@link PhoneAccountHandle} the {@link Call} should be handed over to.
+ * @hide
+ */
+ public static final String EVENT_REQUEST_HANDOVER =
+ "android.telecom.event.REQUEST_HANDOVER";
+
+ /**
+ * Extra key used with the {@link #EVENT_REQUEST_HANDOVER} call event. Specifies the
+ * {@link PhoneAccountHandle} to which a call should be handed over to.
+ * @hide
+ */
+ public static final String EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE =
+ "android.telecom.extra.HANDOVER_PHONE_ACCOUNT_HANDLE";
+
+ /**
+ * Integer extra key used with the {@link #EVENT_REQUEST_HANDOVER} call event. Specifies the
+ * video state of the call when it is handed over to the new {@link PhoneAccount}.
+ * <p>
+ * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY},
+ * {@link VideoProfile#STATE_BIDIRECTIONAL}, {@link VideoProfile#STATE_RX_ENABLED}, and
+ * {@link VideoProfile#STATE_TX_ENABLED}.
+ * @hide
+ */
+ public static final String EXTRA_HANDOVER_VIDEO_STATE =
+ "android.telecom.extra.HANDOVER_VIDEO_STATE";
+
+ /**
+ * Extra key used with the {@link #EVENT_REQUEST_HANDOVER} call event. Used by the
+ * {@link InCallService} initiating a handover to provide a {@link Bundle} with extra
+ * information to the handover {@link ConnectionService} specified by
+ * {@link #EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE}.
+ * <p>
+ * This {@link Bundle} is not interpreted by Telecom, but passed as-is to the
+ * {@link ConnectionService} via the request extras when
+ * {@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}
+ * is called to initate the handover.
+ * @hide
+ */
+ public static final String EXTRA_HANDOVER_EXTRAS = "android.telecom.extra.HANDOVER_EXTRAS";
+
+ /**
+ * Call event sent from Telecom to the handover {@link ConnectionService} via
+ * {@link Connection#onCallEvent(String, Bundle)} to inform a {@link Connection} that a handover
+ * to the {@link ConnectionService} has completed successfully.
+ * <p>
+ * A handover is initiated with the {@link #EVENT_REQUEST_HANDOVER} call event.
+ * @hide
+ */
+ public static final String EVENT_HANDOVER_COMPLETE =
+ "android.telecom.event.HANDOVER_COMPLETE";
+ /**
+ * Call event sent from Telecom to the handover {@link ConnectionService} via
+ * {@link Connection#onCallEvent(String, Bundle)} to inform a {@link Connection} that a handover
+ * to the {@link ConnectionService} has failed.
+ * <p>
+ * A handover is initiated with the {@link #EVENT_REQUEST_HANDOVER} call event.
+ * @hide
+ */
+ public static final String EVENT_HANDOVER_FAILED =
+ "android.telecom.event.HANDOVER_FAILED";
+
public static class Details {
/** Call can currently be put on hold or unheld. */
@@ -1009,15 +1076,24 @@
* @return A string containing text sent by the remote user, or {@code null} if the
* conversation has been terminated or if there was an error while reading.
*/
- public String read() {
- try {
- int numRead = mReceiveStream.read(mReadBuffer, 0, READ_BUFFER_SIZE);
- if (numRead < 0) {
- return null;
- }
- return new String(mReadBuffer, 0, numRead);
- } catch (IOException e) {
- Log.w(this, "Exception encountered when reading from InputStreamReader: %s", e);
+ public String read() throws IOException {
+ int numRead = mReceiveStream.read(mReadBuffer, 0, READ_BUFFER_SIZE);
+ if (numRead < 0) {
+ return null;
+ }
+ return new String(mReadBuffer, 0, numRead);
+ }
+
+ /**
+ * Non-blocking version of {@link #read()}. Returns {@code null} if there is nothing to
+ * be read.
+ * @return A string containing text entered by the user, or {@code null} if the user has
+ * not entered any new text yet.
+ */
+ public String readImmediately() throws IOException {
+ if (mReceiveStream.ready()) {
+ return read();
+ } else {
return null;
}
}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index f373785..f5d37bb 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -24,6 +24,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.app.Notification;
import android.content.Intent;
import android.hardware.camera2.CameraManager;
@@ -395,9 +396,11 @@
public static final int PROPERTY_SELF_MANAGED = 1<<7;
/**
- * When set, indicates that a connection has an active RTT session associated with it.
+ * Set by the framework to indicate that a connection has an active RTT session associated with
+ * it.
* @hide
*/
+ @TestApi
public static final int PROPERTY_IS_RTT = 1 << 8;
//**********************************************************************************************
@@ -568,6 +571,24 @@
public static final String EVENT_CALL_REMOTELY_UNHELD =
"android.telecom.event.CALL_REMOTELY_UNHELD";
+ /**
+ * Connection event used to inform an {@link InCallService} which initiated a call handover via
+ * {@link Call#EVENT_REQUEST_HANDOVER} that the handover from this {@link Connection} has
+ * successfully completed.
+ * @hide
+ */
+ public static final String EVENT_HANDOVER_COMPLETE =
+ "android.telecom.event.HANDOVER_COMPLETE";
+
+ /**
+ * Connection event used to inform an {@link InCallService} which initiated a call handover via
+ * {@link Call#EVENT_REQUEST_HANDOVER} that the handover from this {@link Connection} has failed
+ * to complete.
+ * @hide
+ */
+ public static final String EVENT_HANDOVER_FAILED =
+ "android.telecom.event.HANDOVER_FAILED";
+
// Flag controlling whether PII is emitted into the logs
private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
@@ -811,6 +832,7 @@
* Provides methods to read and write RTT data to/from the in-call app.
* @hide
*/
+ @TestApi
public static final class RttTextStream {
private static final int READ_BUFFER_SIZE = 1000;
private final InputStreamReader mPipeFromInCall;
@@ -856,15 +878,24 @@
* @return A string containing text entered by the user, or {@code null} if the
* conversation has been terminated or if there was an error while reading.
*/
- public String read() {
- try {
- int numRead = mPipeFromInCall.read(mReadBuffer, 0, READ_BUFFER_SIZE);
- if (numRead < 0) {
- return null;
- }
- return new String(mReadBuffer, 0, numRead);
- } catch (IOException e) {
- Log.w(this, "Exception encountered when reading from InputStreamReader: %s", e);
+ public String read() throws IOException {
+ int numRead = mPipeFromInCall.read(mReadBuffer, 0, READ_BUFFER_SIZE);
+ if (numRead < 0) {
+ return null;
+ }
+ return new String(mReadBuffer, 0, numRead);
+ }
+
+ /**
+ * Non-blocking version of {@link #read()}. Returns {@code null} if there is nothing to
+ * be read.
+ * @return A string containing text entered by the user, or {@code null} if the user has
+ * not entered any new text yet.
+ */
+ public String readImmediately() throws IOException {
+ if (mPipeFromInCall.ready()) {
+ return read();
+ } else {
return null;
}
}
@@ -2524,7 +2555,9 @@
* {@link #onStartRtt(ParcelFileDescriptor, ParcelFileDescriptor)} has succeeded.
* @hide
*/
+ @TestApi
public final void sendRttInitiationSuccess() {
+ setRttProperty();
mListeners.forEach((l) -> l.onRttInitiationSuccess(Connection.this));
}
@@ -2537,7 +2570,9 @@
* exception of {@link RttModifyStatus#SESSION_MODIFY_REQUEST_SUCCESS}.
* @hide
*/
+ @TestApi
public final void sendRttInitiationFailure(int reason) {
+ unsetRttProperty();
mListeners.forEach((l) -> l.onRttInitiationFailure(Connection.this, reason));
}
@@ -2546,6 +2581,7 @@
* side of the coll.
* @hide
*/
+ @TestApi
public final void sendRttSessionRemotelyTerminated() {
mListeners.forEach((l) -> l.onRttSessionRemotelyTerminated(Connection.this));
}
@@ -2555,6 +2591,7 @@
* RTT session in the call.
* @hide
*/
+ @TestApi
public final void sendRemoteRttRequest() {
mListeners.forEach((l) -> l.onRemoteRttRequest(Connection.this));
}
@@ -2773,6 +2810,7 @@
* the in-call app.
* @hide
*/
+ @TestApi
public void onStartRtt(@NonNull RttTextStream rttTextStream) {}
/**
@@ -2780,6 +2818,7 @@
* channel. No response to Telecom is needed for this method.
* @hide
*/
+ @TestApi
public void onStopRtt() {}
/**
@@ -2791,8 +2830,25 @@
* @param rttTextStream The object that should be used to send text to or receive text from
* the in-call app.
*/
+ @TestApi
public void handleRttUpgradeResponse(@Nullable RttTextStream rttTextStream) {}
+ /**
+ * Internal method to set {@link #PROPERTY_IS_RTT}.
+ * @hide
+ */
+ void setRttProperty() {
+ setConnectionProperties(getConnectionProperties() | PROPERTY_IS_RTT);
+ }
+
+ /**
+ * Internal method to un-set {@link #PROPERTY_IS_RTT}.
+ * @hide
+ */
+ void unsetRttProperty() {
+ setConnectionProperties(getConnectionProperties() & (~PROPERTY_IS_RTT));
+ }
+
static String toLogSafePhoneNumber(String number) {
// For unknown number, log empty string.
if (number == null) {
diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java
index 054de4c..e169e5f 100644
--- a/telecomm/java/android/telecom/ConnectionRequest.java
+++ b/telecomm/java/android/telecom/ConnectionRequest.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.annotation.TestApi;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
@@ -311,6 +312,7 @@
* if this connection request is not requesting an RTT session upon connection establishment.
* @hide
*/
+ @TestApi
public Connection.RttTextStream getRttTextStream() {
if (isRequestingRtt()) {
return new Connection.RttTextStream(mRttPipeToInCall, mRttPipeFromInCall);
@@ -324,6 +326,7 @@
* @return {@code true} if RTT is requested, {@code false} otherwise.
* @hide
*/
+ @TestApi
public boolean isRequestingRtt() {
return mRttPipeFromInCall != null && mRttPipeToInCall != null;
}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index a9655d8..e3b027a 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -92,6 +92,24 @@
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE = "android.telecom.ConnectionService";
+ /**
+ * Boolean extra used by Telecom to inform a {@link ConnectionService} that the purpose of it
+ * being asked to create a new outgoing {@link Connection} is to perform a handover of an
+ * ongoing call on the device from another {@link PhoneAccount}/{@link ConnectionService}. Will
+ * be specified in the {@link ConnectionRequest#getExtras()} passed by Telecom when
+ * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)} is called.
+ * <p>
+ * When your {@link ConnectionService} receives this extra, it should communicate the fact that
+ * this is a handover to the other device's matching {@link ConnectionService}. That
+ * {@link ConnectionService} will continue the handover using
+ * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)}, specifying
+ * {@link TelecomManager#EXTRA_IS_HANDOVER}. Telecom will match the phone numbers of the
+ * handover call on the other device with ongoing calls for {@link ConnectionService}s which
+ * support {@link PhoneAccount#EXTRA_SUPPORTS_HANDOVER_FROM}.
+ * @hide
+ */
+ public static final String EXTRA_IS_HANDOVER = TelecomManager.EXTRA_IS_HANDOVER;
+
// Flag controlling whether PII is emitted into the logs
private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
@@ -1656,6 +1674,7 @@
Log.d(this, "stopRtt(%s)", callId);
if (mConnectionById.containsKey(callId)) {
findConnectionForAction(callId, "stopRtt").onStopRtt();
+ findConnectionForAction(callId, "stopRtt").unsetRttProperty();
} else if (mConferenceById.containsKey(callId)) {
Log.w(this, "stopRtt called on a conference.");
}
diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
index 6107895..640c9e1 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -269,6 +269,23 @@
}
/**
+ * Dumps the events in a timeline format.
+ * @param pw The {@link IndentingPrintWriter} to write to.
+ * @hide
+ */
+ public static void dumpEventsTimeline(IndentingPrintWriter pw) {
+ // If the Events logger has not been initialized, then there have been no events logged.
+ // Don't load it now!
+ synchronized (sSingletonSync) {
+ if (sEventManager != null) {
+ getEventManager().dumpEventsTimeline(pw);
+ } else {
+ pw.println("No Historical Events Logged.");
+ }
+ }
+ }
+
+ /**
* Enable or disable extended telecom logging.
*
* @param isExtendedLoggingEnabled {@code true} if extended logging should be enabled,
diff --git a/telecomm/java/android/telecom/Logging/EventManager.java b/telecomm/java/android/telecom/Logging/EventManager.java
index 2cd1b96..fddbfce 100644
--- a/telecomm/java/android/telecom/Logging/EventManager.java
+++ b/telecomm/java/android/telecom/Logging/EventManager.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.telecom.Log;
import android.text.TextUtils;
+import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
@@ -27,6 +28,7 @@
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.IllegalFormatException;
@@ -35,6 +37,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
+import java.util.stream.Collectors;
/**
* A utility class that provides the ability to define Events that a subsystem deems important, and
@@ -49,6 +52,7 @@
public static final String TAG = "Logging.Events";
@VisibleForTesting
public static final int DEFAULT_EVENTS_TO_CACHE = 10; // Arbitrarily chosen.
+ private final DateFormat sDateFormat = new SimpleDateFormat("HH:mm:ss.SSS");
public interface Loggable {
/**
@@ -169,7 +173,6 @@
}
}
- private final DateFormat sDateFormat = new SimpleDateFormat("HH:mm:ss.SSS");
private final List<Event> mEvents = new LinkedList<>();
private final Loggable mRecordEntry;
@@ -308,6 +311,41 @@
pw.decreaseIndent();
}
+ /**
+ * Dumps events in a timeline format.
+ * @param pw The {@link IndentingPrintWriter} to output the timeline to.
+ * @hide
+ */
+ public void dumpEventsTimeline(IndentingPrintWriter pw) {
+ pw.println("Historical Events (sorted by time):");
+
+ // Flatten event records out for sorting.
+ List<Pair<Loggable, Event>> events = new ArrayList<>();
+ for (EventRecord er : mEventRecords) {
+ for (Event ev : er.getEvents()) {
+ events.add(new Pair<>(er.getRecordEntry(), ev));
+ }
+ }
+
+ // Sort by event time.
+ Comparator<Pair<Loggable, Event>> byEventTime = (e1, e2) -> {
+ return Long.compare(e1.second.time, e2.second.time);
+ };
+ events.sort(byEventTime);
+
+ pw.increaseIndent();
+ for (Pair<Loggable, Event> event : events) {
+ pw.print(sDateFormat.format(new Date(event.second.time)));
+ pw.print(",");
+ pw.print(event.first.getId());
+ pw.print(",");
+ pw.print(event.second.eventId);
+ pw.print(",");
+ pw.println(event.second.data);
+ }
+ pw.decreaseIndent();
+ }
+
public void changeEventCacheSize(int newSize) {
// Resize the event queue.
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index c42a835..4390fae 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -43,6 +43,15 @@
/**
* {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which determines the
+ * sort order for {@link PhoneAccount}s from the same
+ * {@link android.telecom.ConnectionService}.
+ * @hide
+ */
+ public static final String EXTRA_SORT_ORDER =
+ "android.telecom.extra.SORT_ORDER";
+
+ /**
+ * {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which determines the
* maximum permitted length of a call subject specified via the
* {@link TelecomManager#EXTRA_CALL_SUBJECT} extra on an
* {@link android.content.Intent#ACTION_CALL} intent. Ultimately a {@link ConnectionService} is
@@ -68,6 +77,58 @@
"android.telecom.extra.CALL_SUBJECT_CHARACTER_ENCODING";
/**
+ * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
+ * indicates whether this {@link PhoneAccount} is capable of supporting a request to handover a
+ * connection (see {@link android.telecom.Call#EVENT_REQUEST_HANDOVER}) to this
+ * {@link PhoneAccount} from a {@link PhoneAccount} specifying
+ * {@link #EXTRA_SUPPORTS_HANDOVER_FROM}.
+ * <p>
+ * A handover request is initiated by the user from the default dialer app to indicate a desire
+ * to handover a call from one {@link PhoneAccount}/{@link ConnectionService} to another.
+ * @hide
+ */
+ public static final String EXTRA_SUPPORTS_HANDOVER_TO =
+ "android.telecom.extra.SUPPORTS_HANDOVER_TO";
+
+ /**
+ * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
+ * indicates whether this {@link PhoneAccount} supports using a fallback if video calling is
+ * not available. This extra is for device level support, {@link
+ * android.telephony.CarrierConfigManager#KEY_ALLOW_VIDEO_CALLING_FALLBACK_BOOL} should also
+ * be checked to ensure it is not disabled by individual carrier.
+ *
+ * @hide
+ */
+ public static final String EXTRA_SUPPORTS_VIDEO_CALLING_FALLBACK =
+ "android.telecom.extra.SUPPORTS_VIDEO_CALLING_FALLBACK";
+
+ /**
+ * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
+ * indicates whether this {@link PhoneAccount} is capable of supporting a request to handover a
+ * connection from this {@link PhoneAccount} to another {@link PhoneAccount}.
+ * (see {@link android.telecom.Call#EVENT_REQUEST_HANDOVER}) which specifies
+ * {@link #EXTRA_SUPPORTS_HANDOVER_TO}.
+ * <p>
+ * A handover request is initiated by the user from the default dialer app to indicate a desire
+ * to handover a call from one {@link PhoneAccount}/{@link ConnectionService} to another.
+ * @hide
+ */
+ public static final String EXTRA_SUPPORTS_HANDOVER_FROM =
+ "android.telecom.extra.SUPPORTS_HANDOVER_FROM";
+
+
+ /**
+ * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
+ * indicates whether a Self-Managed {@link PhoneAccount} should log its calls to the call log.
+ * <p>
+ * By default, Self-Managed {@link PhoneAccount}s do not log their calls to the call log.
+ * Setting this extra to {@code true} provides a means for them to log their calls.
+ * @hide
+ */
+ public static final String EXTRA_LOG_SELF_MANAGED_CALLS =
+ "android.telecom.extra.LOG_SELF_MANAGED_CALLS";
+
+ /**
* Flag indicating that this {@code PhoneAccount} can act as a connection manager for
* other connections. The {@link ConnectionService} associated with this {@code PhoneAccount}
* will be allowed to manage phone calls including using its own proprietary phone-call
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index d0b36c9..331328d 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -349,6 +349,35 @@
"android.telecom.extra.NEW_OUTGOING_CALL_CANCEL_TIMEOUT";
/**
+ * Boolean extra specified to indicate that the intention of adding a call is to handover an
+ * existing call from the user's device to a different {@link PhoneAccount}.
+ * <p>
+ * Used when calling {@link #addNewIncomingCall(PhoneAccountHandle, Bundle)}
+ * to indicate to Telecom that the purpose of adding a new incoming call is to handover an
+ * existing call from the user's device to a different {@link PhoneAccount}. This occurs on
+ * the receiving side of a handover.
+ * <p>
+ * Used when Telecom calls
+ * {@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}
+ * to indicate that the purpose of Telecom requesting a new outgoing connection it to request
+ * a handover to this {@link ConnectionService} from an ongoing call on the user's device. This
+ * occurs on the initiating side of a handover.
+ * <p>
+ * The phone number of the call used by Telecom to determine which call should be handed over.
+ * @hide
+ */
+ public static final String EXTRA_IS_HANDOVER = "android.telecom.extra.IS_HANDOVER";
+
+ /**
+ * Extra key specified in the {@link ConnectionRequest#getExtras()} when Telecom calls
+ * {@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}
+ * to inform the {@link ConnectionService} what the initial {@link CallAudioState} of the
+ * {@link Connection} will be.
+ * @hide
+ */
+ public static final String EXTRA_CALL_AUDIO_STATE = "android.telecom.extra.CALL_AUDIO_STATE";
+
+ /**
* A boolean extra, which when set on the {@link Intent#ACTION_CALL} intent or on the bundle
* passed into {@link #placeCall(Uri, Bundle)}, indicates that the call should be initiated with
* an RTT session open. See {@link android.telecom.Call.RttCall} for more information on RTT.
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index c6c1be3..56bc315 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -579,11 +579,19 @@
public static final String KEY_CARRIER_METERED_APN_TYPES_STRINGS =
"carrier_metered_apn_types_strings";
/**
- * Default APN types that are roamig-metered by the carrier
+ * Default APN types that are roaming-metered by the carrier
* @hide
*/
public static final String KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS =
"carrier_metered_roaming_apn_types_strings";
+
+ /**
+ * Default APN types that are metered on IWLAN by the carrier
+ * @hide
+ */
+ public static final String KEY_CARRIER_METERED_IWLAN_APN_TYPES_STRINGS =
+ "carrier_metered_iwlan_apn_types_strings";
+
/**
* CDMA carrier ERI (Enhanced Roaming Indicator) file name
* @hide
@@ -752,6 +760,14 @@
"display_hd_audio_property_bool";
/**
+ * Determines whether IMS conference calls are supported by a carrier. When {@code true},
+ * IMS conference calling is supported, {@code false} otherwise.
+ * @hide
+ */
+ public static final String KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL =
+ "support_ims_conference_call_bool";
+
+ /**
* Determines whether video conference calls are supported by a carrier. When {@code true},
* video calls can be merged into conference calls, {@code false} otherwiwse.
* <p>
@@ -785,6 +801,16 @@
public static final String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
/**
+ * String array for package names that need to be enabled for this carrier.
+ * If user has explicitly disabled some packages in the list, won't re-enable.
+ * Other carrier specific apps which are not in this list may be disabled for current carrier,
+ * and only be re-enabled when this config for another carrier includes it.
+ *
+ * @hide
+ */
+ public static final String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array";
+
+ /**
* Determine whether user can switch Wi-Fi preferred or Cellular preferred in calling preference.
* Some operators support Wi-Fi Calling only, not VoLTE.
* They don't need "Cellular preferred" option.
@@ -877,13 +903,6 @@
"broadcast_emergency_call_state_changes_bool";
/**
- * Cell broadcast additional channels enbled by the carrier
- * @hide
- */
- public static final String KEY_CARRIER_ADDITIONAL_CBS_CHANNELS_STRINGS =
- "carrier_additional_cbs_channels_strings";
-
- /**
* Indicates whether STK LAUNCH_BROWSER command is disabled.
* If {@code true}, then the browser will not be launched
* on UI for the LAUNCH_BROWSER STK command.
@@ -1107,6 +1126,8 @@
/** @hide */
public static final int CDMA_ROAMING_MODE_AFFILIATED = 1;
/** @hide */
+ public static final int IMSI_ENCRYPTION_DAYS_TIME_DISABLED = -1;
+ /** @hide */
public static final int CDMA_ROAMING_MODE_ANY = 2;
/**
* Boolean indicating if support is provided for directly dialing FDN number from FDN list.
@@ -1198,6 +1219,15 @@
public static final String KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO = "video_calls_can_be_hd_audio";
/**
+ * Whether system apps are allowed to use fallback if carrier video call is not available.
+ * Defaults to {@code true}.
+ *
+ * @hide
+ */
+ public static final String KEY_ALLOW_VIDEO_CALLING_FALLBACK_BOOL =
+ "allow_video_calling_fallback_bool";
+
+ /**
* Defines operator-specific {@link com.android.ims.ImsReasonInfo} mappings.
*
* Format: "ORIGINAL_CODE|MESSAGE|NEW_CODE"
@@ -1292,6 +1322,15 @@
"network_notification_delay_int";
/**
+ * When {@code true}, the carrier allows the user of the
+ * {@link TelephonyManager#sendUssdRequest(String, TelephonyManager.UssdResponseCallback,
+ * Handler)} API to perform USSD requests. {@code True} by default.
+ * @hide
+ */
+ public static final String KEY_ALLOW_USSD_REQUESTS_VIA_TELEPHONY_MANAGER_BOOL =
+ "allow_ussd_requests_via_telephony_manager_bool";
+
+ /**
* Indicates whether the carrier supports 3gpp call forwarding MMI codes while roaming. If
* false, the user will be notified that call forwarding is not available when the MMI code
* fails.
@@ -1415,6 +1454,23 @@
public static final String KEY_DISABLE_VOICE_BARRING_NOTIFICATION_BOOL =
"disable_voice_barring_notification_bool";
+ /**
+ * URL from which the proto containing the public key of the Carrier used for
+ * IMSI encryption will be downloaded.
+ * @hide
+ */
+ public static final String IMSI_KEY_DOWNLOAD_URL_STRING = "imsi_key_download_url_string";
+
+ /**
+ * Time in days, after which the key will expire, and a new key will need to be downloaded.
+ * default value is {@link IMSI_ENCRYPTION_DAYS_TIME_DISABLED}, and indicates that IMSI
+ * encryption is not enabled by default for a carrier. Value of 0 indicates that the key
+ * does not expire.
+ * @hide
+ */
+ public static final String IMSI_KEY_EXPIRATION_DAYS_TIME_INT =
+ "imsi_key_expiration_days_time_int";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -1520,6 +1576,9 @@
new String[]{"default", "mms", "dun", "supl"});
sDefaults.putStringArray(KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
new String[]{"default", "mms", "dun", "supl"});
+ // By default all APNs are unmetered if the device is on IWLAN.
+ sDefaults.putStringArray(KEY_CARRIER_METERED_IWLAN_APN_TYPES_STRINGS,
+ new String[]{});
sDefaults.putIntArray(KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY,
new int[]{
@@ -1542,6 +1601,7 @@
sDefaults.putInt(KEY_CDMA_DTMF_TONE_DELAY_INT, 100);
sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
+ sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL, false);
sDefaults.putBoolean(KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL, false);
sDefaults.putInt(KEY_IMS_CONFERENCE_SIZE_LIMIT_INT, 5);
@@ -1551,6 +1611,7 @@
sDefaults.putBoolean(KEY_HIDE_IMS_APN_BOOL, false);
sDefaults.putBoolean(KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL, false);
sDefaults.putBoolean(KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL, false);
+ sDefaults.putStringArray(KEY_ENABLE_APPS_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_EDITABLE_WFC_MODE_BOOL, true);
sDefaults.putStringArray(KEY_WFC_OPERATOR_ERROR_CODES_STRING_ARRAY, null);
sDefaults.putInt(KEY_WFC_SPN_FORMAT_IDX_INT, 0);
@@ -1638,6 +1699,7 @@
sDefaults.putBoolean(KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL, true);
sDefaults.putBoolean(KEY_WIFI_CALLS_CAN_BE_HD_AUDIO, true);
sDefaults.putBoolean(KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO, true);
+ sDefaults.putBoolean(KEY_ALLOW_VIDEO_CALLING_FALLBACK_BOOL, true);
sDefaults.putStringArray(KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_ENHANCED_4G_LTE_TITLE_VARIANT_BOOL, false);
@@ -1648,6 +1710,7 @@
sDefaults.putBoolean(KEY_PERSIST_LPP_MODE_BOOL, false);
sDefaults.putStringArray(KEY_CARRIER_WIFI_STRING_ARRAY, null);
sDefaults.putInt(KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT, -1);
+ sDefaults.putBoolean(KEY_ALLOW_USSD_REQUESTS_VIA_TELEPHONY_MANAGER_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL, true);
sDefaults.putBoolean(KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL, false);
sDefaults.putBoolean(KEY_EDITABLE_TETHER_APN_BOOL, false);
@@ -1656,6 +1719,8 @@
sDefaults.putInt(KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
sDefaults.putStringArray(KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_DISABLE_VOICE_BARRING_NOTIFICATION_BOOL, false);
+ sDefaults.putInt(IMSI_KEY_EXPIRATION_DAYS_TIME_INT, IMSI_ENCRYPTION_DAYS_TIME_DISABLED);
+ sDefaults.putString(IMSI_KEY_DOWNLOAD_URL_STRING, null);
}
/**
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index e53e246..9513517 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -252,6 +252,12 @@
*/
public static final int WIFI_LOST = 59;
+ /**
+ * The call has failed because of access class barring.
+ * {@hide}
+ */
+ public static final int IMS_ACCESS_BLOCKED = 60;
+
//*********************************************************************************************
// When adding a disconnect type:
// 1) Update toString() with the newly added disconnect type.
@@ -384,6 +390,8 @@
return "IMEI_NOT_ACCEPTED";
case WIFI_LOST:
return "WIFI_LOST";
+ case IMS_ACCESS_BLOCKED:
+ return "IMS_ACCESS_BLOCKED";
default:
return "INVALID: " + cause;
}
diff --git a/telephony/java/android/telephony/ImsiEncryptionInfo.aidl b/telephony/java/android/telephony/ImsiEncryptionInfo.aidl
new file mode 100644
index 0000000..080e30a
--- /dev/null
+++ b/telephony/java/android/telephony/ImsiEncryptionInfo.aidl
@@ -0,0 +1,19 @@
+/*
+* Copyright (C) 2017 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package android.telephony;
+
+parcelable ImsiEncryptionInfo;
diff --git a/telephony/java/android/telephony/ImsiEncryptionInfo.java b/telephony/java/android/telephony/ImsiEncryptionInfo.java
new file mode 100644
index 0000000..ecb9d25
--- /dev/null
+++ b/telephony/java/android/telephony/ImsiEncryptionInfo.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+
+/**
+ * Class to represent information sent by the carrier, which will be used to encrypt
+ * the IMSI + IMPI. The ecryption is being done by WLAN, and the modem.
+ *
+ * @hide
+ */
+public final class ImsiEncryptionInfo implements Parcelable {
+
+ private static final String LOG_TAG = "ImsiEncryptionInfo";
+ private static final boolean DBG = false;
+
+
+ private final String mcc;
+ private final String mnc;
+ private final PublicKey publicKey;
+ private final String keyIdentifier;
+ private final int keyType;
+
+ public ImsiEncryptionInfo(String mcc, String mnc, int keyType, String keyIdentifier,
+ PublicKey publicKey) {
+ this.mcc = mcc;
+ this.mnc = mnc;
+ this.keyType = keyType;
+ this.publicKey = publicKey;
+ this.keyIdentifier = keyIdentifier;
+ }
+
+ public ImsiEncryptionInfo(Parcel in) {
+ int length = in.readInt();
+ byte b[] = new byte[length];
+ in.readByteArray(b);
+ publicKey = makeKeyObject(b);
+ mcc = in.readString();
+ mnc = in.readString();
+ keyIdentifier = in.readString();
+ keyType = in.readInt();
+
+ }
+
+ public String getMnc() {
+ return this.mnc;
+ }
+
+ public String getMcc() {
+ return this.mcc;
+ }
+
+ public String getKeyIdentifier() {
+ return this.keyIdentifier;
+ }
+
+ public int getKeyType() {
+ return this.keyType;
+ }
+
+ public PublicKey getPublicKey() {
+ return this.publicKey;
+ }
+
+ private static PublicKey makeKeyObject(byte[] publicKeyBytes) {
+ try {
+ X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(publicKeyBytes);
+ return KeyFactory.getInstance("RSA").generatePublic(pubKeySpec);
+ } catch (InvalidKeySpecException | NoSuchAlgorithmException ex) {
+ Log.e(LOG_TAG, "Error makeKeyObject: unable to convert into PublicKey", ex);
+ }
+ return null;
+ }
+
+ /** Implement the Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<ImsiEncryptionInfo> CREATOR =
+ new Parcelable.Creator<ImsiEncryptionInfo>() {
+ @Override
+ public ImsiEncryptionInfo createFromParcel(Parcel in) {
+ return new ImsiEncryptionInfo(in);
+ }
+
+ @Override
+ public ImsiEncryptionInfo[] newArray(int size) {
+ return new ImsiEncryptionInfo[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ byte[] b = publicKey.getEncoded();
+ dest.writeInt(b.length);
+ dest.writeByteArray(b);
+ dest.writeString(mcc);
+ dest.writeString(mnc);
+ dest.writeString(keyIdentifier);
+ dest.writeInt(keyType);
+ }
+
+ @Override
+ public String toString(){
+ return "[ImsiEncryptionInfo "
+ + "mcc=" + mcc
+ + "mnc=" + mnc
+ + "publicKey=" + publicKey
+ + ", keyIdentifier=" + keyIdentifier
+ + ", keyType=" + keyType
+ + "]";
+ }
+}
diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java
new file mode 100644
index 0000000..a9ec299
--- /dev/null
+++ b/telephony/java/android/telephony/MbmsDownloadManager.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.telephony.mbms.IDownloadCallback;
+import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.DownloadStatus;
+import android.telephony.mbms.IMbmsDownloadManagerCallback;
+import android.telephony.mbms.MbmsException;
+import android.telephony.mbms.vendor.IMbmsDownloadService;
+import android.util.Log;
+
+import java.util.List;
+
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+/** @hide */
+public class MbmsDownloadManager {
+ private static final String LOG_TAG = MbmsDownloadManager.class.getSimpleName();
+
+ /**
+ * The MBMS middleware should send this when a download of single file has completed or
+ * failed. Mandatory extras are
+ * {@link #EXTRA_RESULT}
+ * {@link #EXTRA_INFO}
+ * {@link #EXTRA_REQUEST}
+ * {@link #EXTRA_TEMP_LIST}
+ * {@link #EXTRA_FINAL_URI}
+ *
+ * TODO: future systemapi
+ */
+ public static final String ACTION_DOWNLOAD_RESULT_INTERNAL =
+ "android.telephony.mbms.action.DOWNLOAD_RESULT_INTERNAL";
+
+ /**
+ * The MBMS middleware should send this when it wishes to request {@code content://} URIs to
+ * serve as temp files for downloads or when it wishes to resume paused downloads. Mandatory
+ * extras are
+ * {@link #EXTRA_REQUEST}
+ *
+ * Optional extras are
+ * {@link #EXTRA_FD_COUNT} (0 if not present)
+ * {@link #EXTRA_PAUSED_LIST} (empty if not present)
+ *
+ * TODO: future systemapi
+ */
+ public static final String ACTION_FILE_DESCRIPTOR_REQUEST =
+ "android.telephony.mbms.action.FILE_DESCRIPTOR_REQUEST";
+
+ /**
+ * The MBMS middleware should send this when it wishes to clean up temp files in the app's
+ * filesystem. Mandatory extras are:
+ * {@link #EXTRA_TEMP_FILES_IN_USE}
+ *
+ * TODO: future systemapi
+ */
+ public static final String ACTION_CLEANUP =
+ "android.telephony.mbms.action.CLEANUP";
+
+ /**
+ * Integer extra indicating the result code of the download.
+ * TODO: put in link to error list
+ * TODO: future systemapi (here and and all extras)
+ */
+ public static final String EXTRA_RESULT = "android.telephony.mbms.extra.RESULT";
+
+ /**
+ * Extra containing the {@link android.telephony.mbms.FileInfo} for which the download result
+ * is for. Must not be null.
+ */
+ public static final String EXTRA_INFO = "android.telephony.mbms.extra.INFO";
+
+ /**
+ * Extra containing the {@link DownloadRequest} for which the download result or file
+ * descriptor request is for. Must not be null.
+ */
+ public static final String EXTRA_REQUEST = "android.telephony.mbms.extra.REQUEST";
+
+ /**
+ * Extra containing a {@link List} of {@link Uri}s that were used as temp files for this
+ * completed file. These {@link Uri}s should have scheme {@code file://}, and the temp
+ * files will be deleted upon receipt of the intent.
+ * May be null.
+ */
+ public static final String EXTRA_TEMP_LIST = "android.telephony.mbms.extra.TEMP_LIST";
+
+ /**
+ * Extra containing a single {@link Uri} indicating the path to the temp file in which the
+ * decoded downloaded file resides. Must not be null.
+ */
+ public static final String EXTRA_FINAL_URI = "android.telephony.mbms.extra.FINAL_URI";
+
+ /**
+ * Extra containing an integer indicating the number of temp files requested.
+ */
+ public static final String EXTRA_FD_COUNT = "android.telephony.mbms.extra.FD_COUNT";
+
+ /**
+ * Extra containing a list of {@link Uri}s that the middleware is requesting access to via
+ * {@link #ACTION_FILE_DESCRIPTOR_REQUEST} in order to resume downloading. These {@link Uri}s
+ * should have scheme {@code file://}.
+ */
+ public static final String EXTRA_PAUSED_LIST = "android.telephony.mbms.extra.PAUSED_LIST";
+
+ /**
+ * Extra containing a list of {@link android.telephony.mbms.UriPathPair}s, used in the
+ * response to {@link #ACTION_FILE_DESCRIPTOR_REQUEST}. These are temp files that are meant
+ * to be used for new file downloads.
+ */
+ public static final String EXTRA_FREE_URI_LIST = "android.telephony.mbms.extra.FREE_URI_LIST";
+
+ /**
+ * Extra containing a list of {@link android.telephony.mbms.UriPathPair}s, used in the
+ * response to {@link #ACTION_FILE_DESCRIPTOR_REQUEST}. These
+ * {@link android.telephony.mbms.UriPathPair}s contain {@code content://} URIs that provide
+ * access to previously paused downloads.
+ */
+ public static final String EXTRA_PAUSED_URI_LIST =
+ "android.telephony.mbms.extra.PAUSED_URI_LIST";
+
+ /**
+ * Extra containing a list of {@link Uri}s indicating temp files which the middleware is
+ * still using.
+ */
+ public static final String EXTRA_TEMP_FILES_IN_USE =
+ "android.telephony.mbms.extra.TEMP_FILES_IN_USE";
+
+ public static final int RESULT_SUCCESSFUL = 1;
+ public static final int RESULT_CANCELLED = 2;
+ public static final int RESULT_EXPIRED = 3;
+ // TODO - more results!
+
+ private final Context mContext;
+ private int mSubId = INVALID_SUBSCRIPTION_ID;
+
+ private IMbmsDownloadService mService;
+ private final IMbmsDownloadManagerCallback mCallback;
+ private final String mDownloadAppName;
+
+ private MbmsDownloadManager(Context context, IMbmsDownloadManagerCallback callback,
+ String downloadAppName, int subId) {
+ mContext = context;
+ mCallback = callback;
+ mDownloadAppName = downloadAppName;
+ mSubId = subId;
+ }
+
+ /**
+ * Create a new MbmsDownloadManager using the system default data subscription ID.
+ *
+ * Note that this call will bind a remote service and that may take a bit. This
+ * may throw an Illegal ArgumentException or RemoteException.
+ *
+ * @hide
+ */
+ public static MbmsDownloadManager createManager(Context context,
+ IMbmsDownloadManagerCallback listener, String downloadAppName)
+ throws MbmsException {
+ MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, downloadAppName,
+ SubscriptionManager.getDefaultSubscriptionId());
+ mdm.bindAndInitialize();
+ return mdm;
+ }
+
+ /**
+ * Create a new MbmsDownloadManager using the given subscription ID.
+ *
+ * Note that this call will bind a remote service and that may take a bit. This
+ * may throw an Illegal ArgumentException or RemoteException.
+ *
+ * @hide
+ */
+
+ public static MbmsDownloadManager createManager(Context context,
+ IMbmsDownloadManagerCallback listener, String downloadAppName, int subId)
+ throws MbmsException {
+ MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, downloadAppName,
+ subId);
+ mdm.bindAndInitialize();
+ return mdm;
+ }
+
+ private void bindAndInitialize() throws MbmsException {
+ // TODO: bind
+ try {
+ mService.initialize(mDownloadAppName, mSubId, mCallback);
+ } catch (RemoteException e) {
+ throw new MbmsException(0); // TODO: proper error code
+ }
+ }
+
+ /**
+ * Gets the list of files published for download.
+ * They may occur at times far in the future.
+ * servicesClasses lets the app filter on types of files and is opaque data between
+ * the app and the carrier
+ *
+ * Multiple calls replace trhe list of serviceClasses of interest.
+ *
+ * May throw an IllegalArgumentException or RemoteException.
+ *
+ * Synchronous responses include
+ * <li>SUCCESS</li>
+ * <li>ERROR_MSDC_CONCURRENT_SERVICE_LIMIT_REACHED</li>
+ *
+ * Asynchronous errors through the listener include any of the errors except
+ * <li>ERROR_MSDC_UNABLE_TO_)START_SERVICE</li>
+ * <li>ERROR_MSDC_INVALID_SERVICE_ID</li>
+ * <li>ERROR_MSDC_END_OF_SESSION</li>
+ */
+ public int getFileServices(List<String> serviceClasses) {
+ return 0;
+ }
+
+
+ /**
+ * Requests a future download.
+ * returns a token which may be used to cancel a download.
+ * downloadListener is an optional callback object which can be used to get progress reports
+ * of a currently occuring download. Note this can only run while the calling app
+ * is running, so future downloads will simply result in resultIntents being sent
+ * for completed or errored-out downloads. A NULL indicates no callbacks are needed.
+ *
+ * May throw an IllegalArgumentException or RemoteExcpetion.
+ *
+ * Asynchronous errors through the listener include any of the errors
+ */
+ public DownloadRequest download(DownloadRequest request, IDownloadCallback listener) {
+ request.setAppName(mDownloadAppName);
+ return request;
+ }
+
+ /**
+ * Returns a list DownloadRequests that originated from this application (UID).
+ *
+ * May throw a RemoteException.
+ *
+ * Asynchronous errors through the listener include any of the errors except
+ * <li>ERROR_UNABLED_TO_START_SERVICE</li>
+ * <li>ERROR_MSDC_INVALID_SERVICE_ID</li>
+ * <li>ERROR_MSDC_END_OF_SESSION</li>
+ */
+ public List<DownloadRequest> listPendingDownloads() {
+ return null;
+ }
+
+ /**
+ * Attempts to cancel the specified DownloadRequest.
+ *
+ * May throw a RemoteException.
+ *
+ * Synchronous responses may include
+ * <li>SUCCESS</li>
+ * <li>ERROR_MSDC_CONCURRENT_SERVICE_LIMIT_REACHED</li>
+ * <li>ERROR_MSDC_UNKNOWN_REQUEST</li>
+ */
+ public int cancelDownload(DownloadRequest downloadRequest) {
+ return 0;
+ }
+
+ /**
+ * Gets information about current and known upcoming downloads.
+ *
+ * Current is a straightforward count of the files being downloaded "now"
+ * for some definition of now (may be racey).
+ * Future downloads include counts of files with pending repair operations, counts of
+ * files with future downloads and indication of scheduled download times with unknown
+ * file details.
+ *
+ * May throw an IllegalArgumentException or RemoteException.
+ *
+ * If the DownloadRequest is unknown the results will be null.
+ */
+ public DownloadStatus getDownloadStatus(DownloadRequest downloadRequest) {
+ return null;
+ }
+
+ /**
+ * Resets middleware knowledge regarding this download request.
+ *
+ * This state consists of knowledge of what files have already been downloaded.
+ * Normally the middleware won't download files who's hash matches previously downloaded
+ * content, even if that content has since been deleted. If this function is called
+ * repeated content will be downloaded again when available. This does not interrupt
+ * in-progress downloads.
+ *
+ * May throw an IllegalArgumentException or RemoteException.
+ *
+ * <li>SUCCESS</li>
+ * <li>ERROR_MSDC_CONCURRENT_SERVICE_LIMIT_REACHED</li>
+ * <li>ERROR_MSDC_UNKNOWN_REQUEST</li>
+ */
+ public int resetDownloadKnowledge(DownloadRequest downloadRequest) {
+ return 0;
+ }
+
+ public void dispose() {
+ try {
+ if (mService != null) {
+ mService.dispose(mDownloadAppName, mSubId);
+ } else {
+ Log.i(LOG_TAG, "Service already dead");
+ }
+ } catch (RemoteException e) {
+ // Ignore
+ Log.i(LOG_TAG, "Remote exception while disposing of service");
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/MbmsStreamingManager.java b/telephony/java/android/telephony/MbmsStreamingManager.java
new file mode 100644
index 0000000..e90a63c
--- /dev/null
+++ b/telephony/java/android/telephony/MbmsStreamingManager.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.DeadObjectException;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telephony.mbms.MbmsException;
+import android.telephony.mbms.MbmsStreamingManagerCallback;
+import android.telephony.mbms.StreamingService;
+import android.telephony.mbms.StreamingServiceCallback;
+import android.telephony.mbms.StreamingServiceInfo;
+import android.telephony.mbms.vendor.IMbmsStreamingService;
+import android.util.Log;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+/** @hide */
+public class MbmsStreamingManager {
+ private interface ServiceListener {
+ void onServiceConnected();
+ void onServiceDisconnected();
+ }
+
+ private static final String LOG_TAG = "MbmsStreamingManager";
+ public static final String MBMS_STREAMING_SERVICE_ACTION =
+ "android.telephony.action.EmbmsStreaming";
+
+ private static final boolean DEBUG = true;
+ private static final int BIND_TIMEOUT_MS = 3000;
+
+ private IMbmsStreamingService mService;
+ private ServiceConnection mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (service != null) {
+ Log.i(LOG_TAG, String.format("Connected to service %s", name));
+ synchronized (MbmsStreamingManager.this) {
+ mService = IMbmsStreamingService.Stub.asInterface(service);
+ mServiceListeners.forEach(ServiceListener::onServiceConnected);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.i(LOG_TAG, String.format("Disconnected from service %s", name));
+ synchronized (MbmsStreamingManager.this) {
+ mService = null;
+ mServiceListeners.forEach(ServiceListener::onServiceDisconnected);
+ }
+ }
+ };
+ private List<ServiceListener> mServiceListeners = new LinkedList<>();
+
+ private MbmsStreamingManagerCallback mCallbackToApp;
+ private final String mAppName;
+
+ private final Context mContext;
+ private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
+
+ /** @hide */
+ private MbmsStreamingManager(Context context, MbmsStreamingManagerCallback listener,
+ String streamingAppName, int subscriptionId) {
+ mContext = context;
+ mAppName = streamingAppName;
+ mCallbackToApp = listener;
+ mSubscriptionId = subscriptionId;
+ }
+
+ /**
+ * Create a new MbmsStreamingManager using the given subscription ID.
+ *
+ * Note that this call will bind a remote service. You may not call this method on your app's
+ * main thread. This may throw an {@link MbmsException}, indicating errors that may happen
+ * during the initialization or binding process.
+ *
+ * @param context The {@link Context} to use.
+ * @param listener A callback object on which you wish to receive results of asynchronous
+ * operations.
+ * @param streamingAppName The name of the streaming app, as specified by the carrier.
+ * @param subscriptionId The subscription ID to use.
+ */
+ public static MbmsStreamingManager create(Context context,
+ MbmsStreamingManagerCallback listener, String streamingAppName, int subscriptionId)
+ throws MbmsException {
+ MbmsStreamingManager manager = new MbmsStreamingManager(context, listener,
+ streamingAppName, subscriptionId);
+ manager.bindAndInitialize();
+ return manager;
+ }
+
+ /**
+ * Create a new MbmsStreamingManager using the system default data subscription ID.
+ * See {@link #create(Context, MbmsStreamingManagerCallback, String, int)}.
+ */
+ public static MbmsStreamingManager create(Context context,
+ MbmsStreamingManagerCallback listener, String streamingAppName)
+ throws MbmsException {
+ int subId = SubscriptionManager.getDefaultSubscriptionId();
+ MbmsStreamingManager manager = new MbmsStreamingManager(context, listener,
+ streamingAppName, subId);
+ manager.bindAndInitialize();
+ return manager;
+ }
+
+ /**
+ * Terminates this instance, ending calls to the registered listener. Also terminates
+ * any streaming services spawned from this instance.
+ */
+ public synchronized void dispose() {
+ if (mService == null) {
+ // Ignore and return, assume already disposed.
+ return;
+ }
+ try {
+ mService.dispose(mAppName, mSubscriptionId);
+ } catch (RemoteException e) {
+ // Ignore for now
+ }
+ mService = null;
+ }
+
+ /**
+ * An inspection API to retrieve the list of streaming media currently be advertised.
+ * The results are returned asynchronously through the previously registered callback.
+ * serviceClasses lets the app filter on types of programming and is opaque data between
+ * the app and the carrier.
+ *
+ * Multiple calls replace the list of serviceClasses of interest.
+ *
+ * This may throw an {@link MbmsException} containing one of the following errors:
+ * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
+ * {@link MbmsException#ERROR_CONCURRENT_SERVICE_LIMIT_REACHED}
+ * {@link MbmsException#ERROR_SERVICE_LOST}
+ *
+ * Asynchronous error codes via the {@link MbmsStreamingManagerCallback#error(int, String)}
+ * callback can include any of the errors except:
+ * {@link MbmsException#ERROR_UNABLE_TO_START_SERVICE}
+ * {@link MbmsException#ERROR_END_OF_SESSION}
+ */
+ public void getStreamingServices(List<String> classList) throws MbmsException {
+ if (mService == null) {
+ throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
+ }
+ try {
+ int returnCode = mService.getStreamingServices(mAppName, mSubscriptionId, classList);
+ if (returnCode != MbmsException.SUCCESS) {
+ throw new MbmsException(returnCode);
+ }
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Remote process died");
+ mService = null;
+ throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+ }
+ }
+
+ /**
+ * Starts streaming a requested service, reporting status to the indicated listener.
+ * Returns an object used to control that stream. The stream may not be ready for consumption
+ * immediately upon return from this method -- wait until the streaming state has been
+ * reported via {@link android.telephony.mbms.StreamingServiceCallback#streamStateChanged(int)}.
+ *
+ * May throw an {@link MbmsException} containing any of the following error codes:
+ * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
+ * {@link MbmsException#ERROR_CONCURRENT_SERVICE_LIMIT_REACHED}
+ * {@link MbmsException#ERROR_SERVICE_LOST}
+ *
+ * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
+ *
+ * Asynchronous errors through the listener include any of the errors
+ */
+ public StreamingService startStreaming(StreamingServiceInfo serviceInfo,
+ StreamingServiceCallback listener) throws MbmsException {
+ if (mService == null) {
+ throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
+ }
+
+ try {
+ int returnCode = mService.startStreaming(
+ mAppName, mSubscriptionId, serviceInfo.getServiceId(), listener);
+ if (returnCode != MbmsException.SUCCESS) {
+ throw new MbmsException(returnCode);
+ }
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Remote process died");
+ mService = null;
+ throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+ }
+
+ return new StreamingService(
+ mAppName, mSubscriptionId, mService, serviceInfo, listener);
+ }
+
+ private void bindAndInitialize() throws MbmsException {
+ // Query for the proper service
+ PackageManager packageManager = mContext.getPackageManager();
+ Intent queryIntent = new Intent();
+ queryIntent.setAction(MBMS_STREAMING_SERVICE_ACTION);
+ List<ResolveInfo> streamingServices = packageManager.queryIntentServices(queryIntent,
+ PackageManager.MATCH_SYSTEM_ONLY);
+
+ if (streamingServices == null || streamingServices.size() == 0) {
+ throw new MbmsException(
+ MbmsException.ERROR_NO_SERVICE_INSTALLED);
+ }
+ if (streamingServices.size() > 1) {
+ throw new MbmsException(
+ MbmsException.ERROR_MULTIPLE_SERVICES_INSTALLED);
+ }
+
+ // Kick off the binding, and synchronously wait until binding is complete
+ final CountDownLatch latch = new CountDownLatch(1);
+ ServiceListener bindListener = new ServiceListener() {
+ @Override
+ public void onServiceConnected() {
+ latch.countDown();
+ }
+
+ @Override
+ public void onServiceDisconnected() {
+ }
+ };
+
+ synchronized (this) {
+ mServiceListeners.add(bindListener);
+ }
+
+ Intent bindIntent = new Intent();
+ bindIntent.setComponent(streamingServices.get(0).getComponentInfo().getComponentName());
+
+ mContext.bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+
+ waitOnLatchWithTimeout(latch, BIND_TIMEOUT_MS);
+
+ // Remove the listener and call the initialization method through the interface.
+ synchronized (this) {
+ mServiceListeners.remove(bindListener);
+
+ if (mService == null) {
+ throw new MbmsException(MbmsException.ERROR_BIND_TIMEOUT_OR_FAILURE);
+ }
+
+ try {
+ int returnCode = mService.initialize(mCallbackToApp, mAppName, mSubscriptionId);
+ if (returnCode != MbmsException.SUCCESS) {
+ throw new MbmsException(returnCode);
+ }
+ } catch (RemoteException e) {
+ mService = null;
+ Log.e(LOG_TAG, "Service died before initialization");
+ throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+ }
+ }
+ }
+
+ private static void waitOnLatchWithTimeout(CountDownLatch l, long timeoutMs) {
+ long endTime = System.currentTimeMillis() + timeoutMs;
+ while (System.currentTimeMillis() < endTime) {
+ try {
+ l.await(timeoutMs, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // keep waiting
+ }
+ if (l.getCount() <= 0) {
+ return;
+ }
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/NetworkScan.java b/telephony/java/android/telephony/NetworkScan.java
new file mode 100644
index 0000000..0cb4cff
--- /dev/null
+++ b/telephony/java/android/telephony/NetworkScan.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.telephony.ITelephony;
+
+/**
+ * Allows applications to request the system to perform a network scan.
+ *
+ * The caller of {@link #requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} will
+ * receive a NetworkScan which contains the callback method to stop the scan requested.
+ * @hide
+ */
+public class NetworkScan {
+
+ public static final String TAG = "NetworkScan";
+
+ public static final int SUCCESS = 0;
+ public static final int ERROR_INVALID_SCAN = 1;
+ public static final int ERROR_UNSUPPORTED = 2;
+ public static final int ERROR_INTERRUPTED = 3;
+ public static final int ERROR_CANCELLED = 4;
+
+ private final int mScanId;
+ private final int mSubId;
+
+ /**
+ * Stops the network scan
+ *
+ * This is the callback method to stop an ongoing scan. When user requests a new scan,
+ * a NetworkScan object will be returned, and the user can stop the scan by calling this
+ * method.
+ */
+ public void stop() throws RemoteException {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ telephony.stopNetworkScan(mSubId, mScanId);
+ } else {
+ throw new RemoteException("Failed to get the ITelephony instance.");
+ }
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "stopNetworkScan RemoteException", ex);
+ throw new RemoteException("Failed to stop the network scan with id " + mScanId);
+ }
+ }
+
+ /**
+ * Creates a new NetworkScan with scanId
+ *
+ * @param scanId The id of the scan
+ * @param subId the id of the subscription
+ * @hide
+ */
+ public NetworkScan(int scanId, int subId) {
+ mScanId = scanId;
+ mSubId = subId;
+ }
+
+ private ITelephony getITelephony() {
+ return ITelephony.Stub.asInterface(
+ ServiceManager.getService(Context.TELEPHONY_SERVICE));
+ }
+}
diff --git a/telephony/java/android/telephony/NetworkScanRequest.aidl b/telephony/java/android/telephony/NetworkScanRequest.aidl
new file mode 100644
index 0000000..5addb1c
--- /dev/null
+++ b/telephony/java/android/telephony/NetworkScanRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+parcelable NetworkScanRequest;
diff --git a/telephony/java/android/telephony/NetworkScanRequest.java b/telephony/java/android/telephony/NetworkScanRequest.java
new file mode 100644
index 0000000..0a542a7
--- /dev/null
+++ b/telephony/java/android/telephony/NetworkScanRequest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * Defines a request to peform a network scan.
+ *
+ * This class defines whether the network scan will be performed only once or periodically until
+ * cancelled, when the scan is performed periodically, the time interval is not controlled by the
+ * user but defined by the modem vendor.
+ * @hide
+ */
+public final class NetworkScanRequest implements Parcelable {
+
+ /** Performs the scan only once */
+ public static final int SCAN_TYPE_ONE_SHOT = 0;
+ /**
+ * Performs the scan periodically until cancelled
+ *
+ * The modem will start new scans periodically, and the interval between two scans is usually
+ * multiple minutes.
+ * */
+ public static final int SCAN_TYPE_PERIODIC = 1;
+
+ /** Defines the type of the scan. */
+ public int scanType;
+
+ /** Describes the radio access technologies with bands or channels that need to be scanned. */
+ public RadioAccessSpecifier[] specifiers;
+
+ /**
+ * Creates a new NetworkScanRequest with scanType and network specifiers
+ *
+ * @param scanType The type of the scan
+ * @param specifiers the radio network with bands / channels to be scanned
+ */
+ public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers) {
+ this.scanType = scanType;
+ this.specifiers = specifiers;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(scanType);
+ dest.writeParcelableArray(specifiers, flags);
+ }
+
+ private NetworkScanRequest(Parcel in) {
+ scanType = in.readInt();
+ specifiers = (RadioAccessSpecifier[]) in.readParcelableArray(
+ Object.class.getClassLoader(),
+ RadioAccessSpecifier.class);
+ }
+
+ @Override
+ public boolean equals (Object o) {
+ NetworkScanRequest nsr;
+
+ try {
+ nsr = (NetworkScanRequest) o;
+ } catch (ClassCastException ex) {
+ return false;
+ }
+
+ if (o == null) {
+ return false;
+ }
+
+ return (scanType == nsr.scanType
+ && Arrays.equals(specifiers, nsr.specifiers));
+ }
+
+ @Override
+ public int hashCode () {
+ return ((scanType * 31)
+ + (Arrays.hashCode(specifiers)) * 37);
+ }
+
+ public static final Creator<NetworkScanRequest> CREATOR =
+ new Creator<NetworkScanRequest>() {
+ @Override
+ public NetworkScanRequest createFromParcel(Parcel in) {
+ return new NetworkScanRequest(in);
+ }
+
+ @Override
+ public NetworkScanRequest[] newArray(int size) {
+ return new NetworkScanRequest[size];
+ }
+ };
+}
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index fa7b9b0..8705446 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -3098,34 +3098,20 @@
/*
* The config held calling number conversion map, expected to convert to emergency number.
*/
- private static final String[] CONVERT_TO_EMERGENCY_MAP = Resources.getSystem().getStringArray(
- com.android.internal.R.array.config_convert_to_emergency_number_map);
- /**
- * Check whether conversion to emergency number is enabled
- *
- * @return {@code true} when conversion to emergency numbers is enabled,
- * {@code false} otherwise
- *
- * @hide
- */
- public static boolean isConvertToEmergencyNumberEnabled() {
- return CONVERT_TO_EMERGENCY_MAP != null && CONVERT_TO_EMERGENCY_MAP.length > 0;
- }
+ private static String[] sConvertToEmergencyMap = null;
/**
* Converts to emergency number based on the conversion map.
* The conversion map is declared as config_convert_to_emergency_number_map.
*
- * Make sure {@link #isConvertToEmergencyNumberEnabled} is true before calling
- * this function.
- *
+ * @param context a context to use for accessing resources
* @return The converted emergency number if the number matches conversion map,
* otherwise original number.
*
* @hide
*/
- public static String convertToEmergencyNumber(String number) {
- if (TextUtils.isEmpty(number)) {
+ public static String convertToEmergencyNumber(Context context, String number) {
+ if (context == null || TextUtils.isEmpty(number)) {
return number;
}
@@ -3136,7 +3122,17 @@
return number;
}
- for (String convertMap : CONVERT_TO_EMERGENCY_MAP) {
+ if (sConvertToEmergencyMap == null) {
+ sConvertToEmergencyMap = context.getResources().getStringArray(
+ com.android.internal.R.array.config_convert_to_emergency_number_map);
+ }
+
+ // The conversion map is not defined (this is default). Skip conversion.
+ if (sConvertToEmergencyMap == null || sConvertToEmergencyMap.length == 0 ) {
+ return number;
+ }
+
+ for (String convertMap : sConvertToEmergencyMap) {
if (DBG) log("convertToEmergencyNumber: " + convertMap);
String[] entry = null;
String[] filterNumbers = null;
diff --git a/telephony/java/android/telephony/RadioAccessSpecifier.aidl b/telephony/java/android/telephony/RadioAccessSpecifier.aidl
new file mode 100644
index 0000000..7e09e0b
--- /dev/null
+++ b/telephony/java/android/telephony/RadioAccessSpecifier.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+parcelable RadioAccessSpecifier;
diff --git a/telephony/java/android/telephony/RadioAccessSpecifier.java b/telephony/java/android/telephony/RadioAccessSpecifier.java
new file mode 100644
index 0000000..33ce8b4
--- /dev/null
+++ b/telephony/java/android/telephony/RadioAccessSpecifier.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * Describes a particular radio access network to be scanned.
+ *
+ * The scan can be performed on either bands or channels for a specific radio access network type.
+ * @hide
+ */
+public final class RadioAccessSpecifier implements Parcelable {
+
+ /**
+ * The radio access network that needs to be scanned
+ *
+ * See {@link RadioNetworkConstants.RadioAccessNetworks} for details.
+ */
+ public int radioAccessNetwork;
+
+ /**
+ * The frequency bands that need to be scanned
+ *
+ * bands must be used together with radioAccessNetwork
+ *
+ * See {@link RadioNetworkConstants} for details.
+ */
+ public int[] bands;
+
+ /**
+ * The frequency channels that need to be scanned
+ *
+ * channels must be used together with radioAccessNetwork
+ *
+ * See {@link RadioNetworkConstants.RadioAccessNetworks} for details.
+ */
+ public int[] channels;
+
+ /**
+ * Creates a new RadioAccessSpecifier with radio network, bands and channels
+ *
+ * The user must specify the radio network type, and at least specify either of frequency
+ * bands or channels.
+ *
+ * @param ran The type of the radio access network
+ * @param bands the frequency bands to be scanned
+ * @param channels the frequency bands to be scanned
+ */
+ public RadioAccessSpecifier(int ran, int[] bands, int[] channels) {
+ this.radioAccessNetwork = ran;
+ this.bands = bands;
+ this.channels = channels;
+ }
+
+ public static final Parcelable.Creator<RadioAccessSpecifier> CREATOR =
+ new Parcelable.Creator<RadioAccessSpecifier> (){
+ @Override
+ public RadioAccessSpecifier createFromParcel(Parcel in) {
+ return new RadioAccessSpecifier(in);
+ }
+
+ @Override
+ public RadioAccessSpecifier[] newArray(int size) {
+ return new RadioAccessSpecifier[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(radioAccessNetwork);
+ dest.writeIntArray(bands);
+ dest.writeIntArray(channels);
+ }
+
+ private RadioAccessSpecifier(Parcel in) {
+ radioAccessNetwork = in.readInt();
+ bands = in.createIntArray();
+ channels = in.createIntArray();
+ }
+
+ @Override
+ public boolean equals (Object o) {
+ RadioAccessSpecifier ras;
+
+ try {
+ ras = (RadioAccessSpecifier) o;
+ } catch (ClassCastException ex) {
+ return false;
+ }
+
+ if (o == null) {
+ return false;
+ }
+
+ return (radioAccessNetwork == ras.radioAccessNetwork
+ && Arrays.equals(bands, ras.bands)
+ && Arrays.equals(channels, ras.channels));
+ }
+
+ @Override
+ public int hashCode () {
+ return ((radioAccessNetwork * 31)
+ + (Arrays.hashCode(bands) * 37)
+ + (Arrays.hashCode(channels)) * 39);
+ }
+}
diff --git a/telephony/java/android/telephony/RadioNetworkConstants.java b/telephony/java/android/telephony/RadioNetworkConstants.java
new file mode 100644
index 0000000..1a9072d
--- /dev/null
+++ b/telephony/java/android/telephony/RadioNetworkConstants.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+/**
+ * Contains radio access network related constants.
+ * @hide
+ */
+public final class RadioNetworkConstants {
+
+ public static final class RadioAccessNetworks {
+ public static final int GERAN = 1;
+ public static final int UTRAN = 2;
+ public static final int EUTRAN = 3;
+ /** @hide */
+ public static final int CDMA2000 = 4;
+ }
+
+ /**
+ * Frenquency bands for GERAN.
+ * http://www.etsi.org/deliver/etsi_ts/145000_145099/145005/14.00.00_60/ts_145005v140000p.pdf
+ */
+ public static final class GeranBands {
+ public static final int BAND_T380 = 1;
+ public static final int BAND_T410 = 2;
+ public static final int BAND_450 = 3;
+ public static final int BAND_480 = 4;
+ public static final int BAND_710 = 5;
+ public static final int BAND_750 = 6;
+ public static final int BAND_T810 = 7;
+ public static final int BAND_850 = 8;
+ public static final int BAND_P900 = 9;
+ public static final int BAND_E900 = 10;
+ public static final int BAND_R900 = 11;
+ public static final int BAND_DCS1800 = 12;
+ public static final int BAND_PCS1900 = 13;
+ public static final int BAND_ER900 = 14;
+ }
+
+ /**
+ * Frenquency bands for UTRAN.
+ * http://www.etsi.org/deliver/etsi_ts/125100_125199/125104/13.03.00_60/ts_125104v130p.pdf
+ */
+ public static final class UtranBands {
+ public static final int BAND_1 = 1;
+ public static final int BAND_2 = 2;
+ public static final int BAND_3 = 3;
+ public static final int BAND_4 = 4;
+ public static final int BAND_5 = 5;
+ public static final int BAND_6 = 6;
+ public static final int BAND_7 = 7;
+ public static final int BAND_8 = 8;
+ public static final int BAND_9 = 9;
+ public static final int BAND_10 = 10;
+ public static final int BAND_11 = 11;
+ public static final int BAND_12 = 12;
+ public static final int BAND_13 = 13;
+ public static final int BAND_14 = 14;
+ /** band 15, 16, 17, 18 are reserved */
+ public static final int BAND_19 = 19;
+ public static final int BAND_20 = 20;
+ public static final int BAND_21 = 21;
+ public static final int BAND_22 = 22;
+ /** band 23, 24 are reserved */
+ public static final int BAND_25 = 25;
+ public static final int BAND_26 = 26;
+ }
+
+ /**
+ * Frenquency bands for EUTRAN.
+ * http://www.etsi.org/deliver/etsi_ts/136100_136199/136101/14.03.00_60/ts_136101v140p.pdf
+ */
+ public static final class EutranBands {
+ public static final int BAND_1 = 1;
+ public static final int BAND_2 = 2;
+ public static final int BAND_3 = 3;
+ public static final int BAND_4 = 4;
+ public static final int BAND_5 = 5;
+ public static final int BAND_6 = 6;
+ public static final int BAND_7 = 7;
+ public static final int BAND_8 = 8;
+ public static final int BAND_9 = 9;
+ public static final int BAND_10 = 10;
+ public static final int BAND_11 = 11;
+ public static final int BAND_12 = 12;
+ public static final int BAND_13 = 13;
+ public static final int BAND_14 = 14;
+ public static final int BAND_17 = 17;
+ public static final int BAND_18 = 18;
+ public static final int BAND_19 = 19;
+ public static final int BAND_20 = 20;
+ public static final int BAND_21 = 21;
+ public static final int BAND_22 = 22;
+ public static final int BAND_23 = 23;
+ public static final int BAND_24 = 24;
+ public static final int BAND_25 = 25;
+ public static final int BAND_26 = 26;
+ public static final int BAND_27 = 27;
+ public static final int BAND_28 = 28;
+ public static final int BAND_30 = 30;
+ public static final int BAND_31 = 31;
+ public static final int BAND_33 = 33;
+ public static final int BAND_34 = 34;
+ public static final int BAND_35 = 35;
+ public static final int BAND_36 = 36;
+ public static final int BAND_37 = 37;
+ public static final int BAND_38 = 38;
+ public static final int BAND_39 = 39;
+ public static final int BAND_40 = 40;
+ public static final int BAND_41 = 41;
+ public static final int BAND_42 = 42;
+ public static final int BAND_43 = 43;
+ public static final int BAND_44 = 44;
+ public static final int BAND_45 = 45;
+ public static final int BAND_46 = 46;
+ public static final int BAND_47 = 47;
+ public static final int BAND_48 = 48;
+ public static final int BAND_65 = 65;
+ public static final int BAND_66 = 66;
+ public static final int BAND_68 = 68;
+ public static final int BAND_70 = 70;
+ }
+
+ /**
+ * Frenquency bands for CDMA2000.
+ * http://www.3gpp2.org/Public_html/Specs/C.S0057-E_v1.0_Bandclass_Specification.pdf
+ * @hide
+ *
+ * TODO(yinxu): Check with the nexus team about the definition of CDMA bands.
+ */
+ public static final class CdmaBands {
+ public static final int BAND_0 = 1;
+ public static final int BAND_1 = 2;
+ public static final int BAND_2 = 3;
+ public static final int BAND_3 = 4;
+ public static final int BAND_4 = 5;
+ public static final int BAND_5 = 6;
+ public static final int BAND_6 = 7;
+ public static final int BAND_7 = 8;
+ public static final int BAND_8 = 9;
+ public static final int BAND_9 = 10;
+ public static final int BAND_10 = 11;
+ public static final int BAND_11 = 12;
+ public static final int BAND_12 = 13;
+ public static final int BAND_13 = 14;
+ public static final int BAND_14 = 15;
+ public static final int BAND_15 = 16;
+ public static final int BAND_16 = 17;
+ public static final int BAND_17 = 18;
+ public static final int BAND_18 = 19;
+ public static final int BAND_19 = 20;
+ public static final int BAND_20 = 21;
+ public static final int BAND_21 = 22;
+ }
+}
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index d6e74cf..dcdda86 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -463,9 +463,28 @@
*/
public static SubmitPdu getSubmitPdu(String scAddress,
String destinationAddress, String message, boolean statusReportRequested) {
- SubmitPduBase spb;
+ return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested,
+ SubscriptionManager.getDefaultSmsSubscriptionId());
+ }
- if (useCdmaFormatForMoSms()) {
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message.
+ * This method will not attempt to use any GSM national language 7 bit encodings.
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @param destinationAddress the address of the destination for the message.
+ * @param message String representation of the message payload.
+ * @param statusReportRequested Indicates whether a report is requested for this message.
+ * @param subId Subscription of the message
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ * @hide
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message, boolean statusReportRequested, int subId) {
+ SubmitPduBase spb;
+ if (useCdmaFormatForMoSms(subId)) {
spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
destinationAddress, message, statusReportRequested, null);
} else {
@@ -758,12 +777,27 @@
* @return true if Cdma format should be used for MO SMS, false otherwise.
*/
private static boolean useCdmaFormatForMoSms() {
- if (!SmsManager.getDefault().isImsSmsSupported()) {
+ // IMS is registered with SMS support, check the SMS format supported
+ return useCdmaFormatForMoSms(SubscriptionManager.getDefaultSmsSubscriptionId());
+ }
+
+ /**
+ * Determines whether or not to use CDMA format for MO SMS.
+ * If SMS over IMS is supported, then format is based on IMS SMS format,
+ * otherwise format is based on current phone type.
+ *
+ * @param subId Subscription for which phone type is returned.
+ *
+ * @return true if Cdma format should be used for MO SMS, false otherwise.
+ */
+ private static boolean useCdmaFormatForMoSms(int subId) {
+ SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId);
+ if (!smsManager.isImsSmsSupported()) {
// use Voice technology to determine SMS format.
- return isCdmaVoice();
+ return isCdmaVoice(subId);
}
// IMS is registered with SMS support, check the SMS format supported
- return (SmsConstants.FORMAT_3GPP2.equals(SmsManager.getDefault().getImsSmsFormat()));
+ return (SmsConstants.FORMAT_3GPP2.equals(smsManager.getImsSmsFormat()));
}
/**
@@ -772,10 +806,18 @@
* @return true if current phone type is cdma, false otherwise.
*/
private static boolean isCdmaVoice() {
- int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
- return (PHONE_TYPE_CDMA == activePhone);
+ return isCdmaVoice(SubscriptionManager.getDefaultSmsSubscriptionId());
}
+ /**
+ * Determines whether or not to current phone type is cdma
+ *
+ * @return true if current phone type is cdma, false otherwise.
+ */
+ private static boolean isCdmaVoice(int subId) {
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(subId);
+ return (PHONE_TYPE_CDMA == activePhone);
+ }
/**
* Decide if the carrier supports long SMS.
* {@hide}
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index cf2d27e..4e1c15f 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -16,7 +16,10 @@
package android.telephony;
+import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -30,6 +33,8 @@
import android.os.Parcelable;
import android.util.DisplayMetrics;
+import java.util.Arrays;
+
/**
* A Parcelable class for Subscription Information.
*/
@@ -110,11 +115,34 @@
private String mCountryIso;
/**
+ * Whether the subscription is an embedded one.
+ */
+ private boolean mIsEmbedded;
+
+ /**
+ * The access rules for this subscription, if it is embedded and defines any.
+ */
+ @Nullable
+ private UiccAccessRule[] mAccessRules;
+
+ /**
* @hide
*/
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
Bitmap icon, int mcc, int mnc, String countryIso) {
+ this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
+ roaming, icon, mcc, mnc, countryIso, false /* isEmbedded */,
+ null /* accessRules */);
+ }
+
+ /**
+ * @hide
+ */
+ public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
+ CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
+ Bitmap icon, int mcc, int mnc, String countryIso, boolean isEmbedded,
+ @Nullable UiccAccessRule[] accessRules) {
this.mId = id;
this.mIccId = iccId;
this.mSimSlotIndex = simSlotIndex;
@@ -128,6 +156,8 @@
this.mMcc = mcc;
this.mMnc = mnc;
this.mCountryIso = countryIso;
+ this.mIsEmbedded = isEmbedded;
+ this.mAccessRules = accessRules;
}
/**
@@ -284,6 +314,79 @@
return this.mCountryIso;
}
+ /**
+ * @return whether the subscription is an embedded one.
+ * @hide
+ *
+ * TODO(b/35851809): Make this public.
+ */
+ public boolean isEmbedded() {
+ return this.mIsEmbedded;
+ }
+
+ /**
+ * Checks whether the app with the given context is authorized to manage this subscription
+ * according to its metadata. Only supported for embedded subscriptions (if {@link #isEmbedded}
+ * returns true).
+ *
+ * @param context Context of the application to check.
+ * @return whether the app is authorized to manage this subscription per its metadata.
+ * @throws UnsupportedOperationException if this subscription is not embedded.
+ * @hide
+ *
+ * TODO(b/35851809): Make this public.
+ */
+ public boolean canManageSubscription(Context context) {
+ return canManageSubscription(context, context.getPackageName());
+ }
+
+ /**
+ * Checks whether the given app is authorized to manage this subscription according to its
+ * metadata. Only supported for embedded subscriptions (if {@link #isEmbedded} returns true).
+ *
+ * @param context Any context.
+ * @param packageName Package name of the app to check.
+ * @return whether the app is authorized to manage this subscription per its metadata.
+ * @throws UnsupportedOperationException if this subscription is not embedded.
+ * @hide
+ */
+ public boolean canManageSubscription(Context context, String packageName) {
+ if (!isEmbedded()) {
+ throw new UnsupportedOperationException("Not an embedded subscription");
+ }
+ if (mAccessRules == null) {
+ return false;
+ }
+ PackageManager packageManager = context.getPackageManager();
+ PackageInfo packageInfo;
+ try {
+ packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalArgumentException("Unknown package: " + packageName, e);
+ }
+ for (UiccAccessRule rule : mAccessRules) {
+ if (rule.getCarrierPrivilegeStatus(packageInfo)
+ == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return the {@link UiccAccessRule}s dictating who is authorized to manage this subscription.
+ * @throws UnsupportedOperationException if this subscription is not embedded.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+ public @Nullable UiccAccessRule[] getAccessRules() {
+ if (!isEmbedded()) {
+ throw new UnsupportedOperationException("Not an embedded subscription");
+ }
+ return mAccessRules;
+ }
+
public static final Parcelable.Creator<SubscriptionInfo> CREATOR = new Parcelable.Creator<SubscriptionInfo>() {
@Override
public SubscriptionInfo createFromParcel(Parcel source) {
@@ -300,9 +403,12 @@
int mnc = source.readInt();
String countryIso = source.readString();
Bitmap iconBitmap = Bitmap.CREATOR.createFromParcel(source);
+ boolean isEmbedded = source.readBoolean();
+ UiccAccessRule[] accessRules = source.createTypedArray(UiccAccessRule.CREATOR);
return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
- nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso);
+ nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
+ isEmbedded, accessRules);
}
@Override
@@ -326,6 +432,8 @@
dest.writeInt(mMnc);
dest.writeString(mCountryIso);
mIconBitmap.writeToParcel(dest, flags);
+ dest.writeBoolean(mIsEmbedded);
+ dest.writeTypedArray(mAccessRules, flags);
}
@Override
@@ -355,6 +463,7 @@
+ " displayName=" + mDisplayName + " carrierName=" + mCarrierName
+ " nameSource=" + mNameSource + " iconTint=" + mIconTint
+ " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc " + mMcc
- + " mnc " + mMnc + "}";
+ + " mnc " + mMnc + " isEmbedded " + mIsEmbedded
+ + " accessRules " + Arrays.toString(mAccessRules) + "}";
}
}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 709877d..0d1764b 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -25,17 +25,17 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.Uri;
-import android.telephony.Rlog;
import android.os.Handler;
import android.os.Message;
-import android.os.ServiceManager;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.DisplayMetrics;
-import com.android.internal.telephony.ISub;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
+import com.android.internal.telephony.ISub;
import com.android.internal.telephony.ITelephonyRegistry;
import com.android.internal.telephony.PhoneConstants;
+
import java.util.ArrayList;
import java.util.List;
@@ -43,7 +43,8 @@
* SubscriptionManager is the application interface to SubscriptionController
* and provides information about the current Telephony Subscriptions.
* <p>
- * All SDK public methods require android.Manifest.permission.READ_PHONE_STATE.
+ * All SDK public methods require android.Manifest.permission.READ_PHONE_STATE unless otherwise
+ * specified.
*/
@SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
public class SubscriptionManager {
@@ -259,6 +260,32 @@
public static final String SIM_PROVISIONING_STATUS = "sim_provisioning_status";
/**
+ * TelephonyProvider column name for whether a subscription is embedded (that is, present on an
+ * eSIM).
+ * <p>Type: INTEGER (int), 1 for embedded or 0 for non-embedded.
+ * @hide
+ */
+ public static final String IS_EMBEDDED = "is_embedded";
+
+ /**
+ * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from
+ * {@link UiccAccessRule#encodeRules}. Only present if {@link #IS_EMBEDDED} is 1.
+ * <p>TYPE: BLOB
+ * @hide
+ */
+ public static final String ACCESS_RULES = "access_rules";
+
+ /**
+ * TelephonyProvider column name identifying whether an embedded subscription is on a removable
+ * card. Such subscriptions are marked inaccessible as soon as the current card is removed.
+ * Otherwise, they will remain accessible unless explicitly deleted. Only present if
+ * {@link #IS_EMBEDDED} is 1.
+ * <p>TYPE: INTEGER (int), 1 for removable or 0 for non-removable.
+ * @hide
+ */
+ public static final String IS_REMOVABLE = "is_removable";
+
+ /**
* TelephonyProvider column name for extreme threat in CB settings
* @hide
*/
@@ -632,6 +659,112 @@
}
/**
+ * Gets the SubscriptionInfo(s) of all available subscriptions, if any.
+ *
+ * <p>Available subscriptions include active ones (those with a non-negative
+ * {@link SubscriptionInfo#getSimSlotIndex()}) as well as inactive but installed embedded
+ * subscriptions.
+ *
+ * <p>The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
+ * {@link SubscriptionInfo#getSubscriptionId}.
+ *
+ * @return Sorted list of the current {@link SubscriptionInfo} records available on the
+ * device.
+ * <ul>
+ * <li>
+ * If null is returned the current state is unknown but if a
+ * {@link OnSubscriptionsChangedListener} has been registered
+ * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged} will be invoked in the future.
+ * <li>
+ * If the list is empty then there are no {@link SubscriptionInfo} records currently available.
+ * <li>
+ * if the list is non-empty the list is sorted by {@link SubscriptionInfo#getSimSlotIndex}
+ * then by {@link SubscriptionInfo#getSubscriptionId}.
+ * </ul>
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+ public List<SubscriptionInfo> getAvailableSubscriptionInfoList() {
+ List<SubscriptionInfo> result = null;
+
+ try {
+ ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ if (iSub != null) {
+ result = iSub.getAvailableSubscriptionInfoList(mContext.getOpPackageName());
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return result;
+ }
+
+ /**
+ * Gets the SubscriptionInfo(s) of all embedded subscriptions accessible to the calling app, if
+ * any.
+ *
+ * <p>Only those subscriptions for which the calling app has carrier privileges per the
+ * subscription metadata, if any, will be included in the returned list.
+ *
+ * <p>The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
+ * {@link SubscriptionInfo#getSubscriptionId}.
+ *
+ * @return Sorted list of the current embedded {@link SubscriptionInfo} records available on the
+ * device which are accessible to the caller.
+ * <ul>
+ * <li>
+ * If null is returned the current state is unknown but if a
+ * {@link OnSubscriptionsChangedListener} has been registered
+ * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged} will be invoked in the future.
+ * <li>
+ * If the list is empty then there are no {@link SubscriptionInfo} records currently available.
+ * <li>
+ * if the list is non-empty the list is sorted by {@link SubscriptionInfo#getSimSlotIndex}
+ * then by {@link SubscriptionInfo#getSubscriptionId}.
+ * </ul>
+ * @hide
+ *
+ * TODO(b/35851809): Make this public.
+ */
+ public List<SubscriptionInfo> getAccessibleSubscriptionInfoList() {
+ List<SubscriptionInfo> result = null;
+
+ try {
+ ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ if (iSub != null) {
+ result = iSub.getAccessibleSubscriptionInfoList(mContext.getOpPackageName());
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return result;
+ }
+
+ /**
+ * Request a refresh of the platform cache of profile information.
+ *
+ * <p>Should be called by the EuiccService implementation whenever this information changes due
+ * to an operation done outside the scope of a request initiated by the platform to the
+ * EuiccService. There is no need to refresh for downloads, deletes, or other operations that
+ * were made through the EuiccService.
+ *
+ * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+ public void requestEmbeddedSubscriptionInfoListRefresh() {
+ try {
+ ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ if (iSub != null) {
+ iSub.requestEmbeddedSubscriptionInfoListRefresh();
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
* @return the count of all subscriptions in the database, this includes
* all subscriptions that have been seen.
* @hide
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 6b921c7..74327ce 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -33,6 +33,7 @@
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
+import android.net.NetworkStats;
import android.net.Uri;
import android.os.BatteryStats;
import android.os.Bundle;
@@ -135,9 +136,16 @@
static public final int OTASP_SIM_UNPROVISIONED = 5;
+ /** @hide */
+ static public final int KEY_TYPE_EPDDG = 1;
+
+ /** @hide */
+ static public final int KEY_TYPE_WLAN = 2;
+
private final Context mContext;
private final int mSubId;
private SubscriptionManager mSubscriptionManager;
+ private TelephonyScanManager mTelephonyScanManager;
private static String multiSimConfig =
SystemProperties.get(TelephonyProperties.PROPERTY_MULTI_SIM_CONFIG);
@@ -2325,6 +2333,73 @@
}
/**
+ * Returns Carrier specific information that will be used to encrypt the IMSI and IMPI.
+ * This includes the public key and the key identifier. For multi-sim devices, if no subId
+ * has been specified, we will return the value for the dafault data sim.
+ * Return null if it is unavailable.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ * @param keyType whether the key is being used for wlan or epdg. Valid key types are
+ * {@link TelephonyManager#KEY_TYPE_EPDDG} or
+ * {@link TelephonyManager#KEY_TYPE_WLAN}.
+ * @return ImsiEncryptionInfo Carrier specific information that will be used to encrypt the
+ * IMSI and IMPI. This includes the public key and the key identifier. This information
+ * will be stored in the device keystore.
+ * @hide
+ */
+ public ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType) {
+ try {
+ IPhoneSubInfo info = getSubscriberInfo();
+ if (info == null) return null;
+ int subId = getSubId(SubscriptionManager.getDefaultDataSubscriptionId());
+ if (keyType != KEY_TYPE_EPDDG && keyType != KEY_TYPE_WLAN) {
+ throw new IllegalArgumentException("Invalid key type");
+ }
+ return info.getCarrierInfoForImsiEncryption(subId, keyType,
+ mContext.getOpPackageName());
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "getCarrierInfoForImsiEncryption RemoteException", ex);
+ return null;
+ } catch (NullPointerException ex) {
+ // This could happen before phone restarts due to crashing
+ Rlog.e(TAG, "getCarrierInfoForImsiEncryption NullPointerException", ex);
+ return null;
+ }
+ }
+
+ /**
+ * Sets the Carrier specific information that will be used to encrypt the IMSI and IMPI.
+ * This includes the public key and the key identifier. This information will be stored in the
+ * device keystore.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ * @param imsiEncryptionInfo which includes the Key Type, the Public Key
+ * (java.security.PublicKey) and the Key Identifier.and the Key Identifier.
+ * The keyIdentifier Attribute value pair that helps a server locate
+ * the private key to decrypt the permanent identity. This field is
+ * optional and if it is present then it’s always separated from encrypted
+ * permanent identity with “,”. Key identifier AVP is presented in ASCII string
+ * with “name=value” format.
+ * @hide
+ */
+ public void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo) {
+ try {
+ IPhoneSubInfo info = getSubscriberInfo();
+ if (info == null) return;
+ info.setCarrierInfoForImsiEncryption(mSubId, mContext.getOpPackageName(),
+ imsiEncryptionInfo);
+ } catch (NullPointerException ex) {
+ // This could happen before phone restarts due to crashing
+ return;
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "setCarrierInfoForImsiEncryption RemoteException", ex);
+ return;
+ }
+ }
+
+ /**
* Returns the Group Identifier Level1 for a GSM phone.
* Return null if it is unavailable.
*/
@@ -4590,6 +4665,32 @@
}
/**
+ * Request a network scan.
+ *
+ * This method is asynchronous, so the network scan results will be returned by callback.
+ * The returned NetworkScan will contain a callback method which can be used to stop the scan.
+ *
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+ *
+ * @param request Contains all the RAT with bands/channels that need to be scanned.
+ * @param callback Returns network scan results or errors.
+ * @return A NetworkScan obj which contains a callback which can stop the scan.
+ * @hide
+ */
+ public NetworkScan requestNetworkScan(
+ NetworkScanRequest request, TelephonyScanManager.NetworkScanCallback callback) {
+ synchronized (this) {
+ if (mTelephonyScanManager == null) {
+ mTelephonyScanManager = new TelephonyScanManager();
+ }
+ }
+ return mTelephonyScanManager.requestNetworkScan(getSubId(), request, callback);
+ }
+
+ /**
* Ask the radio to connect to the input network and change selection mode to manual.
*
* <p>
@@ -5377,9 +5478,10 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null)
- return telephony.isDataConnectivityPossible();
+ return telephony.isDataConnectivityPossible(getSubId(SubscriptionManager
+ .getDefaultDataSubscriptionId()));
} catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#isDataConnectivityPossible", e);
+ Log.e(TAG, "Error calling ITelephony#isDataAllowed", e);
}
return false;
}
@@ -5791,35 +5893,75 @@
}
/**
- * Set SIM card power state. Request is equivalent to inserting or removing the card.
+ * Requested state of SIM
*
- * @param powerUp True if powering up the SIM, otherwise powering down
+ * CARD_POWER_DOWN
+ * Powers down the SIM. SIM must be up prior.
+ *
+ * CARD_POWER_UP
+ * Powers up the SIM normally. SIM must be down prior.
+ *
+ * CARD_POWER_UP_PASS_THROUGH
+ * Powers up the SIM in PASS_THROUGH mode. SIM must be down prior.
+ * When SIM is powered up in PASS_THOUGH mode, the modem does not send
+ * any command to it (for example SELECT of MF, or TERMINAL CAPABILITY),
+ * and the SIM card is controlled completely by Telephony sending APDUs
+ * directly. The SIM card state will be RIL_CARDSTATE_PRESENT and the
+ * number of card apps will be 0.
+ * No new error code is generated. Emergency calls are supported in the
+ * same way as if the SIM card is absent.
+ * The PASS_THROUGH mode is valid only for the specific card session where it
+ * is activated, and normal behavior occurs at the next SIM initialization,
+ * unless PASS_THROUGH mode is requested again. Hence, the last power-up mode
+ * is NOT persistent across boots. On reboot, SIM will power up normally.
+ */
+ /** @hide */
+ public static final int CARD_POWER_DOWN = 0;
+ /** @hide */
+ public static final int CARD_POWER_UP = 1;
+ /** @hide */
+ public static final int CARD_POWER_UP_PASS_THROUGH = 2;
+
+ /**
+ * Set SIM card power state.
+ *
+ * @param state State of SIM (power down, power up, pass through)
+ * @see #CARD_POWER_DOWN
+ * @see #CARD_POWER_UP
+ * @see #CARD_POWER_UP_PASS_THROUGH
+ * Callers should monitor for {@link TelephonyIntents#ACTION_SIM_STATE_CHANGED}
+ * broadcasts to determine success or failure and timeout if needed.
*
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
*
* @hide
**/
- public void setSimPowerState(boolean powerUp) {
- setSimPowerStateForSlot(getSlotIndex(), powerUp);
+ public void setSimPowerState(int state) {
+ setSimPowerStateForSlot(getSlotIndex(), state);
}
/**
- * Set SIM card power state. Request is equivalent to inserting or removing the card.
+ * Set SIM card power state.
*
* @param slotIndex SIM slot id
- * @param powerUp True if powering up the SIM, otherwise powering down
+ * @param state State of SIM (power down, power up, pass through)
+ * @see #CARD_POWER_DOWN
+ * @see #CARD_POWER_UP
+ * @see #CARD_POWER_UP_PASS_THROUGH
+ * Callers should monitor for {@link TelephonyIntents#ACTION_SIM_STATE_CHANGED}
+ * broadcasts to determine success or failure and timeout if needed.
*
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
*
* @hide
**/
- public void setSimPowerStateForSlot(int slotIndex, boolean powerUp) {
+ public void setSimPowerStateForSlot(int slotIndex, int state) {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- telephony.setSimPowerStateForSlot(slotIndex, powerUp);
+ telephony.setSimPowerStateForSlot(slotIndex, state);
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#setSimPowerStateForSlot", e);
@@ -6457,7 +6599,7 @@
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#getAllowedCarriers", e);
} catch (NullPointerException e) {
- Log.e(TAG, "Error calling ITelephony#setAllowedCarriers", e);
+ Log.e(TAG, "Error calling ITelephony#getAllowedCarriers", e);
}
return new ArrayList<CarrierIdentifier>(0);
}
@@ -6501,20 +6643,21 @@
/**
* Get aggregated video call data usage since boot.
* Permissions android.Manifest.permission.READ_NETWORK_USAGE_HISTORY is required.
- * @return total data usage in bytes
+ *
+ * @param perUidStats True if requesting data usage per uid, otherwise overall usage.
+ * @return Snapshot of video call data usage
* @hide
*/
- public long getVtDataUsage() {
-
+ public NetworkStats getVtDataUsage(boolean perUidStats) {
try {
ITelephony service = getITelephony();
if (service != null) {
- return service.getVtDataUsage();
+ return service.getVtDataUsage(getSubId(), perUidStats);
}
} catch (RemoteException e) {
- Log.e(TAG, "Error calling getVtDataUsage", e);
+ Log.e(TAG, "Error calling ITelephony#getVtDataUsage", e);
}
- return 0;
+ return null;
}
/**
@@ -6583,5 +6726,26 @@
}
return false;
}
+
+ /**
+ * Get the most recently available signal strength information.
+ *
+ * Get the most recent SignalStrength information reported by the modem. Due
+ * to power saving this information may not always be current.
+ * @return the most recent cached signal strength info from the modem
+ * @hide
+ */
+ @Nullable
+ public SignalStrength getSignalStrength() {
+ try {
+ ITelephony service = getITelephony();
+ if (service != null) {
+ return service.getSignalStrength(getSubId());
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#getSignalStrength", e);
+ }
+ return null;
+ }
}
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
new file mode 100644
index 0000000..c905d3a
--- /dev/null
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.util.SparseArray;
+import java.util.List;
+
+import com.android.internal.telephony.ITelephony;
+
+/**
+ * Manages the radio access network scan requests and callbacks.
+ * @hide
+ */
+public final class TelephonyScanManager {
+
+ private static final String TAG = "TelephonyScanManager";
+
+ /** @hide */
+ public static final int CALLBACK_SCAN_RESULTS = 1;
+ /** @hide */
+ public static final int CALLBACK_SCAN_ERROR = 2;
+ /** @hide */
+ public static final int CALLBACK_SCAN_COMPLETE = 3;
+
+ /**
+ * The caller of {@link #requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} should
+ * implement and provide this callback so that the scan results or errors can be returned.
+ */
+ public static abstract class NetworkScanCallback {
+ /** Returns the scan results to the user, this callback will be called multiple times. */
+ public void onResults(List<CellInfo> results) {}
+
+ /**
+ * Informs the user that the scan has stopped.
+ *
+ * This callback will be called when the scan is finished or cancelled by the user.
+ * The related NetworkScanRequest will be deleted after this callback.
+ */
+ public void onComplete() {}
+
+ /**
+ * Informs the user that there is some error about the scan.
+ *
+ * This callback will be called whenever there is any error about the scan, but the scan
+ * won't stop unless the onComplete() callback is called.
+ */
+ public void onError(int error) {}
+ }
+
+ private static class NetworkScanInfo {
+ private final NetworkScanRequest mRequest;
+ private final NetworkScanCallback mCallback;
+
+ NetworkScanInfo(NetworkScanRequest request, NetworkScanCallback callback) {
+ mRequest = request;
+ mCallback = callback;
+ }
+ }
+
+ private final Looper mLooper;
+ private final Messenger mMessenger;
+ private SparseArray<NetworkScanInfo> mScanInfo = new SparseArray<NetworkScanInfo>();
+
+ public TelephonyScanManager() {
+ HandlerThread thread = new HandlerThread(TAG);
+ thread.start();
+ mLooper = thread.getLooper();
+ mMessenger = new Messenger(new Handler(mLooper) {
+ @Override
+ public void handleMessage(Message message) {
+ checkNotNull(message, "message cannot be null");
+ NetworkScanInfo nsi;
+ synchronized (mScanInfo) {
+ nsi = mScanInfo.get(message.arg2);
+ }
+ if (nsi == null) {
+ throw new RuntimeException(
+ "Failed to find NetworkScanInfo with id " + message.arg2);
+ }
+ NetworkScanCallback callback = nsi.mCallback;
+ if (callback == null) {
+ throw new RuntimeException(
+ "Failed to find NetworkScanCallback with id " + message.arg2);
+ }
+
+ switch (message.what) {
+ case CALLBACK_SCAN_RESULTS:
+ try {
+ callback.onResults((List<CellInfo>) message.obj);
+ } catch (Exception e) {
+ Rlog.e(TAG, "Exception in networkscan callback onResults", e);
+ }
+ break;
+ case CALLBACK_SCAN_ERROR:
+ try {
+ callback.onError(message.arg1);
+ } catch (Exception e) {
+ Rlog.e(TAG, "Exception in networkscan callback onError", e);
+ }
+ break;
+ case CALLBACK_SCAN_COMPLETE:
+ try {
+ callback.onComplete();
+ mScanInfo.remove(message.arg2);
+ } catch (Exception e) {
+ Rlog.e(TAG, "Exception in networkscan callback onComplete", e);
+ }
+ break;
+ default:
+ Rlog.e(TAG, "Unhandled message " + Integer.toHexString(message.what));
+ break;
+ }
+ }
+ });
+ }
+
+ /**
+ * Request a network scan.
+ *
+ * This method is asynchronous, so the network scan results will be returned by callback.
+ * The returned NetworkScan will contain a callback method which can be used to stop the scan.
+ *
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+ *
+ * @param request Contains all the RAT with bands/channels that need to be scanned.
+ * @param callback Returns network scan results or errors.
+ * @return A NetworkScan obj which contains a callback which can stop the scan.
+ * @hide
+ */
+ public NetworkScan requestNetworkScan(int subId,
+ NetworkScanRequest request, NetworkScanCallback callback) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ int scanId = telephony.requestNetworkScan(subId, request, mMessenger, new Binder());
+ saveScanInfo(scanId, request, callback);
+ return new NetworkScan(scanId, subId);
+ }
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "requestNetworkScan RemoteException", ex);
+ } catch (NullPointerException ex) {
+ Rlog.e(TAG, "requestNetworkScan NPE", ex);
+ }
+ return null;
+ }
+
+ private void saveScanInfo(int id, NetworkScanRequest request, NetworkScanCallback callback) {
+ synchronized (mScanInfo) {
+ mScanInfo.put(id, new NetworkScanInfo(request, callback));
+ }
+ }
+
+ private ITelephony getITelephony() {
+ return ITelephony.Stub.asInterface(
+ ServiceManager.getService(Context.TELEPHONY_SERVICE));
+ }
+}
diff --git a/telephony/java/android/telephony/UiccAccessRule.aidl b/telephony/java/android/telephony/UiccAccessRule.aidl
new file mode 100644
index 0000000..f923c45
--- /dev/null
+++ b/telephony/java/android/telephony/UiccAccessRule.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+parcelable UiccAccessRule;
diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java
new file mode 100644
index 0000000..e42a758
--- /dev/null
+++ b/telephony/java/android/telephony/UiccAccessRule.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.telephony;
+
+import android.annotation.Nullable;
+import android.content.pm.PackageInfo;
+import android.content.pm.Signature;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.uicc.IccUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+/**
+ * Describes a single UICC access rule according to the GlobalPlatform Secure Element Access Control
+ * specification.
+ *
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+public final class UiccAccessRule implements Parcelable {
+ private static final String TAG = "UiccAccessRule";
+
+ private static final int ENCODING_VERSION = 1;
+
+ public static final Creator<UiccAccessRule> CREATOR = new Creator<UiccAccessRule>() {
+ @Override
+ public UiccAccessRule createFromParcel(Parcel in) {
+ return new UiccAccessRule(in);
+ }
+
+ @Override
+ public UiccAccessRule[] newArray(int size) {
+ return new UiccAccessRule[size];
+ }
+ };
+
+ /**
+ * Encode these access rules as a byte array which can be parsed with {@link #decodeRules}.
+ * @hide
+ */
+ @Nullable
+ public static byte[] encodeRules(@Nullable UiccAccessRule[] accessRules) {
+ if (accessRules == null) {
+ return null;
+ }
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream output = new DataOutputStream(baos);
+ output.writeInt(ENCODING_VERSION);
+ output.writeInt(accessRules.length);
+ for (UiccAccessRule accessRule : accessRules) {
+ output.writeInt(accessRule.mCertificateHash.length);
+ output.write(accessRule.mCertificateHash);
+ if (accessRule.mPackageName != null) {
+ output.writeBoolean(true);
+ output.writeUTF(accessRule.mPackageName);
+ } else {
+ output.writeBoolean(false);
+ }
+ output.writeLong(accessRule.mAccessType);
+ }
+ output.close();
+ return baos.toByteArray();
+ } catch (IOException e) {
+ throw new IllegalStateException(
+ "ByteArrayOutputStream should never lead to an IOException", e);
+ }
+ }
+
+ /**
+ * Decodes a byte array generated with {@link #encodeRules}.
+ * @hide
+ */
+ @Nullable
+ public static UiccAccessRule[] decodeRules(@Nullable byte[] encodedRules) {
+ if (encodedRules == null) {
+ return null;
+ }
+ ByteArrayInputStream bais = new ByteArrayInputStream(encodedRules);
+ try (DataInputStream input = new DataInputStream(bais)) {
+ input.readInt(); // version; currently ignored
+ int count = input.readInt();
+ UiccAccessRule[] accessRules = new UiccAccessRule[count];
+ for (int i = 0; i < count; i++) {
+ int certificateHashLength = input.readInt();
+ byte[] certificateHash = new byte[certificateHashLength];
+ input.readFully(certificateHash);
+ String packageName = input.readBoolean() ? input.readUTF() : null;
+ long accessType = input.readLong();
+ accessRules[i] = new UiccAccessRule(certificateHash, packageName, accessType);
+ }
+ input.close();
+ return accessRules;
+ } catch (IOException e) {
+ throw new IllegalStateException(
+ "ByteArrayInputStream should never lead to an IOException", e);
+ }
+ }
+
+ private final byte[] mCertificateHash;
+ private final @Nullable String mPackageName;
+ // This bit is not currently used, but reserved for future use.
+ private final long mAccessType;
+
+ public UiccAccessRule(byte[] certificateHash, @Nullable String packageName, long accessType) {
+ this.mCertificateHash = certificateHash;
+ this.mPackageName = packageName;
+ this.mAccessType = accessType;
+ }
+
+ UiccAccessRule(Parcel in) {
+ mCertificateHash = in.createByteArray();
+ mPackageName = in.readString();
+ mAccessType = in.readLong();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByteArray(mCertificateHash);
+ dest.writeString(mPackageName);
+ dest.writeLong(mAccessType);
+ }
+
+ /**
+ * Return the package name this rule applies to.
+ *
+ * @return the package name, or null if this rule applies to any package signed with the given
+ * certificate.
+ */
+ public @Nullable String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Returns the carrier privilege status associated with the given package.
+ *
+ * @param packageInfo package info fetched from
+ * {@link android.content.pm.PackageManager#getPackageInfo}.
+ * {@link android.content.pm.PackageManager#GET_SIGNATURES} must have been passed in.
+ * @return either {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_HAS_ACCESS} or
+ * {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_NO_ACCESS}.
+ */
+ public int getCarrierPrivilegeStatus(PackageInfo packageInfo) {
+ if (packageInfo.signatures == null || packageInfo.signatures.length == 0) {
+ throw new IllegalArgumentException(
+ "Must use GET_SIGNATURES when looking up package info");
+ }
+
+ for (Signature sig : packageInfo.signatures) {
+ int accessStatus = getCarrierPrivilegeStatus(sig, packageInfo.packageName);
+ if (accessStatus != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) {
+ return accessStatus;
+ }
+ }
+
+ return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
+ }
+
+ /**
+ * Returns the carrier privilege status for the given certificate and package name.
+ *
+ * @param signature The signature of the certificate.
+ * @param packageName name of the package.
+ * @return either {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_HAS_ACCESS} or
+ * {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_NO_ACCESS}.
+ */
+ public int getCarrierPrivilegeStatus(Signature signature, String packageName) {
+ // SHA-1 is for backward compatible support only, strongly discouraged for new use.
+ byte[] certHash = getCertHash(signature, "SHA-1");
+ byte[] certHash256 = getCertHash(signature, "SHA-256");
+ if (matches(certHash, packageName) || matches(certHash256, packageName)) {
+ return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+ }
+
+ return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
+ }
+
+ private boolean matches(byte[] certHash, String packageName) {
+ return certHash != null && Arrays.equals(this.mCertificateHash, certHash) &&
+ (TextUtils.isEmpty(this.mPackageName) || this.mPackageName.equals(packageName));
+ }
+
+ @Override
+ public String toString() {
+ return "cert: " + IccUtils.bytesToHexString(mCertificateHash) + " pkg: " +
+ mPackageName + " access: " + mAccessType;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Converts a Signature into a Certificate hash usable for comparison.
+ */
+ private static byte[] getCertHash(Signature signature, String algo) {
+ try {
+ MessageDigest md = MessageDigest.getInstance(algo);
+ return md.digest(signature.toByteArray());
+ } catch (NoSuchAlgorithmException ex) {
+ Rlog.e(TAG, "NoSuchAlgorithmException: " + ex);
+ }
+ return null;
+ }
+}
diff --git a/telephony/java/android/telephony/euicc/DownloadableSubscription.aidl b/telephony/java/android/telephony/euicc/DownloadableSubscription.aidl
new file mode 100644
index 0000000..26679c9
--- /dev/null
+++ b/telephony/java/android/telephony/euicc/DownloadableSubscription.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.euicc;
+
+parcelable DownloadableSubscription;
diff --git a/telephony/java/android/telephony/euicc/DownloadableSubscription.java b/telephony/java/android/telephony/euicc/DownloadableSubscription.java
new file mode 100644
index 0000000..b5484e34
--- /dev/null
+++ b/telephony/java/android/telephony/euicc/DownloadableSubscription.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.telephony.euicc;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.UiccAccessRule;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Information about a subscription which is available for download.
+ *
+ * TODO(b/35851809): Make this public.
+ * @hide
+ */
+public final class DownloadableSubscription implements Parcelable {
+
+ public static final Creator<DownloadableSubscription> CREATOR =
+ new Creator<DownloadableSubscription>() {
+ @Override
+ public DownloadableSubscription createFromParcel(Parcel in) {
+ return new DownloadableSubscription(in);
+ }
+
+ @Override
+ public DownloadableSubscription[] newArray(int size) {
+ return new DownloadableSubscription[size];
+ }
+ };
+
+ /**
+ * Activation code. May be null for subscriptions which are not based on activation codes, e.g.
+ * to download a default subscription assigned to this device.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+ @Nullable
+ public final String encodedActivationCode;
+
+ // see getCarrierName and setCarrierName
+ @Nullable
+ private String carrierName;
+ // see getAccessRules and setAccessRules
+ private UiccAccessRule[] accessRules;
+
+ /** @hide */
+ private DownloadableSubscription(String encodedActivationCode) {
+ this.encodedActivationCode = encodedActivationCode;
+ }
+
+ private DownloadableSubscription(Parcel in) {
+ encodedActivationCode = in.readString();
+ carrierName = in.readString();
+ accessRules = in.createTypedArray(UiccAccessRule.CREATOR);
+ }
+
+ /**
+ * Create a DownloadableSubscription for the given activation code.
+ *
+ * @param encodedActivationCode the activation code to use. Must not be null.
+ * @return the {@link DownloadableSubscription} which may be passed to
+ * {@link EuiccManager#downloadSubscription}.
+ */
+ public static DownloadableSubscription forActivationCode(String encodedActivationCode) {
+ Preconditions.checkNotNull(encodedActivationCode, "Activation code may not be null");
+ return new DownloadableSubscription(encodedActivationCode);
+ }
+
+ /**
+ * Set the user-visible carrier name.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+ public void setCarrierName(String carrierName) {
+ this.carrierName = carrierName;
+ }
+
+ /**
+ * Returns the user-visible carrier name.
+ *
+ * <p>Only present for downloadable subscriptions that were queried from a server (as opposed to
+ * those created with {@link #forActivationCode}). May be populated with
+ * {@link EuiccManager#getDownloadableSubscriptionMetadata}.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+ @Nullable
+ public String getCarrierName() {
+ return carrierName;
+ }
+
+ /**
+ * Returns the {@link UiccAccessRule}s dictating access to this subscription.
+ *
+ * <p>Only present for downloadable subscriptions that were queried from a server (as opposed to
+ * those created with {@link #forActivationCode}). May be populated with
+ * {@link EuiccManager#getDownloadableSubscriptionMetadata}.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+ public UiccAccessRule[] getAccessRules() {
+ return accessRules;
+ }
+
+ /**
+ * Set the {@link UiccAccessRule}s dictating access to this subscription.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+ public void setAccessRules(UiccAccessRule[] accessRules) {
+ this.accessRules = accessRules;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(encodedActivationCode);
+ dest.writeString(carrierName);
+ dest.writeTypedArray(accessRules, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/telephony/java/android/telephony/euicc/EuiccInfo.aidl b/telephony/java/android/telephony/euicc/EuiccInfo.aidl
new file mode 100644
index 0000000..3cfba75
--- /dev/null
+++ b/telephony/java/android/telephony/euicc/EuiccInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.euicc;
+
+parcelable EuiccInfo;
diff --git a/telephony/java/android/telephony/euicc/EuiccInfo.java b/telephony/java/android/telephony/euicc/EuiccInfo.java
new file mode 100644
index 0000000..5bfff08
--- /dev/null
+++ b/telephony/java/android/telephony/euicc/EuiccInfo.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.telephony.euicc;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information about an eUICC chip/device.
+ *
+ * @see EuiccManager#getEuiccInfo
+ * @hide
+ *
+ * TODO(b/35851809): Make this public.
+ */
+// WARNING: Do not add any privacy-sensitive fields to this class (such as an eUICC identifier)!
+// This API is accessible to all applications. Privacy-sensitive fields should be returned in their
+// own APIs guarded with appropriate permission checks.
+public final class EuiccInfo implements Parcelable {
+
+ public static final Creator<EuiccInfo> CREATOR =
+ new Creator<EuiccInfo>() {
+ @Override
+ public EuiccInfo createFromParcel(Parcel in) {
+ return new EuiccInfo(in);
+ }
+
+ @Override
+ public EuiccInfo[] newArray(int size) {
+ return new EuiccInfo[size];
+ }
+ };
+
+ /**
+ * Version of the operating system running on the eUICC. This field is hardware-specific and is
+ * not guaranteed to match any particular format.
+ */
+ @Nullable
+ public final String osVersion;
+
+ public EuiccInfo(@Nullable String osVersion) {
+ this.osVersion = osVersion;
+ }
+
+ private EuiccInfo(Parcel in) {
+ osVersion = in.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(osVersion);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
new file mode 100644
index 0000000..be32f72
--- /dev/null
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -0,0 +1,480 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.telephony.euicc;
+
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import com.android.internal.telephony.euicc.IEuiccController;
+
+/**
+ * EuiccManager is the application interface to eUICCs, or eSIMs/embedded SIMs.
+ *
+ * <p>You do not instantiate this class directly; instead, you retrieve an instance through
+ * {@link Context#getSystemService(String)} and {@link Context#EUICC_SERVICE}.
+ *
+ * <p>See {@link #isEnabled} before attempting to use these APIs.
+ *
+ * TODO(b/35851809): Make this public.
+ * @hide
+ */
+public class EuiccManager {
+
+ /**
+ * Intent action to launch the embedded SIM (eUICC) management settings screen.
+ *
+ * <p>This screen shows a list of embedded profiles and offers the user the ability to switch
+ * between them, download new profiles, and delete unused profiles.
+ *
+ * <p>The activity will immediately finish with {@link android.app.Activity#RESULT_CANCELED} if
+ * {@link #isEnabled} is false.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS =
+ "android.telephony.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
+
+ /**
+ * Intent action to provision an embedded subscription.
+ *
+ * <p>May be called during device provisioning to launch a screen to perform embedded SIM
+ * provisioning, e.g. if no physical SIM is present and the user elects to configure their
+ * embedded SIM.
+ *
+ * <p>The activity will immediately finish with {@link android.app.Activity#RESULT_CANCELED} if
+ * {@link #isEnabled} is false or if the device is already provisioned.
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION =
+ "android.telephony.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
+
+ /**
+ * Intent action to handle a resolvable error.
+ * @hide
+ */
+ public static final String ACTION_RESOLVE_ERROR =
+ "android.telephony.euicc.action.RESOLVE_ERROR";
+
+ /**
+ * Result code for an operation indicating that the operation succeeded.
+ */
+ public static final int EMBEDDED_SUBSCRIPTION_RESULT_OK = 0;
+
+ /**
+ * Result code for an operation indicating that the user must take some action before the
+ * operation can continue.
+ *
+ * @see #startResolutionActivity
+ */
+ public static final int EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR = 1;
+
+ /**
+ * Result code for an operation indicating that a generic error occurred.
+ *
+ * <p>Note that in the future, other result codes may be returned indicating more specific
+ * errors. Thus, the caller should check for {@link #EMBEDDED_SUBSCRIPTION_RESULT_OK} or
+ * {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} to determine if the operation
+ * succeeded or failed with a user-resolvable error, and assume the operation failed for any
+ * other result, rather than checking for this specific value.
+ */
+ public static final int EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR = 2;
+
+ /**
+ * Key for an extra set on {@link PendingIntent} result callbacks providing a detailed result
+ * code.
+ *
+ * <p>This code is an implementation detail of the embedded subscription manager and is only
+ * intended for logging or debugging purposes.
+ */
+ public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE =
+ "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DETAILED_CODE";
+
+ /**
+ * Key for an extra set on {@link #getDownloadableSubscriptionMetadata} PendingIntent result
+ * callbacks providing the downloadable subscription metadata.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+ public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION =
+ "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION";
+
+ /**
+ * Key for an extra set on {@link #getDefaultDownloadableSubscriptionList} PendingIntent result
+ * callbacks providing the list of available downloadable subscriptions.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+ public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS =
+ "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS";
+
+ /**
+ * Key for an extra set on {@link PendingIntent} result callbacks providing the resolution
+ * pending intent for {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR}s.
+ * @hide
+ */
+ public static final String EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT =
+ "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT";
+
+ /**
+ * Key for an extra set on the {@link #EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT} intent
+ * containing the EuiccService action to launch for resolution.
+ * @hide
+ */
+ public static final String EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_ACTION =
+ "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_RESOLUTION_ACTION";
+
+ /**
+ * Key for an extra set on the {@link #EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT} intent
+ * providing the callback to execute after resolution is completed.
+ * @hide
+ */
+ public static final String EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT =
+ "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT";
+
+ private final Context mContext;
+ private final IEuiccController mController;
+
+ /** @hide */
+ public EuiccManager(Context context) {
+ mContext = context;
+ mController = IEuiccController.Stub.asInterface(ServiceManager.getService("econtroller"));
+ }
+
+ /**
+ * Whether embedded subscriptions are currently enabled.
+ *
+ * <p>Even on devices with the {@link PackageManager#FEATURE_TELEPHONY_EUICC} feature, embedded
+ * subscriptions may be turned off, e.g. because of a carrier restriction from an inserted
+ * physical SIM. Therefore, this runtime check should be used before accessing embedded
+ * subscription APIs.
+ *
+ * @return true if embedded subscriptions are currently enabled.
+ */
+ public boolean isEnabled() {
+ // In the future, this may reach out to IEuiccController (if non-null) to check any dynamic
+ // restrictions.
+ return mController != null;
+ }
+
+ /**
+ * Returns the EID identifying the eUICC hardware.
+ *
+ * <p>Requires that the calling app has carrier privileges on the active subscription on the
+ * eUICC.
+ *
+ * @return the EID. May be null if {@link #isEnabled()} is false or the eUICC is not ready.
+ */
+ @Nullable
+ public String getEid() {
+ if (!isEnabled()) {
+ return null;
+ }
+ try {
+ return mController.getEid();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Attempt to download the given {@link DownloadableSubscription}.
+ *
+ * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission,
+ * or the calling app must be authorized to manage both the currently-active subscription and
+ * the subscription to be downloaded according to the subscription metadata. Without the former,
+ * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned in the callback
+ * intent to prompt the user to accept the download.
+ *
+ * @param subscription the subscription to download.
+ * @param switchAfterDownload if true, the profile will be activated upon successful download.
+ * @param callbackIntent a PendingIntent to launch when the operation completes.
+ */
+ public void downloadSubscription(DownloadableSubscription subscription,
+ boolean switchAfterDownload, PendingIntent callbackIntent) {
+ if (!isEnabled()) {
+ sendUnavailableError(callbackIntent);
+ return;
+ }
+ try {
+ mController.downloadSubscription(subscription, switchAfterDownload,
+ mContext.getOpPackageName(), callbackIntent);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Start an activity to resolve a user-resolvable error.
+ *
+ * <p>If an operation returns {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR}, this
+ * method may be called to prompt the user to resolve the issue.
+ *
+ * <p>This method may only be called once for a particular error.
+ *
+ * @param activity the calling activity (which should be in the foreground).
+ * @param requestCode an application-specific request code which will be provided to
+ * {@link Activity#onActivityResult} upon completion. Note that the operation may still be
+ * in progress when the resolution activity completes; it is not fully finished until the
+ * callback intent is triggered.
+ * @param resultIntent the Intent provided to the initial callback intent which failed with
+ * {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR}.
+ * @param callbackIntent a PendingIntent to launch when the operation completes. This is
+ * trigered upon completion of the original operation that required user resolution.
+ * @throws android.content.IntentSender.SendIntentException if called more than once.
+ */
+ public void startResolutionActivity(Activity activity, int requestCode, Intent resultIntent,
+ PendingIntent callbackIntent) throws IntentSender.SendIntentException {
+ PendingIntent resolutionIntent =
+ resultIntent.getParcelableExtra(EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT);
+ if (resolutionIntent == null) {
+ throw new IllegalArgumentException("Invalid result intent");
+ }
+ Intent fillInIntent = new Intent();
+ fillInIntent.putExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT,
+ callbackIntent);
+ activity.startIntentSenderForResult(resolutionIntent.getIntentSender(), requestCode,
+ fillInIntent, 0 /* flagsMask */, 0 /* flagsValues */, 0 /* extraFlags */);
+ }
+
+ /**
+ * Continue an operation after the user resolves an error.
+ *
+ * <p>To be called by the LUI upon completion of a resolvable error flow.
+ *
+ * @param resolutionIntent The original intent used to start the LUI.
+ * @param resolutionExtras Resolution-specific extras depending on the result of the resolution.
+ * For example, this may indicate whether the user has consented or may include the input
+ * they provided.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+ public void continueOperation(Intent resolutionIntent, Bundle resolutionExtras) {
+ if (!isEnabled()) {
+ PendingIntent callbackIntent =
+ resolutionIntent.getParcelableExtra(
+ EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT);
+ if (callbackIntent != null) {
+ sendUnavailableError(callbackIntent);
+ }
+ return;
+ }
+ try {
+ mController.continueOperation(resolutionIntent, resolutionExtras);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Fills in the metadata for a DownloadableSubscription.
+ *
+ * <p>May be used in cases that a DownloadableSubscription was constructed to download a
+ * profile, but the metadata for the profile is unknown (e.g. we only know the activation code).
+ * The callback will be triggered with an Intent with
+ * {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION} set to the
+ * downloadable subscription metadata upon success.
+ *
+ * <p>Requires that the calling app has the
+ * {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. This is for
+ * internal system use only.
+ *
+ * @param subscription the subscription which needs metadata filled in
+ * @param callbackIntent a PendingIntent to launch when the operation completes.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+ public void getDownloadableSubscriptionMetadata(
+ DownloadableSubscription subscription, PendingIntent callbackIntent) {
+ if (!isEnabled()) {
+ sendUnavailableError(callbackIntent);
+ return;
+ }
+ try {
+ mController.getDownloadableSubscriptionMetadata(subscription, callbackIntent);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets metadata for subscription which are available for download on this device.
+ *
+ * <p>Subscriptions returned here may be passed to {@link #downloadSubscription}. They may have
+ * been pre-assigned to this particular device, for example. The callback will be triggered with
+ * an Intent with {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS} set to the
+ * list of available subscriptions upon success.
+ *
+ * <p>Requires that the calling app has the
+ * {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. This is for
+ * internal system use only.
+ *
+ * @param callbackIntent a PendingIntent to launch when the operation completes.
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+ public void getDefaultDownloadableSubscriptionList(PendingIntent callbackIntent) {
+ if (!isEnabled()) {
+ sendUnavailableError(callbackIntent);
+ return;
+ }
+ try {
+ mController.getDefaultDownloadableSubscriptionList(callbackIntent);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns information about the eUICC chip/device.
+ *
+ * @return the {@link EuiccInfo}. May be null if {@link #isEnabled()} is false or the eUICC is
+ * not ready.
+ */
+ @Nullable
+ public EuiccInfo getEuiccInfo() {
+ if (!isEnabled()) {
+ return null;
+ }
+ try {
+ return mController.getEuiccInfo();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Deletes the given subscription.
+ *
+ * <p>If this subscription is currently active, the device will first switch away from it onto
+ * an "empty" subscription.
+ *
+ * <p>Requires that the calling app has carrier privileges according to the metadata of the
+ * profile to be deleted, or the
+ * {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
+ *
+ * @param subscriptionId the ID of the subscription to delete.
+ * @param callbackIntent a PendingIntent to launch when the operation completes.
+ */
+ public void deleteSubscription(int subscriptionId, PendingIntent callbackIntent) {
+ if (!isEnabled()) {
+ sendUnavailableError(callbackIntent);
+ return;
+ }
+ try {
+ mController.deleteSubscription(
+ subscriptionId, mContext.getOpPackageName(), callbackIntent);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Switch to (enable) the given subscription.
+ *
+ * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission,
+ * or the calling app must be authorized to manage both the currently-active subscription and
+ * the subscription to be enabled according to the subscription metadata. Without the former,
+ * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned in the callback
+ * intent to prompt the user to accept the download.
+ *
+ * @param subscriptionId the ID of the subscription to enable. May be
+ * {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID} to deactivate the
+ * current profile without activating another profile to replace it.
+ * @param callbackIntent a PendingIntent to launch when the operation completes.
+ */
+ public void switchToSubscription(int subscriptionId, PendingIntent callbackIntent) {
+ if (!isEnabled()) {
+ sendUnavailableError(callbackIntent);
+ return;
+ }
+ try {
+ mController.switchToSubscription(
+ subscriptionId, mContext.getOpPackageName(), callbackIntent);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Update the nickname for the given subscription.
+ *
+ * <p>Requires that the calling app has the
+ * {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. This is for
+ * internal system use only.
+ *
+ * @param subscriptionId the ID of the subscription to update.
+ * @param nickname the new nickname to apply.
+ * @param callbackIntent a PendingIntent to launch when the operation completes.
+ * @hide
+ */
+ public void updateSubscriptionNickname(
+ int subscriptionId, String nickname, PendingIntent callbackIntent) {
+ if (!isEnabled()) {
+ sendUnavailableError(callbackIntent);
+ return;
+ }
+ try {
+ mController.updateSubscriptionNickname(subscriptionId, nickname, callbackIntent);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Erase all subscriptions and reset the eUICC.
+ *
+ * <p>Requires that the calling app has the
+ * {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. This is for
+ * internal system use only.
+ *
+ * @param callbackIntent a PendingIntent to launch when the operation completes.
+ * @hide
+ */
+ public void eraseSubscriptions(PendingIntent callbackIntent) {
+ if (!isEnabled()) {
+ sendUnavailableError(callbackIntent);
+ return;
+ }
+ try {
+ mController.eraseSubscriptions(callbackIntent);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static void sendUnavailableError(PendingIntent callbackIntent) {
+ try {
+ callbackIntent.send(EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR);
+ } catch (PendingIntent.CanceledException e) {
+ // Caller canceled the callback; do nothing.
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
index 69b8acc..80b2f78 100644
--- a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
@@ -345,4 +345,29 @@
public boolean isMultiparty() throws RemoteException {
return false;
}
+
+ /**
+ * Device issues RTT modify request
+ * @param toProfile The profile with requested changes made
+ */
+ @Override
+ public void sendRttModifyRequest(ImsCallProfile toProfile) {
+ }
+
+ /**
+ * Device responds to Remote RTT modify request
+ * @param status true Accepted the request
+ * false Declined the request
+ */
+ @Override
+ public void sendRttModifyResponse(boolean status) {
+ }
+
+ /**
+ * Device sends RTT message
+ * @param rttMessage RTT message to be sent
+ */
+ @Override
+ public void sendRttMessage(String rttMessage) {
+ }
}
diff --git a/telephony/java/android/telephony/ims/stub/ImsCallSessionListenerImplBase.java b/telephony/java/android/telephony/ims/stub/ImsCallSessionListenerImplBase.java
index 46f8f80..12228a1 100644
--- a/telephony/java/android/telephony/ims/stub/ImsCallSessionListenerImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsCallSessionListenerImplBase.java
@@ -247,5 +247,35 @@
ImsSuppServiceNotification suppSrvNotification) {
// no-op
}
+
+ /**
+ * Received RTT modify request from Remote Party
+ * @param session The call session.
+ * @param callProfile ImsCallProfile with updated attribute
+ */
+ @Override
+ public void callSessionRttModifyRequestReceived(IImsCallSession session,
+ ImsCallProfile callProfile) {
+ // no-op
+ }
+
+ /**
+ * Received response for RTT modify request
+ * @param status true : Accepted the request
+ * false : Declined the request
+ */
+ @Override
+ public void callSessionRttModifyResponseReceived(int status) {
+ // no -op
+ }
+
+ /**
+ * Device received RTT message from Remote UE
+ * @param rttMessage RTT message received
+ */
+ @Override
+ public void callSessionRttMessageReceived(String rttMessage) {
+ // no-op
+ }
}
diff --git a/telephony/java/android/telephony/mbms/DownloadCallback.java b/telephony/java/android/telephony/mbms/DownloadCallback.java
new file mode 100644
index 0000000..0c6fec4
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/DownloadCallback.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mbms;
+
+/**
+ * A optional listener class used by download clients to track progress.
+ * @hide
+ */
+public class DownloadCallback extends IDownloadCallback.Stub {
+ /**
+ * Gives process callbacks for a given DownloadRequest.
+ * request indicates which download is being referenced.
+ * fileInfo gives information about the file being downloaded. Note that
+ * the request may result in many files being downloaded and the client
+ * may not have been able to get a list of them in advance.
+ * downloadSize is the final amount to be downloaded. This may be different
+ * from the decoded final size, but is useful in gauging download progress.
+ * currentSize is the amount currently downloaded.
+ * decodedPercent is the percent from 0 to 100 of the file decoded. After the
+ * download completes the contents needs to be processed. It is perhaps
+ * uncompressed, transcoded and/or decrypted. Generally the download completes
+ * before the decode is started, but that's not required.
+ */
+ public void progress(DownloadRequest request, FileInfo fileInfo,
+ int downloadSize, int currentSize, int decodedPercent) {
+ }
+}
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.aidl b/telephony/java/android/telephony/mbms/DownloadRequest.aidl
new file mode 100755
index 0000000..ece577d
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.telephony.mbms;
+
+parcelable DownloadRequest;
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
new file mode 100644
index 0000000..f3ca058
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mbms;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.IllegalStateException;
+import java.net.URISyntaxException;
+
+/**
+ * A Parcelable class describing a pending Cell-Broadcast download request
+ * @hide
+ */
+public class DownloadRequest implements Parcelable {
+ /** @hide */
+ public static class Builder {
+ private int id;
+ private FileServiceInfo serviceInfo;
+ private Uri source;
+ private Uri dest;
+ private int sub;
+ private String appIntent;
+ private String appName; // not the Android app Name, the embms app Name
+
+ public Builder setId(int id) {
+ this.id = id;
+ return this;
+ }
+
+ public Builder setServiceInfo(FileServiceInfo serviceInfo) {
+ this.serviceInfo = serviceInfo;
+ return this;
+ }
+
+ public Builder setSource(Uri source) {
+ this.source = source;
+ return this;
+ }
+
+ public Builder setDest(Uri dest) {
+ this.dest = dest;
+ return this;
+ }
+
+ public Builder setSub(int sub) {
+ this.sub = sub;
+ return this;
+ }
+
+ public Builder setAppIntent(Intent intent) {
+ this.appIntent = intent.toUri(0);
+ return this;
+ }
+
+ public DownloadRequest build() {
+ return new DownloadRequest(id, serviceInfo, source, dest, sub, appIntent, appName);
+ }
+ }
+
+ private final int downloadId;
+ private final FileServiceInfo fileServiceInfo;
+ private final Uri sourceUri;
+ private final Uri destinationUri;
+ private final int subId;
+ private final String serializedResultIntentForApp;
+ private String appName; // not the Android app Name, the embms app name
+
+ private DownloadRequest(int id, FileServiceInfo serviceInfo,
+ Uri source, Uri dest,
+ int sub, String appIntent, String name) {
+ downloadId = id;
+ fileServiceInfo = serviceInfo;
+ sourceUri = source;
+ destinationUri = dest;
+ subId = sub;
+ serializedResultIntentForApp = appIntent;
+ appName = name;
+ }
+
+ public static DownloadRequest copy(DownloadRequest other) {
+ return new DownloadRequest(other);
+ }
+
+ private DownloadRequest(DownloadRequest dr) {
+ downloadId = dr.downloadId;
+ fileServiceInfo = dr.fileServiceInfo;
+ sourceUri = dr.sourceUri;
+ destinationUri = dr.destinationUri;
+ subId = dr.subId;
+ serializedResultIntentForApp = dr.serializedResultIntentForApp;
+ appName = dr.appName;
+ }
+
+ private DownloadRequest(Parcel in) {
+ downloadId = in.readInt();
+ fileServiceInfo = in.readParcelable(getClass().getClassLoader());
+ sourceUri = in.readParcelable(getClass().getClassLoader());
+ destinationUri = in.readParcelable(getClass().getClassLoader());
+ subId = in.readInt();
+ serializedResultIntentForApp = in.readString();
+ appName = in.readString();
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(downloadId);
+ out.writeParcelable(fileServiceInfo, flags);
+ out.writeParcelable(sourceUri, flags);
+ out.writeParcelable(destinationUri, flags);
+ out.writeInt(subId);
+ out.writeString(serializedResultIntentForApp);
+ out.writeString(appName);
+ }
+
+ public int getDownloadId() {
+ return downloadId;
+ }
+
+ public FileServiceInfo getFileServiceInfo() {
+ return fileServiceInfo;
+ }
+
+ public Uri getSourceUri() {
+ return sourceUri;
+ }
+
+ public Uri getDestinationUri() {
+ return destinationUri;
+ }
+
+ public int getSubId() {
+ return subId;
+ }
+
+ public Intent getIntentForApp() {
+ try {
+ return Intent.parseUri(serializedResultIntentForApp, 0);
+ } catch (URISyntaxException e) {
+ return null;
+ }
+ }
+
+ /** @hide */
+ public synchronized void setAppName(String newAppName) {
+ if (appName != null) {
+ throw new IllegalStateException("Attempting to reset appName");
+ }
+ appName = newAppName;
+ }
+
+ public String getAppName() {
+ return appName;
+ }
+
+ public static final Parcelable.Creator<DownloadRequest> CREATOR =
+ new Parcelable.Creator<DownloadRequest>() {
+ public DownloadRequest createFromParcel(Parcel in) {
+ return new DownloadRequest(in);
+ }
+ public DownloadRequest[] newArray(int size) {
+ return new DownloadRequest[size];
+ }
+ };
+}
diff --git a/telephony/java/android/telephony/mbms/DownloadStatus.aidl b/telephony/java/android/telephony/mbms/DownloadStatus.aidl
new file mode 100755
index 0000000..e7cfd39
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/DownloadStatus.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.telephony.mbms;
+
+parcelable DownloadStatus;
diff --git a/telephony/java/android/telephony/mbms/DownloadStatus.java b/telephony/java/android/telephony/mbms/DownloadStatus.java
new file mode 100644
index 0000000..90eb53f
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/DownloadStatus.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mbms;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A Parcelable class describing the status of a Cell-Broadcast download request
+ * @hide
+ */
+public class DownloadStatus implements Parcelable {
+ // includes downloads and active repair work
+ public final int activelyDownloading;
+
+ // files scheduled for future broadcast
+ public final int pendingDownloads;
+
+ // files scheduled for future repairs
+ public final int pendingRepairs;
+
+ // is a future download window scheduled with unknown
+ // number of files
+ public final boolean windowPending;
+
+ public DownloadStatus(int downloading, int downloads, int repairs, boolean window) {
+ activelyDownloading = downloading;
+ pendingDownloads = downloads;
+ pendingRepairs = repairs;
+ windowPending = window;
+ }
+
+ public static final Parcelable.Creator<DownloadStatus> CREATOR =
+ new Parcelable.Creator<DownloadStatus>() {
+ @Override
+ public DownloadStatus createFromParcel(Parcel in) {
+ return new DownloadStatus(in);
+ }
+
+ @Override
+ public DownloadStatus[] newArray(int size) {
+ return new DownloadStatus[size];
+ }
+ };
+
+ DownloadStatus(Parcel in) {
+ activelyDownloading = in.readInt();
+ pendingDownloads = in.readInt();
+ pendingRepairs = in.readInt();
+ windowPending = (in.readInt() == 1);
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(activelyDownloading);
+ dest.writeInt(pendingDownloads);
+ dest.writeInt(pendingRepairs);
+ dest.writeInt((windowPending ? 1 : 0));
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/telephony/java/android/telephony/mbms/FileInfo.aidl b/telephony/java/android/telephony/mbms/FileInfo.aidl
new file mode 100755
index 0000000..62926e1
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/FileInfo.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.telephony.mbms;
+
+parcelable FileInfo;
diff --git a/telephony/java/android/telephony/mbms/FileInfo.java b/telephony/java/android/telephony/mbms/FileInfo.java
new file mode 100644
index 0000000..d3888bd
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/FileInfo.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mbms;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A Parcelable class Cell-Broadcast downloadable file information.
+ * @hide
+ */
+public class FileInfo implements Parcelable {
+
+ /**
+ * The URI into the carriers infrastructure which points to this file.
+ * This is used internally but is also one of the few pieces of data about the content that is
+ * exposed and may be needed for disambiguation by the application.
+ */
+ final Uri uri;
+
+ /**
+ * The mime type of the content.
+ */
+ final String mimeType;
+
+ /**
+ * The size of the file in bytes.
+ */
+ final long size;
+
+ /**
+ * The MD5 hash of the file.
+ */
+ final byte md5Hash[];
+
+ /**
+ * Gets the parent service for this file.
+ */
+ public FileServiceInfo getFileServiceInfo() {
+ return null;
+ }
+
+ public static final Parcelable.Creator<FileInfo> CREATOR =
+ new Parcelable.Creator<FileInfo>() {
+ @Override
+ public FileInfo createFromParcel(Parcel source) {
+ return new FileInfo(source);
+ }
+
+ @Override
+ public FileInfo[] newArray(int size) {
+ return new FileInfo[size];
+ }
+ };
+
+ private FileInfo(Parcel in) {
+ uri = in.readParcelable(null);
+ mimeType = in.readString();
+ size = in.readLong();
+ int arraySize = in.readInt();
+ md5Hash = new byte[arraySize];
+ in.readByteArray(md5Hash);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(uri, flags);
+ dest.writeString(mimeType);
+ dest.writeLong(size);
+ dest.writeInt(md5Hash.length);
+ dest.writeByteArray(md5Hash);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.aidl b/telephony/java/android/telephony/mbms/FileServiceInfo.aidl
new file mode 100755
index 0000000..4646bad
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/FileServiceInfo.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.telephony.mbms;
+
+parcelable FileServiceInfo;
diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java
new file mode 100644
index 0000000..8bda370
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mbms;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * A Parcelable class Cell-Broadcast downloadable file information.
+ * @hide
+ */
+public class FileServiceInfo extends ServiceInfo implements Parcelable {
+ public List<FileInfo> files;
+
+ public FileServiceInfo(Map<Locale, String> newNames, String newClassName, Locale newLocale,
+ String newServiceId, Date start, Date end, List<FileInfo> newFiles) {
+ super(newNames, newClassName, newLocale, newServiceId, start, end);
+ files = new ArrayList(newFiles);
+ }
+
+ public static final Parcelable.Creator<FileServiceInfo> CREATOR =
+ new Parcelable.Creator<FileServiceInfo>() {
+ @Override
+ public FileServiceInfo createFromParcel(Parcel source) {
+ return new FileServiceInfo(source);
+ }
+
+ @Override
+ public FileServiceInfo[] newArray(int size) {
+ return new FileServiceInfo[size];
+ }
+ };
+
+ FileServiceInfo(Parcel in) {
+ super(in);
+ files = new ArrayList<FileInfo>();
+ in.readList(files, null);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeList(files);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/telephony/java/android/telephony/mbms/IDownloadCallback.aidl b/telephony/java/android/telephony/mbms/IDownloadCallback.aidl
new file mode 100755
index 0000000..a6bd7e5
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/IDownloadCallback.aidl
@@ -0,0 +1,34 @@
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.telephony.mbms;
+
+import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.FileInfo;
+
+/**
+ * The optional interface used by download clients to track progress.
+ * @hide
+ */
+interface IDownloadCallback
+{
+ /**
+ * Gives progress callbacks for a given DownloadRequest. Includes a FileInfo
+ * as the list of files may not have been known at request-time.
+ */
+ void progress(in DownloadRequest request, in FileInfo fileInfo, int downloadSize,
+ int currentSize, int decodedPercent);
+}
diff --git a/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl
new file mode 100755
index 0000000..03227d0
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl
@@ -0,0 +1,42 @@
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.telephony.mbms;
+
+import android.telephony.mbms.FileServiceInfo;
+
+import java.util.List;
+
+/**
+ * The interface the clients top-level file download listener will satisfy.
+ * @hide
+ */
+interface IMbmsDownloadManagerCallback
+{
+ void error(int errorCode, String message);
+
+ /**
+ * Called to indicate published File Services have changed.
+ *
+ * This will only be called after the application has requested
+ * a list of file services and specified a service class list
+ * of interest AND the results of a subsequent getFileServices
+ * call with the same service class list would
+ * return different
+ * results.
+ */
+ void fileServicesUpdated(in List<FileServiceInfo> services);
+}
diff --git a/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl
new file mode 100755
index 0000000..cbf0fca
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl
@@ -0,0 +1,53 @@
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.telephony.mbms;
+
+import android.telephony.mbms.StreamingServiceInfo;
+
+import java.util.List;
+
+/**
+ * The interface the clients top-level streaming listener will satisfy.
+ * @hide
+ */
+interface IMbmsStreamingManagerCallback
+{
+ void error(int errorCode, String message);
+
+ /**
+ * Called to indicate published Streaming Services have changed.
+ *
+ * This will only be called after the application has requested
+ * a list of streaming services and specified a service class list
+ * of interest AND the results of a subsequent getStreamServices
+ * call with the same service class list would
+ * return different
+ * results.
+ */
+ void streamingServicesUpdated(in List<StreamingServiceInfo> services);
+
+ /**
+ * Called to indicate the active Streaming Services have changed.
+ *
+ * This will be caused whenever a new service starts streaming or whenever
+ * MbmsStreamServiceManager.getActiveStreamingServices is called.
+ *
+ * @param services a list of StreamingServiceInfos. May be empty if
+ * there are no active StreamingServices
+ */
+ void activeStreamingServicesUpdated(in List<StreamingServiceInfo> services);
+}
diff --git a/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl b/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
new file mode 100755
index 0000000..891edad
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mbms;
+
+import android.net.Uri;
+
+/**
+ * @hide
+ */
+oneway interface IStreamingServiceCallback {
+ void error(int errorCode, String message);
+ void streamStateChanged(int state);
+ void uriUpdated(in Uri uri);
+ void broadcastSignalStrengthUpdated(int signalStrength);
+}
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java
new file mode 100644
index 0000000..16fafe4
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mbms;
+
+import java.util.List;
+
+/**
+ * A Parcelable class with Cell-Broadcast service information.
+ * @hide
+ */
+public class MbmsDownloadManagerCallback extends IMbmsDownloadManagerCallback.Stub {
+
+ public final static int ERROR_CARRIER_NOT_SUPPORTED = 1;
+ public final static int ERROR_UNABLE_TO_INITIALIZE = 2;
+ public final static int ERROR_UNABLE_TO_ALLOCATE_MEMORY = 3;
+
+
+ public void error(int errorCode, String message) {
+ // default implementation empty
+ }
+
+ /**
+ * Called to indicate published File Services have changed.
+ *
+ * This will only be called after the application has requested
+ * a list of file services and specified a service class list
+ * of interest AND the results of a subsequent getFileServices
+ * call with the same service class list would return different
+ * results.
+ *
+ * @param services a List of FileServiceInfos
+ *
+ */
+ public void fileServicesUpdated(List<FileServiceInfo> services) {
+ // default implementation empty
+ }
+}
diff --git a/telephony/java/android/telephony/mbms/MbmsException.java b/telephony/java/android/telephony/mbms/MbmsException.java
new file mode 100644
index 0000000..6b90592
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/MbmsException.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telephony.mbms;
+
+/** @hide */
+public class MbmsException extends Exception {
+ public static final int SUCCESS = 0;
+ public static final int ERROR_NO_SERVICE_INSTALLED = 1;
+ public static final int ERROR_MULTIPLE_SERVICES_INSTALLED = 2;
+ public static final int ERROR_BIND_TIMEOUT_OR_FAILURE = 3;
+ public static final int ERROR_UNABLE_TO_INITIALIZE = 4;
+ public static final int ERROR_ALREADY_INITIALIZED = 5;
+ public static final int ERROR_CONCURRENT_SERVICE_LIMIT_REACHED = 6;
+ public static final int ERROR_MIDDLEWARE_NOT_BOUND = 7;
+ public static final int ERROR_UNABLE_TO_START_SERVICE = 8;
+ public static final int ERROR_STREAM_ALREADY_STARTED = 9;
+ public static final int ERROR_END_OF_SESSION = 10;
+ public static final int ERROR_SERVICE_LOST = 11;
+ public static final int ERROR_APP_PERMISSIONS_NOT_GRANTED = 12;
+ public static final int ERROR_IN_E911 = 13;
+ public static final int ERROR_OUT_OF_MEMORY = 14;
+ public static final int ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE = 15;
+ public static final int ERROR_UNABLE_TO_READ_SIM = 16;
+ public static final int ERROR_CARRIER_CHANGE_NOT_ALLOWED = 17;
+
+ private final int mErrorCode;
+
+ /** @hide
+ * TODO: future systemapi
+ */
+ public MbmsException(int errorCode) {
+ super();
+ mErrorCode = errorCode;
+ }
+
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+}
diff --git a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
new file mode 100644
index 0000000..b3bc814
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mbms;
+
+import java.util.List;
+
+/**
+ * A Parcelable class with Cell-Broadcast service information.
+ * @hide
+ */
+public class MbmsStreamingManagerCallback extends IMbmsStreamingManagerCallback.Stub {
+
+ public final static int ERROR_CARRIER_NOT_SUPPORTED = 1;
+ public final static int ERROR_UNABLE_TO_INITIALIZE = 2;
+ public final static int ERROR_UNABLE_TO_ALLOCATE_MEMORY = 3;
+
+
+ public void error(int errorCode, String message) {
+ // default implementation empty
+ }
+
+ /**
+ * Called to indicate published Streaming Services have changed.
+ *
+ * This will only be called after the application has requested
+ * a list of streaming services and specified a service class list
+ * of interest AND the results of a subsequent getStreamServices
+ * call with the same service class list would return different
+ * results.
+ *
+ * @param services a List of StreamingServiceInfos
+ *
+ */
+ public void streamingServicesUpdated(List<StreamingServiceInfo> services) {
+ // default implementation empty
+ }
+
+ /**
+ * Called to indicate the active Streaming Services have changed.
+ *
+ * This will be caused whenever a new service starts streaming or whenever
+ * MbmsStreamServiceManager.getActiveStreamingServices is called.
+ *
+ * @param services a list of StreamingServiceInfos. May be empty if
+ * there are no active StreamingServices
+ */
+ public void activeStreamingServicesUpdated(List<StreamingServiceInfo> services) {
+ // default implementation empty
+ }
+}
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.aidl b/telephony/java/android/telephony/mbms/ServiceInfo.aidl
new file mode 100755
index 0000000..6661c26
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.telephony.mbms;
+
+parcelable ServiceInfo;
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java
new file mode 100644
index 0000000..f167f0ab
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mbms;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A Parcelable class with Cell-Broadcast service information.
+ * @hide
+ */
+public class ServiceInfo implements Parcelable {
+ // arbitrary limit on the number of locale -> name pairs we support
+ final static int MAP_LIMIT = 1000;
+ /**
+ * User displayable names listed by language. Unmodifiable.
+ */
+ final Map<Locale, String> names;
+
+ /**
+ * The class name for this service - used to catagorize and filter
+ */
+ final String className;
+
+ /**
+ * The language for this service content
+ */
+ final Locale locale;
+
+ /**
+ * The carrier's identifier for the service.
+ */
+ final String serviceId;
+
+ /**
+ * The start time indicating when this service will be available.
+ */
+ final Date sessionStartTime;
+
+ /**
+ * The end time indicating when this sesion stops being available.
+ */
+ final Date sessionEndTime;
+
+
+ public ServiceInfo(Map<Locale, String> newNames, String newClassName, Locale newLocale,
+ String newServiceId, Date start, Date end) {
+ if (newNames == null || newNames.isEmpty() || TextUtils.isEmpty(newClassName)
+ || newLocale == null || TextUtils.isEmpty(newServiceId)
+ || start == null || end == null) {
+ throw new IllegalArgumentException("Bad ServiceInfo construction");
+ }
+ if (newNames.size() > MAP_LIMIT) {
+ throw new RuntimeException("bad map length" + newNames.size());
+ }
+ names = new HashMap(newNames.size());
+ names.putAll(newNames);
+ className = newClassName;
+ locale = (Locale)newLocale.clone();
+ serviceId = newServiceId;
+ sessionStartTime = (Date)start.clone();
+ sessionEndTime = (Date)end.clone();
+ }
+
+ public static final Parcelable.Creator<FileServiceInfo> CREATOR =
+ new Parcelable.Creator<FileServiceInfo>() {
+ @Override
+ public FileServiceInfo createFromParcel(Parcel source) {
+ return new FileServiceInfo(source);
+ }
+
+ @Override
+ public FileServiceInfo[] newArray(int size) {
+ return new FileServiceInfo[size];
+ }
+ };
+
+ ServiceInfo(Parcel in) {
+ int mapCount = in.readInt();
+ if (mapCount > MAP_LIMIT || mapCount < 0) {
+ throw new RuntimeException("bad map length" + mapCount);
+ }
+ names = new HashMap(mapCount);
+ while (mapCount-- > 0) {
+ Locale locale = (java.util.Locale) in.readSerializable();
+ String name = in.readString();
+ names.put(locale, name);
+ }
+ className = in.readString();
+ locale = (java.util.Locale) in.readSerializable();
+ serviceId = in.readString();
+ sessionStartTime = (java.util.Date) in.readSerializable();
+ sessionEndTime = (java.util.Date) in.readSerializable();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ Set<Locale> keySet = names.keySet();
+ dest.writeInt(keySet.size());
+ for (Locale l : keySet) {
+ dest.writeSerializable(l);
+ dest.writeString(names.get(l));
+ }
+ dest.writeString(className);
+ dest.writeSerializable(locale);
+ dest.writeString(serviceId);
+ dest.writeSerializable(sessionStartTime);
+ dest.writeSerializable(sessionEndTime);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public Map<Locale, String> getNames() {
+ return names;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public Locale getLocale() {
+ return locale;
+ }
+
+ public String getServiceId() {
+ return serviceId;
+ }
+
+ public Date getSessionStartTime() {
+ return sessionStartTime;
+ }
+
+ public Date getSessionEndTime() {
+ return sessionEndTime;
+ }
+
+}
diff --git a/telephony/java/android/telephony/mbms/StreamingService.java b/telephony/java/android/telephony/mbms/StreamingService.java
new file mode 100644
index 0000000..85ba625
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/StreamingService.java
@@ -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.
+ */
+
+package android.telephony.mbms;
+
+import android.net.Uri;
+import android.os.DeadObjectException;
+import android.os.RemoteException;
+import android.telephony.mbms.vendor.IMbmsStreamingService;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class StreamingService {
+ private static final String LOG_TAG = "MbmsStreamingService";
+ public final static int STATE_STOPPED = 1;
+ public final static int STATE_STARTED = 2;
+ public final static int STATE_STALLED = 3;
+
+ private final String mAppName;
+ private final int mSubscriptionId;
+ private final StreamingServiceInfo mServiceInfo;
+ private final IStreamingServiceCallback mCallback;
+
+ private IMbmsStreamingService mService;
+ /**
+ * @hide
+ */
+ public StreamingService(String appName,
+ int subscriptionId,
+ IMbmsStreamingService service,
+ StreamingServiceInfo streamingServiceInfo,
+ IStreamingServiceCallback callback) {
+ mAppName = appName;
+ mSubscriptionId = subscriptionId;
+ mService = service;
+ mServiceInfo = streamingServiceInfo;
+ mCallback = callback;
+ }
+
+ /**
+ * Retreive the Uri used to play this stream.
+ *
+ * This may throw a {@link MbmsException} with the error code
+ * {@link MbmsException#ERROR_SERVICE_LOST}
+ *
+ * @return The {@link Uri} to pass to the streaming client.
+ */
+ public Uri getPlaybackUri() throws MbmsException {
+ if (mService == null) {
+ throw new IllegalStateException("No streaming service attached");
+ }
+
+ try {
+ return mService.getPlaybackUri(mAppName, mSubscriptionId, mServiceInfo.getServiceId());
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Remote process died");
+ mService = null;
+ throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+ }
+ }
+
+ /**
+ * Retreive the info for this StreamingService.
+ */
+ public StreamingServiceInfo getInfo() {
+ return mServiceInfo;
+ }
+
+ /**
+ * Stop streaming this service.
+ * This may throw a {@link MbmsException} with the error code
+ * {@link MbmsException#ERROR_SERVICE_LOST}
+ */
+ public void stopStreaming() throws MbmsException {
+ if (mService == null) {
+ throw new IllegalStateException("No streaming service attached");
+ }
+
+ try {
+ mService.stopStreaming(mAppName, mSubscriptionId, mServiceInfo.getServiceId());
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Remote process died");
+ mService = null;
+ throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+ }
+ }
+
+ public void dispose() throws MbmsException {
+ if (mService == null) {
+ throw new IllegalStateException("No streaming service attached");
+ }
+
+ try {
+ mService.disposeStream(mAppName, mSubscriptionId, mServiceInfo.getServiceId());
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Remote process died");
+ mService = null;
+ throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
+ }
+ }
+}
+
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceCallback.java b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
new file mode 100644
index 0000000..bd0a1b3
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mbms;
+
+import android.net.Uri;
+
+/**
+ * A Callback class for use when the application is actively streaming content.
+ * @hide
+ */
+public class StreamingServiceCallback extends IStreamingServiceCallback.Stub {
+
+ /**
+ * Indicates broadcast signal strength is not available for this service.
+ *
+ * This may be due to the service no longer being available due to geography
+ * or timing (end of service) or because lack of demand has caused the service
+ * to be delivered via unicast.
+ */
+ public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1;
+
+ public void error(int errorCode, String message) {
+ // default implementation empty
+ }
+
+ /**
+ * Called to indicate this stream has changed state.
+ *
+ * See {@link StreamingService#STATE_STOPPED}, {@link StreamingService#STATE_STARTED}
+ * and {@link StreamingService#STATE_STALLED}.
+ */
+ public void streamStateChanged(int state) {
+ // default implementation empty
+ }
+
+ /**
+ * Called to indicate published Download Services have changed.
+ *
+ * This may be called when a looping stream hits the end or
+ * when the a new URI should be used to correct for time drift.
+ */
+ public void uriUpdated(Uri uri) {
+ // default implementation empty
+ }
+
+ /**
+ * Broadcast Signal Strength updated.
+ *
+ * This signal strength is the BROADCAST signal strength which,
+ * depending on technology in play and it's deployment, may be
+ * stronger or weaker than the traditional UNICAST signal
+ * strength. It a simple int from 0-4 for valid levels or
+ * {@link #SIGNAL_STRENGTH_UNAVAILABLE} if broadcast is not available
+ * for this service due to timing, geography or popularity.
+ */
+ public void broadcastSignalStrengthUpdated(int signalStrength) {
+ // default implementation empty
+ }
+}
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceInfo.aidl b/telephony/java/android/telephony/mbms/StreamingServiceInfo.aidl
new file mode 100755
index 0000000..b902f27
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/StreamingServiceInfo.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.telephony.mbms;
+
+parcelable StreamingServiceInfo;
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceInfo.java b/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
new file mode 100644
index 0000000..f559585
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mbms;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * A Parcelable class Cell-Broadcast media stream information.
+ * This may not have any more info than ServiceInfo, but kept for completeness.
+ * @hide
+ */
+public class StreamingServiceInfo extends ServiceInfo implements Parcelable {
+
+ public StreamingServiceInfo(Map<Locale, String> newNames, String newClassName,
+ Locale newLocale, String newServiceId, Date start, Date end) {
+ super(newNames, newClassName, newLocale, newServiceId, start, end);
+ }
+
+ public static final Parcelable.Creator<StreamingServiceInfo> CREATOR =
+ new Parcelable.Creator<StreamingServiceInfo>() {
+ @Override
+ public StreamingServiceInfo createFromParcel(Parcel source) {
+ return new StreamingServiceInfo(source);
+ }
+
+ @Override
+ public StreamingServiceInfo[] newArray(int size) {
+ return new StreamingServiceInfo[size];
+ }
+ };
+
+ StreamingServiceInfo(Parcel in) {
+ super(in);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/telephony/java/android/telephony/mbms/UriPathPair.aidl b/telephony/java/android/telephony/mbms/UriPathPair.aidl
new file mode 100755
index 0000000..8bf76824
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/UriPathPair.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.telephony.mbms;
+
+parcelable UriPathPair;
diff --git a/telephony/java/android/telephony/mbms/UriPathPair.java b/telephony/java/android/telephony/mbms/UriPathPair.java
new file mode 100644
index 0000000..7acc270
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/UriPathPair.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telephony.mbms;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class UriPathPair implements Parcelable {
+ private final Uri mFilePathUri;
+ private final Uri mContentUri;
+
+ /** @hide */
+ public UriPathPair(Uri fileUri, Uri contentUri) {
+ if (fileUri == null || !ContentResolver.SCHEME_FILE.equals(fileUri.getScheme())) {
+ throw new IllegalArgumentException("File URI must have file scheme");
+ }
+ if (contentUri == null || !ContentResolver.SCHEME_CONTENT.equals(contentUri.getScheme())) {
+ throw new IllegalArgumentException("Content URI must have content scheme");
+ }
+
+ mFilePathUri = fileUri;
+ mContentUri = contentUri;
+ }
+
+ /** @hide */
+ protected UriPathPair(Parcel in) {
+ mFilePathUri = in.readParcelable(Uri.class.getClassLoader());
+ mContentUri = in.readParcelable(Uri.class.getClassLoader());
+ }
+
+ public static final Creator<UriPathPair> CREATOR = new Creator<UriPathPair>() {
+ @Override
+ public UriPathPair createFromParcel(Parcel in) {
+ return new UriPathPair(in);
+ }
+
+ @Override
+ public UriPathPair[] newArray(int size) {
+ return new UriPathPair[size];
+ }
+ };
+
+ /** future systemapi */
+ public Uri getFilePathUri() {
+ return mFilePathUri;
+ }
+
+ /** future systemapi */
+ public Uri getContentUri() {
+ return mContentUri;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mFilePathUri, flags);
+ dest.writeParcelable(mContentUri, flags);
+ }
+}
diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
new file mode 100755
index 0000000..6c2b816
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
@@ -0,0 +1,73 @@
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.telephony.mbms.vendor;
+
+import android.app.PendingIntent;
+import android.net.Uri;
+import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.DownloadStatus;
+import android.telephony.mbms.IMbmsDownloadManagerCallback;
+import android.telephony.mbms.IDownloadCallback;
+
+/**
+ * The interface the opaque MbmsStreamingService will satisfy.
+ * @hide
+ */
+interface IMbmsDownloadService
+{
+ /**
+ * Initialize download service
+ * Registers this listener, subId with this appName
+ *
+ * No return value. Async errors may be reported, but none expected (not doing anything yet).
+ */
+ void initialize(String appName, int subId, IMbmsDownloadManagerCallback listener);
+
+ /**
+ * - Registers serviceClasses of interest with the uid/appName/subId key.
+ * - Starts asynch fetching data on download services of matching classes to be reported
+ * later by callback.
+ *
+ * Note that subsequent calls with the same callback, appName, subId and uid will replace
+ * the service class list.
+ */
+ int getFileServices(String appName, int subId, in List<String> serviceClasses);
+
+ /**
+ * should move the params into a DownloadRequest parcelable
+ */
+ int download(in DownloadRequest downloadRequest, IDownloadCallback listener);
+
+ List<DownloadRequest> listPendingDownloads(String appName, int subscriptionId);
+
+ int cancelDownload(in DownloadRequest downloadRequest);
+
+ DownloadStatus getDownloadStatus(in DownloadRequest downloadRequest);
+
+ /*
+ * named this for 2 reasons:
+ * 1 don't want 'State' here as it conflicts with 'Status' of the previous function
+ * 2 want to perfect typing 'Knowledge'
+ */
+ void resetDownloadKnowledge(in DownloadRequest downloadRequest);
+
+ /**
+ * End of life for this MbmsDownloadManager.
+ * Any pending downloads remain in affect and may start up independently in the future.
+ */
+ void dispose(String appName, int subId);
+}
diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl
new file mode 100755
index 0000000..8ff7fa7
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl
@@ -0,0 +1,54 @@
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.telephony.mbms.vendor;
+
+import android.net.Uri;
+import android.telephony.mbms.IMbmsStreamingManagerCallback;
+import android.telephony.mbms.IStreamingServiceCallback;
+import android.telephony.mbms.StreamingServiceInfo;
+
+/**
+ * The interface the opaque MbmsStreamingService will satisfy.
+ * @hide
+ */
+interface IMbmsStreamingService
+{
+ int initialize(IMbmsStreamingManagerCallback listener, String appName, int subId);
+
+ int getStreamingServices(String appName, int subId, in List<String> serviceClasses);
+
+ int startStreaming(String appName, int subId, String serviceId,
+ IStreamingServiceCallback listener);
+
+ /**
+ * Per-stream api. Note each specifies what stream they apply to.
+ */
+
+ Uri getPlaybackUri(String appName, int subId, String serviceId);
+
+ void stopStreaming(String appName, int subId, String serviceId);
+
+ void disposeStream(String appName, int subId, String serviceId);
+
+ /**
+ * End of life for all MbmsStreamingManager's created by this uid/appName/subId.
+ * Ends any streams run under this uid/appname/subId and calls the disposed methods
+ * an callbacks registered for this uid/appName/subId and the disposed methods on any
+ * listeners registered with startStreaming.
+ */
+ void dispose(String appName, int subId);
+}
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
new file mode 100644
index 0000000..505aeae
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telephony.mbms.vendor;
+
+import android.os.RemoteException;
+import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.DownloadStatus;
+import android.telephony.mbms.IDownloadCallback;
+import android.telephony.mbms.IMbmsDownloadManagerCallback;
+
+import java.util.List;
+
+/**
+ * Base class for MbmsDownloadService. The middleware should extend this base class rather than
+ * the aidl stub for compatibility
+ * @hide
+ * TODO: future systemapi
+ */
+public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
+ @Override
+ public void initialize(String appName, int subId, IMbmsDownloadManagerCallback listener)
+ throws RemoteException {
+ }
+
+ @Override
+ public int getFileServices(String appName, int subId, List<String> serviceClasses) throws
+ RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int download(DownloadRequest downloadRequest, IDownloadCallback listener)
+ throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public List<DownloadRequest> listPendingDownloads(String appName, int subscriptionId)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public int cancelDownload(DownloadRequest downloadRequest) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public DownloadStatus getDownloadStatus(DownloadRequest downloadRequest)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void resetDownloadKnowledge(DownloadRequest downloadRequest)
+ throws RemoteException {
+ }
+
+ @Override
+ public void dispose(String appName, int subscriptionId) throws RemoteException {
+ }
+}
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
new file mode 100644
index 0000000..5b74312
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telephony.mbms.vendor;
+
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.telephony.mbms.IMbmsStreamingManagerCallback;
+import android.telephony.mbms.IStreamingServiceCallback;
+import android.telephony.mbms.MbmsException;
+
+import java.util.List;
+
+/**
+ * @hide
+ * TODO: future systemapi
+ */
+public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
+ /**
+ * Initialize streaming service for this app and subId, registering the listener.
+ *
+ * May throw an {@link IllegalArgumentException} or a {@link SecurityException}
+ *
+ * @param listener The callback to use to communicate with the app.
+ * @param appName The app name as negotiated with the wireless carrier.
+ * @param subscriptionId The subscription ID to use.
+ * @return {@link MbmsException#SUCCESS} or {@link MbmsException#ERROR_ALREADY_INITIALIZED}
+ */
+ @Override
+ public int initialize(IMbmsStreamingManagerCallback listener, String appName,
+ int subscriptionId) throws RemoteException {
+ return 0;
+ }
+
+ /**
+ * Registers serviceClasses of interest with the appName/subId key.
+ * Starts async fetching data on streaming services of matching classes to be reported
+ * later via {@link IMbmsStreamingManagerCallback#streamingServicesUpdated(List)}
+ *
+ * Note that subsequent calls with the same uid, appName and subId will replace
+ * the service class list.
+ *
+ * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
+ *
+ * @param appName The app name as negotiated with the wireless carrier.
+ * @param subscriptionId The subscription id to use.
+ * @param serviceClasses The service classes that the app wishes to get info on. The strings
+ * may contain arbitrary data as negotiated between the app and the
+ * carrier.
+ * @return One of {@link MbmsException#SUCCESS},
+ * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND},
+ * {@link MbmsException#ERROR_CONCURRENT_SERVICE_LIMIT_REACHED}
+ */
+ @Override
+ public int getStreamingServices(String appName, int subscriptionId,
+ List<String> serviceClasses) throws RemoteException {
+ return 0;
+ }
+
+ /**
+ * Starts streaming on a particular service. This method may perform asynchronous work. When
+ * the middleware is ready to send bits to the frontend, it should inform the app via
+ * {@link IStreamingServiceCallback#streamStateChanged(int)}.
+ *
+ * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
+ *
+ * @param appName The app name as negotiated with the wireless carrier.
+ * @param subscriptionId The subscription id to use.
+ * @param serviceId The ID of the streaming service that the app has requested.
+ * @param listener The listener object on which the app wishes to receive updates.
+ * @return TODO: document possible errors
+ */
+ @Override
+ public int startStreaming(String appName, int subscriptionId,
+ String serviceId, IStreamingServiceCallback listener) throws RemoteException {
+ return 0;
+ }
+
+ /**
+ * Retrieves the streaming URI for a particular service. If the middleware is not yet ready to
+ * stream the service, this method may return null.
+ *
+ * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
+ *
+ * @param appName The app name as negotiated with the wireless carrier.
+ * @param subscriptionId The subscription id to use.
+ * @param serviceId The ID of the streaming service that the app has requested.
+ * @return An opaque {@link Uri} to be passed to a video player that understands the format.
+ */
+ @Override
+ public @Nullable Uri getPlaybackUri(String appName, int subscriptionId, String serviceId)
+ throws RemoteException {
+ return null;
+ }
+
+ /**
+ * Stop streaming the stream identified by {@code serviceId}. Notification of the resulting
+ * stream state change should be reported to the app via
+ * {@link IStreamingServiceCallback#streamStateChanged(int)}.
+ * @param appName The app name as negotiated with the wireless carrier.
+ * @param subscriptionId The subscription id to use.
+ * @param serviceId The ID of the streaming service that the app wishes to stop.
+ */
+ @Override
+ public void stopStreaming(String appName, int subscriptionId, String serviceId)
+ throws RemoteException {
+ }
+
+ /**
+ * Dispose of the stream identified by {@code serviceId} for the app identified by the
+ * {@code appName} and {@code subscriptionId} arguments along with the caller's uid.
+ * No notification back to the app is required for this operation, and the callback provided via
+ * {@link #startStreaming(String, int, String, IStreamingServiceCallback)} should no longer be
+ * used after this method has called by the app.
+ * @param appName The app name as negotiated with the wireless carrier.
+ * @param subscriptionId The subscription id to use.
+ * @param serviceId The ID of the streaming service that the app wishes to dispose of.
+ */
+ @Override
+ public void disposeStream(String appName, int subscriptionId, String serviceId)
+ throws RemoteException {
+ }
+
+ /**
+ * Signals that the app wishes to dispose of the session identified by the {@code appName} and
+ * {@code subscriptionId} arguments, as well as the caller's uid. No notification back to the
+ * app is required for this operation, and the corresponding callback provided via
+ * {@link #initialize(IMbmsStreamingManagerCallback, String, int)} should no longer be used
+ * after this method has been called by the app.
+ * @param appName The app name as negotiated with the wireless carrier.
+ * @param subscriptionId The subscription id to use.
+ */
+ @Override
+ public void dispose(String appName, int subscriptionId) throws RemoteException {
+ }
+}
diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java
index cd076b1..c301029 100644
--- a/telephony/java/com/android/ims/ImsConfig.java
+++ b/telephony/java/com/android/ims/ImsConfig.java
@@ -475,9 +475,15 @@
*/
public static final int VICE_SETTING_ENABLED = 65;
+ /**
+ * RTT status: Enabled (1), or Disabled (0).
+ * Value is in Integer format.
+ */
+ public static final int RTT_SETTING_ENABLED = 66;
+
// Expand the operator config items as needed here, need to change
// PROVISIONED_CONFIG_END after that.
- public static final int PROVISIONED_CONFIG_END = VICE_SETTING_ENABLED;
+ public static final int PROVISIONED_CONFIG_END = RTT_SETTING_ENABLED;
// Expand the operator config items as needed here.
}
diff --git a/telephony/java/com/android/ims/ImsException.java b/telephony/java/com/android/ims/ImsException.java
index 74b20f4..0e8bad7 100644
--- a/telephony/java/com/android/ims/ImsException.java
+++ b/telephony/java/com/android/ims/ImsException.java
@@ -32,7 +32,7 @@
}
public ImsException(String message, int code) {
- super(message + ", code = " + code);
+ super(message + "(" + code + ")");
mCode = code;
}
diff --git a/telephony/java/com/android/ims/ImsReasonInfo.java b/telephony/java/com/android/ims/ImsReasonInfo.java
index 48f07d6..cdfc1fd 100644
--- a/telephony/java/com/android/ims/ImsReasonInfo.java
+++ b/telephony/java/com/android/ims/ImsReasonInfo.java
@@ -107,6 +107,10 @@
//Call failures for FDN
public static final int CODE_FDN_BLOCKED = 241;
+ // Network does not accept the emergency call request because IMEI was used as identification
+ // and this capability is not supported by the network.
+ public static final int CODE_IMEI_NOT_ACCEPTED = 243;
+
/**
* STATUSCODE (SIP response code) (IMS -> Telephony)
*/
diff --git a/telephony/java/com/android/ims/ImsStreamMediaProfile.java b/telephony/java/com/android/ims/ImsStreamMediaProfile.java
index 216cef5..cfe37b5 100644
--- a/telephony/java/com/android/ims/ImsStreamMediaProfile.java
+++ b/telephony/java/com/android/ims/ImsStreamMediaProfile.java
@@ -72,14 +72,20 @@
public static final int VIDEO_QUALITY_VGA_LANDSCAPE = (1 << 3);
public static final int VIDEO_QUALITY_VGA_PORTRAIT = (1 << 4);
+ /**
+ * RTT Modes
+ */
+ public static final int RTT_MODE_DISABLED = 0;
+ public static final int RTT_MODE_FULL = 1;
+
// Audio related information
public int mAudioQuality;
public int mAudioDirection;
// Video related information
public int mVideoQuality;
public int mVideoDirection;
-
-
+ // Rtt related information
+ public int mRttMode;
public ImsStreamMediaProfile(Parcel in) {
readFromParcel(in);
@@ -90,6 +96,7 @@
mAudioDirection = DIRECTION_SEND_RECEIVE;
mVideoQuality = VIDEO_QUALITY_NONE;
mVideoDirection = DIRECTION_INVALID;
+ mRttMode = RTT_MODE_DISABLED;
}
public ImsStreamMediaProfile(int audioQuality, int audioDirection,
@@ -100,11 +107,16 @@
mVideoDirection = videoDirection;
}
+ public ImsStreamMediaProfile(int rttMode) {
+ mRttMode = rttMode;
+ }
+
public void copyFrom(ImsStreamMediaProfile profile) {
mAudioQuality = profile.mAudioQuality;
mAudioDirection = profile.mAudioDirection;
mVideoQuality = profile.mVideoQuality;
mVideoDirection = profile.mVideoDirection;
+ mRttMode = profile.mRttMode;
}
@Override
@@ -112,7 +124,8 @@
return "{ audioQuality=" + mAudioQuality +
", audioDirection=" + mAudioDirection +
", videoQuality=" + mVideoQuality +
- ", videoDirection=" + mVideoDirection + " }";
+ ", videoDirection=" + mVideoDirection +
+ ", rttMode=" + mRttMode + " }";
}
@Override
@@ -126,6 +139,7 @@
out.writeInt(mAudioDirection);
out.writeInt(mVideoQuality);
out.writeInt(mVideoDirection);
+ out.writeInt(mRttMode);
}
private void readFromParcel(Parcel in) {
@@ -133,6 +147,7 @@
mAudioDirection = in.readInt();
mVideoQuality = in.readInt();
mVideoDirection = in.readInt();
+ mRttMode = in.readInt();
}
public static final Creator<ImsStreamMediaProfile> CREATOR =
@@ -147,4 +162,20 @@
return new ImsStreamMediaProfile[size];
}
};
+
+ /**
+ * Determines if it's RTT call
+ * @return true if RTT call, false otherwise.
+ */
+ public boolean isRttCall() {
+ return (mRttMode == RTT_MODE_FULL);
+ }
+
+ /**
+ * Updates the RttCall attribute
+ */
+ public void setRttMode(int rttMode) {
+ mRttMode = rttMode;
+ }
+
}
diff --git a/telephony/java/com/android/ims/internal/IImsCallSession.aidl b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
index b1f2d32..c6fc5e5 100644
--- a/telephony/java/com/android/ims/internal/IImsCallSession.aidl
+++ b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
@@ -255,4 +255,23 @@
* @return {@code True} if the session is multiparty.
*/
boolean isMultiparty();
+
+ /**
+ * Device issues RTT modify request
+ * @param toProfile The profile with requested changes made
+ */
+ void sendRttModifyRequest(in ImsCallProfile toProfile);
+
+ /*
+ * Device responds to Remote RTT modify request
+ * @param status true : Accepted the request
+ * false : Declined the request
+ */
+ void sendRttModifyResponse(in boolean status);
+
+ /*
+ * Device sends RTT message
+ * @param rttMessage RTT message to be sent
+ */
+ void sendRttMessage(in String rttMessage);
}
diff --git a/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl
index d562ecc..ad59c1d8 100644
--- a/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl
+++ b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl
@@ -130,4 +130,24 @@
*/
void callSessionSuppServiceReceived(in IImsCallSession session,
in ImsSuppServiceNotification suppSrvNotification);
+
+ /**
+ * Device received RTT modify request from Remote UE
+ * @param session ImsCallProfile with updated attribute
+ */
+ void callSessionRttModifyRequestReceived(in IImsCallSession session,
+ in ImsCallProfile callProfile);
+
+ /* Device issued RTT modify request and inturn received response
+ * from Remote UE
+ * @param status Will be one of the following values from:
+ * - {@link Connection.RttModifyStatus}
+ */
+ void callSessionRttModifyResponseReceived(in int status);
+
+ /*
+ * While in call, device received RTT message from Remote UE
+ * @param rttMessage Received RTT message
+ */
+ void callSessionRttMessageReceived(in String rttMessage);
}
diff --git a/telephony/java/com/android/ims/internal/ImsCallSession.java b/telephony/java/com/android/ims/internal/ImsCallSession.java
index 8196b23..f20f7c8 100644
--- a/telephony/java/com/android/ims/internal/ImsCallSession.java
+++ b/telephony/java/com/android/ims/internal/ImsCallSession.java
@@ -403,6 +403,28 @@
public void callSessionSuppServiceReceived(ImsCallSession session,
ImsSuppServiceNotification suppServiceInfo) {
}
+
+ /**
+ * Received RTT modify request from Remote Party
+ */
+ public void callSessionRttModifyRequestReceived(ImsCallSession session,
+ ImsCallProfile callProfile) {
+ // no-op
+ }
+
+ /**
+ * Received response for RTT modify request
+ */
+ public void callSessionRttModifyResponseReceived(int status) {
+ // no -op
+ }
+
+ /**
+ * Device received RTT message from Remote UE
+ */
+ public void callSessionRttMessageReceived(String rttMessage) {
+ // no-op
+ }
}
private final IImsCallSession miSession;
@@ -944,6 +966,57 @@
}
/**
+ * Sends Rtt Message
+ *
+ * @param rttMessage rtt text to be sent
+ * @throws ImsException if call is absent
+ */
+ public void sendRttMessage(String rttMessage) {
+ if (mClosed) {
+ return;
+ }
+
+ try {
+ miSession.sendRttMessage(rttMessage);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Sends RTT Upgrade request
+ *
+ * @param to : expected profile
+ * @throws CallStateException
+ */
+ public void sendRttModifyRequest(ImsCallProfile to) {
+ if (mClosed) {
+ return;
+ }
+
+ try {
+ miSession.sendRttModifyRequest(to);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Sends RTT Upgrade response
+ *
+ * @param response : response for upgrade
+ * @throws CallStateException
+ */
+ public void sendRttModifyResponse(boolean response) {
+ if (mClosed) {
+ return;
+ }
+
+ try {
+ miSession.sendRttModifyResponse(response);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* A listener type for receiving notification on IMS call session events.
* When an event is generated for an {@link IImsCallSession},
* the application is notified by having one of the methods called on
@@ -1267,6 +1340,36 @@
}
}
+ /**
+ * Received RTT modify request from remote party
+ */
+ @Override
+ public void callSessionRttModifyRequestReceived(IImsCallSession session,
+ ImsCallProfile callProfile) {
+ if (mListener != null) {
+ mListener.callSessionRttModifyRequestReceived(ImsCallSession.this, callProfile);
+ }
+ }
+
+ /**
+ * Received response for RTT modify request
+ */
+ @Override
+ public void callSessionRttModifyResponseReceived(int status) {
+ if (mListener != null) {
+ mListener.callSessionRttModifyResponseReceived(status);
+ }
+ }
+
+ /**
+ * RTT Message received
+ */
+ @Override
+ public void callSessionRttMessageReceived(String rttMessage) {
+ if (mListener != null) {
+ mListener.callSessionRttMessageReceived(rttMessage);
+ }
+ }
}
/**
diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java
index c9c48dc..f646028 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfo.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfo.java
@@ -576,7 +576,7 @@
* @return a geographical description string for the specified number.
* @see com.android.i18n.phonenumbers.PhoneNumberOfflineGeocoder
*/
- private static String getGeoDescription(Context context, String number) {
+ public static String getGeoDescription(Context context, String number) {
if (VDBG) Rlog.v(TAG, "getGeoDescription('" + number + "')...");
if (TextUtils.isEmpty(number)) {
diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
index 20c303e..af993be 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
@@ -27,6 +27,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.ContactsContract.PhoneLookup;
@@ -35,6 +36,9 @@
import android.telephony.Rlog;
import android.telephony.SubscriptionManager;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Helper class to make it easier to run asynchronous caller-id lookup queries.
* @see CallerInfo
@@ -50,6 +54,7 @@
private static final int EVENT_END_OF_QUEUE = 3;
private static final int EVENT_EMERGENCY_NUMBER = 4;
private static final int EVENT_VOICEMAIL_NUMBER = 5;
+ private static final int EVENT_GET_GEO_DESCRIPTION = 6;
private CallerInfoAsyncQueryHandler mHandler;
@@ -79,6 +84,7 @@
public Object cookie;
public int event;
public String number;
+ public String geoDescription;
public int subId;
}
@@ -142,6 +148,7 @@
private Context mContext;
private Uri mQueryUri;
private CallerInfo mCallerInfo;
+ private List<Runnable> mPendingListenerCallbacks = new ArrayList<>();
/**
* Our own query worker thread.
@@ -207,10 +214,31 @@
reply.sendToTarget();
break;
+ case EVENT_GET_GEO_DESCRIPTION:
+ handleGeoDescription(msg);
+ break;
default:
}
}
}
+
+ private void handleGeoDescription(Message msg) {
+ WorkerArgs args = (WorkerArgs) msg.obj;
+ CookieWrapper cw = (CookieWrapper) args.cookie;
+ if (!TextUtils.isEmpty(cw.number) && cw.cookie != null && mContext != null) {
+ final long startTimeMillis = SystemClock.elapsedRealtime();
+ cw.geoDescription = CallerInfo.getGeoDescription(mContext, cw.number);
+ final long duration = SystemClock.elapsedRealtime() - startTimeMillis;
+ if (duration > 500) {
+ if (DBG) Rlog.d(LOG_TAG, "[handleGeoDescription]" +
+ "Spends long time to retrieve Geo description: " + duration);
+ }
+ }
+ Message reply = args.handler.obtainMessage(msg.what);
+ reply.obj = args;
+ reply.arg1 = msg.arg1;
+ reply.sendToTarget();
+ }
}
@@ -256,6 +284,11 @@
}
if (cw.event == EVENT_END_OF_QUEUE) {
+ for (Runnable r : mPendingListenerCallbacks) {
+ r.run();
+ }
+ mPendingListenerCallbacks.clear();
+
release();
if (cursor != null) {
cursor.close();
@@ -263,6 +296,18 @@
return;
}
+ // If the cw.event == EVENT_GET_GEO_DESCRIPTION, means it would not be the 1st
+ // time entering the onQueryComplete(), mCallerInfo should not be null.
+ if (cw.event == EVENT_GET_GEO_DESCRIPTION) {
+ if (mCallerInfo != null) {
+ mCallerInfo.geoDescription = cw.geoDescription;
+ }
+ // notify that we can clean up the queue after this.
+ CookieWrapper endMarker = new CookieWrapper();
+ endMarker.event = EVENT_END_OF_QUEUE;
+ startQuery(token, endMarker, null, null, null, null, null);
+ }
+
// check the token and if needed, create the callerinfo object.
if (mCallerInfo == null) {
if ((mContext == null) || (mQueryUri == null)) {
@@ -293,34 +338,24 @@
+ mCallerInfo);
}
- // Final step: look up the geocoded description.
- if (ENABLE_UNKNOWN_NUMBER_GEO_DESCRIPTION) {
- // Note we do this only if we *don't* have a valid name (i.e. if
- // no contacts matched the phone number of the incoming call),
- // since that's the only case where the incoming-call UI cares
- // about this field.
- //
- // (TODO: But if we ever want the UI to show the geoDescription
- // even when we *do* match a contact, we'll need to either call
- // updateGeoDescription() unconditionally here, or possibly add a
- // new parameter to CallerInfoAsyncQuery.startQuery() to force
- // the geoDescription field to be populated.)
-
- if (TextUtils.isEmpty(mCallerInfo.name)) {
- // Actually when no contacts match the incoming phone number,
- // the CallerInfo object is totally blank here (i.e. no name
- // *or* phoneNumber). So we need to pass in cw.number as
- // a fallback number.
- mCallerInfo.updateGeoDescription(mContext, cw.number);
- }
- }
-
// Use the number entered by the user for display.
if (!TextUtils.isEmpty(cw.number)) {
mCallerInfo.phoneNumber = PhoneNumberUtils.formatNumber(cw.number,
mCallerInfo.normalizedNumber,
CallerInfo.getCurrentCountryIso(mContext));
}
+
+ // This condition refer to the google default code for geo.
+ // If the number exists in Contacts, the CallCard would never show
+ // the geo description, so it would be unnecessary to query it.
+ if (ENABLE_UNKNOWN_NUMBER_GEO_DESCRIPTION) {
+ if (TextUtils.isEmpty(mCallerInfo.name)) {
+ if (DBG) Rlog.d(LOG_TAG, "start querying geo description");
+ cw.event = EVENT_GET_GEO_DESCRIPTION;
+ startQuery(token, cw, null, null, null, null, null);
+ return;
+ }
+ }
}
if (DBG) Rlog.d(LOG_TAG, "constructing CallerInfo object for token: " + token);
@@ -333,9 +368,15 @@
//notify the listener that the query is complete.
if (cw.listener != null) {
- Rlog.d(LOG_TAG, "notifying listener: " + cw.listener.getClass().toString() +
- " for token: " + token + mCallerInfo);
- cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo);
+ mPendingListenerCallbacks.add(new Runnable() {
+ @Override
+ public void run() {
+ if (DBG) Rlog.d(LOG_TAG, "notifying listener: "
+ + cw.listener.getClass().toString() + " for token: " + token
+ + mCallerInfo);
+ cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo);
+ }
+ });
} else {
Rlog.w(LOG_TAG, "There is no listener to notify for this query.");
}
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index f01e4c0..256e13b 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -106,6 +106,7 @@
public static final int EVENT_REDIRECTION_DETECTED = BASE + 44;
public static final int EVENT_PCO_DATA_RECEIVED = BASE + 45;
public static final int EVENT_SET_CARRIER_DATA_ENABLED = BASE + 46;
+ public static final int EVENT_DATA_RECONNECT = BASE + 47;
/***** Constants *****/
diff --git a/telephony/java/com/android/internal/telephony/IApnSourceService.aidl b/telephony/java/com/android/internal/telephony/IApnSourceService.aidl
new file mode 100644
index 0000000..07bb18b
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IApnSourceService.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.content.ContentValues;
+
+interface IApnSourceService {
+ /** Retreive APNs. */
+ ContentValues[] getApns();
+}
diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
index 02baa34..dbd8867 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
@@ -16,6 +16,8 @@
package com.android.internal.telephony;
+import android.telephony.ImsiEncryptionInfo;
+
/**
* Interface used to retrieve various phone-related subscriber information.
*
@@ -138,6 +140,18 @@
String getCompleteVoiceMailNumberForSubscriber(int subId);
/**
+ * Retrieves the Carrier information used to encrypt IMSI and IMPI.
+ */
+ ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int subId, int keyType,
+ String callingPackage);
+
+ /**
+ * Stores the Carrier information used to encrypt IMSI and IMPI.
+ */
+ void setCarrierInfoForImsiEncryption(int subId, String callingPackage,
+ in ImsiEncryptionInfo imsiEncryptionInfo);
+
+ /**
* Retrieves the alpha identifier associated with the voice mail number.
*/
String getVoiceMailAlphaTag(String callingPackage);
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 71f2c6b..644ad49 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -94,6 +94,21 @@
int getActiveSubInfoCountMax();
/**
+ * @see android.telephony.SubscriptionManager#getAvailableSubscriptionInfoList
+ */
+ List<SubscriptionInfo> getAvailableSubscriptionInfoList(String callingPackage);
+
+ /**
+ * @see android.telephony.SubscriptionManager#getAccessibleSubscriptionInfoList
+ */
+ List<SubscriptionInfo> getAccessibleSubscriptionInfoList(String callingPackage);
+
+ /**
+ * @see android.telephony.SubscriptionManager#requestEmbeddedSubscriptionInfoListRefresh
+ */
+ oneway void requestEmbeddedSubscriptionInfoListRefresh();
+
+ /**
* Add a new SubscriptionInfo to subinfo database if needed
* @param iccId the IccId of the SIM card
* @param slotIndex the slot which the SIM is inserted
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index da333ae..ab7c5e7 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -19,7 +19,10 @@
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Messenger;
import android.os.ResultReceiver;
+import android.net.NetworkStats;
import android.net.Uri;
import android.service.carrier.CarrierIdentifier;
import android.telecom.PhoneAccount;
@@ -29,8 +32,10 @@
import android.telephony.IccOpenLogicalChannelResponse;
import android.telephony.ModemActivityInfo;
import android.telephony.NeighboringCellInfo;
+import android.telephony.NetworkScanRequest;
import android.telephony.RadioAccessFamily;
import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
import android.telephony.TelephonyHistogram;
import android.telephony.VisualVoicemailSmsFilterSettings;
import com.android.ims.internal.IImsServiceController;
@@ -367,7 +372,7 @@
/**
* Report whether data connectivity is possible.
*/
- boolean isDataConnectivityPossible();
+ boolean isDataConnectivityPossible(int subId);
Bundle getCellLocation(String callingPkg);
@@ -793,6 +798,26 @@
CellNetworkScanResult getCellNetworkScanResults(int subId);
/**
+ * Perform a radio network scan and return the id of this scan.
+ *
+ * @param subId the id of the subscription.
+ * @param request Defines all the configs for network scan.
+ * @param messenger Callback messages will be sent using this messenger.
+ * @param binder the binder object instantiated in TelephonyManager.
+ * @return An id for this scan.
+ */
+ int requestNetworkScan(int subId, in NetworkScanRequest request, in Messenger messenger,
+ in IBinder binder);
+
+ /**
+ * Stop an existing radio network scan.
+ *
+ * @param subId the id of the subscription.
+ * @param scanId The id of the scan that is going to be stopped.
+ */
+ void stopNetworkScan(int subId, int scanId);
+
+ /**
* Ask the radio to connect to the input network and change selection mode to manual.
*
* @param subId the id of the subscription.
@@ -1271,10 +1296,12 @@
/**
* Get aggregated video call data usage since boot.
* Permissions android.Manifest.permission.READ_NETWORK_USAGE_HISTORY is required.
- * @return total data usage in bytes
+ *
+ * @param perUidStats True if requesting data usage per uid, otherwise overall usage.
+ * @return Snapshot of video call data usage
* @hide
*/
- long getVtDataUsage();
+ NetworkStats getVtDataUsage(int subId, boolean perUidStats);
/**
* Policy control of data connection. Usually used when data limit is passed.
@@ -1294,12 +1321,12 @@
List<ClientRequestStats> getClientRequestStats(String callingPackage, int subid);
/**
- * Set SIM card power state. Request is equivalent to inserting or removing the card.
+ * Set SIM card power state.
* @param slotIndex SIM slot id
- * @param powerUp True if powering up the SIM, otherwise powering down
+ * @param state State of SIM (power down, power up, pass through)
* @hide
* */
- void setSimPowerStateForSlot(int slotIndex, boolean powerUp);
+ void setSimPowerStateForSlot(int slotIndex, int state);
/**
* Returns a list of Forbidden PLMNs from the specified SIM App
@@ -1320,4 +1347,15 @@
* @hide
*/
boolean getEmergencyCallbackMode(int subId);
+
+ /**
+ * Get the most recently available signal strength information.
+ *
+ * Get the most recent SignalStrength information reported by the modem. Due
+ * to power saving this information may not always be current.
+ * @param subId Subscription index
+ * @return the most recent cached signal strength info from the modem
+ * @hide
+ */
+ SignalStrength getSignalStrength(int subId);
}
diff --git a/telephony/java/com/android/internal/telephony/NetworkScanResult.java b/telephony/java/com/android/internal/telephony/NetworkScanResult.java
new file mode 100644
index 0000000..2f0fdba
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/NetworkScanResult.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.CellInfo;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Defines the incremental network scan result.
+ *
+ * This class contains the network scan results. When the user starts a new scan, multiple
+ * NetworkScanResult may be returned, containing either the scan result or error. When the user
+ * stops an ongoing scan, only one NetworkScanResult will be returned to indicate either the scan
+ * is now complete or there is some error stopping it.
+ * @hide
+ */
+public final class NetworkScanResult implements Parcelable {
+
+ // Contains only part of the scan result and more are coming.
+ public static final int SCAN_STATUS_PARTIAL = 0;
+
+ // Contains the last part of the scan result and the scan is now complete.
+ public static final int SCAN_STATUS_COMPLETE = 1;
+
+ // The status of the scan, only valid when scanError = SUCCESS.
+ public int scanStatus;
+
+ /**
+ * The error code of the scan
+ *
+ * This is the error code returned from the RIL, see {@link RILConstants} for more details
+ */
+ public int scanError;
+
+ // The scan results, only valid when scanError = SUCCESS.
+ public List<CellInfo> networkInfos;
+
+ /**
+ * Creates a new NetworkScanResult with scanStatus, scanError and networkInfos
+ *
+ * @param scanStatus The status of the scan.
+ * @param scanError The error code of the scan.
+ * @param networkInfos List of the CellInfo.
+ */
+ public NetworkScanResult(int scanStatus, int scanError, List<CellInfo> networkInfos) {
+ this.scanStatus = scanStatus;
+ this.scanError = scanError;
+ this.networkInfos = networkInfos;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(scanStatus);
+ dest.writeInt(scanError);
+ dest.writeParcelableList(networkInfos, flags);
+ }
+
+ private NetworkScanResult(Parcel in) {
+ scanStatus = in.readInt();
+ scanError = in.readInt();
+ List<CellInfo> ni = new ArrayList<>();
+ in.readParcelableList(ni, Object.class.getClassLoader());
+ networkInfos = ni;
+ }
+
+ @Override
+ public boolean equals (Object o) {
+ NetworkScanResult nsr;
+
+ try {
+ nsr = (NetworkScanResult) o;
+ } catch (ClassCastException ex) {
+ return false;
+ }
+
+ if (o == null) {
+ return false;
+ }
+
+ return (scanStatus == nsr.scanStatus
+ && scanError == nsr.scanError
+ && networkInfos.equals(nsr.networkInfos));
+ }
+
+ @Override
+ public int hashCode () {
+ return ((scanStatus * 31)
+ + (scanError * 23)
+ + (networkInfos.hashCode() * 37));
+ }
+
+ public static final Creator<NetworkScanResult> CREATOR =
+ new Creator<NetworkScanResult>() {
+ @Override
+ public NetworkScanResult createFromParcel(Parcel in) {
+ return new NetworkScanResult(in);
+ }
+
+ @Override
+ public NetworkScanResult[] newArray(int size) {
+ return new NetworkScanResult[size];
+ }
+ };
+}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index eafa6b7..e2d25b8 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -414,6 +414,9 @@
int RIL_REQUEST_SEND_DEVICE_STATE = 138;
int RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER = 139;
int RIL_REQUEST_SET_SIM_CARD_POWER = 140;
+ int RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION = 141;
+ int RIL_REQUEST_START_NETWORK_SCAN = 142;
+ int RIL_REQUEST_STOP_NETWORK_SCAN = 143;
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -466,4 +469,6 @@
int RIL_UNSOL_LCEDATA_RECV = 1045;
int RIL_UNSOL_PCO_DATA = 1046;
int RIL_UNSOL_MODEM_RESTART = 1047;
+ int RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION = 1048;
+ int RIL_UNSOL_NETWORK_SCAN_RESULT = 1049;
}
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index 1f32766..0343890 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -15,6 +15,7 @@
*/
package com.android.internal.telephony;
+import android.content.Intent;
import android.content.Intent;
import android.telephony.SubscriptionManager;
@@ -207,6 +208,9 @@
* <dt>{@code PUK}</dt><dd>locked on PUK1</dd>
* <dt>{@code NETWORK}</dt><dd>locked on network personalization</dd>
* </dl>
+ * <dt>rebroadcastOnUnlock</dt>
+ * <dd>A boolean indicates a rebroadcast on unlock. optional extra, defaults to {@code false}
+ * if not specified </dd>
* </dl>
*
* <p class="note">
@@ -218,6 +222,8 @@
public static final String ACTION_SIM_STATE_CHANGED
= Intent.ACTION_SIM_STATE_CHANGED;
+ public static final String EXTRA_REBROADCAST_ON_UNLOCK= "rebroadcastOnUnlock";
+
/**
* Broadcast Action: The time was set by the carrier (typically by the NITZ string).
* This is a sticky broadcast.
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
new file mode 100644
index 0000000..725b89b
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.euicc;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telephony.euicc.DownloadableSubscription;
+import android.telephony.euicc.EuiccInfo;
+
+/** @hide */
+interface IEuiccController {
+ oneway void continueOperation(in Intent resolutionIntent, in Bundle resolutionExtras);
+ oneway void getDownloadableSubscriptionMetadata(in DownloadableSubscription subscription,
+ in PendingIntent callbackIntent);
+ oneway void getDefaultDownloadableSubscriptionList(in PendingIntent callbackIntent);
+ String getEid();
+ oneway void downloadSubscription(in DownloadableSubscription subscription,
+ boolean switchAfterDownload, String callingPackage, in PendingIntent callbackIntent);
+ EuiccInfo getEuiccInfo();
+ oneway void deleteSubscription(int subscriptionId, String callingPackage,
+ in PendingIntent callbackIntent);
+ oneway void switchToSubscription(int subscriptionId, String callingPackage,
+ in PendingIntent callbackIntent);
+ oneway void updateSubscriptionNickname(int subscriptionId, String nickname,
+ in PendingIntent callbackIntent);
+ oneway void eraseSubscriptions(in PendingIntent callbackIntent);
+}
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java
index bce5680..f28d126 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java
@@ -34,18 +34,6 @@
public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_50
= 0x0032;
- /** Channel 911 required by Taiwan NCC. ID 0~999 is allocated by GSMA */
- public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_911
- = 0x038F; // 911
-
- /** Channel 919 required by Taiwan NCC and Israel. ID 0~999 is allocated by GSMA */
- public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_919
- = 0x0397; // 919
-
- /** Channel 928 required by Israel. ID 0~999 is allocated by GSMA */
- public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_928
- = 0x03A0; // 928
-
/** Start of PWS Message Identifier range (includes ETWS and CMAS). */
public static final int MESSAGE_ID_PWS_FIRST_IDENTIFIER
= 0x1100; // 4352
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index 3c36e42..0752661 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -16,6 +16,8 @@
LOCAL_PATH:= $(call my-dir)
+# Build the android.test.runner library
+# =====================================
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -26,5 +28,56 @@
include $(BUILD_JAVA_LIBRARY)
+# Build the android.test.mock library
+# ===================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src/android/test/mock)
+
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
+
+LOCAL_MODULE:= android.test.mock
+
+include $(BUILD_JAVA_LIBRARY)
+
+# Generate the stub source files for android.test.mock.sdk
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, src/android/test/mock)
+
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/src/android/test/mock
+
+LOCAL_DROIDDOC_OPTIONS:= \
+ -stubpackages android.test.mock \
+ -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.mock.sdk_intermediates/src \
+ -nodocs
+
+LOCAL_UNINSTALLABLE_MODULE := true
+LOCAL_MODULE := android-test-mock-stubs-gen
+
+include $(BUILD_DROIDDOC)
+
+# Remember the target that will trigger the code generation.
+android_test_mock_gen_stamp := $(full_target)
+
+# Build the android.test.mock.sdk library
+# =======================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android.test.mock.sdk
+
+LOCAL_SOURCE_FILES_ALL_GENERATED := true
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Make sure to run droiddoc first to generate the stub source files.
+$(full_classes_compiled_jar) : $(android_test_mock_gen_stamp)
+$(full_classes_jack) : $(android_test_mock_gen_stamp)
+
+# Archive a copy of the classes.jar in SDK build.
+$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.mock.jar)
+
# additionally, build unit tests in a separate .apk
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/test-runner/src/android/test/AndroidTestRunner.java b/test-runner/src/android/test/AndroidTestRunner.java
index 50eaafb..7313a28 100644
--- a/test-runner/src/android/test/AndroidTestRunner.java
+++ b/test-runner/src/android/test/AndroidTestRunner.java
@@ -20,8 +20,7 @@
import android.content.Context;
import android.os.PerformanceCollector.PerformanceResultsWriter;
-import com.google.android.collect.Lists;
-
+import java.util.ArrayList;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestListener;
@@ -48,7 +47,7 @@
private Context mContext;
private boolean mSkipExecution = false;
- private List<TestListener> mTestListeners = Lists.newArrayList();
+ private List<TestListener> mTestListeners = new ArrayList<>();
private Instrumentation mInstrumentation;
private PerformanceResultsWriter mPerfWriter;
@@ -58,7 +57,8 @@
if (shouldRunSingleTestMethod(testMethodName, testClass)) {
TestCase testCase = buildSingleTestMethod(testClass, testMethodName);
- mTestCases = Lists.newArrayList(testCase);
+ mTestCases = new ArrayList<>();
+ mTestCases.add(testCase);
mTestClassName = testClass.getSimpleName();
} else {
setTest(getTest(testClass), testClass);
diff --git a/test-runner/src/android/test/ClassPathPackageInfo.java b/test-runner/src/android/test/ClassPathPackageInfo.java
index 1ab7c7f..2cf76af 100644
--- a/test-runner/src/android/test/ClassPathPackageInfo.java
+++ b/test-runner/src/android/test/ClassPathPackageInfo.java
@@ -16,9 +16,8 @@
package android.test;
-import com.google.android.collect.Sets;
-
import java.util.Collections;
+import java.util.HashSet;
import java.util.Set;
/**
@@ -44,7 +43,7 @@
}
public Set<ClassPathPackageInfo> getSubpackages() {
- Set<ClassPathPackageInfo> info = Sets.newHashSet();
+ Set<ClassPathPackageInfo> info = new HashSet<>();
for (String name : subpackageNames) {
info.add(source.getPackageInfo(name));
}
@@ -52,7 +51,7 @@
}
public Set<Class<?>> getTopLevelClassesRecursive() {
- Set<Class<?>> set = Sets.newHashSet();
+ Set<Class<?>> set = new HashSet<>();
addTopLevelClassesTo(set);
return set;
}
diff --git a/test-runner/src/android/test/ClassPathPackageInfoSource.java b/test-runner/src/android/test/ClassPathPackageInfoSource.java
index 89bb494..9bcc25a 100644
--- a/test-runner/src/android/test/ClassPathPackageInfoSource.java
+++ b/test-runner/src/android/test/ClassPathPackageInfoSource.java
@@ -17,13 +17,13 @@
package android.test;
import android.util.Log;
-import com.google.android.collect.Maps;
-import com.google.android.collect.Sets;
import dalvik.system.DexFile;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
@@ -57,7 +57,7 @@
private static String[] apkPaths;
// A cache of jar file contents
- private final Map<File, Set<String>> jarFiles = Maps.newHashMap();
+ private final Map<File, Set<String>> jarFiles = new HashMap<>();
private ClassLoader classLoader;
ClassPathPackageInfoSource() {
@@ -76,7 +76,7 @@
private ClassPathPackageInfo createPackageInfo(String packageName) {
Set<String> subpackageNames = new TreeSet<String>();
Set<String> classNames = new TreeSet<String>();
- Set<Class<?>> topLevelClasses = Sets.newHashSet();
+ Set<Class<?>> topLevelClasses = new HashSet<>();
findClasses(packageName, classNames, subpackageNames);
for (String className : classNames) {
if (className.endsWith(".R") || className.endsWith(".Manifest")) {
@@ -248,7 +248,7 @@
throws IOException {
Set<String> entryNames = jarFiles.get(jarFile);
if (entryNames == null) {
- entryNames = Sets.newHashSet();
+ entryNames = new HashSet<>();
ZipFile zipFile = new ZipFile(jarFile);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
diff --git a/test-runner/src/android/test/DatabaseTestUtils.java b/test-runner/src/android/test/DatabaseTestUtils.java
index 42ef48b..1980d92 100644
--- a/test-runner/src/android/test/DatabaseTestUtils.java
+++ b/test-runner/src/android/test/DatabaseTestUtils.java
@@ -16,11 +16,10 @@
package android.test;
-import com.google.android.collect.Sets;
-
import android.database.sqlite.SQLiteDatabase;
import android.database.Cursor;
+import java.util.HashSet;
import java.util.Set;
/**
@@ -42,7 +41,7 @@
}
private static Set<String> getSchemaSet(SQLiteDatabase db) {
- Set<String> schemaSet = Sets.newHashSet();
+ Set<String> schemaSet = new HashSet<>();
Cursor entityCursor = db.rawQuery("SELECT sql FROM sqlite_master", null);
try {
diff --git a/test-runner/src/android/test/IsolatedContext.java b/test-runner/src/android/test/IsolatedContext.java
index 3abf38f..0b77c00 100644
--- a/test-runner/src/android/test/IsolatedContext.java
+++ b/test-runner/src/android/test/IsolatedContext.java
@@ -16,8 +16,6 @@
package android.test;
-import com.google.android.collect.Lists;
-
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
@@ -38,6 +36,7 @@
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.List;
@@ -55,7 +54,7 @@
private ContentResolver mResolver;
private final MockAccountManager mMockAccountManager;
- private List<Intent> mBroadcastIntents = Lists.newArrayList();
+ private List<Intent> mBroadcastIntents = new ArrayList<>();
public IsolatedContext(
ContentResolver resolver, Context targetContext) {
@@ -67,7 +66,7 @@
/** Returns the list of intents that were broadcast since the last call to this method. */
public List<Intent> getAndClearBroadcastIntents() {
List<Intent> intents = mBroadcastIntents;
- mBroadcastIntents = Lists.newArrayList();
+ mBroadcastIntents = new ArrayList<>();
return intents;
}
diff --git a/test-runner/src/android/test/RenamingDelegatingContext.java b/test-runner/src/android/test/RenamingDelegatingContext.java
index 36786b0..fd33321 100644
--- a/test-runner/src/android/test/RenamingDelegatingContext.java
+++ b/test-runner/src/android/test/RenamingDelegatingContext.java
@@ -16,20 +16,24 @@
package android.test;
-import com.google.android.collect.Sets;
-
import android.content.Context;
import android.content.ContextWrapper;
import android.content.ContentProvider;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
-import android.os.FileUtils;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.EnumSet;
+import java.util.HashSet;
import java.util.Set;
/**
@@ -48,8 +52,8 @@
private File mCacheDir;
private final Object mSync = new Object();
- private Set<String> mDatabaseNames = Sets.newHashSet();
- private Set<String> mFileNames = Sets.newHashSet();
+ private Set<String> mDatabaseNames = new HashSet<>();
+ private Set<String> mFileNames = new HashSet<>();
public static <T extends ContentProvider> T providerWithRenamedContext(
Class<T> contentProvider, Context c, String filePrefix)
@@ -237,10 +241,14 @@
Log.w("RenamingDelegatingContext", "Unable to create cache directory");
return null;
}
- FileUtils.setPermissions(
- mCacheDir.getPath(),
- FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
- -1, -1);
+ try {
+ // Give the directory all possible permissions.
+ Files.setPosixFilePermissions(mCacheDir.toPath(),
+ EnumSet.allOf(PosixFilePermission.class));
+ } catch (IOException e) {
+ Log.e("RenamingDelegatingContext",
+ "Could not set permissions of test cacheDir", e);
+ }
}
}
return mCacheDir;
diff --git a/test-runner/src/android/test/TestCaseUtil.java b/test-runner/src/android/test/TestCaseUtil.java
index c46d403..dc053a2 100644
--- a/test-runner/src/android/test/TestCaseUtil.java
+++ b/test-runner/src/android/test/TestCaseUtil.java
@@ -16,8 +16,7 @@
package android.test;
-import com.google.android.collect.Lists;
-
+import java.util.ArrayList;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
@@ -44,7 +43,7 @@
@SuppressWarnings("unchecked")
public static List<String> getTestCaseNames(Test test, boolean flatten) {
List<Test> tests = (List<Test>) getTests(test, flatten);
- List<String> testCaseNames = Lists.newArrayList();
+ List<String> testCaseNames = new ArrayList<>();
for (Test aTest : tests) {
testCaseNames.add(getTestName(aTest));
}
@@ -57,7 +56,7 @@
private static List<? extends Test> getTests(Test test, boolean flatten,
Set<Class<?>> seen) {
- List<Test> testCases = Lists.newArrayList();
+ List<Test> testCases = new ArrayList<>();
if (test != null) {
Test workingTest = null;
diff --git a/test-runner/src/android/test/TestRunner.java b/test-runner/src/android/test/TestRunner.java
index beecc6f..ff045c3 100644
--- a/test-runner/src/android/test/TestRunner.java
+++ b/test-runner/src/android/test/TestRunner.java
@@ -32,7 +32,6 @@
import junit.framework.TestListener;
import junit.framework.Test;
import junit.framework.TestResult;
-import com.google.android.collect.Lists;
/**
* Support class that actually runs a test. Android uses this class,
@@ -54,7 +53,7 @@
private int mMode = REGRESSION;
- private List<Listener> mListeners = Lists.newArrayList();
+ private List<Listener> mListeners = new ArrayList<>();
private int mPassed;
private int mFailed;
diff --git a/test-runner/src/android/test/mock/MockContentResolver.java b/test-runner/src/android/test/mock/MockContentResolver.java
index d8e0977..a70152c 100644
--- a/test-runner/src/android/test/mock/MockContentResolver.java
+++ b/test-runner/src/android/test/mock/MockContentResolver.java
@@ -23,8 +23,7 @@
import android.database.ContentObserver;
import android.net.Uri;
-import com.google.android.collect.Maps;
-
+import java.util.HashMap;
import java.util.Map;
/**
@@ -67,7 +66,7 @@
*/
public MockContentResolver(Context context) {
super(context);
- mProviders = Maps.newHashMap();
+ mProviders = new HashMap<>();
}
/**
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index bfc2d72..ebad81c 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -149,6 +149,12 @@
throw new UnsupportedOperationException();
}
+ /** @hide */
+ @Override
+ public void reloadSharedPreferences() {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public boolean moveSharedPreferencesFrom(Context sourceContext, String name) {
throw new UnsupportedOperationException();
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index bcde519..3cb1f39 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -17,6 +17,7 @@
package android.test.mock;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.PackageInstallObserver;
import android.content.ComponentName;
import android.content.Intent;
@@ -1168,4 +1169,13 @@
public String getInstantAppAndroidId(String packageName, UserHandle user) {
throw new UnsupportedOperationException();
}
+
+ /**
+ * @hide
+ */
+ @Override
+ public void registerDexModule(String dexModulePath,
+ @Nullable DexModuleRegisterCallback callback) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java b/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java
index 3b920cf..cf6936b 100644
--- a/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java
+++ b/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java
@@ -21,7 +21,6 @@
import android.test.TestCaseUtil;
import android.util.Log;
import com.android.internal.util.Predicate;
-import com.google.android.collect.Lists;
import static android.test.suitebuilder.TestGrouping.SORT_BY_FULLY_QUALIFIED_NAME;
import static android.test.suitebuilder.TestPredicates.REJECT_SUPPRESSED;
@@ -69,7 +68,7 @@
public TestSuiteBuilder(String name, ClassLoader classLoader) {
this.suiteName = name;
this.testGrouping.setClassLoader(classLoader);
- this.testCases = Lists.newArrayList();
+ this.testCases = new ArrayList<>();
addRequirements(REJECT_SUPPRESSED);
}
diff --git a/test-runner/tests/src/android/test/AndroidTestRunnerTest.java b/test-runner/tests/src/android/test/AndroidTestRunnerTest.java
index 0574704..6723548 100644
--- a/test-runner/tests/src/android/test/AndroidTestRunnerTest.java
+++ b/test-runner/tests/src/android/test/AndroidTestRunnerTest.java
@@ -19,8 +19,7 @@
import android.test.mock.MockContext;
import android.test.suitebuilder.annotation.SmallTest;
-import com.google.android.collect.Lists;
-
+import java.util.ArrayList;
import junit.framework.TestCase;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
@@ -140,7 +139,7 @@
public void testSetTestClassWithTestSuiteProvider() throws Exception {
mAndroidTestRunner.setTestClassName(SampleTestSuiteProvider.class.getName(), null);
List<TestCase> testCases = mAndroidTestRunner.getTestCases();
- List<String> testNames = Lists.newArrayList();
+ List<String> testNames = new ArrayList<>();
for (TestCase testCase : testCases) {
testNames.add(testCase.getName());
}
@@ -152,7 +151,7 @@
public void testSetTestClassWithTestSuite() throws Exception {
mAndroidTestRunner.setTestClassName(SampleTestSuite.class.getName(), null);
List<TestCase> testCases = mAndroidTestRunner.getTestCases();
- List<String> testNames = Lists.newArrayList();
+ List<String> testNames = new ArrayList<>();
for (TestCase testCase : testCases) {
testNames.add(testCase.getName());
}
@@ -163,7 +162,7 @@
String testMethodName = "testTwo";
mAndroidTestRunner.setTestClassName(TwoTestTestCase.class.getName(), testMethodName);
List<TestCase> testCases = mAndroidTestRunner.getTestCases();
- List<String> testNames = Lists.newArrayList();
+ List<String> testNames = new ArrayList<>();
for (TestCase testCase : testCases) {
testNames.add(testCase.getName());
}
@@ -255,7 +254,7 @@
}
private static class TestListenerStub implements TestListener {
- List<String> testNames = Lists.newArrayList();
+ List<String> testNames = new ArrayList<>();
public void addError(Test test, Throwable t) {
}
diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
index 8a5fd4b..4d2f97f 100644
--- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
@@ -111,7 +111,6 @@
private Map<String, Intent> mNameToIntent;
- private Map<String, String> mNameToProcess;
private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>();
private Map<String, String> mNameToResultKey;
private Map<String, List<Long>> mNameToLaunchTime;
@@ -431,7 +430,6 @@
private void createMappings() {
mNameToIntent = new LinkedHashMap<String, Intent>();
- mNameToProcess = new LinkedHashMap<String, String>();
PackageManager pm = getInstrumentation().getContext()
.getPackageManager();
@@ -459,8 +457,9 @@
ri.activityInfo.name);
String appName = ri.loadLabel(pm).toString();
if (appName != null) {
+ // Support launching intent using package name or app name
+ mNameToIntent.put(ri.activityInfo.packageName, startIntent);
mNameToIntent.put(appName, startIntent);
- mNameToProcess.put(appName, ri.activityInfo.processName);
}
}
}
@@ -665,7 +664,8 @@
if (lineCount == 2 && line.contains(SUCCESS_MESSAGE)) {
launchSuccess = true;
}
- if (launchSuccess && lineCount == 4) {
+ // Parse TotalTime which is the launch time
+ if (launchSuccess && lineCount == 5) {
String launchSplit[] = line.split(":");
launchTime = launchSplit[1].trim();
}
diff --git a/tests/CoreTests/android/core/NsdServiceInfoTest.java b/tests/CoreTests/android/core/NsdServiceInfoTest.java
deleted file mode 100644
index 5bf0167..0000000
--- a/tests/CoreTests/android/core/NsdServiceInfoTest.java
+++ /dev/null
@@ -1,163 +0,0 @@
-package android.core;
-
-import android.test.AndroidTestCase;
-
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.StrictMode;
-import android.net.nsd.NsdServiceInfo;
-import android.util.Log;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-
-public class NsdServiceInfoTest extends AndroidTestCase {
-
- public final static InetAddress LOCALHOST;
- static {
- // Because test.
- StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
- StrictMode.setThreadPolicy(policy);
-
- InetAddress _host = null;
- try {
- _host = InetAddress.getLocalHost();
- } catch (UnknownHostException e) { }
- LOCALHOST = _host;
- }
-
- public void testLimits() throws Exception {
- NsdServiceInfo info = new NsdServiceInfo();
-
- // Non-ASCII keys.
- boolean exceptionThrown = false;
- try {
- info.setAttribute("猫", "meow");
- } catch (IllegalArgumentException e) {
- exceptionThrown = true;
- }
- assertTrue(exceptionThrown);
- assertEmptyServiceInfo(info);
-
- // ASCII keys with '=' character.
- exceptionThrown = false;
- try {
- info.setAttribute("kitten=", "meow");
- } catch (IllegalArgumentException e) {
- exceptionThrown = true;
- }
- assertTrue(exceptionThrown);
- assertEmptyServiceInfo(info);
-
- // Single key + value length too long.
- exceptionThrown = false;
- try {
- String longValue = "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" +
- "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" +
- "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" +
- "ooooooooooooooooooooooooooooong"; // 248 characters.
- info.setAttribute("longcat", longValue); // Key + value == 255 characters.
- } catch (IllegalArgumentException e) {
- exceptionThrown = true;
- }
- assertTrue(exceptionThrown);
- assertEmptyServiceInfo(info);
-
- // Total TXT record length too long.
- exceptionThrown = false;
- int recordsAdded = 0;
- try {
- for (int i = 100; i < 300; ++i) {
- // 6 char key + 5 char value + 2 bytes overhead = 13 byte record length.
- String key = String.format("key%d", i);
- info.setAttribute(key, "12345");
- recordsAdded++;
- }
- } catch (IllegalArgumentException e) {
- exceptionThrown = true;
- }
- assertTrue(exceptionThrown);
- assertTrue(100 == recordsAdded);
- assertTrue(info.getTxtRecord().length == 1300);
- }
-
- public void testParcel() throws Exception {
- NsdServiceInfo emptyInfo = new NsdServiceInfo();
- checkParcelable(emptyInfo);
-
- NsdServiceInfo fullInfo = new NsdServiceInfo();
- fullInfo.setServiceName("kitten");
- fullInfo.setServiceType("_kitten._tcp");
- fullInfo.setPort(4242);
- fullInfo.setHost(LOCALHOST);
- checkParcelable(fullInfo);
-
- NsdServiceInfo noHostInfo = new NsdServiceInfo();
- noHostInfo.setServiceName("kitten");
- noHostInfo.setServiceType("_kitten._tcp");
- noHostInfo.setPort(4242);
- checkParcelable(noHostInfo);
-
- NsdServiceInfo attributedInfo = new NsdServiceInfo();
- attributedInfo.setServiceName("kitten");
- attributedInfo.setServiceType("_kitten._tcp");
- attributedInfo.setPort(4242);
- attributedInfo.setHost(LOCALHOST);
- attributedInfo.setAttribute("color", "pink");
- attributedInfo.setAttribute("sound", (new String("にゃあ")).getBytes("UTF-8"));
- attributedInfo.setAttribute("adorable", (String) null);
- attributedInfo.setAttribute("sticky", "yes");
- attributedInfo.setAttribute("siblings", new byte[] {});
- attributedInfo.setAttribute("edge cases", new byte[] {0, -1, 127, -128});
- attributedInfo.removeAttribute("sticky");
- checkParcelable(attributedInfo);
-
- // Sanity check that we actually wrote attributes to attributedInfo.
- assertTrue(attributedInfo.getAttributes().keySet().contains("adorable"));
- String sound = new String(attributedInfo.getAttributes().get("sound"), "UTF-8");
- assertTrue(sound.equals("にゃあ"));
- byte[] edgeCases = attributedInfo.getAttributes().get("edge cases");
- assertTrue(Arrays.equals(edgeCases, new byte[] {0, -1, 127, -128}));
- assertFalse(attributedInfo.getAttributes().keySet().contains("sticky"));
- }
-
- public void checkParcelable(NsdServiceInfo original) {
- // Write to parcel.
- Parcel p = Parcel.obtain();
- Bundle writer = new Bundle();
- writer.putParcelable("test_info", original);
- writer.writeToParcel(p, 0);
-
- // Extract from parcel.
- p.setDataPosition(0);
- Bundle reader = p.readBundle();
- reader.setClassLoader(NsdServiceInfo.class.getClassLoader());
- NsdServiceInfo result = reader.getParcelable("test_info");
-
- // Assert equality of base fields.
- assertEquality(original.getServiceName(), result.getServiceName());
- assertEquality(original.getServiceType(), result.getServiceType());
- assertEquality(original.getHost(), result.getHost());
- assertTrue(original.getPort() == result.getPort());
-
- // Assert equality of attribute map.
- Map<String, byte[]> originalMap = original.getAttributes();
- Map<String, byte[]> resultMap = result.getAttributes();
- assertEquality(originalMap.keySet(), resultMap.keySet());
- for (String key : originalMap.keySet()) {
- assertTrue(Arrays.equals(originalMap.get(key), resultMap.get(key)));
- }
- }
-
- public void assertEquality(Object expected, Object result) {
- assertTrue(expected == result || expected.equals(result));
- }
-
- public void assertEmptyServiceInfo(NsdServiceInfo shouldBeEmpty) {
- assertTrue(null == shouldBeEmpty.getTxtRecord());
- }
-}
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
index b550cfa..00bf33a 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
@@ -689,8 +689,20 @@
AudioFormat format = event.getCaptureAudioFormat();
result = result + "AudioFormat: " + ((format == null) ? "null" : format.toString());
byte[] triggerAudio = event.getTriggerAudio();
- result = result + "TriggerAudio: " + (triggerAudio == null ? "null" : triggerAudio.length);
- result = result + "CaptureSession: " + event.getCaptureSession();
+ result = result + ", TriggerAudio: " + (triggerAudio == null ? "null" : triggerAudio.length);
+ byte[] data = event.getData();
+ result = result + ", Data: " + (data == null ? "null" : data.length);
+ if (data != null) {
+ try {
+ String decodedData = new String(data, "UTF-8");
+ if (decodedData.chars().allMatch(c -> (c >= 32 && c < 128) || c == 0)) {
+ result = result + ", Decoded Data: '" + decodedData + "'";
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to decode data");
+ }
+ }
+ result = result + ", CaptureSession: " + event.getCaptureSession();
result += " )";
return result;
}
diff --git a/tests/SurfaceComposition/Android.mk b/tests/SurfaceComposition/Android.mk
index 95f69f1..d97c3f4 100644
--- a/tests/SurfaceComposition/Android.mk
+++ b/tests/SurfaceComposition/Android.mk
@@ -27,6 +27,8 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := legacy-android-test junit
+
LOCAL_PACKAGE_NAME := SurfaceComposition
LOCAL_SDK_VERSION := current
diff --git a/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbDeviceStateController.java b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbDeviceStateController.java
index 1cb394e..42f7955 100644
--- a/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbDeviceStateController.java
+++ b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbDeviceStateController.java
@@ -89,7 +89,10 @@
@Override
protected void finalize() throws Throwable {
try {
- mCloseGuard.warnIfOpen();
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
release();
} finally {
super.finalize();
diff --git a/tests/WindowAnimationJank/Android.mk b/tests/WindowAnimationJank/Android.mk
index 888ae64..f356afb 100644
--- a/tests/WindowAnimationJank/Android.mk
+++ b/tests/WindowAnimationJank/Android.mk
@@ -24,7 +24,11 @@
LOCAL_PACKAGE_NAME := WindowAnimationJank
-LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator ub-janktesthelper
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ ub-uiautomator \
+ ub-janktesthelper \
+ legacy-android-test \
+ junit
LOCAL_SDK_VERSION := current
diff --git a/tests/net/Android.mk b/tests/net/Android.mk
index 504d54e..e4bf590 100644
--- a/tests/net/Android.mk
+++ b/tests/net/Android.mk
@@ -42,6 +42,7 @@
libui \
libunwind \
libutils \
+ libvndksupport \
libcrypto \
libhidl-gen-utils \
libhidlbase \
@@ -52,7 +53,6 @@
libtinyxml2 \
libvintf \
libhwbinder \
- android.hidl.base@1.0 \
android.hidl.token@1.0
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
diff --git a/tests/net/AndroidTest.xml b/tests/net/AndroidTest.xml
index 6c0a6d0..f8ecc6b 100644
--- a/tests/net/AndroidTest.xml
+++ b/tests/net/AndroidTest.xml
@@ -20,7 +20,7 @@
<option name="test-suite-tag" value="apct" />
<option name="test-tag" value="FrameworksNetTests" />
- <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.frameworks.tests.net" />
<option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
</test>
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index b984bbf..cc792cc 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -36,21 +36,49 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.app.PendingIntent;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
-
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.content.pm.ApplicationInfo;
+import android.os.Build.VERSION_CODES;
+import android.net.ConnectivityManager.NetworkCallback;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import org.junit.runner.RunWith;
+import org.junit.Before;
import org.junit.Test;
-
-
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ConnectivityManagerTest {
+
+ @Mock Context mCtx;
+ @Mock IConnectivityManager mService;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
static NetworkCapabilities verifyNetworkCapabilities(
int legacyType, int transportType, int... capabilities) {
final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType);
@@ -173,4 +201,161 @@
verifyUnrestrictedNetworkCapabilities(
ConnectivityManager.TYPE_ETHERNET, TRANSPORT_ETHERNET);
}
+
+ @Test
+ public void testCallbackRelease() throws Exception {
+ ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ NetworkRequest request = makeRequest(1);
+ NetworkCallback callback = mock(ConnectivityManager.NetworkCallback.class);
+ Handler handler = new Handler(Looper.getMainLooper());
+ ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class);
+
+ // register callback
+ when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt()))
+ .thenReturn(request);
+ manager.requestNetwork(request, callback, handler);
+
+ // callback triggers
+ captor.getValue().send(makeMessage(request, ConnectivityManager.CALLBACK_AVAILABLE));
+ verify(callback, timeout(500).times(1)).onAvailable(any());
+
+ // unregister callback
+ manager.unregisterNetworkCallback(callback);
+ verify(mService, times(1)).releaseNetworkRequest(request);
+
+ // callback does not trigger anymore.
+ captor.getValue().send(makeMessage(request, ConnectivityManager.CALLBACK_LOSING));
+ verify(callback, timeout(500).times(0)).onLosing(any(), anyInt());
+ }
+
+ @Test
+ public void testCallbackRecycling() throws Exception {
+ ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ NetworkRequest req1 = makeRequest(1);
+ NetworkRequest req2 = makeRequest(2);
+ NetworkCallback callback = mock(ConnectivityManager.NetworkCallback.class);
+ Handler handler = new Handler(Looper.getMainLooper());
+ ArgumentCaptor<Messenger> captor = ArgumentCaptor.forClass(Messenger.class);
+
+ // register callback
+ when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt()))
+ .thenReturn(req1);
+ manager.requestNetwork(req1, callback, handler);
+
+ // callback triggers
+ captor.getValue().send(makeMessage(req1, ConnectivityManager.CALLBACK_AVAILABLE));
+ verify(callback, timeout(100).times(1)).onAvailable(any());
+
+ // unregister callback
+ manager.unregisterNetworkCallback(callback);
+ verify(mService, times(1)).releaseNetworkRequest(req1);
+
+ // callback does not trigger anymore.
+ captor.getValue().send(makeMessage(req1, ConnectivityManager.CALLBACK_LOSING));
+ verify(callback, timeout(100).times(0)).onLosing(any(), anyInt());
+
+ // callback can be registered again
+ when(mService.requestNetwork(any(), captor.capture(), anyInt(), any(), anyInt()))
+ .thenReturn(req2);
+ manager.requestNetwork(req2, callback, handler);
+
+ // callback triggers
+ captor.getValue().send(makeMessage(req2, ConnectivityManager.CALLBACK_LOST));
+ verify(callback, timeout(100).times(1)).onLost(any());
+
+ // unregister callback
+ manager.unregisterNetworkCallback(callback);
+ verify(mService, times(1)).releaseNetworkRequest(req2);
+ }
+
+ // TODO: turn on this test when request callback 1:1 mapping is enforced
+ //@Test
+ private void noDoubleCallbackRegistration() throws Exception {
+ ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ NetworkRequest request = makeRequest(1);
+ NetworkCallback callback = new ConnectivityManager.NetworkCallback();
+ ApplicationInfo info = new ApplicationInfo();
+ // TODO: update version when starting to enforce 1:1 mapping
+ info.targetSdkVersion = VERSION_CODES.N_MR1 + 1;
+
+ when(mCtx.getApplicationInfo()).thenReturn(info);
+ when(mService.requestNetwork(any(), any(), anyInt(), any(), anyInt())).thenReturn(request);
+
+ Handler handler = new Handler(Looper.getMainLooper());
+ manager.requestNetwork(request, callback, handler);
+
+ // callback is already registered, reregistration should fail.
+ Class<IllegalArgumentException> wantException = IllegalArgumentException.class;
+ expectThrowable(() -> manager.requestNetwork(request, callback), wantException);
+
+ manager.unregisterNetworkCallback(callback);
+ verify(mService, times(1)).releaseNetworkRequest(request);
+
+ // unregistering the callback should make it registrable again.
+ manager.requestNetwork(request, callback);
+ }
+
+ @Test
+ public void testArgumentValidation() throws Exception {
+ ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+
+ NetworkRequest request = mock(NetworkRequest.class);
+ NetworkCallback callback = mock(NetworkCallback.class);
+ Handler handler = mock(Handler.class);
+ NetworkCallback nullCallback = null;
+ PendingIntent nullIntent = null;
+
+ mustFail(() -> { manager.requestNetwork(null, callback); });
+ mustFail(() -> { manager.requestNetwork(request, nullCallback); });
+ mustFail(() -> { manager.requestNetwork(request, callback, null); });
+ mustFail(() -> { manager.requestNetwork(request, callback, -1); });
+ mustFail(() -> { manager.requestNetwork(request, nullIntent); });
+
+ mustFail(() -> { manager.registerNetworkCallback(null, callback, handler); });
+ mustFail(() -> { manager.registerNetworkCallback(request, null, handler); });
+ mustFail(() -> { manager.registerNetworkCallback(request, callback, null); });
+ mustFail(() -> { manager.registerNetworkCallback(request, nullIntent); });
+
+ mustFail(() -> { manager.registerDefaultNetworkCallback(null, handler); });
+ mustFail(() -> { manager.registerDefaultNetworkCallback(callback, null); });
+
+ mustFail(() -> { manager.unregisterNetworkCallback(nullCallback); });
+ mustFail(() -> { manager.unregisterNetworkCallback(nullIntent); });
+ mustFail(() -> { manager.releaseNetworkRequest(nullIntent); });
+ }
+
+ static void mustFail(Runnable fn) {
+ try {
+ fn.run();
+ fail();
+ } catch (Exception expected) {
+ }
+ }
+
+ static Message makeMessage(NetworkRequest req, int messageType) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(NetworkRequest.class.getSimpleName(), req);
+ Message msg = Message.obtain();
+ msg.what = messageType;
+ msg.setData(bundle);
+ return msg;
+ }
+
+ static NetworkRequest makeRequest(int requestId) {
+ NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
+ return new NetworkRequest(request.networkCapabilities, ConnectivityManager.TYPE_NONE,
+ requestId, NetworkRequest.Type.NONE);
+ }
+
+ static void expectThrowable(Runnable block, Class<? extends Throwable> throwableType) {
+ try {
+ block.run();
+ } catch (Throwable t) {
+ if (t.getClass().equals(throwableType)) {
+ return;
+ }
+ fail("expected exception of type " + throwableType + ", but was " + t.getClass());
+ }
+ fail("expected exception of type " + throwableType);
+ }
}
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
index 91d6c68..6bf3b6b 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -39,6 +39,8 @@
import com.android.frameworks.tests.net.R;
import com.android.internal.util.HexDump;
+import static com.android.internal.util.BitUtils.bytesToBEInt;
+import static com.android.internal.util.BitUtils.put;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -65,7 +67,7 @@
* Tests for APF program generator and interpreter.
*
* Build, install and run with:
- * runtest frameworks-services -c android.net.apf.ApfTest
+ * runtest frameworks-net -c android.net.apf.ApfTest
*/
public class ApfTest extends AndroidTestCase {
private static final int TIMEOUT_MS = 500;
@@ -1064,10 +1066,15 @@
final int ROUTE_LIFETIME = 400;
// Note that lifetime of 2000 will be ignored in favor of shorter route lifetime of 1000.
final int DNSSL_LIFETIME = 2000;
+ final int VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET = ETH_HEADER_LEN;
+ // IPv6, traffic class = 0, flow label = 0x12345
+ final int VERSION_TRAFFIC_CLASS_FLOW_LABEL = 0x60012345;
// Verify RA is passed the first time
ByteBuffer basePacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
basePacket.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ basePacket.putInt(VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET,
+ VERSION_TRAFFIC_CLASS_FLOW_LABEL);
basePacket.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
basePacket.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_ADVERTISEMENT);
basePacket.putShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET, (short)ROUTER_LIFETIME);
@@ -1078,6 +1085,13 @@
testRaLifetime(apfFilter, ipManagerCallback, basePacket, ROUTER_LIFETIME);
verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, -1));
+ ByteBuffer newFlowLabelPacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
+ basePacket.clear();
+ newFlowLabelPacket.put(basePacket);
+ // Check that changes are ignored in every byte of the flow label.
+ newFlowLabelPacket.putInt(VERSION_TRAFFIC_CLASS_FLOW_LABEL_OFFSET,
+ VERSION_TRAFFIC_CLASS_FLOW_LABEL + 0x11111);
+
// Ensure zero-length options cause the packet to be silently skipped.
// Do this before we test other packets. http://b/29586253
ByteBuffer zeroLengthOptionPacket = ByteBuffer.wrap(
@@ -1143,6 +1157,7 @@
// Verify that current program filters all five RAs:
program = ipManagerCallback.getApfProgram();
verifyRaLifetime(program, basePacket, ROUTER_LIFETIME);
+ verifyRaLifetime(program, newFlowLabelPacket, ROUTER_LIFETIME);
verifyRaLifetime(program, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
verifyRaLifetime(program, rdnssOptionPacket, RDNSS_LIFETIME);
verifyRaLifetime(program, routeInfoOptionPacket, ROUTE_LIFETIME);
@@ -1235,15 +1250,6 @@
byte[] apf_program);
@SmallTest
- public void testBytesToInt() {
- assertEquals(0x00000000, ApfFilter.bytesToInt(IPV4_ANY_HOST_ADDR));
- assertEquals(0xffffffff, ApfFilter.bytesToInt(IPV4_BROADCAST_ADDRESS));
- assertEquals(0x0a000001, ApfFilter.bytesToInt(MOCK_IPV4_ADDR));
- assertEquals(0x0a000002, ApfFilter.bytesToInt(ANOTHER_IPV4_ADDR));
- assertEquals(0x0a001fff, ApfFilter.bytesToInt(MOCK_BROADCAST_IPV4_ADDR));
- assertEquals(0xe0000001, ApfFilter.bytesToInt(MOCK_MULTICAST_IPV4_ADDR));
- }
-
public void testBroadcastAddress() throws Exception {
assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 0));
assertEqualsIp("0.0.0.0", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 32));
@@ -1257,7 +1263,7 @@
}
public void assertEqualsIp(String expected, int got) throws Exception {
- int want = ApfFilter.bytesToInt(InetAddress.getByName(expected).getAddress());
+ int want = bytesToBEInt(InetAddress.getByName(expected).getAddress());
assertEquals(want, got);
}
}
diff --git a/tests/net/java/android/net/nsd/NsdManagerTest.java b/tests/net/java/android/net/nsd/NsdManagerTest.java
new file mode 100644
index 0000000..adf6998
--- /dev/null
+++ b/tests/net/java/android/net/nsd/NsdManagerTest.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.nsd;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.HandlerThread;
+import android.os.Handler;
+import android.os.Looper;
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.os.Message;
+import android.os.Messenger;
+import com.android.internal.util.AsyncChannel;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NsdManagerTest {
+
+ static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
+
+ @Mock Context mContext;
+ @Mock INsdManager mService;
+ MockServiceHandler mServiceHandler;
+
+ long mTimeoutMs = 100; // non-final so that tests can adjust the value.
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mServiceHandler = spy(MockServiceHandler.create(mContext));
+ when(mService.getMessenger()).thenReturn(new Messenger(mServiceHandler));
+ }
+
+ @Test
+ public void testResolveService() {
+ NsdManager manager = makeManager();
+
+ NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
+ NsdServiceInfo reply = new NsdServiceInfo("resolved_name", "resolved_type");
+ NsdManager.ResolveListener listener = mock(NsdManager.ResolveListener.class);
+
+ manager.resolveService(request, listener);
+ int key1 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+ int err = 33;
+ sendResponse(NsdManager.RESOLVE_SERVICE_FAILED, err, key1, null);
+ verify(listener, timeout(mTimeoutMs).times(1)).onResolveFailed(request, err);
+
+ manager.resolveService(request, listener);
+ int key2 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+ sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key2, reply);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
+ }
+
+ @Test
+ public void testParallelResolveService() {
+ NsdManager manager = makeManager();
+
+ NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
+ NsdServiceInfo reply = new NsdServiceInfo("resolved_name", "resolved_type");
+
+ NsdManager.ResolveListener listener1 = mock(NsdManager.ResolveListener.class);
+ NsdManager.ResolveListener listener2 = mock(NsdManager.ResolveListener.class);
+
+ manager.resolveService(request, listener1);
+ int key1 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+
+ manager.resolveService(request, listener2);
+ int key2 = verifyRequest(NsdManager.RESOLVE_SERVICE);
+
+ sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key2, reply);
+ sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key1, reply);
+
+ verify(listener1, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
+ verify(listener2, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
+ }
+
+ @Test
+ public void testRegisterService() {
+ NsdManager manager = makeManager();
+
+ NsdServiceInfo request1 = new NsdServiceInfo("a_name", "a_type");
+ NsdServiceInfo request2 = new NsdServiceInfo("another_name", "another_type");
+ request1.setPort(2201);
+ request2.setPort(2202);
+ NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
+ NsdManager.RegistrationListener listener2 = mock(NsdManager.RegistrationListener.class);
+
+ // Register two services
+ manager.registerService(request1, PROTOCOL, listener1);
+ int key1 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+ manager.registerService(request2, PROTOCOL, listener2);
+ int key2 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+ // First reques fails, second request succeeds
+ sendResponse(NsdManager.REGISTER_SERVICE_SUCCEEDED, 0, key2, request2);
+ verify(listener2, timeout(mTimeoutMs).times(1)).onServiceRegistered(request2);
+
+ int err = 1;
+ sendResponse(NsdManager.REGISTER_SERVICE_FAILED, err, key1, request1);
+ verify(listener1, timeout(mTimeoutMs).times(1)).onRegistrationFailed(request1, err);
+
+ // Client retries first request, it succeeds
+ manager.registerService(request1, PROTOCOL, listener1);
+ int key3 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+ sendResponse(NsdManager.REGISTER_SERVICE_SUCCEEDED, 0, key3, request1);
+ verify(listener1, timeout(mTimeoutMs).times(1)).onServiceRegistered(request1);
+
+ // First request is unregistered, it succeeds
+ manager.unregisterService(listener1);
+ int key3again = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+ assertEquals(key3, key3again);
+
+ sendResponse(NsdManager.UNREGISTER_SERVICE_SUCCEEDED, 0, key3again, null);
+ verify(listener1, timeout(mTimeoutMs).times(1)).onServiceUnregistered(request1);
+
+ // Second request is unregistered, it fails
+ manager.unregisterService(listener2);
+ int key2again = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+ assertEquals(key2, key2again);
+
+ sendResponse(NsdManager.UNREGISTER_SERVICE_FAILED, err, key2again, null);
+ verify(listener2, timeout(mTimeoutMs).times(1)).onUnregistrationFailed(request2, err);
+
+ // TODO: do not unregister listener until service is unregistered
+ // Client retries unregistration of second request, it succeeds
+ //manager.unregisterService(listener2);
+ //int key2yetAgain = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+ //assertEquals(key2, key2yetAgain);
+
+ //sendResponse(NsdManager.UNREGISTER_SERVICE_SUCCEEDED, 0, key2yetAgain, null);
+ //verify(listener2, timeout(mTimeoutMs).times(1)).onServiceUnregistered(request2);
+ }
+
+ @Test
+ public void testDiscoverService() {
+ NsdManager manager = makeManager();
+
+ NsdServiceInfo reply1 = new NsdServiceInfo("a_name", "a_type");
+ NsdServiceInfo reply2 = new NsdServiceInfo("another_name", "a_type");
+ NsdServiceInfo reply3 = new NsdServiceInfo("a_third_name", "a_type");
+
+ NsdManager.DiscoveryListener listener = mock(NsdManager.DiscoveryListener.class);
+
+ // Client registers for discovery, request fails
+ manager.discoverServices("a_type", PROTOCOL, listener);
+ int key1 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+ int err = 1;
+ sendResponse(NsdManager.DISCOVER_SERVICES_FAILED, err, key1, null);
+ verify(listener, timeout(mTimeoutMs).times(1)).onStartDiscoveryFailed("a_type", err);
+
+ // Client retries, request succeeds
+ manager.discoverServices("a_type", PROTOCOL, listener);
+ int key2 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+ sendResponse(NsdManager.DISCOVER_SERVICES_STARTED, 0, key2, reply1);
+ verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
+
+
+ // mdns notifies about services
+ sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply1);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceFound(reply1);
+
+ sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply2);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceFound(reply2);
+
+ sendResponse(NsdManager.SERVICE_LOST, 0, key2, reply2);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceLost(reply2);
+
+
+ // Client unregisters its listener
+ manager.stopServiceDiscovery(listener);
+ int key2again = verifyRequest(NsdManager.STOP_DISCOVERY);
+ assertEquals(key2, key2again);
+
+ // TODO: unregister listener immediately and stop notifying it about services
+ // Notifications are still passed to the client's listener
+ sendResponse(NsdManager.SERVICE_LOST, 0, key2, reply1);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceLost(reply1);
+
+ // Client is notified of complete unregistration
+ sendResponse(NsdManager.STOP_DISCOVERY_SUCCEEDED, 0, key2again, "a_type");
+ verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStopped("a_type");
+
+ // Notifications are not passed to the client anymore
+ sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply3);
+ verify(listener, timeout(mTimeoutMs).times(0)).onServiceLost(reply3);
+
+
+ // Client registers for service discovery
+ reset(listener);
+ manager.discoverServices("a_type", PROTOCOL, listener);
+ int key3 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+ sendResponse(NsdManager.DISCOVER_SERVICES_STARTED, 0, key3, reply1);
+ verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
+
+ // Client unregisters immediately, it fails
+ manager.stopServiceDiscovery(listener);
+ int key3again = verifyRequest(NsdManager.STOP_DISCOVERY);
+ assertEquals(key3, key3again);
+
+ err = 2;
+ sendResponse(NsdManager.STOP_DISCOVERY_FAILED, err, key3again, "a_type");
+ verify(listener, timeout(mTimeoutMs).times(1)).onStopDiscoveryFailed("a_type", err);
+
+ // New notifications are not passed to the client anymore
+ sendResponse(NsdManager.SERVICE_FOUND, 0, key3, reply1);
+ verify(listener, timeout(mTimeoutMs).times(0)).onServiceFound(reply1);
+ }
+
+ @Test
+ public void testInvalidCalls() {
+ NsdManager manager = new NsdManager(mContext, mService);
+
+ NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
+ NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
+ NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
+
+ NsdServiceInfo invalidService = new NsdServiceInfo(null, null);
+ NsdServiceInfo validService = new NsdServiceInfo("a_name", "a_type");
+ validService.setPort(2222);
+
+ // Service registration
+ // - invalid arguments
+ mustFail(() -> { manager.unregisterService(null); });
+ mustFail(() -> { manager.registerService(null, -1, null); });
+ mustFail(() -> { manager.registerService(null, PROTOCOL, listener1); });
+ mustFail(() -> { manager.registerService(invalidService, PROTOCOL, listener1); });
+ mustFail(() -> { manager.registerService(validService, -1, listener1); });
+ mustFail(() -> { manager.registerService(validService, PROTOCOL, null); });
+ manager.registerService(validService, PROTOCOL, listener1);
+ // - listener already registered
+ mustFail(() -> { manager.registerService(validService, PROTOCOL, listener1); });
+ manager.unregisterService(listener1);
+ // TODO: make listener immediately reusable
+ //mustFail(() -> { manager.unregisterService(listener1); });
+ //manager.registerService(validService, PROTOCOL, listener1);
+
+ // Discover service
+ // - invalid arguments
+ mustFail(() -> { manager.stopServiceDiscovery(null); });
+ mustFail(() -> { manager.discoverServices(null, -1, null); });
+ mustFail(() -> { manager.discoverServices(null, PROTOCOL, listener2); });
+ mustFail(() -> { manager.discoverServices("a_service", -1, listener2); });
+ mustFail(() -> { manager.discoverServices("a_service", PROTOCOL, null); });
+ manager.discoverServices("a_service", PROTOCOL, listener2);
+ // - listener already registered
+ mustFail(() -> { manager.discoverServices("another_service", PROTOCOL, listener2); });
+ manager.stopServiceDiscovery(listener2);
+ // TODO: make listener immediately reusable
+ //mustFail(() -> { manager.stopServiceDiscovery(listener2); });
+ //manager.discoverServices("another_service", PROTOCOL, listener2);
+
+ // Resolver service
+ // - invalid arguments
+ mustFail(() -> { manager.resolveService(null, null); });
+ mustFail(() -> { manager.resolveService(null, listener3); });
+ mustFail(() -> { manager.resolveService(invalidService, listener3); });
+ mustFail(() -> { manager.resolveService(validService, null); });
+ manager.resolveService(validService, listener3);
+ // - listener already registered:w
+ mustFail(() -> { manager.resolveService(validService, listener3); });
+ }
+
+ public void mustFail(Runnable fn) {
+ try {
+ fn.run();
+ fail();
+ } catch (Exception expected) {
+ }
+ }
+
+ NsdManager makeManager() {
+ NsdManager manager = new NsdManager(mContext, mService);
+ // Acknowledge first two messages connecting the AsyncChannel.
+ verify(mServiceHandler, timeout(mTimeoutMs).times(2)).handleMessage(any());
+ reset(mServiceHandler);
+ assertNotNull(mServiceHandler.chan);
+ return manager;
+ }
+
+ int verifyRequest(int expectedMessageType) {
+ verify(mServiceHandler, timeout(mTimeoutMs)).handleMessage(any());
+ reset(mServiceHandler);
+ Message received = mServiceHandler.lastMessage;
+ assertEquals(NsdManager.nameOf(expectedMessageType), NsdManager.nameOf(received.what));
+ return received.arg2;
+ }
+
+ void sendResponse(int replyType, int arg, int key, Object obj) {
+ mServiceHandler.chan.sendMessage(replyType, arg, key, obj);
+ }
+
+ // Implements the server side of AsyncChannel connection protocol
+ public static class MockServiceHandler extends Handler {
+ public Context mContext;
+ public AsyncChannel chan;
+ public volatile Message lastMessage;
+
+ MockServiceHandler(Looper looper, Context context) {
+ super(looper);
+ mContext = context;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ lastMessage = obtainMessage();
+ lastMessage.copyFrom(msg);
+ if (msg.what == AsyncChannel.CMD_CHANNEL_FULL_CONNECTION) {
+ chan = new AsyncChannel();
+ chan.connect(mContext, this, msg.replyTo);
+ chan.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
+ }
+
+ }
+
+ public static MockServiceHandler create(Context context) {
+ HandlerThread t = new HandlerThread("mock-service-handler");
+ t.start();
+ return new MockServiceHandler(t.getLooper(), context);
+ }
+ }
+}
diff --git a/tests/net/java/android/net/nsd/NsdServiceInfoTest.java b/tests/net/java/android/net/nsd/NsdServiceInfoTest.java
new file mode 100644
index 0000000..e48b522
--- /dev/null
+++ b/tests/net/java/android/net/nsd/NsdServiceInfoTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.nsd;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.StrictMode;
+import android.net.nsd.NsdServiceInfo;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NsdServiceInfoTest {
+
+ public final static InetAddress LOCALHOST;
+ static {
+ // Because test.
+ StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
+ StrictMode.setThreadPolicy(policy);
+
+ InetAddress _host = null;
+ try {
+ _host = InetAddress.getLocalHost();
+ } catch (UnknownHostException e) { }
+ LOCALHOST = _host;
+ }
+
+ @Test
+ public void testLimits() throws Exception {
+ NsdServiceInfo info = new NsdServiceInfo();
+
+ // Non-ASCII keys.
+ boolean exceptionThrown = false;
+ try {
+ info.setAttribute("猫", "meow");
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+ assertEmptyServiceInfo(info);
+
+ // ASCII keys with '=' character.
+ exceptionThrown = false;
+ try {
+ info.setAttribute("kitten=", "meow");
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+ assertEmptyServiceInfo(info);
+
+ // Single key + value length too long.
+ exceptionThrown = false;
+ try {
+ String longValue = "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" +
+ "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" +
+ "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" +
+ "ooooooooooooooooooooooooooooong"; // 248 characters.
+ info.setAttribute("longcat", longValue); // Key + value == 255 characters.
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+ assertEmptyServiceInfo(info);
+
+ // Total TXT record length too long.
+ exceptionThrown = false;
+ int recordsAdded = 0;
+ try {
+ for (int i = 100; i < 300; ++i) {
+ // 6 char key + 5 char value + 2 bytes overhead = 13 byte record length.
+ String key = String.format("key%d", i);
+ info.setAttribute(key, "12345");
+ recordsAdded++;
+ }
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+ assertTrue(100 == recordsAdded);
+ assertTrue(info.getTxtRecord().length == 1300);
+ }
+
+ @Test
+ public void testParcel() throws Exception {
+ NsdServiceInfo emptyInfo = new NsdServiceInfo();
+ checkParcelable(emptyInfo);
+
+ NsdServiceInfo fullInfo = new NsdServiceInfo();
+ fullInfo.setServiceName("kitten");
+ fullInfo.setServiceType("_kitten._tcp");
+ fullInfo.setPort(4242);
+ fullInfo.setHost(LOCALHOST);
+ checkParcelable(fullInfo);
+
+ NsdServiceInfo noHostInfo = new NsdServiceInfo();
+ noHostInfo.setServiceName("kitten");
+ noHostInfo.setServiceType("_kitten._tcp");
+ noHostInfo.setPort(4242);
+ checkParcelable(noHostInfo);
+
+ NsdServiceInfo attributedInfo = new NsdServiceInfo();
+ attributedInfo.setServiceName("kitten");
+ attributedInfo.setServiceType("_kitten._tcp");
+ attributedInfo.setPort(4242);
+ attributedInfo.setHost(LOCALHOST);
+ attributedInfo.setAttribute("color", "pink");
+ attributedInfo.setAttribute("sound", (new String("にゃあ")).getBytes("UTF-8"));
+ attributedInfo.setAttribute("adorable", (String) null);
+ attributedInfo.setAttribute("sticky", "yes");
+ attributedInfo.setAttribute("siblings", new byte[] {});
+ attributedInfo.setAttribute("edge cases", new byte[] {0, -1, 127, -128});
+ attributedInfo.removeAttribute("sticky");
+ checkParcelable(attributedInfo);
+
+ // Sanity check that we actually wrote attributes to attributedInfo.
+ assertTrue(attributedInfo.getAttributes().keySet().contains("adorable"));
+ String sound = new String(attributedInfo.getAttributes().get("sound"), "UTF-8");
+ assertTrue(sound.equals("にゃあ"));
+ byte[] edgeCases = attributedInfo.getAttributes().get("edge cases");
+ assertTrue(Arrays.equals(edgeCases, new byte[] {0, -1, 127, -128}));
+ assertFalse(attributedInfo.getAttributes().keySet().contains("sticky"));
+ }
+
+ public void checkParcelable(NsdServiceInfo original) {
+ // Write to parcel.
+ Parcel p = Parcel.obtain();
+ Bundle writer = new Bundle();
+ writer.putParcelable("test_info", original);
+ writer.writeToParcel(p, 0);
+
+ // Extract from parcel.
+ p.setDataPosition(0);
+ Bundle reader = p.readBundle();
+ reader.setClassLoader(NsdServiceInfo.class.getClassLoader());
+ NsdServiceInfo result = reader.getParcelable("test_info");
+
+ // Assert equality of base fields.
+ assertEquals(original.getServiceName(), result.getServiceName());
+ assertEquals(original.getServiceType(), result.getServiceType());
+ assertEquals(original.getHost(), result.getHost());
+ assertTrue(original.getPort() == result.getPort());
+
+ // Assert equality of attribute map.
+ Map<String, byte[]> originalMap = original.getAttributes();
+ Map<String, byte[]> resultMap = result.getAttributes();
+ assertEquals(originalMap.keySet(), resultMap.keySet());
+ for (String key : originalMap.keySet()) {
+ assertTrue(Arrays.equals(originalMap.get(key), resultMap.get(key)));
+ }
+ }
+
+ public void assertEmptyServiceInfo(NsdServiceInfo shouldBeEmpty) {
+ byte[] txtRecord = shouldBeEmpty.getTxtRecord();
+ if (txtRecord == null || txtRecord.length == 0) {
+ return;
+ }
+ fail("NsdServiceInfo.getTxtRecord did not return null but " + Arrays.toString(txtRecord));
+ }
+}
diff --git a/tests/net/java/com/android/internal/util/BitUtilsTest.java b/tests/net/java/com/android/internal/util/BitUtilsTest.java
new file mode 100644
index 0000000..0ad8a21
--- /dev/null
+++ b/tests/net/java/com/android/internal/util/BitUtilsTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import java.nio.ByteBuffer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static com.android.internal.util.BitUtils.*;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BitUtilsTest {
+
+ @Test
+ public void testUnsignedByteWideningConversions() {
+ byte b0 = 0;
+ byte b1 = 1;
+ byte bm1 = -1;
+ assertEquals(0, uint8(b0));
+ assertEquals(1, uint8(b1));
+ assertEquals(127, uint8(Byte.MAX_VALUE));
+ assertEquals(128, uint8(Byte.MIN_VALUE));
+ assertEquals(255, uint8(bm1));
+ assertEquals(255, uint8((byte)255));
+ }
+
+ @Test
+ public void testUnsignedShortWideningConversions() {
+ short s0 = 0;
+ short s1 = 1;
+ short sm1 = -1;
+ assertEquals(0, uint16(s0));
+ assertEquals(1, uint16(s1));
+ assertEquals(32767, uint16(Short.MAX_VALUE));
+ assertEquals(32768, uint16(Short.MIN_VALUE));
+ assertEquals(65535, uint16(sm1));
+ assertEquals(65535, uint16((short)65535));
+ }
+
+ @Test
+ public void testUnsignedIntWideningConversions() {
+ assertEquals(0, uint32(0));
+ assertEquals(1, uint32(1));
+ assertEquals(2147483647L, uint32(Integer.MAX_VALUE));
+ assertEquals(2147483648L, uint32(Integer.MIN_VALUE));
+ assertEquals(4294967295L, uint32(-1));
+ assertEquals(4294967295L, uint32((int)4294967295L));
+ }
+
+ @Test
+ public void testBytesToInt() {
+ assertEquals(0x00000000, bytesToBEInt(bytes(0, 0, 0, 0)));
+ assertEquals(0xffffffff, bytesToBEInt(bytes(255, 255, 255, 255)));
+ assertEquals(0x0a000001, bytesToBEInt(bytes(10, 0, 0, 1)));
+ assertEquals(0x0a000002, bytesToBEInt(bytes(10, 0, 0, 2)));
+ assertEquals(0x0a001fff, bytesToBEInt(bytes(10, 0, 31, 255)));
+ assertEquals(0xe0000001, bytesToBEInt(bytes(224, 0, 0, 1)));
+
+ assertEquals(0x00000000, bytesToLEInt(bytes(0, 0, 0, 0)));
+ assertEquals(0x01020304, bytesToLEInt(bytes(4, 3, 2, 1)));
+ assertEquals(0xffff0000, bytesToLEInt(bytes(0, 0, 255, 255)));
+ }
+
+ @Test
+ public void testUnsignedGetters() {
+ ByteBuffer b = ByteBuffer.allocate(4);
+ b.putInt(0xffff);
+
+ assertEquals(0x0, getUint8(b, 0));
+ assertEquals(0x0, getUint8(b, 1));
+ assertEquals(0xff, getUint8(b, 2));
+ assertEquals(0xff, getUint8(b, 3));
+
+ assertEquals(0x0, getUint16(b, 0));
+ assertEquals(0xffff, getUint16(b, 2));
+
+ b.rewind();
+ b.putInt(0xffffffff);
+ assertEquals(0xffffffffL, getUint32(b, 0));
+ }
+
+ static byte[] bytes(int b1, int b2, int b3, int b4) {
+ return new byte[] {b(b1), b(b2), b(b3), b(b4)};
+ }
+
+ static byte b(int i) {
+ return (byte) i;
+ }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 6e5ac8a..198ddc6 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -44,6 +44,7 @@
import android.net.ConnectivityManager.NetworkCallback;
import android.net.ConnectivityManager.PacketKeepalive;
import android.net.ConnectivityManager.PacketKeepaliveCallback;
+import android.net.ConnectivityManager.TooManyRequestsException;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.net.IpPrefix;
@@ -97,6 +98,7 @@
import com.android.server.net.NetworkPinner;
import com.android.server.net.NetworkPolicyManagerInternal;
+import org.junit.Ignore;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
@@ -115,7 +117,7 @@
* Tests for {@link ConnectivityService}.
*
* Build, install and run with:
- * runtest frameworks-services -c com.android.server.ConnectivityServiceTest
+ * runtest frameworks-net -c com.android.server.ConnectivityServiceTest
*/
public class ConnectivityServiceTest extends AndroidTestCase {
private static final String TAG = "ConnectivityServiceTest";
@@ -223,13 +225,32 @@
}
}
+ public void waitForIdle(int timeoutMs) {
+ waitForIdleHandler(mService.mHandlerThread, timeoutMs);
+ waitForIdle(mCellNetworkAgent, timeoutMs);
+ waitForIdle(mWiFiNetworkAgent, timeoutMs);
+ waitForIdle(mEthernetNetworkAgent, timeoutMs);
+ waitForIdleHandler(mService.mHandlerThread, timeoutMs);
+ }
+
+ public void waitForIdle(MockNetworkAgent agent, int timeoutMs) {
+ if (agent == null) {
+ return;
+ }
+ waitForIdleHandler(agent.mHandlerThread, timeoutMs);
+ }
+
+ private void waitForIdle() {
+ waitForIdle(TIMEOUT_MS);
+ }
+
@SmallTest
public void testWaitForIdle() {
final int attempts = 50; // Causes the test to take about 200ms on bullhead-eng.
// Tests that waitForIdle returns immediately if the service is already idle.
for (int i = 0; i < attempts; i++) {
- mService.waitForIdle();
+ waitForIdle();
}
// Bring up a network that we can use to send messages to ConnectivityService.
@@ -243,7 +264,7 @@
// Tests that calling waitForIdle waits for messages to be processed.
for (int i = 0; i < attempts; i++) {
mWiFiNetworkAgent.setSignalStrength(i);
- mService.waitForIdle();
+ waitForIdle();
assertEquals(i, mCm.getNetworkCapabilities(n).getSignalStrength());
}
}
@@ -344,23 +365,19 @@
};
// Waits for the NetworkAgent to be registered, which includes the creation of the
// NetworkMonitor.
- mService.waitForIdle();
+ waitForIdle();
mWrappedNetworkMonitor = mService.getLastCreatedWrappedNetworkMonitor();
}
- public void waitForIdle(int timeoutMs) {
- waitForIdleHandler(mHandlerThread, timeoutMs);
- }
-
- public void waitForIdle() {
- waitForIdle(TIMEOUT_MS);
- }
-
public void adjustScore(int change) {
mScore += change;
mNetworkAgent.sendNetworkScore(mScore);
}
+ public void explicitlySelected(boolean acceptUnvalidated) {
+ mNetworkAgent.explicitlySelected(acceptUnvalidated);
+ }
+
public void addCapability(int capability) {
mNetworkCapabilities.addCapability(capability);
mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
@@ -796,17 +813,27 @@
// Ensure that the default setting for Captive Portals is used for most tests
setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
+ setMobileDataAlwaysOn(false);
}
public void tearDown() throws Exception {
setMobileDataAlwaysOn(false);
- if (mCellNetworkAgent != null) { mCellNetworkAgent.disconnect(); }
- if (mWiFiNetworkAgent != null) { mWiFiNetworkAgent.disconnect(); }
- mCellNetworkAgent = mWiFiNetworkAgent = null;
+ if (mCellNetworkAgent != null) {
+ mCellNetworkAgent.disconnect();
+ mCellNetworkAgent = null;
+ }
+ if (mWiFiNetworkAgent != null) {
+ mWiFiNetworkAgent.disconnect();
+ mWiFiNetworkAgent = null;
+ }
+ if (mEthernetNetworkAgent != null) {
+ mEthernetNetworkAgent.disconnect();
+ mEthernetNetworkAgent = null;
+ }
super.tearDown();
}
- private int transportToLegacyType(int transport) {
+ private static int transportToLegacyType(int transport) {
switch (transport) {
case TRANSPORT_ETHERNET:
return TYPE_ETHERNET;
@@ -838,7 +865,8 @@
}
// Test getNetworkInfo(Network)
assertNotNull(mCm.getNetworkInfo(mCm.getActiveNetwork()));
- assertEquals(transportToLegacyType(transport), mCm.getNetworkInfo(mCm.getActiveNetwork()).getType());
+ assertEquals(transportToLegacyType(transport),
+ mCm.getNetworkInfo(mCm.getActiveNetwork()).getType());
// Test getNetworkCapabilities(Network)
assertNotNull(mCm.getNetworkCapabilities(mCm.getActiveNetwork()));
assertTrue(mCm.getNetworkCapabilities(mCm.getActiveNetwork()).hasTransport(transport));
@@ -909,7 +937,7 @@
mCm.getAllNetworks()[1].equals(mCellNetworkAgent.getNetwork()));
// Test cellular linger timeout.
waitFor(mCellNetworkAgent.getDisconnectedCV());
- mService.waitForIdle();
+ waitForIdle();
assertEquals(1, mCm.getAllNetworks().length);
verifyActiveNetwork(TRANSPORT_WIFI);
assertEquals(1, mCm.getAllNetworks().length);
@@ -932,11 +960,11 @@
// Test bringing up unvalidated cellular
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(false);
- mService.waitForIdle();
+ waitForIdle();
verifyActiveNetwork(TRANSPORT_WIFI);
// Test cellular disconnect.
mCellNetworkAgent.disconnect();
- mService.waitForIdle();
+ waitForIdle();
verifyActiveNetwork(TRANSPORT_WIFI);
// Test bringing up validated cellular
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -1288,7 +1316,7 @@
}
void assertNoCallback() {
- mService.waitForIdle();
+ waitForIdle();
CallbackInfo c = mCallbacks.peek();
assertNull("Unexpected callback: " + c, c);
}
@@ -1329,7 +1357,7 @@
// This should not trigger spurious onAvailable() callbacks, b/21762680.
mCellNetworkAgent.adjustScore(-1);
- mService.waitForIdle();
+ waitForIdle();
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1367,7 +1395,7 @@
// This should not trigger spurious onAvailable() callbacks, b/21762680.
mCellNetworkAgent.adjustScore(-1);
- mService.waitForIdle();
+ waitForIdle();
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1479,7 +1507,7 @@
defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
mCm.unregisterNetworkCallback(callback);
- mService.waitForIdle();
+ waitForIdle();
// Check that a network is only lingered or torn down if it would not satisfy a request even
// if it validated.
@@ -1594,13 +1622,104 @@
final int lingerTimeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
callback.expectCallback(CallbackState.LOST, mCellNetworkAgent, lingerTimeoutMs);
+ // Register a TRACK_DEFAULT request and check that it does not affect lingering.
+ TestNetworkCallback trackDefaultCallback = new TestNetworkCallback();
+ mCm.registerDefaultNetworkCallback(trackDefaultCallback);
+ trackDefaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
+ mEthernetNetworkAgent.connect(true);
+ callback.expectAvailableCallbacks(mEthernetNetworkAgent);
+ callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
+ callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
+ trackDefaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+ defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+
+ // Let linger run its course.
+ callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent, lingerTimeoutMs);
+
// Clean up.
- mWiFiNetworkAgent.disconnect();
- callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ mEthernetNetworkAgent.disconnect();
+ callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+ defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+ trackDefaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
mCm.unregisterNetworkCallback(callback);
mCm.unregisterNetworkCallback(defaultCallback);
+ mCm.unregisterNetworkCallback(trackDefaultCallback);
+ }
+
+ @SmallTest
+ public void testExplicitlySelected() {
+ NetworkRequest request = new NetworkRequest.Builder()
+ .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
+ .build();
+ TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(request, callback);
+
+ // Bring up validated cell.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+
+ // Bring up unvalidated wifi with explicitlySelected=true.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.explicitlySelected(false);
+ mWiFiNetworkAgent.connect(false);
+ callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+
+ // Cell Remains the default.
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // Lower wifi's score to below than cell, and check that it doesn't disconnect because
+ // it's explicitly selected.
+ mWiFiNetworkAgent.adjustScore(-40);
+ mWiFiNetworkAgent.adjustScore(40);
+ callback.assertNoCallback();
+
+ // If the user chooses yes on the "No Internet access, stay connected?" dialog, we switch to
+ // wifi even though it's unvalidated.
+ mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), true, false);
+ callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // Disconnect wifi, and then reconnect, again with explicitlySelected=true.
+ mWiFiNetworkAgent.disconnect();
+ callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.explicitlySelected(false);
+ mWiFiNetworkAgent.connect(false);
+ callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+
+ // If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the
+ // network to disconnect.
+ mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), false, false);
+ callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+ // Reconnect, again with explicitlySelected=true, but this time validate.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.explicitlySelected(false);
+ mWiFiNetworkAgent.connect(true);
+ callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // BUG: the network will no longer linger, even though it's validated and outscored.
+ // TODO: fix this.
+ mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
+ mEthernetNetworkAgent.connect(true);
+ callback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+ assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ callback.assertNoCallback();
+
+ // Clean up.
+ mWiFiNetworkAgent.disconnect();
+ mCellNetworkAgent.disconnect();
+ mEthernetNetworkAgent.disconnect();
+
+ callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
}
private void tryNetworkFactoryRequests(int capability) throws Exception {
@@ -1749,26 +1868,30 @@
ConditionVariable cv = mCellNetworkAgent.getDisconnectedCV();
mCellNetworkAgent.connectWithoutInternet();
waitFor(cv);
- mService.waitForIdle();
+ waitForIdle();
assertEquals(0, mCm.getAllNetworks().length);
verifyNoNetwork();
+
// Test bringing up validated WiFi.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
cv = waitForConnectivityBroadcasts(1);
mWiFiNetworkAgent.connect(true);
waitFor(cv);
verifyActiveNetwork(TRANSPORT_WIFI);
+
// Register MMS NetworkRequest
NetworkRequest.Builder builder = new NetworkRequest.Builder();
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
final TestNetworkCallback networkCallback = new TestNetworkCallback();
mCm.requestNetwork(builder.build(), networkCallback);
+
// Test bringing up unvalidated cellular with MMS
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
mCellNetworkAgent.connectWithoutInternet();
networkCallback.expectAvailableCallbacks(mCellNetworkAgent);
verifyActiveNetwork(TRANSPORT_WIFI);
+
// Test releasing NetworkRequest disconnects cellular with MMS
cv = mCellNetworkAgent.getDisconnectedCV();
mCm.unregisterNetworkCallback(networkCallback);
@@ -1784,17 +1907,20 @@
mCellNetworkAgent.connect(false);
waitFor(cv);
verifyActiveNetwork(TRANSPORT_CELLULAR);
+
// Register MMS NetworkRequest
NetworkRequest.Builder builder = new NetworkRequest.Builder();
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
final TestNetworkCallback networkCallback = new TestNetworkCallback();
mCm.requestNetwork(builder.build(), networkCallback);
+
// Test bringing up MMS cellular network
MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS);
mmsNetworkAgent.connectWithoutInternet();
networkCallback.expectAvailableCallbacks(mmsNetworkAgent);
verifyActiveNetwork(TRANSPORT_CELLULAR);
+
// Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent
cv = mmsNetworkAgent.getDisconnectedCV();
mCm.unregisterNetworkCallback(networkCallback);
@@ -2067,6 +2193,40 @@
}
@SmallTest
+ public void testNetworkSpecifierUidSpoofSecurityException() {
+ class UidAwareNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+ @Override
+ public boolean satisfiedBy(NetworkSpecifier other) {
+ return true;
+ }
+
+ @Override
+ public void assertValidFromUid(int requestorUid) {
+ throw new SecurityException("failure");
+ }
+
+ @Override
+ public int describeContents() { return 0; }
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {}
+ }
+
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(false);
+
+ UidAwareNetworkSpecifier networkSpecifier = new UidAwareNetworkSpecifier();
+ NetworkRequest networkRequest = newWifiRequestBuilder().setNetworkSpecifier(
+ networkSpecifier).build();
+ TestNetworkCallback networkCallback = new TestNetworkCallback();
+ try {
+ mCm.requestNetwork(networkRequest, networkCallback);
+ fail("Network request with spoofed UID did not throw a SecurityException");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ @SmallTest
public void testRegisterDefaultNetworkCallback() throws Exception {
final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback();
mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
@@ -2167,7 +2327,7 @@
ContentResolver cr = mServiceContext.getContentResolver();
Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, enable ? 1 : 0);
mService.updateMobileDataAlwaysOn();
- mService.waitForIdle();
+ waitForIdle();
}
private boolean isForegroundNetwork(MockNetworkAgent network) {
@@ -2209,7 +2369,7 @@
assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
// When lingering is complete, cell is still there but is now in the background.
- mService.waitForIdle();
+ waitForIdle();
int timeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, timeoutMs);
// Expect a network capabilities update sans FOREGROUND.
@@ -2254,8 +2414,8 @@
mCm.unregisterNetworkCallback(fgCallback);
}
- @SmallTest
- public void testRequestBenchmark() throws Exception {
+ @Ignore // This test has instrinsic chances of spurious failures: ignore for continuous testing.
+ public void benchmarkRequestRegistrationAndCallbackDispatch() throws Exception {
// TODO: turn this unit test into a real benchmarking test.
// Benchmarks connecting and switching performance in the presence of a large number of
// NetworkRequests.
@@ -2265,6 +2425,11 @@
// and NUM_REQUESTS onAvailable callbacks to fire.
// See how long it took.
final int NUM_REQUESTS = 90;
+ final int REGISTER_TIME_LIMIT_MS = 200;
+ final int CONNECT_TIME_LIMIT_MS = 60;
+ final int SWITCH_TIME_LIMIT_MS = 60;
+ final int UNREGISTER_TIME_LIMIT_MS = 20;
+
final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
final NetworkCallback[] callbacks = new NetworkCallback[NUM_REQUESTS];
final CountDownLatch availableLatch = new CountDownLatch(NUM_REQUESTS);
@@ -2277,14 +2442,12 @@
};
}
- final int REGISTER_TIME_LIMIT_MS = 180;
assertTimeLimit("Registering callbacks", REGISTER_TIME_LIMIT_MS, () -> {
for (NetworkCallback cb : callbacks) {
mCm.registerNetworkCallback(request, cb);
}
});
- final int CONNECT_TIME_LIMIT_MS = 40;
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
// Don't request that the network validate, because otherwise connect() will block until
// the network gets NET_CAPABILITY_VALIDATED, after all the callbacks below have fired,
@@ -2292,31 +2455,29 @@
mCellNetworkAgent.connect(false);
long onAvailableDispatchingDuration = durationOf(() -> {
- if (!awaitLatch(availableLatch, CONNECT_TIME_LIMIT_MS)) {
- fail(String.format("Only dispatched %d/%d onAvailable callbacks in %dms",
- NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS,
- CONNECT_TIME_LIMIT_MS));
- }
+ awaitLatch(availableLatch, 10 * CONNECT_TIME_LIMIT_MS);
});
- Log.d(TAG, String.format("Connect, %d callbacks: %dms, acceptable %dms",
- NUM_REQUESTS, onAvailableDispatchingDuration, CONNECT_TIME_LIMIT_MS));
+ Log.d(TAG, String.format("Dispatched %d of %d onAvailable callbacks in %dms",
+ NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS,
+ onAvailableDispatchingDuration));
+ assertTrue(String.format("Dispatching %d onAvailable callbacks in %dms, expected %dms",
+ NUM_REQUESTS, onAvailableDispatchingDuration, CONNECT_TIME_LIMIT_MS),
+ onAvailableDispatchingDuration <= CONNECT_TIME_LIMIT_MS);
- final int SWITCH_TIME_LIMIT_MS = 40;
- mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
// Give wifi a high enough score that we'll linger cell when wifi comes up.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.adjustScore(40);
mWiFiNetworkAgent.connect(false);
long onLostDispatchingDuration = durationOf(() -> {
- if (!awaitLatch(losingLatch, SWITCH_TIME_LIMIT_MS)) {
- fail(String.format("Only dispatched %d/%d onLosing callbacks in %dms",
- NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, SWITCH_TIME_LIMIT_MS));
- }
+ awaitLatch(losingLatch, 10 * SWITCH_TIME_LIMIT_MS);
});
- Log.d(TAG, String.format("Linger, %d callbacks: %dms, acceptable %dms",
- NUM_REQUESTS, onLostDispatchingDuration, SWITCH_TIME_LIMIT_MS));
+ Log.d(TAG, String.format("Dispatched %d of %d onLosing callbacks in %dms",
+ NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, onLostDispatchingDuration));
+ assertTrue(String.format("Dispatching %d onLosing callbacks in %dms, expected %dms",
+ NUM_REQUESTS, onLostDispatchingDuration, SWITCH_TIME_LIMIT_MS),
+ onLostDispatchingDuration <= SWITCH_TIME_LIMIT_MS);
- final int UNREGISTER_TIME_LIMIT_MS = 10;
assertTimeLimit("Unregistering callbacks", UNREGISTER_TIME_LIMIT_MS, () -> {
for (NetworkCallback cb : callbacks) {
mCm.unregisterNetworkCallback(cb);
@@ -2339,9 +2500,7 @@
private boolean awaitLatch(CountDownLatch l, long timeoutMs) {
try {
- if (l.await(timeoutMs, TimeUnit.MILLISECONDS)) {
- return true;
- }
+ return l.await(timeoutMs, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {}
return false;
}
@@ -2393,7 +2552,7 @@
assertFalse(testFactory.getMyStartRequested()); // Because the cell network outscores us.
// Check that cell data stays up.
- mService.waitForIdle();
+ waitForIdle();
verifyActiveNetwork(TRANSPORT_WIFI);
assertEquals(2, mCm.getAllNetworks().length);
@@ -2422,7 +2581,7 @@
for (int i = 0; i < values.length; i++) {
Settings.Global.putInt(cr, settingName, 1);
tracker.reevaluate();
- mService.waitForIdle();
+ waitForIdle();
String msg = String.format("config=false, setting=%s", values[i]);
assertTrue(mService.avoidBadWifi());
assertFalse(msg, tracker.shouldNotifyWifiUnvalidated());
@@ -2432,19 +2591,19 @@
Settings.Global.putInt(cr, settingName, 0);
tracker.reevaluate();
- mService.waitForIdle();
+ waitForIdle();
assertFalse(mService.avoidBadWifi());
assertFalse(tracker.shouldNotifyWifiUnvalidated());
Settings.Global.putInt(cr, settingName, 1);
tracker.reevaluate();
- mService.waitForIdle();
+ waitForIdle();
assertTrue(mService.avoidBadWifi());
assertFalse(tracker.shouldNotifyWifiUnvalidated());
Settings.Global.putString(cr, settingName, null);
tracker.reevaluate();
- mService.waitForIdle();
+ waitForIdle();
assertFalse(mService.avoidBadWifi());
assertTrue(tracker.shouldNotifyWifiUnvalidated());
}
@@ -2587,7 +2746,7 @@
tracker.configMeteredMultipathPreference = config;
Settings.Global.putString(cr, settingName, setting);
tracker.reevaluate();
- mService.waitForIdle();
+ waitForIdle();
final int expected = (setting != null) ? Integer.parseInt(setting) : config;
String msg = String.format("config=%d, setting=%s", config, setting);
@@ -2775,7 +2934,7 @@
waitFor(cv);
verifyActiveNetwork(TRANSPORT_WIFI);
mWiFiNetworkAgent.sendLinkProperties(lp);
- mService.waitForIdle();
+ waitForIdle();
return mWiFiNetworkAgent.getNetwork();
}
@@ -2854,7 +3013,7 @@
callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK);
// ... and that stopping it after that has no adverse effects.
- mService.waitForIdle();
+ waitForIdle();
final Network myNetAlias = myNet;
assertNull(mCm.getNetworkCapabilities(myNetAlias));
ka.stop();
@@ -2869,7 +3028,7 @@
ka.stop();
mWiFiNetworkAgent.disconnect();
waitFor(mWiFiNetworkAgent.getDisconnectedCV());
- mService.waitForIdle();
+ waitForIdle();
callback.expectStopped();
// Reconnect.
@@ -3021,7 +3180,7 @@
networkCallbacks.add(networkCallback);
}
fail("Registering " + MAX_REQUESTS + " NetworkRequests did not throw exception");
- } catch (IllegalArgumentException expected) {}
+ } catch (TooManyRequestsException expected) {}
for (NetworkCallback networkCallback : networkCallbacks) {
mCm.unregisterNetworkCallback(networkCallback);
}
@@ -3034,7 +3193,7 @@
networkCallbacks.add(networkCallback);
}
fail("Registering " + MAX_REQUESTS + " NetworkCallbacks did not throw exception");
- } catch (IllegalArgumentException expected) {}
+ } catch (TooManyRequestsException expected) {}
for (NetworkCallback networkCallback : networkCallbacks) {
mCm.unregisterNetworkCallback(networkCallback);
}
@@ -3050,7 +3209,7 @@
}
fail("Registering " + MAX_REQUESTS +
" PendingIntent NetworkRequests did not throw exception");
- } catch (IllegalArgumentException expected) {}
+ } catch (TooManyRequestsException expected) {}
for (PendingIntent pendingIntent : pendingIntents) {
mCm.unregisterNetworkCallback(pendingIntent);
}
@@ -3065,12 +3224,12 @@
}
fail("Registering " + MAX_REQUESTS +
" PendingIntent NetworkCallbacks did not throw exception");
- } catch (IllegalArgumentException expected) {}
+ } catch (TooManyRequestsException expected) {}
for (PendingIntent pendingIntent : pendingIntents) {
mCm.unregisterNetworkCallback(pendingIntent);
}
pendingIntents.clear();
- mService.waitForIdle(5000);
+ waitForIdle(5000);
// Test that the limit is not hit when MAX_REQUESTS requests are added and removed.
for (int i = 0; i < MAX_REQUESTS; i++) {
@@ -3078,20 +3237,20 @@
mCm.requestNetwork(networkRequest, networkCallback);
mCm.unregisterNetworkCallback(networkCallback);
}
- mService.waitForIdle();
+ waitForIdle();
for (int i = 0; i < MAX_REQUESTS; i++) {
NetworkCallback networkCallback = new NetworkCallback();
mCm.registerNetworkCallback(networkRequest, networkCallback);
mCm.unregisterNetworkCallback(networkCallback);
}
- mService.waitForIdle();
+ waitForIdle();
for (int i = 0; i < MAX_REQUESTS; i++) {
PendingIntent pendingIntent =
PendingIntent.getBroadcast(mContext, 0, new Intent("b" + i), 0);
mCm.requestNetwork(networkRequest, pendingIntent);
mCm.unregisterNetworkCallback(pendingIntent);
}
- mService.waitForIdle();
+ waitForIdle();
for (int i = 0; i < MAX_REQUESTS; i++) {
PendingIntent pendingIntent =
PendingIntent.getBroadcast(mContext, 0, new Intent("c" + i), 0);
diff --git a/tests/net/java/com/android/server/NsdServiceTest.java b/tests/net/java/com/android/server/NsdServiceTest.java
new file mode 100644
index 0000000..68cb251
--- /dev/null
+++ b/tests/net/java/com/android/server/NsdServiceTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.content.Context;
+import android.content.ContentResolver;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import com.android.server.NsdService.DaemonConnection;
+import com.android.server.NsdService.DaemonConnectionSupplier;
+import com.android.server.NsdService.NativeCallbackReceiver;
+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 org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+// TODOs:
+// - test client can send requests and receive replies
+// - test NSD_ON ENABLE/DISABLED listening
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NsdServiceTest {
+
+ static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
+
+ long mTimeoutMs = 100; // non-final so that tests can adjust the value.
+
+ @Mock Context mContext;
+ @Mock ContentResolver mResolver;
+ @Mock NsdService.NsdSettings mSettings;
+ @Mock DaemonConnection mDaemon;
+ NativeCallbackReceiver mDaemonCallback;
+ HandlerThread mThread;
+ TestHandler mHandler;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mThread = new HandlerThread("mock-service-handler");
+ mThread.start();
+ mHandler = new TestHandler(mThread.getLooper());
+ when(mContext.getContentResolver()).thenReturn(mResolver);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mThread.quit();
+ }
+
+ @Test
+ public void testClientsCanConnectAndDisconnect() {
+ when(mSettings.isEnabled()).thenReturn(true);
+
+ NsdService service = makeService();
+
+ NsdManager client1 = connectClient(service);
+ verify(mDaemon, timeout(100).times(1)).start();
+
+ NsdManager client2 = connectClient(service);
+
+ client1.disconnect();
+ client2.disconnect();
+
+ verify(mDaemon, timeout(mTimeoutMs).times(1)).stop();
+ }
+
+ @Test
+ public void testClientRequestsAreGCedAtDisconnection() {
+ when(mSettings.isEnabled()).thenReturn(true);
+ when(mDaemon.execute(any())).thenReturn(true);
+
+ NsdService service = makeService();
+ NsdManager client = connectClient(service);
+
+ verify(mDaemon, timeout(100).times(1)).start();
+
+ NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
+ request.setPort(2201);
+
+ // Client registration request
+ NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
+ client.registerService(request, PROTOCOL, listener1);
+ verifyDaemonCommand("register 2 a_name a_type 2201");
+
+ // Client discovery request
+ NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
+ client.discoverServices("a_type", PROTOCOL, listener2);
+ verifyDaemonCommand("discover 3 a_type");
+
+ // Client resolve request
+ NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
+ client.resolveService(request, listener3);
+ verifyDaemonCommand("resolve 4 a_name a_type local.");
+
+ // Client disconnects
+ client.disconnect();
+ verify(mDaemon, timeout(mTimeoutMs).times(1)).stop();
+
+ // checks that request are cleaned
+ verifyDaemonCommands("stop-register 2", "stop-discover 3", "stop-resolve 4");
+ }
+
+ NsdService makeService() {
+ DaemonConnectionSupplier supplier = (callback) -> {
+ mDaemonCallback = callback;
+ return mDaemon;
+ };
+ NsdService service = new NsdService(mContext, mSettings, mHandler, supplier);
+ verify(mDaemon, never()).execute(any(String.class));
+ return service;
+ }
+
+ NsdManager connectClient(NsdService service) {
+ return new NsdManager(mContext, service);
+ }
+
+ void verifyDaemonCommands(String... wants) {
+ verifyDaemonCommand(String.join(" ", wants), wants.length);
+ }
+
+ void verifyDaemonCommand(String want) {
+ verifyDaemonCommand(want, 1);
+ }
+
+ void verifyDaemonCommand(String want, int n) {
+ ArgumentCaptor<Object> argumentsCaptor = ArgumentCaptor.forClass(Object.class);
+ verify(mDaemon, timeout(mTimeoutMs).times(n)).execute(argumentsCaptor.capture());
+ String got = "";
+ for (Object o : argumentsCaptor.getAllValues()) {
+ got += o + " ";
+ }
+ assertEquals(want, got.trim());
+ // rearm deamon for next command verification
+ reset(mDaemon);
+ when(mDaemon.execute(any())).thenReturn(true);
+ }
+
+ public static class TestHandler extends Handler {
+ public Message lastMessage;
+
+ TestHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ lastMessage = obtainMessage();
+ lastMessage.copyFrom(msg);
+ }
+ }
+}
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index 50d7f04..bd049b8 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -37,6 +37,7 @@
import static org.mockito.Mockito.when;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
@@ -49,6 +50,7 @@
import android.net.INetworkStatsService;
import android.net.InterfaceConfiguration;
import android.net.NetworkRequest;
+import android.net.util.SharedLog;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Handler;
@@ -57,11 +59,16 @@
import android.os.RemoteException;
import android.os.test.TestLooper;
import android.os.UserHandle;
+import android.provider.Settings;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.telephony.CarrierConfigManager;
+import android.test.mock.MockContentResolver;
import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.connectivity.tethering.OffloadHardwareInterface;
+import com.android.server.connectivity.tethering.TetheringDependencies;
import org.junit.After;
import org.junit.Before;
@@ -84,7 +91,9 @@
@Mock private INetworkStatsService mStatsService;
@Mock private INetworkPolicyManager mPolicyManager;
@Mock private MockableSystemProperties mSystemProperties;
+ @Mock private OffloadHardwareInterface mOffloadHardwareInterface;
@Mock private Resources mResources;
+ @Mock private TetheringDependencies mTetheringDependencies;
@Mock private UsbManager mUsbManager;
@Mock private WifiManager mWifiManager;
@Mock private CarrierConfigManager mCarrierConfigManager;
@@ -97,6 +106,7 @@
private Vector<Intent> mIntents;
private BroadcastInterceptingContext mServiceContext;
+ private MockContentResolver mContentResolver;
private BroadcastReceiver mBroadcastReceiver;
private Tethering mTethering;
@@ -106,6 +116,12 @@
}
@Override
+ public ContentResolver getContentResolver() { return mContentResolver; }
+
+ @Override
+ public String getPackageName() { return "TetheringTest"; }
+
+ @Override
public Resources getResources() { return mResources; }
@Override
@@ -135,6 +151,8 @@
.thenReturn(new InterfaceConfiguration());
mServiceContext = new MockContext(mContext);
+ mContentResolver = new MockContentResolver(mServiceContext);
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
mIntents = new Vector<>();
mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -144,8 +162,11 @@
};
mServiceContext.registerReceiver(mBroadcastReceiver,
new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
+ when(mTetheringDependencies.getOffloadHardwareInterface(
+ any(Handler.class), any(SharedLog.class))).thenReturn(mOffloadHardwareInterface);
mTethering = new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager,
- mLooper.getLooper(), mSystemProperties);
+ mLooper.getLooper(), mSystemProperties,
+ mTetheringDependencies);
}
@After
@@ -219,10 +240,7 @@
mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
- private void verifyInterfaceServingModeStarted(boolean ifnameKnown) throws Exception {
- if (!ifnameKnown) {
- verify(mNMService, times(1)).listInterfaces();
- }
+ private void verifyInterfaceServingModeStarted() throws Exception {
verify(mNMService, times(1)).getInterfaceConfig(mTestIfname);
verify(mNMService, times(1))
.setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class));
@@ -238,21 +256,36 @@
mIntents.remove(bcast);
}
- public void workingLocalOnlyHotspot(boolean enrichedApBroadcast) throws Exception {
+ @Test
+ public void failingLocalOnlyHotspotLegacyApBroadcast() throws Exception {
when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
// Emulate externally-visible WifiManager effects, causing the
// per-interface state machine to start up, and telling us that
// hotspot mode is to be started.
mTethering.interfaceStatusChanged(mTestIfname, true);
- if (enrichedApBroadcast) {
- sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, mTestIfname, IFACE_IP_MODE_LOCAL_ONLY);
- } else {
- sendWifiApStateChanged(WIFI_AP_STATE_ENABLED);
- }
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED);
mLooper.dispatchAll();
- verifyInterfaceServingModeStarted(enrichedApBroadcast);
+ verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
+ verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+ verifyNoMoreInteractions(mConnectivityManager);
+ verifyNoMoreInteractions(mNMService);
+ verifyNoMoreInteractions(mWifiManager);
+ }
+
+ @Test
+ public void workingLocalOnlyHotspotEnrichedApBroadcast() throws Exception {
+ when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
+
+ // Emulate externally-visible WifiManager effects, causing the
+ // per-interface state machine to start up, and telling us that
+ // hotspot mode is to be started.
+ mTethering.interfaceStatusChanged(mTestIfname, true);
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, mTestIfname, IFACE_IP_MODE_LOCAL_ONLY);
+ mLooper.dispatchAll();
+
+ verifyInterfaceServingModeStarted();
verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
verify(mNMService, times(1)).setIpForwardingEnabled(true);
verify(mNMService, times(1)).startTethering(any(String[].class));
@@ -293,16 +326,7 @@
}
@Test
- public void workingLocalOnlyHotspotLegacyApBroadcast() throws Exception {
- workingLocalOnlyHotspot(false);
- }
-
- @Test
- public void workingLocalOnlyHotspotEnrichedApBroadcast() throws Exception {
- workingLocalOnlyHotspot(true);
- }
-
- public void workingWifiTethering(boolean enrichedApBroadcast) throws Exception {
+ public void failingWifiTetheringLegacyApBroadcast() throws Exception {
when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
@@ -318,14 +342,37 @@
// per-interface state machine to start up, and telling us that
// tethering mode is to be started.
mTethering.interfaceStatusChanged(mTestIfname, true);
- if (enrichedApBroadcast) {
- sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, mTestIfname, IFACE_IP_MODE_TETHERED);
- } else {
- sendWifiApStateChanged(WIFI_AP_STATE_ENABLED);
- }
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED);
mLooper.dispatchAll();
- verifyInterfaceServingModeStarted(enrichedApBroadcast);
+ verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
+ verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+ verifyNoMoreInteractions(mConnectivityManager);
+ verifyNoMoreInteractions(mNMService);
+ verifyNoMoreInteractions(mWifiManager);
+ }
+
+ @Test
+ public void workingWifiTetheringEnrichedApBroadcast() throws Exception {
+ when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
+ when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
+
+ // Emulate pressing the WiFi tethering button.
+ mTethering.startTethering(ConnectivityManager.TETHERING_WIFI, null, false);
+ mLooper.dispatchAll();
+ verify(mWifiManager, times(1)).startSoftAp(null);
+ verifyNoMoreInteractions(mWifiManager);
+ verifyNoMoreInteractions(mConnectivityManager);
+ verifyNoMoreInteractions(mNMService);
+
+ // Emulate externally-visible WifiManager effects, causing the
+ // per-interface state machine to start up, and telling us that
+ // tethering mode is to be started.
+ mTethering.interfaceStatusChanged(mTestIfname, true);
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, mTestIfname, IFACE_IP_MODE_TETHERED);
+ mLooper.dispatchAll();
+
+ verifyInterfaceServingModeStarted();
verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
verify(mNMService, times(1)).setIpForwardingEnabled(true);
verify(mNMService, times(1)).startTethering(any(String[].class));
@@ -387,16 +434,6 @@
}
@Test
- public void workingWifiTetheringLegacyApBroadcast() throws Exception {
- workingWifiTethering(false);
- }
-
- @Test
- public void workingWifiTetheringEnrichedApBroadcast() throws Exception {
- workingWifiTethering(true);
- }
-
- @Test
public void failureEnablingIpForwarding() throws Exception {
when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
@@ -414,11 +451,9 @@
// per-interface state machine to start up, and telling us that
// tethering mode is to be started.
mTethering.interfaceStatusChanged(mTestIfname, true);
- sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_ENABLED);
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, mTestIfname, IFACE_IP_MODE_TETHERED);
mLooper.dispatchAll();
- // Activity caused by test_wlan0 becoming available.
- verify(mNMService, times(1)).listInterfaces();
// We verify get/set called twice here: once for setup and once during
// teardown because all events happen over the course of the single
// dispatchAll() above.
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index b51b277..506d9e5 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -27,10 +27,13 @@
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.UidRange;
+import android.os.Build;
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.UserHandle;
@@ -40,11 +43,14 @@
import android.util.ArrayMap;
import android.util.ArraySet;
+import com.android.internal.net.VpnConfig;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
+import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
@@ -87,23 +93,32 @@
}
}
- @Mock private Context mContext;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext;
@Mock private UserManager mUserManager;
@Mock private PackageManager mPackageManager;
@Mock private INetworkManagementService mNetService;
@Mock private AppOpsManager mAppOps;
@Mock private NotificationManager mNotificationManager;
+ @Mock private Vpn.SystemServices mSystemServices;
@Override
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+
when(mContext.getPackageManager()).thenReturn(mPackageManager);
setMockedPackages(mPackages);
+
when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName());
when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
when(mContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
.thenReturn(mNotificationManager);
+
+ // Used by {@link Notification.Builder}
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+ when(mContext.getApplicationInfo()).thenReturn(applicationInfo);
+
doNothing().when(mNetService).registerObserver(any());
}
@@ -111,7 +126,7 @@
public void testRestrictedProfilesAreAddedToVpn() {
setMockedUsers(primaryUser, secondaryUser, restrictedProfileA, restrictedProfileB);
- final Vpn vpn = spyVpn(primaryUser.id);
+ final Vpn vpn = createVpn(primaryUser.id);
final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
null, null);
@@ -125,7 +140,7 @@
public void testManagedProfilesAreNotAddedToVpn() {
setMockedUsers(primaryUser, managedProfileA);
- final Vpn vpn = spyVpn(primaryUser.id);
+ final Vpn vpn = createVpn(primaryUser.id);
final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
null, null);
@@ -138,7 +153,7 @@
public void testAddUserToVpnOnlyAddsOneUser() {
setMockedUsers(primaryUser, restrictedProfileA, managedProfileA);
- final Vpn vpn = spyVpn(primaryUser.id);
+ final Vpn vpn = createVpn(primaryUser.id);
final Set<UidRange> ranges = new ArraySet<>();
vpn.addUserToRanges(ranges, primaryUser.id, null, null);
@@ -149,7 +164,7 @@
@SmallTest
public void testUidWhiteAndBlacklist() throws Exception {
- final Vpn vpn = spyVpn(primaryUser.id);
+ final Vpn vpn = createVpn(primaryUser.id);
final UidRange user = UidRange.createForUser(primaryUser.id);
final String[] packages = {PKGS[0], PKGS[1], PKGS[2]};
@@ -174,7 +189,7 @@
@SmallTest
public void testLockdownChangingPackage() throws Exception {
- final Vpn vpn = spyVpn(primaryUser.id);
+ final Vpn vpn = createVpn(primaryUser.id);
final UidRange user = UidRange.createForUser(primaryUser.id);
// Default state.
@@ -209,7 +224,7 @@
@SmallTest
public void testLockdownAddingAProfile() throws Exception {
- final Vpn vpn = spyVpn(primaryUser.id);
+ final Vpn vpn = createVpn(primaryUser.id);
setMockedUsers(primaryUser);
// Make a copy of the restricted profile, as we're going to mark it deleted halfway through.
@@ -248,41 +263,94 @@
}
@SmallTest
+ public void testLockdownRuleRepeatability() throws Exception {
+ final Vpn vpn = createVpn(primaryUser.id);
+
+ // Given legacy lockdown is already enabled,
+ vpn.setLockdown(true);
+ verify(mNetService, times(1)).setAllowOnlyVpnForUids(
+ eq(true), aryEq(new UidRange[] {UidRange.createForUser(primaryUser.id)}));
+
+ // Enabling legacy lockdown twice should do nothing.
+ vpn.setLockdown(true);
+ verify(mNetService, times(1)).setAllowOnlyVpnForUids(anyBoolean(), any(UidRange[].class));
+
+ // And disabling should remove the rules exactly once.
+ vpn.setLockdown(false);
+ verify(mNetService, times(1)).setAllowOnlyVpnForUids(
+ eq(false), aryEq(new UidRange[] {UidRange.createForUser(primaryUser.id)}));
+
+ // Removing the lockdown again should have no effect.
+ vpn.setLockdown(false);
+ verify(mNetService, times(2)).setAllowOnlyVpnForUids(anyBoolean(), any(UidRange[].class));
+ }
+
+ @SmallTest
+ public void testLockdownRuleReversibility() throws Exception {
+ final Vpn vpn = createVpn(primaryUser.id);
+
+ final UidRange[] entireUser = {
+ UidRange.createForUser(primaryUser.id)
+ };
+ final UidRange[] exceptPkg0 = {
+ new UidRange(entireUser[0].start, entireUser[0].start + PKG_UIDS[0] - 1),
+ new UidRange(entireUser[0].start + PKG_UIDS[0] + 1, entireUser[0].stop)
+ };
+
+ final InOrder order = inOrder(mNetService);
+
+ // Given lockdown is enabled with no package (legacy VPN),
+ vpn.setLockdown(true);
+ order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser));
+
+ // When a new VPN package is set the rules should change to cover that package.
+ vpn.prepare(null, PKGS[0]);
+ order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(entireUser));
+ order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(exceptPkg0));
+
+ // When that VPN package is unset, everything should be undone again in reverse.
+ vpn.prepare(null, VpnConfig.LEGACY_VPN);
+ order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(exceptPkg0));
+ order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser));
+ }
+
+ @SmallTest
public void testNotificationShownForAlwaysOnApp() {
- final Vpn vpn = spyVpn(primaryUser.id);
- final InOrder order = inOrder(vpn);
+ final UserHandle userHandle = UserHandle.of(primaryUser.id);
+ final Vpn vpn = createVpn(primaryUser.id);
setMockedUsers(primaryUser);
+ final InOrder order = inOrder(mNotificationManager);
+
// Don't show a notification for regular disconnected states.
vpn.updateState(DetailedState.DISCONNECTED, TAG);
- order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+ order.verify(mNotificationManager, atLeastOnce())
+ .cancelAsUser(anyString(), anyInt(), eq(userHandle));
// Start showing a notification for disconnected once always-on.
vpn.setAlwaysOnPackage(PKGS[0], false);
- order.verify(vpn).updateAlwaysOnNotificationInternal(true);
+ order.verify(mNotificationManager)
+ .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
// Stop showing the notification once connected.
vpn.updateState(DetailedState.CONNECTED, TAG);
- order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+ order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle));
// Show the notification if we disconnect again.
vpn.updateState(DetailedState.DISCONNECTED, TAG);
- order.verify(vpn).updateAlwaysOnNotificationInternal(true);
+ order.verify(mNotificationManager)
+ .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
// Notification should be cleared after unsetting always-on package.
vpn.setAlwaysOnPackage(null, false);
- order.verify(vpn).updateAlwaysOnNotificationInternal(false);
+ order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle));
}
/**
* Mock some methods of vpn object.
*/
- private Vpn spyVpn(@UserIdInt int userId) {
- final Vpn vpn = spy(new Vpn(Looper.myLooper(), mContext, mNetService, userId));
-
- // Block calls to the NotificationManager or PendingIntent#getActivity.
- doNothing().when(vpn).updateAlwaysOnNotificationInternal(anyBoolean());
- return vpn;
+ private Vpn createVpn(@UserIdInt int userId) {
+ return new Vpn(Looper.myLooper(), mContext, mNetService, userId, mSystemServices);
}
private static void assertBlocked(Vpn vpn, int... uids) {
diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
new file mode 100644
index 0000000..c535c45
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
+import android.net.util.SharedLog;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.mock.MockContentResolver;
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class OffloadControllerTest {
+
+ @Mock private OffloadHardwareInterface mHardware;
+ @Mock private Context mContext;
+ final ArgumentCaptor<ArrayList> mStringArrayCaptor = ArgumentCaptor.forClass(ArrayList.class);
+ private MockContentResolver mContentResolver;
+
+ @Before public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContentResolver = new MockContentResolver(mContext);
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mContext.getPackageName()).thenReturn("OffloadControllerTest");
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ }
+
+ private void setupFunctioningHardwareInterface() {
+ when(mHardware.initOffloadConfig()).thenReturn(true);
+ when(mHardware.initOffloadControl(any(OffloadHardwareInterface.ControlCallback.class)))
+ .thenReturn(true);
+ }
+
+ @Test
+ public void testNoSettingsValueAllowsStart() {
+ setupFunctioningHardwareInterface();
+ try {
+ Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED);
+ fail();
+ } catch (SettingNotFoundException expected) {}
+
+ final OffloadController offload =
+ new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
+ offload.start();
+
+ final InOrder inOrder = inOrder(mHardware);
+ inOrder.verify(mHardware, times(1)).initOffloadConfig();
+ inOrder.verify(mHardware, times(1)).initOffloadControl(
+ any(OffloadHardwareInterface.ControlCallback.class));
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testSettingsAllowsStart() {
+ setupFunctioningHardwareInterface();
+ Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0);
+
+ final OffloadController offload =
+ new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
+ offload.start();
+
+ final InOrder inOrder = inOrder(mHardware);
+ inOrder.verify(mHardware, times(1)).initOffloadConfig();
+ inOrder.verify(mHardware, times(1)).initOffloadControl(
+ any(OffloadHardwareInterface.ControlCallback.class));
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testSettingsDisablesStart() {
+ setupFunctioningHardwareInterface();
+ Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 1);
+
+ final OffloadController offload =
+ new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
+ offload.start();
+
+ final InOrder inOrder = inOrder(mHardware);
+ inOrder.verify(mHardware, never()).initOffloadConfig();
+ inOrder.verify(mHardware, never()).initOffloadControl(anyObject());
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testSetUpstreamLinkPropertiesWorking() throws Exception {
+ setupFunctioningHardwareInterface();
+ final OffloadController offload =
+ new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
+ offload.start();
+
+ final InOrder inOrder = inOrder(mHardware);
+ inOrder.verify(mHardware, times(1)).initOffloadConfig();
+ inOrder.verify(mHardware, times(1)).initOffloadControl(
+ any(OffloadHardwareInterface.ControlCallback.class));
+ inOrder.verifyNoMoreInteractions();
+
+ offload.setUpstreamLinkProperties(null);
+ inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+ eq(null), eq(null), eq(null), eq(null));
+ inOrder.verifyNoMoreInteractions();
+ reset(mHardware);
+
+ final LinkProperties lp = new LinkProperties();
+
+ final String testIfName = "rmnet_data17";
+ lp.setInterfaceName(testIfName);
+ offload.setUpstreamLinkProperties(lp);
+ inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+ eq(testIfName), eq(null), eq(null), mStringArrayCaptor.capture());
+ assertTrue(mStringArrayCaptor.getValue().isEmpty());
+ inOrder.verifyNoMoreInteractions();
+
+ final String ipv4Addr = "192.0.2.5";
+ final String linkAddr = ipv4Addr + "/24";
+ lp.addLinkAddress(new LinkAddress(linkAddr));
+ offload.setUpstreamLinkProperties(lp);
+ inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+ eq(testIfName), eq(ipv4Addr), eq(null), mStringArrayCaptor.capture());
+ assertTrue(mStringArrayCaptor.getValue().isEmpty());
+ inOrder.verifyNoMoreInteractions();
+
+ final String ipv4Gateway = "192.0.2.1";
+ lp.addRoute(new RouteInfo(InetAddress.getByName(ipv4Gateway)));
+ offload.setUpstreamLinkProperties(lp);
+ inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+ eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
+ assertTrue(mStringArrayCaptor.getValue().isEmpty());
+ inOrder.verifyNoMoreInteractions();
+
+ final String ipv6Gw1 = "fe80::cafe";
+ lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw1)));
+ offload.setUpstreamLinkProperties(lp);
+ inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+ eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
+ ArrayList<String> v6gws = mStringArrayCaptor.getValue();
+ assertEquals(1, v6gws.size());
+ assertTrue(v6gws.contains(ipv6Gw1));
+ inOrder.verifyNoMoreInteractions();
+
+ final String ipv6Gw2 = "fe80::d00d";
+ lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw2)));
+ offload.setUpstreamLinkProperties(lp);
+ inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+ eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
+ v6gws = mStringArrayCaptor.getValue();
+ assertEquals(2, v6gws.size());
+ assertTrue(v6gws.contains(ipv6Gw1));
+ assertTrue(v6gws.contains(ipv6Gw2));
+ inOrder.verifyNoMoreInteractions();
+
+ final LinkProperties stacked = new LinkProperties();
+ stacked.setInterfaceName("stacked");
+ stacked.addLinkAddress(new LinkAddress("192.0.2.129/25"));
+ stacked.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
+ stacked.addRoute(new RouteInfo(InetAddress.getByName("fe80::bad:f00")));
+ assertTrue(lp.addStackedLink(stacked));
+ offload.setUpstreamLinkProperties(lp);
+ inOrder.verify(mHardware, times(1)).setUpstreamParameters(
+ eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
+ v6gws = mStringArrayCaptor.getValue();
+ assertEquals(2, v6gws.size());
+ assertTrue(v6gws.contains(ipv6Gw1));
+ assertTrue(v6gws.contains(ipv6Gw2));
+ inOrder.verifyNoMoreInteractions();
+ }
+}
diff --git a/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java b/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java
new file mode 100644
index 0000000..b5d333b
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import static com.android.internal.telephony.IccCardConstants.INTENT_VALUE_ICC_ABSENT;
+import static com.android.internal.telephony.IccCardConstants.INTENT_VALUE_ICC_LOADED;
+import static com.android.internal.telephony.IccCardConstants.INTENT_KEY_ICC_STATE;
+import static com.android.internal.telephony.TelephonyIntents.ACTION_SIM_STATE_CHANGED;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.reset;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.test.BroadcastInterceptingContext;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SimChangeListenerTest {
+ private static final int EVENT_UNM_UPDATE = 1;
+
+ @Mock private Context mContext;
+ private BroadcastInterceptingContext mServiceContext;
+ private Handler mHandler;
+ private SimChangeListener mSCL;
+ private int mCallbackCount;
+
+ private void doCallback() { mCallbackCount++; }
+
+ private class MockContext extends BroadcastInterceptingContext {
+ MockContext(Context base) {
+ super(base);
+ }
+ }
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ @Before public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ reset(mContext);
+ mServiceContext = new MockContext(mContext);
+ mHandler = new Handler(Looper.myLooper());
+ mCallbackCount = 0;
+ mSCL = new SimChangeListener(mServiceContext, mHandler, () -> doCallback());
+ }
+
+ @After public void tearDown() throws Exception {
+ if (mSCL != null) {
+ mSCL.stopListening();
+ mSCL = null;
+ }
+ }
+
+ private void sendSimStateChangeIntent(String state) {
+ final Intent intent = new Intent(ACTION_SIM_STATE_CHANGED);
+ intent.putExtra(INTENT_KEY_ICC_STATE, state);
+ mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ @Test
+ public void testNotSeenFollowedBySeenCallsCallback() {
+ mSCL.startListening();
+
+ sendSimStateChangeIntent(INTENT_VALUE_ICC_ABSENT);
+ sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
+ assertEquals(1, mCallbackCount);
+
+ sendSimStateChangeIntent(INTENT_VALUE_ICC_ABSENT);
+ sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
+ assertEquals(2, mCallbackCount);
+
+ mSCL.stopListening();
+ }
+
+ @Test
+ public void testNotListeningDoesNotCallback() {
+ sendSimStateChangeIntent(INTENT_VALUE_ICC_ABSENT);
+ sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
+ assertEquals(0, mCallbackCount);
+
+ sendSimStateChangeIntent(INTENT_VALUE_ICC_ABSENT);
+ sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
+ assertEquals(0, mCallbackCount);
+ }
+
+ @Test
+ public void testSeenOnlyDoesNotCallback() {
+ mSCL.startListening();
+
+ sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
+ assertEquals(0, mCallbackCount);
+
+ sendSimStateChangeIntent(INTENT_VALUE_ICC_LOADED);
+ assertEquals(0, mCallbackCount);
+
+ mSCL.stopListening();
+ }
+}
diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
index ddceea2..27be135 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
@@ -31,6 +31,7 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Resources;
+import android.net.util.SharedLog;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.telephony.TelephonyManager;
@@ -47,6 +48,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class TetheringConfigurationTest {
+ private final SharedLog mLog = new SharedLog("TetheringConfigurationTest");
@Mock private Context mContext;
@Mock private TelephonyManager mTelephonyManager;
@Mock private Resources mResources;
@@ -91,7 +93,7 @@
mHasTelephonyManager = true;
when(mTelephonyManager.getTetherApnRequired()).thenReturn(DUN_REQUIRED);
- final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext);
+ final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog);
assertTrue(cfg.isDunRequired);
assertTrue(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN));
assertFalse(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE));
@@ -107,7 +109,7 @@
mHasTelephonyManager = true;
when(mTelephonyManager.getTetherApnRequired()).thenReturn(DUN_NOT_REQUIRED);
- final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext);
+ final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog);
assertFalse(cfg.isDunRequired);
assertFalse(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN));
assertTrue(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE));
@@ -123,7 +125,7 @@
mHasTelephonyManager = false;
when(mTelephonyManager.getTetherApnRequired()).thenReturn(DUN_UNSPECIFIED);
- final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext);
+ final TetheringConfiguration cfg = new TetheringConfiguration(mMockContext, mLog);
assertTrue(cfg.isDunRequired);
assertTrue(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN));
// Just to prove we haven't clobbered Wi-Fi:
diff --git a/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
new file mode 100644
index 0000000..23318c2
--- /dev/null
+++ b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2015 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.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.Manifest.permission;
+import android.app.AppOpsManager;
+import android.app.admin.DeviceAdminInfo;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.TelephonyManager;
+
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkStatsAccessTest {
+ private static final String TEST_PKG = "com.example.test";
+ private static final int TEST_UID = 12345;
+
+ @Mock private Context mContext;
+ @Mock private DevicePolicyManagerInternal mDpmi;
+ @Mock private TelephonyManager mTm;
+ @Mock private AppOpsManager mAppOps;
+
+ // Hold the real service so we can restore it when tearing down the test.
+ private DevicePolicyManagerInternal mSystemDpmi;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mSystemDpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+ LocalServices.addService(DevicePolicyManagerInternal.class, mDpmi);
+
+ when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTm);
+ when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOps);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+ LocalServices.addService(DevicePolicyManagerInternal.class, mSystemDpmi);
+ }
+
+ @Test
+ public void testCheckAccessLevel_hasCarrierPrivileges() throws Exception {
+ setHasCarrierPrivileges(true);
+ setIsDeviceOwner(false);
+ setIsProfileOwner(false);
+ setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
+ setHasReadHistoryPermission(false);
+ assertEquals(NetworkStatsAccess.Level.DEVICE,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ }
+
+ @Test
+ public void testCheckAccessLevel_isDeviceOwner() throws Exception {
+ setHasCarrierPrivileges(false);
+ setIsDeviceOwner(true);
+ setIsProfileOwner(false);
+ setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
+ setHasReadHistoryPermission(false);
+ assertEquals(NetworkStatsAccess.Level.DEVICE,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ }
+
+ @Test
+ public void testCheckAccessLevel_isProfileOwner() throws Exception {
+ setHasCarrierPrivileges(false);
+ setIsDeviceOwner(false);
+ setIsProfileOwner(true);
+ setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
+ setHasReadHistoryPermission(false);
+ assertEquals(NetworkStatsAccess.Level.USER,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ }
+
+ @Test
+ public void testCheckAccessLevel_hasAppOpsBitAllowed() throws Exception {
+ setHasCarrierPrivileges(false);
+ setIsDeviceOwner(false);
+ setIsProfileOwner(true);
+ setHasAppOpsPermission(AppOpsManager.MODE_ALLOWED, false);
+ setHasReadHistoryPermission(false);
+ assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ }
+
+ @Test
+ public void testCheckAccessLevel_hasAppOpsBitDefault_grantedPermission() throws Exception {
+ setHasCarrierPrivileges(false);
+ setIsDeviceOwner(false);
+ setIsProfileOwner(true);
+ setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, true);
+ setHasReadHistoryPermission(false);
+ assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ }
+
+ @Test
+ public void testCheckAccessLevel_hasReadHistoryPermission() throws Exception {
+ setHasCarrierPrivileges(false);
+ setIsDeviceOwner(false);
+ setIsProfileOwner(true);
+ setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
+ setHasReadHistoryPermission(true);
+ assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ }
+
+ @Test
+ public void testCheckAccessLevel_deniedAppOpsBit() throws Exception {
+ setHasCarrierPrivileges(false);
+ setIsDeviceOwner(false);
+ setIsProfileOwner(false);
+ setHasAppOpsPermission(AppOpsManager.MODE_ERRORED, true);
+ setHasReadHistoryPermission(false);
+ assertEquals(NetworkStatsAccess.Level.DEFAULT,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ }
+
+ @Test
+ public void testCheckAccessLevel_deniedAppOpsBit_deniedPermission() throws Exception {
+ setHasCarrierPrivileges(false);
+ setIsDeviceOwner(false);
+ setIsProfileOwner(false);
+ setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
+ setHasReadHistoryPermission(false);
+ assertEquals(NetworkStatsAccess.Level.DEFAULT,
+ NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+ }
+
+ private void setHasCarrierPrivileges(boolean hasPrivileges) {
+ when(mTm.checkCarrierPrivilegesForPackage(TEST_PKG)).thenReturn(
+ hasPrivileges ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
+ : TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+ }
+
+ private void setIsDeviceOwner(boolean isOwner) {
+ when(mDpmi.isActiveAdminWithPolicy(TEST_UID, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER))
+ .thenReturn(isOwner);
+ }
+
+ private void setIsProfileOwner(boolean isOwner) {
+ when(mDpmi.isActiveAdminWithPolicy(TEST_UID, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER))
+ .thenReturn(isOwner);
+ }
+
+ private void setHasAppOpsPermission(int appOpsMode, boolean hasPermission) {
+ when(mAppOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS, TEST_UID, TEST_PKG))
+ .thenReturn(appOpsMode);
+ when(mContext.checkCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS)).thenReturn(
+ hasPermission ? PackageManager.PERMISSION_GRANTED
+ : PackageManager.PERMISSION_DENIED);
+ }
+
+ private void setHasReadHistoryPermission(boolean hasPermission) {
+ when(mContext.checkCallingOrSelfPermission(permission.READ_NETWORK_USAGE_HISTORY))
+ .thenReturn(hasPermission ? PackageManager.PERMISSION_GRANTED
+ : PackageManager.PERMISSION_DENIED);
+ }
+}
diff --git a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
new file mode 100644
index 0000000..2a32b73
--- /dev/null
+++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2012 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.net;
+
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import android.content.res.Resources;
+import android.net.NetworkIdentity;
+import android.net.NetworkStats;
+import android.net.NetworkTemplate;
+import android.os.Process;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
+import android.support.test.filters.SmallTest;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+import com.android.frameworks.tests.net.R;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * Tests for {@link NetworkStatsCollection}.
+ */
+@SmallTest
+public class NetworkStatsCollectionTest extends AndroidTestCase {
+
+ private static final String TEST_FILE = "test.bin";
+ private static final String TEST_IMSI = "310260000000000";
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // ignore any device overlay while testing
+ NetworkTemplate.forceAllNetworkTypes();
+ }
+
+ public void testReadLegacyNetwork() throws Exception {
+ final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
+ stageFile(R.raw.netstats_v1, testFile);
+
+ final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
+ collection.readLegacyNetwork(testFile);
+
+ // verify that history read correctly
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 636016770L, 709306L, 88038768L, 518836L, NetworkStatsAccess.Level.DEVICE);
+
+ // now export into a unified format
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ collection.write(new DataOutputStream(bos));
+
+ // clear structure completely
+ collection.reset();
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 0L, 0L, 0L, 0L, NetworkStatsAccess.Level.DEVICE);
+
+ // and read back into structure, verifying that totals are same
+ collection.read(new ByteArrayInputStream(bos.toByteArray()));
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 636016770L, 709306L, 88038768L, 518836L, NetworkStatsAccess.Level.DEVICE);
+ }
+
+ public void testReadLegacyUid() throws Exception {
+ final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
+ stageFile(R.raw.netstats_uid_v4, testFile);
+
+ final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
+ collection.readLegacyUid(testFile, false);
+
+ // verify that history read correctly
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 637076152L, 711413L, 88343717L, 521022L, NetworkStatsAccess.Level.DEVICE);
+
+ // now export into a unified format
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ collection.write(new DataOutputStream(bos));
+
+ // clear structure completely
+ collection.reset();
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 0L, 0L, 0L, 0L, NetworkStatsAccess.Level.DEVICE);
+
+ // and read back into structure, verifying that totals are same
+ collection.read(new ByteArrayInputStream(bos.toByteArray()));
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 637076152L, 711413L, 88343717L, 521022L, NetworkStatsAccess.Level.DEVICE);
+ }
+
+ public void testReadLegacyUidTags() throws Exception {
+ final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
+ stageFile(R.raw.netstats_uid_v4, testFile);
+
+ final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
+ collection.readLegacyUid(testFile, true);
+
+ // verify that history read correctly
+ assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
+ 77017831L, 100995L, 35436758L, 92344L);
+
+ // now export into a unified format
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ collection.write(new DataOutputStream(bos));
+
+ // clear structure completely
+ collection.reset();
+ assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
+ 0L, 0L, 0L, 0L);
+
+ // and read back into structure, verifying that totals are same
+ collection.read(new ByteArrayInputStream(bos.toByteArray()));
+ assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
+ 77017831L, 100995L, 35436758L, 92344L);
+ }
+
+ public void testStartEndAtomicBuckets() throws Exception {
+ final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS);
+
+ // record empty data straddling between buckets
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ entry.rxBytes = 32;
+ collection.recordData(null, UID_ALL, SET_DEFAULT, TAG_NONE, 30 * MINUTE_IN_MILLIS,
+ 90 * MINUTE_IN_MILLIS, entry);
+
+ // assert that we report boundary in atomic buckets
+ assertEquals(0, collection.getStartMillis());
+ assertEquals(2 * HOUR_IN_MILLIS, collection.getEndMillis());
+ }
+
+ public void testAccessLevels() throws Exception {
+ final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS);
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ final NetworkIdentitySet identSet = new NetworkIdentitySet();
+ identSet.add(new NetworkIdentity(TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ TEST_IMSI, null, false, true));
+
+ int myUid = Process.myUid();
+ int otherUidInSameUser = Process.myUid() + 1;
+ int uidInDifferentUser = Process.myUid() + UserHandle.PER_USER_RANGE;
+
+ // Record one entry for the current UID.
+ entry.rxBytes = 32;
+ collection.recordData(identSet, myUid, SET_DEFAULT, TAG_NONE, 0, 60 * MINUTE_IN_MILLIS,
+ entry);
+
+ // Record one entry for another UID in this user.
+ entry.rxBytes = 64;
+ collection.recordData(identSet, otherUidInSameUser, SET_DEFAULT, TAG_NONE, 0,
+ 60 * MINUTE_IN_MILLIS, entry);
+
+ // Record one entry for the system UID.
+ entry.rxBytes = 128;
+ collection.recordData(identSet, Process.SYSTEM_UID, SET_DEFAULT, TAG_NONE, 0,
+ 60 * MINUTE_IN_MILLIS, entry);
+
+ // Record one entry for a UID in a different user.
+ entry.rxBytes = 256;
+ collection.recordData(identSet, uidInDifferentUser, SET_DEFAULT, TAG_NONE, 0,
+ 60 * MINUTE_IN_MILLIS, entry);
+
+ // Verify the set of relevant UIDs for each access level.
+ MoreAsserts.assertEquals(new int[] { myUid },
+ collection.getRelevantUids(NetworkStatsAccess.Level.DEFAULT));
+ MoreAsserts.assertEquals(new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser },
+ collection.getRelevantUids(NetworkStatsAccess.Level.USER));
+ MoreAsserts.assertEquals(
+ new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser, uidInDifferentUser },
+ collection.getRelevantUids(NetworkStatsAccess.Level.DEVICE));
+
+ // Verify security check in getHistory.
+ assertNotNull(collection.getHistory(buildTemplateMobileAll(TEST_IMSI), myUid, SET_DEFAULT,
+ TAG_NONE, 0, NetworkStatsAccess.Level.DEFAULT));
+ try {
+ collection.getHistory(buildTemplateMobileAll(TEST_IMSI), otherUidInSameUser,
+ SET_DEFAULT, TAG_NONE, 0, NetworkStatsAccess.Level.DEFAULT);
+ fail("Should have thrown SecurityException for accessing different UID");
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ // Verify appropriate aggregation in getSummary.
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32, 0, 0, 0,
+ NetworkStatsAccess.Level.DEFAULT);
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32 + 64 + 128, 0, 0, 0,
+ NetworkStatsAccess.Level.USER);
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32 + 64 + 128 + 256, 0, 0,
+ 0, NetworkStatsAccess.Level.DEVICE);
+ }
+
+ /**
+ * Copy a {@link Resources#openRawResource(int)} into {@link File} for
+ * testing purposes.
+ */
+ private void stageFile(int rawId, File file) throws Exception {
+ new File(file.getParent()).mkdirs();
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = getContext().getResources().openRawResource(rawId);
+ out = new FileOutputStream(file);
+ Streams.copy(in, out);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ private static void assertSummaryTotal(NetworkStatsCollection collection,
+ NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets,
+ @NetworkStatsAccess.Level int accessLevel) {
+ final NetworkStats.Entry entry = collection.getSummary(
+ template, Long.MIN_VALUE, Long.MAX_VALUE, accessLevel)
+ .getTotal(null);
+ assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
+ }
+
+ private static void assertSummaryTotalIncludingTags(NetworkStatsCollection collection,
+ NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ final NetworkStats.Entry entry = collection.getSummary(
+ template, Long.MIN_VALUE, Long.MAX_VALUE, NetworkStatsAccess.Level.DEVICE)
+ .getTotalIncludingTags(null);
+ assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
+ }
+
+ private static void assertEntry(
+ NetworkStats.Entry entry, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ }
+}
diff --git a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
new file mode 100644
index 0000000..92dcdac
--- /dev/null
+++ b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -0,0 +1,488 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
+import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.when;
+
+import android.app.usage.NetworkStatsManager;
+import android.net.DataUsageRequest;
+import android.net.NetworkIdentity;
+import android.net.NetworkStats;
+import android.net.NetworkTemplate;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Process;
+import android.os.UserHandle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+
+import com.android.internal.net.VpnInfo;
+import com.android.server.net.NetworkStatsService;
+import com.android.server.net.NetworkStatsServiceTest.IdleableHandlerThread;
+import com.android.server.net.NetworkStatsServiceTest.LatchedHandler;
+
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link NetworkStatsObservers}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkStatsObserversTest {
+ private static final String TEST_IFACE = "test0";
+ private static final String TEST_IFACE2 = "test1";
+ private static final long TEST_START = 1194220800000L;
+
+ private static final String IMSI_1 = "310004";
+ private static final String IMSI_2 = "310260";
+ private static final String TEST_SSID = "AndroidAP";
+
+ private static NetworkTemplate sTemplateWifi = buildTemplateWifiWildcard();
+ private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
+ private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
+
+ private static final int UID_RED = UserHandle.PER_USER_RANGE + 1;
+ private static final int UID_BLUE = UserHandle.PER_USER_RANGE + 2;
+ private static final int UID_GREEN = UserHandle.PER_USER_RANGE + 3;
+ private static final int UID_ANOTHER_USER = 2 * UserHandle.PER_USER_RANGE + 4;
+
+ private static final long WAIT_TIMEOUT_MS = 500;
+ private static final long THRESHOLD_BYTES = 2 * MB_IN_BYTES;
+ private static final long BASE_BYTES = 7 * MB_IN_BYTES;
+ private static final int INVALID_TYPE = -1;
+
+ private static final VpnInfo[] VPN_INFO = new VpnInfo[0];
+
+ private long mElapsedRealtime;
+
+ private IdleableHandlerThread mObserverHandlerThread;
+ private Handler mObserverNoopHandler;
+
+ private LatchedHandler mHandler;
+
+ private NetworkStatsObservers mStatsObservers;
+ private Messenger mMessenger;
+ private ArrayMap<String, NetworkIdentitySet> mActiveIfaces;
+ private ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
+
+ @Mock private IBinder mockBinder;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mObserverHandlerThread = new IdleableHandlerThread("HandlerThread");
+ mObserverHandlerThread.start();
+ final Looper observerLooper = mObserverHandlerThread.getLooper();
+ mStatsObservers = new NetworkStatsObservers() {
+ @Override
+ protected Looper getHandlerLooperLocked() {
+ return observerLooper;
+ }
+ };
+
+ mHandler = new LatchedHandler(Looper.getMainLooper(), new ConditionVariable());
+ mMessenger = new Messenger(mHandler);
+
+ mActiveIfaces = new ArrayMap<>();
+ mActiveUidIfaces = new ArrayMap<>();
+ }
+
+ @Test
+ public void testRegister_thresholdTooLow_setsDefaultThreshold() throws Exception {
+ long thresholdTooLowBytes = 1L;
+ DataUsageRequest inputRequest = new DataUsageRequest(
+ DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdTooLowBytes);
+
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ assertTrue(request.requestId > 0);
+ assertTrue(Objects.equals(sTemplateWifi, request.template));
+ assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+ }
+
+ @Test
+ public void testRegister_highThreshold_accepted() throws Exception {
+ long highThresholdBytes = 2 * THRESHOLD_BYTES;
+ DataUsageRequest inputRequest = new DataUsageRequest(
+ DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, highThresholdBytes);
+
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ assertTrue(request.requestId > 0);
+ assertTrue(Objects.equals(sTemplateWifi, request.template));
+ assertEquals(highThresholdBytes, request.thresholdInBytes);
+ }
+
+ @Test
+ public void testRegister_twoRequests_twoIds() throws Exception {
+ DataUsageRequest inputRequest = new DataUsageRequest(
+ DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, THRESHOLD_BYTES);
+
+ DataUsageRequest request1 = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ assertTrue(request1.requestId > 0);
+ assertTrue(Objects.equals(sTemplateWifi, request1.template));
+ assertEquals(THRESHOLD_BYTES, request1.thresholdInBytes);
+
+ DataUsageRequest request2 = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ assertTrue(request2.requestId > request1.requestId);
+ assertTrue(Objects.equals(sTemplateWifi, request2.template));
+ assertEquals(THRESHOLD_BYTES, request2.thresholdInBytes);
+ }
+
+ @Test
+ public void testUnregister_unknownRequest_noop() throws Exception {
+ DataUsageRequest unknownRequest = new DataUsageRequest(
+ 123456 /* id */, sTemplateWifi, THRESHOLD_BYTES);
+
+ mStatsObservers.unregister(unknownRequest, UID_RED);
+ }
+
+ @Test
+ public void testUnregister_knownRequest_releasesCaller() throws Exception {
+ DataUsageRequest inputRequest = new DataUsageRequest(
+ DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
+
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ assertTrue(request.requestId > 0);
+ assertTrue(Objects.equals(sTemplateImsi1, request.template));
+ assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+ Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+
+ mStatsObservers.unregister(request, Process.SYSTEM_UID);
+ waitForObserverToIdle();
+
+ Mockito.verify(mockBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+ }
+
+ @Test
+ public void testUnregister_knownRequest_invalidUid_doesNotUnregister() throws Exception {
+ DataUsageRequest inputRequest = new DataUsageRequest(
+ DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
+
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ UID_RED, NetworkStatsAccess.Level.DEVICE);
+ assertTrue(request.requestId > 0);
+ assertTrue(Objects.equals(sTemplateImsi1, request.template));
+ assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+ Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+
+ mStatsObservers.unregister(request, UID_BLUE);
+ waitForObserverToIdle();
+
+ Mockito.verifyZeroInteractions(mockBinder);
+ }
+
+ @Test
+ public void testUpdateStats_initialSample_doesNotNotify() throws Exception {
+ DataUsageRequest inputRequest = new DataUsageRequest(
+ DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
+
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ assertTrue(request.requestId > 0);
+ assertTrue(Objects.equals(sTemplateImsi1, request.template));
+ assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+ NetworkIdentitySet identSet = new NetworkIdentitySet();
+ identSet.add(new NetworkIdentity(
+ TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
+ mActiveIfaces.put(TEST_IFACE, identSet);
+
+ // Baseline
+ NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
+ .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
+ NetworkStats uidSnapshot = null;
+
+ mStatsObservers.updateStats(
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+ VPN_INFO, TEST_START);
+ waitForObserverToIdle();
+ }
+
+ @Test
+ public void testUpdateStats_belowThreshold_doesNotNotify() throws Exception {
+ DataUsageRequest inputRequest = new DataUsageRequest(
+ DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
+
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ assertTrue(request.requestId > 0);
+ assertTrue(Objects.equals(sTemplateImsi1, request.template));
+ assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+ NetworkIdentitySet identSet = new NetworkIdentitySet();
+ identSet.add(new NetworkIdentity(
+ TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
+ mActiveIfaces.put(TEST_IFACE, identSet);
+
+ // Baseline
+ NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
+ .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
+ NetworkStats uidSnapshot = null;
+ mStatsObservers.updateStats(
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+ VPN_INFO, TEST_START);
+
+ // Delta
+ xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
+ .addIfaceValues(TEST_IFACE, BASE_BYTES + 1024L, 10L, BASE_BYTES + 2048L, 20L);
+ mStatsObservers.updateStats(
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+ VPN_INFO, TEST_START);
+ waitForObserverToIdle();
+ }
+
+
+ @Test
+ public void testUpdateStats_deviceAccess_notifies() throws Exception {
+ DataUsageRequest inputRequest = new DataUsageRequest(
+ DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
+
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ assertTrue(request.requestId > 0);
+ assertTrue(Objects.equals(sTemplateImsi1, request.template));
+ assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+ NetworkIdentitySet identSet = new NetworkIdentitySet();
+ identSet.add(new NetworkIdentity(
+ TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
+ mActiveIfaces.put(TEST_IFACE, identSet);
+
+ // Baseline
+ NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
+ .addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
+ NetworkStats uidSnapshot = null;
+ mStatsObservers.updateStats(
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+ VPN_INFO, TEST_START);
+
+ // Delta
+ xtSnapshot = new NetworkStats(TEST_START + MINUTE_IN_MILLIS, 1 /* initialSize */)
+ .addIfaceValues(TEST_IFACE, BASE_BYTES + THRESHOLD_BYTES, 12L,
+ BASE_BYTES + THRESHOLD_BYTES, 22L);
+ mStatsObservers.updateStats(
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+ VPN_INFO, TEST_START);
+ waitForObserverToIdle();
+ assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
+ }
+
+ @Test
+ public void testUpdateStats_defaultAccess_notifiesSameUid() throws Exception {
+ DataUsageRequest inputRequest = new DataUsageRequest(
+ DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
+
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ UID_RED, NetworkStatsAccess.Level.DEFAULT);
+ assertTrue(request.requestId > 0);
+ assertTrue(Objects.equals(sTemplateImsi1, request.template));
+ assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+ NetworkIdentitySet identSet = new NetworkIdentitySet();
+ identSet.add(new NetworkIdentity(
+ TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
+ mActiveUidIfaces.put(TEST_IFACE, identSet);
+
+ // Baseline
+ NetworkStats xtSnapshot = null;
+ NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
+ mStatsObservers.updateStats(
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+ VPN_INFO, TEST_START);
+
+ // Delta
+ uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
+ mStatsObservers.updateStats(
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+ VPN_INFO, TEST_START);
+ waitForObserverToIdle();
+ assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
+ }
+
+ @Test
+ public void testUpdateStats_defaultAccess_usageOtherUid_doesNotNotify() throws Exception {
+ DataUsageRequest inputRequest = new DataUsageRequest(
+ DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
+
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ UID_BLUE, NetworkStatsAccess.Level.DEFAULT);
+ assertTrue(request.requestId > 0);
+ assertTrue(Objects.equals(sTemplateImsi1, request.template));
+ assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+ NetworkIdentitySet identSet = new NetworkIdentitySet();
+ identSet.add(new NetworkIdentity(
+ TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
+ mActiveUidIfaces.put(TEST_IFACE, identSet);
+
+ // Baseline
+ NetworkStats xtSnapshot = null;
+ NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
+ mStatsObservers.updateStats(
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+ VPN_INFO, TEST_START);
+
+ // Delta
+ uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
+ mStatsObservers.updateStats(
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+ VPN_INFO, TEST_START);
+ waitForObserverToIdle();
+ }
+
+ @Test
+ public void testUpdateStats_userAccess_usageSameUser_notifies() throws Exception {
+ DataUsageRequest inputRequest = new DataUsageRequest(
+ DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
+
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ UID_BLUE, NetworkStatsAccess.Level.USER);
+ assertTrue(request.requestId > 0);
+ assertTrue(Objects.equals(sTemplateImsi1, request.template));
+ assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+ NetworkIdentitySet identSet = new NetworkIdentitySet();
+ identSet.add(new NetworkIdentity(
+ TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
+ mActiveUidIfaces.put(TEST_IFACE, identSet);
+
+ // Baseline
+ NetworkStats xtSnapshot = null;
+ NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
+ mStatsObservers.updateStats(
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+ VPN_INFO, TEST_START);
+
+ // Delta
+ uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
+ mStatsObservers.updateStats(
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+ VPN_INFO, TEST_START);
+ waitForObserverToIdle();
+ assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.mLastMessageType);
+ }
+
+ @Test
+ public void testUpdateStats_userAccess_usageAnotherUser_doesNotNotify() throws Exception {
+ DataUsageRequest inputRequest = new DataUsageRequest(
+ DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
+
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ UID_RED, NetworkStatsAccess.Level.USER);
+ assertTrue(request.requestId > 0);
+ assertTrue(Objects.equals(sTemplateImsi1, request.template));
+ assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+
+ NetworkIdentitySet identSet = new NetworkIdentitySet();
+ identSet.add(new NetworkIdentity(
+ TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ IMSI_1, null /* networkId */, false /* roaming */, true /* metered */));
+ mActiveUidIfaces.put(TEST_IFACE, identSet);
+
+ // Baseline
+ NetworkStats xtSnapshot = null;
+ NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
+ .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
+ mStatsObservers.updateStats(
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+ VPN_INFO, TEST_START);
+
+ // Delta
+ uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
+ .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES,
+ 2L, 0L);
+ mStatsObservers.updateStats(
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
+ VPN_INFO, TEST_START);
+ waitForObserverToIdle();
+ }
+
+ private void waitForObserverToIdle() {
+ waitForIdleLooper(mObserverHandlerThread.getLooper(), WAIT_TIMEOUT_MS);
+ waitForIdleLooper(mHandler.getLooper(), WAIT_TIMEOUT_MS);
+ }
+
+ // TODO: unify with ConnectivityService.waitForIdleHandler and
+ // NetworkServiceStatsTest.IdleableHandlerThread
+ private static void waitForIdleLooper(Looper looper, long timeoutMs) {
+ final ConditionVariable cv = new ConditionVariable();
+ final Handler handler = new Handler(looper);
+ handler.post(() -> cv.open());
+ if (!cv.block(timeoutMs)) {
+ fail("Looper did not become idle after " + timeoutMs + " ms");
+ }
+ }
+
+}
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
new file mode 100644
index 0000000..029693f
--- /dev/null
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -0,0 +1,1265 @@
+/*
+ * Copyright (C) 2011 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.net;
+
+import static android.content.Intent.ACTION_UID_REMOVED;
+import static android.content.Intent.EXTRA_UID;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.METERED_ALL;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkStats.ROAMING_ALL;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.ROAMING_YES;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkStatsHistory.FIELD_ALL;
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
+import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.net.TrafficStats.UID_REMOVED;
+import static android.net.TrafficStats.UID_TETHERING;
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+
+import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
+import android.app.AlarmManager;
+import android.app.usage.NetworkStatsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.DataUsageRequest;
+import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
+import android.net.INetworkStatsSession;
+import android.net.LinkProperties;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkState;
+import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.INetworkManagementService;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Messenger;
+import android.os.MessageQueue;
+import android.os.MessageQueue.IdleHandler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.TelephonyManager;
+import android.test.AndroidTestCase;
+import android.util.TrustedTime;
+
+import com.android.internal.net.VpnInfo;
+import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.server.net.NetworkStatsService;
+import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
+import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
+
+import libcore.io.IoUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.List;
+
+/**
+ * Tests for {@link NetworkStatsService}.
+ *
+ * TODO: This test used to be really brittle because it used Easymock - it uses Mockito now, but
+ * still uses the Easymock structure, which could be simplified.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkStatsServiceTest {
+ private static final String TAG = "NetworkStatsServiceTest";
+
+ private static final String TEST_IFACE = "test0";
+ private static final String TEST_IFACE2 = "test1";
+ private static final long TEST_START = 1194220800000L;
+
+ private static final String IMSI_1 = "310004";
+ private static final String IMSI_2 = "310260";
+ private static final String TEST_SSID = "AndroidAP";
+
+ private static NetworkTemplate sTemplateWifi = buildTemplateWifiWildcard();
+ private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
+ private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
+
+ private static final int UID_RED = 1001;
+ private static final int UID_BLUE = 1002;
+ private static final int UID_GREEN = 1003;
+
+ private static final long WAIT_TIMEOUT = 2 * 1000; // 2 secs
+ private static final int INVALID_TYPE = -1;
+
+ private long mElapsedRealtime;
+
+ private BroadcastInterceptingContext mServiceContext;
+ private File mStatsDir;
+
+ private @Mock INetworkManagementService mNetManager;
+ private @Mock TrustedTime mTime;
+ private @Mock NetworkStatsSettings mSettings;
+ private @Mock IConnectivityManager mConnManager;
+ private @Mock IBinder mBinder;
+ private @Mock AlarmManager mAlarmManager;
+ private IdleableHandlerThread mHandlerThread;
+ private Handler mHandler;
+
+ private NetworkStatsService mService;
+ private INetworkStatsSession mSession;
+ private INetworkManagementEventObserver mNetworkObserver;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ final Context context = InstrumentationRegistry.getContext();
+
+ mServiceContext = new BroadcastInterceptingContext(context);
+ mStatsDir = context.getFilesDir();
+ if (mStatsDir.exists()) {
+ IoUtils.deleteContents(mStatsDir);
+ }
+
+ PowerManager powerManager = (PowerManager) mServiceContext.getSystemService(
+ Context.POWER_SERVICE);
+ PowerManager.WakeLock wakeLock =
+ powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
+ mService = new NetworkStatsService(
+ mServiceContext, mNetManager, mAlarmManager, wakeLock, mTime,
+ TelephonyManager.getDefault(), mSettings, new NetworkStatsObservers(),
+ mStatsDir, getBaseDir(mStatsDir));
+ mHandlerThread = new IdleableHandlerThread("HandlerThread");
+ mHandlerThread.start();
+ Handler.Callback callback = new NetworkStatsService.HandlerCallback(mService);
+ mHandler = new Handler(mHandlerThread.getLooper(), callback);
+ mService.setHandler(mHandler, callback);
+ mService.bindConnectivityManager(mConnManager);
+
+ mElapsedRealtime = 0L;
+
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectSystemReady();
+
+ mService.systemReady();
+ mSession = mService.openSession();
+ assertNotNull("openSession() failed", mSession);
+
+
+ // catch INetworkManagementEventObserver during systemReady()
+ ArgumentCaptor<INetworkManagementEventObserver> networkObserver =
+ ArgumentCaptor.forClass(INetworkManagementEventObserver.class);
+ verify(mNetManager).registerObserver(networkObserver.capture());
+ mNetworkObserver = networkObserver.getValue();
+
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ IoUtils.deleteContents(mStatsDir);
+
+ mServiceContext = null;
+ mStatsDir = null;
+
+ mNetManager = null;
+ mTime = null;
+ mSettings = null;
+ mConnManager = null;
+
+ mSession.close();
+ mService = null;
+ }
+
+ @Test
+ public void testNetworkStatsWifi() throws Exception {
+ // pretend that wifi network comes online; service should ask about full
+ // network state, and poll any existing interfaces before updating.
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectBandwidthControlCheck();
+
+ mService.forceUpdateIfaces();
+
+ // verify service has empty history for wifi
+ assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+
+
+ // modify some number on wifi, and trigger poll event
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 1024L, 1L, 2048L, 2L));
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ forcePollAndWaitForIdle();
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
+
+
+ // and bump forward again, with counters going higher. this is
+ // important, since polling should correctly subtract last snapshot.
+ incrementCurrentTime(DAY_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 4096L, 4L, 8192L, 8L));
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ forcePollAndWaitForIdle();
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateWifi, 4096L, 4L, 8192L, 8L, 0);
+
+ }
+
+ @Test
+ public void testStatsRebootPersist() throws Exception {
+ assertStatsFilesExist(false);
+
+ // pretend that wifi network comes online; service should ask about full
+ // network state, and poll any existing interfaces before updating.
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectBandwidthControlCheck();
+
+ mService.forceUpdateIfaces();
+
+ // verify service has empty history for wifi
+ assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+
+
+ // modify some number on wifi, and trigger poll event
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 1024L, 8L, 2048L, 16L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
+ mService.setUidForeground(UID_RED, false);
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
+ mService.setUidForeground(UID_RED, true);
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
+
+ forcePollAndWaitForIdle();
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0);
+ assertUidTotal(sTemplateWifi, UID_RED, 1024L, 8L, 512L, 4L, 10);
+ assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, METERED_NO, ROAMING_NO, 512L, 4L, 256L,
+ 2L, 4);
+ assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, METERED_NO, ROAMING_NO, 512L, 4L,
+ 256L, 2L, 6);
+ assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 1L, 128L, 1L, 0);
+
+
+ // graceful shutdown system, which should trigger persist of stats, and
+ // clear any values in memory.
+ expectCurrentTime();
+ expectDefaultSettings();
+ mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
+ assertStatsFilesExist(true);
+
+ // boot through serviceReady() again
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectSystemReady();
+
+ mService.systemReady();
+
+ // after systemReady(), we should have historical stats loaded again
+ assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0);
+ assertUidTotal(sTemplateWifi, UID_RED, 1024L, 8L, 512L, 4L, 10);
+ assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, METERED_NO, ROAMING_NO, 512L, 4L, 256L,
+ 2L, 4);
+ assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, METERED_NO, ROAMING_NO, 512L, 4L,
+ 256L, 2L, 6);
+ assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 1L, 128L, 1L, 0);
+
+ }
+
+ // TODO: simulate reboot to test bucket resize
+ @Test
+ @Ignore
+ public void testStatsBucketResize() throws Exception {
+ NetworkStatsHistory history = null;
+
+ assertStatsFilesExist(false);
+
+ // pretend that wifi network comes online; service should ask about full
+ // network state, and poll any existing interfaces before updating.
+ expectCurrentTime();
+ expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectBandwidthControlCheck();
+
+ mService.forceUpdateIfaces();
+
+
+ // modify some number on wifi, and trigger poll event
+ incrementCurrentTime(2 * HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 512L, 4L, 512L, 4L));
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ forcePollAndWaitForIdle();
+
+ // verify service recorded history
+ history = mSession.getHistoryForNetwork(sTemplateWifi, FIELD_ALL);
+ assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0);
+ assertEquals(HOUR_IN_MILLIS, history.getBucketDuration());
+ assertEquals(2, history.size());
+
+
+ // now change bucket duration setting and trigger another poll with
+ // exact same values, which should resize existing buckets.
+ expectCurrentTime();
+ expectSettings(0L, 30 * MINUTE_IN_MILLIS, WEEK_IN_MILLIS);
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ forcePollAndWaitForIdle();
+
+ // verify identical stats, but spread across 4 buckets now
+ history = mSession.getHistoryForNetwork(sTemplateWifi, FIELD_ALL);
+ assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0);
+ assertEquals(30 * MINUTE_IN_MILLIS, history.getBucketDuration());
+ assertEquals(4, history.size());
+
+ }
+
+ @Test
+ public void testUidStatsAcrossNetworks() throws Exception {
+ // pretend first mobile network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildMobile3gState(IMSI_1));
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectBandwidthControlCheck();
+
+ mService.forceUpdateIfaces();
+
+
+ // create some traffic on first network
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
+ mService.incrementOperationCount(UID_RED, 0xF00D, 10);
+
+ forcePollAndWaitForIdle();
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
+ assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+ assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 512L, 4L, 10);
+ assertUidTotal(sTemplateImsi1, UID_BLUE, 512L, 4L, 0L, 0L, 0);
+
+
+ // now switch networks; this also tests that we're okay with interfaces
+ // disappearing, to verify we don't count backwards.
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildMobile3gState(IMSI_2));
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
+ expectBandwidthControlCheck();
+
+ mService.forceUpdateIfaces();
+ forcePollAndWaitForIdle();
+
+
+ // create traffic on second network
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 2176L, 17L, 1536L, 12L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L));
+ mService.incrementOperationCount(UID_BLUE, 0xFAAD, 10);
+
+ forcePollAndWaitForIdle();
+
+ // verify original history still intact
+ assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
+ assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 512L, 4L, 10);
+ assertUidTotal(sTemplateImsi1, UID_BLUE, 512L, 4L, 0L, 0L, 0);
+
+ // and verify new history also recorded under different template, which
+ // verifies that we didn't cross the streams.
+ assertNetworkTotal(sTemplateImsi2, 128L, 1L, 1024L, 8L, 0);
+ assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+ assertUidTotal(sTemplateImsi2, UID_BLUE, 128L, 1L, 1024L, 8L, 10);
+
+ }
+
+ @Test
+ public void testUidRemovedIsMoved() throws Exception {
+ // pretend that network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectBandwidthControlCheck();
+
+ mService.forceUpdateIfaces();
+
+
+ // create some traffic
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
+ .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 10);
+
+ forcePollAndWaitForIdle();
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateWifi, 4128L, 258L, 544L, 34L, 0);
+ assertUidTotal(sTemplateWifi, UID_RED, 16L, 1L, 16L, 1L, 10);
+ assertUidTotal(sTemplateWifi, UID_BLUE, 4096L, 258L, 512L, 32L, 0);
+ assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 1L, 16L, 1L, 0);
+
+
+ // now pretend two UIDs are uninstalled, which should migrate stats to
+ // special "removed" bucket.
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
+ .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
+ final Intent intent = new Intent(ACTION_UID_REMOVED);
+ intent.putExtra(EXTRA_UID, UID_BLUE);
+ mServiceContext.sendBroadcast(intent);
+ intent.putExtra(EXTRA_UID, UID_RED);
+ mServiceContext.sendBroadcast(intent);
+
+ // existing uid and total should remain unchanged; but removed UID
+ // should be gone completely.
+ assertNetworkTotal(sTemplateWifi, 4128L, 258L, 544L, 34L, 0);
+ assertUidTotal(sTemplateWifi, UID_RED, 0L, 0L, 0L, 0L, 0);
+ assertUidTotal(sTemplateWifi, UID_BLUE, 0L, 0L, 0L, 0L, 0);
+ assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 1L, 16L, 1L, 0);
+ assertUidTotal(sTemplateWifi, UID_REMOVED, 4112L, 259L, 528L, 33L, 10);
+
+ }
+
+ @Test
+ public void testUid3g4gCombinedByTemplate() throws Exception {
+ // pretend that network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildMobile3gState(IMSI_1));
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectBandwidthControlCheck();
+
+ mService.forceUpdateIfaces();
+
+
+ // create some traffic
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
+ mService.incrementOperationCount(UID_RED, 0xF00D, 5);
+
+ forcePollAndWaitForIdle();
+
+ // verify service recorded history
+ assertUidTotal(sTemplateImsi1, UID_RED, 1024L, 8L, 1024L, 8L, 5);
+
+
+ // now switch over to 4g network
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildMobile4gState(TEST_IFACE2));
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
+ expectBandwidthControlCheck();
+
+ mService.forceUpdateIfaces();
+ forcePollAndWaitForIdle();
+
+
+ // create traffic on second network
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L));
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 5);
+
+ forcePollAndWaitForIdle();
+
+ // verify that ALL_MOBILE template combines both
+ assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 1280L, 10L, 10);
+ }
+
+ @Test
+ public void testSummaryForAllUid() throws Exception {
+ // pretend that network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectBandwidthControlCheck();
+
+ mService.forceUpdateIfaces();
+
+
+ // create some traffic for two apps
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L));
+ mService.incrementOperationCount(UID_RED, 0xF00D, 1);
+
+ forcePollAndWaitForIdle();
+
+ // verify service recorded history
+ assertUidTotal(sTemplateWifi, UID_RED, 50L, 5L, 50L, 5L, 1);
+ assertUidTotal(sTemplateWifi, UID_BLUE, 1024L, 8L, 512L, 4L, 0);
+
+
+ // now create more traffic in next hour, but only for one app
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0L));
+ forcePollAndWaitForIdle();
+
+ // first verify entire history present
+ NetworkStats stats = mSession.getSummaryForAllUid(
+ sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
+ assertEquals(3, stats.size());
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 50L,
+ 5L, 50L, 5L, 1);
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 10L,
+ 1L, 10L, 1L, 1);
+ assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ 2048L, 16L, 1024L, 8L, 0);
+
+ // now verify that recent history only contains one uid
+ final long currentTime = currentTimeMillis();
+ stats = mSession.getSummaryForAllUid(
+ sTemplateWifi, currentTime - HOUR_IN_MILLIS, currentTime, true);
+ assertEquals(1, stats.size());
+ assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ 1024L, 8L, 512L, 4L, 0);
+ }
+
+ @Test
+ public void testForegroundBackground() throws Exception {
+ // pretend that network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectBandwidthControlCheck();
+
+ mService.forceUpdateIfaces();
+
+
+ // create some initial traffic
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L));
+ mService.incrementOperationCount(UID_RED, 0xF00D, 1);
+
+ forcePollAndWaitForIdle();
+
+ // verify service recorded history
+ assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1);
+
+
+ // now switch to foreground
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L));
+ mService.setUidForeground(UID_RED, true);
+ mService.incrementOperationCount(UID_RED, 0xFAAD, 1);
+
+ forcePollAndWaitForIdle();
+
+ // test that we combined correctly
+ assertUidTotal(sTemplateWifi, UID_RED, 160L, 4L, 160L, 4L, 2);
+
+ // verify entire history present
+ final NetworkStats stats = mSession.getSummaryForAllUid(
+ sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
+ assertEquals(4, stats.size());
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 128L,
+ 2L, 128L, 2L, 1);
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 64L,
+ 1L, 64L, 1L, 1);
+ assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ 32L, 2L, 32L, 2L, 1);
+ assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, 0xFAAD, METERED_NO, ROAMING_NO, 1L,
+ 1L, 1L, 1L, 1);
+ }
+
+ @Test
+ public void testMetered() throws Exception {
+ // pretend that network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildWifiState(true /* isMetered */));
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectBandwidthControlCheck();
+
+ mService.forceUpdateIfaces();
+
+
+ // create some initial traffic
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ // Note that all traffic from NetworkManagementService is tagged as METERED_NO and
+ // ROAMING_NO, because metered and roaming isn't tracked at that layer. We layer it
+ // on top by inspecting the iface properties.
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 128L,
+ 2L, 128L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, 64L,
+ 1L, 64L, 1L, 0L));
+ mService.incrementOperationCount(UID_RED, 0xF00D, 1);
+
+ forcePollAndWaitForIdle();
+
+ // verify service recorded history
+ assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1);
+ // verify entire history present
+ final NetworkStats stats = mSession.getSummaryForAllUid(
+ sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
+ assertEquals(2, stats.size());
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ 128L, 2L, 128L, 2L, 1);
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, 64L,
+ 1L, 64L, 1L, 1);
+ }
+
+ @Test
+ public void testRoaming() throws Exception {
+ // pretend that network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildMobile3gState(IMSI_1, true /* isRoaming */));
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectBandwidthControlCheck();
+
+ mService.forceUpdateIfaces();
+
+
+ // Create some traffic
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ // Note that all traffic from NetworkManagementService is tagged as METERED_NO and
+ // ROAMING_NO, because metered and roaming isn't tracked at that layer. We layer it
+ // on top by inspecting the iface properties.
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO,
+ 128L, 2L, 128L, 2L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_NO, 64L,
+ 1L, 64L, 1L, 0L));
+ forcePollAndWaitForIdle();
+
+ // verify service recorded history
+ assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 0);
+
+ // verify entire history present
+ final NetworkStats stats = mSession.getSummaryForAllUid(
+ sTemplateImsi1, Long.MIN_VALUE, Long.MAX_VALUE, true);
+ assertEquals(2, stats.size());
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_YES,
+ 128L, 2L, 128L, 2L, 0);
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_YES, 64L,
+ 1L, 64L, 1L, 0);
+ }
+
+ @Test
+ public void testTethering() throws Exception {
+ // pretend first mobile network comes online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildMobile3gState(IMSI_1));
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectBandwidthControlCheck();
+
+ mService.forceUpdateIfaces();
+
+
+ // create some tethering traffic
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
+
+ final NetworkStats uidStats = new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L);
+ final String[] tetherIfacePairs = new String[] { TEST_IFACE, "wlan0" };
+ final NetworkStats tetherStats = new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L,
+ 0L);
+
+ expectNetworkStatsUidDetail(uidStats, tetherIfacePairs, tetherStats);
+ forcePollAndWaitForIdle();
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
+ assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 0);
+ assertUidTotal(sTemplateImsi1, UID_TETHERING, 1920L, 14L, 384L, 2L, 0);
+
+ }
+
+ @Test
+ public void testRegisterUsageCallback() throws Exception {
+ // pretend that wifi network comes online; service should ask about full
+ // network state, and poll any existing interfaces before updating.
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildWifiState());
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectBandwidthControlCheck();
+
+ mService.forceUpdateIfaces();
+
+ // verify service has empty history for wifi
+ assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
+ String callingPackage = "the.calling.package";
+ long thresholdInBytes = 1L; // very small; should be overriden by framework
+ DataUsageRequest inputRequest = new DataUsageRequest(
+ DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdInBytes);
+
+ // Create a messenger that waits for callback activity
+ ConditionVariable cv = new ConditionVariable(false);
+ LatchedHandler latchedHandler = new LatchedHandler(Looper.getMainLooper(), cv);
+ Messenger messenger = new Messenger(latchedHandler);
+
+ // Force poll
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+
+
+ // Register and verify request and that binder was called
+ DataUsageRequest request =
+ mService.registerUsageCallback(callingPackage, inputRequest,
+ messenger, mBinder);
+ assertTrue(request.requestId > 0);
+ assertTrue(Objects.equals(sTemplateWifi, request.template));
+ long minThresholdInBytes = 2 * 1024 * 1024; // 2 MB
+ assertEquals(minThresholdInBytes, request.thresholdInBytes);
+
+ // Send dummy message to make sure that any previous message has been handled
+ mHandler.sendMessage(mHandler.obtainMessage(-1));
+ mHandlerThread.waitForIdle(WAIT_TIMEOUT);
+
+
+
+ // Make sure that the caller binder gets connected
+ verify(mBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+
+
+ // modify some number on wifi, and trigger poll event
+ // not enough traffic to call data usage callback
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 1024L, 1L, 2048L, 2L));
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ forcePollAndWaitForIdle();
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
+
+ // make sure callback has not being called
+ assertEquals(INVALID_TYPE, latchedHandler.mLastMessageType);
+
+ // and bump forward again, with counters going higher. this is
+ // important, since it will trigger the data usage callback
+ incrementCurrentTime(DAY_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 4096000L, 4L, 8192000L, 8L));
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ forcePollAndWaitForIdle();
+
+ // verify service recorded history
+ assertNetworkTotal(sTemplateWifi, 4096000L, 4L, 8192000L, 8L, 0);
+
+
+ // Wait for the caller to ack receipt of CALLBACK_LIMIT_REACHED
+ assertTrue(cv.block(WAIT_TIMEOUT));
+ assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, latchedHandler.mLastMessageType);
+ cv.close();
+
+ // Allow binder to disconnect
+ when(mBinder.unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt())).thenReturn(true);
+
+ // Unregister request
+ mService.unregisterUsageRequest(request);
+
+ // Wait for the caller to ack receipt of CALLBACK_RELEASED
+ assertTrue(cv.block(WAIT_TIMEOUT));
+ assertEquals(NetworkStatsManager.CALLBACK_RELEASED, latchedHandler.mLastMessageType);
+
+ // Make sure that the caller binder gets disconnected
+ verify(mBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+ }
+
+ @Test
+ public void testUnregisterUsageCallback_unknown_noop() throws Exception {
+ String callingPackage = "the.calling.package";
+ long thresholdInBytes = 10 * 1024 * 1024; // 10 MB
+ DataUsageRequest unknownRequest = new DataUsageRequest(
+ 2 /* requestId */, sTemplateImsi1, thresholdInBytes);
+
+ mService.unregisterUsageRequest(unknownRequest);
+ }
+
+ private static File getBaseDir(File statsDir) {
+ File baseDir = new File(statsDir, "netstats");
+ baseDir.mkdirs();
+ return baseDir;
+ }
+
+ private void assertNetworkTotal(NetworkTemplate template, long rxBytes, long rxPackets,
+ long txBytes, long txPackets, int operations) throws Exception {
+ assertNetworkTotal(template, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, rxPackets, txBytes,
+ txPackets, operations);
+ }
+
+ private void assertNetworkTotal(NetworkTemplate template, long start, long end, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, int operations) throws Exception {
+ // verify history API
+ final NetworkStatsHistory history = mSession.getHistoryForNetwork(template, FIELD_ALL);
+ assertValues(history, start, end, rxBytes, rxPackets, txBytes, txPackets, operations);
+
+ // verify summary API
+ final NetworkStats stats = mSession.getSummaryForNetwork(template, start, end);
+ assertValues(stats, IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO,
+ rxBytes, rxPackets, txBytes, txPackets, operations);
+ }
+
+ private void assertUidTotal(NetworkTemplate template, int uid, long rxBytes, long rxPackets,
+ long txBytes, long txPackets, int operations) throws Exception {
+ assertUidTotal(template, uid, SET_ALL, METERED_ALL, ROAMING_ALL, rxBytes, rxPackets,
+ txBytes, txPackets, operations);
+ }
+
+ private void assertUidTotal(NetworkTemplate template, int uid, int set, int metered,
+ int roaming, long rxBytes, long rxPackets, long txBytes, long txPackets, int operations)
+ throws Exception {
+ // verify history API
+ final NetworkStatsHistory history = mSession.getHistoryForUid(
+ template, uid, set, TAG_NONE, FIELD_ALL);
+ assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, rxPackets, txBytes,
+ txPackets, operations);
+
+ // verify summary API
+ final NetworkStats stats = mSession.getSummaryForAllUid(
+ template, Long.MIN_VALUE, Long.MAX_VALUE, false);
+ assertValues(stats, IFACE_ALL, uid, set, TAG_NONE, metered, roaming, rxBytes, rxPackets,
+ txBytes, txPackets, operations);
+ }
+
+ private void expectSystemReady() throws Exception {
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectBandwidthControlCheck();
+ }
+
+ private void expectNetworkState(NetworkState... state) throws Exception {
+ when(mConnManager.getAllNetworkState()).thenReturn(state);
+
+ final LinkProperties linkProp = state.length > 0 ? state[0].linkProperties : null;
+ when(mConnManager.getActiveLinkProperties()).thenReturn(linkProp);
+ }
+
+ private void expectNetworkStatsSummary(NetworkStats summary) throws Exception {
+ when(mConnManager.getAllVpnInfo()).thenReturn(new VpnInfo[0]);
+
+ expectNetworkStatsSummaryDev(summary);
+ expectNetworkStatsSummaryXt(summary);
+ }
+
+ private void expectNetworkStatsSummaryDev(NetworkStats summary) throws Exception {
+ when(mNetManager.getNetworkStatsSummaryDev()).thenReturn(summary);
+ }
+
+ private void expectNetworkStatsSummaryXt(NetworkStats summary) throws Exception {
+ when(mNetManager.getNetworkStatsSummaryXt()).thenReturn(summary);
+ }
+
+ private void expectNetworkStatsUidDetail(NetworkStats detail) throws Exception {
+ expectNetworkStatsUidDetail(detail, new String[0], new NetworkStats(0L, 0));
+ }
+
+ private void expectNetworkStatsUidDetail(
+ NetworkStats detail, String[] tetherIfacePairs, NetworkStats tetherStats)
+ throws Exception {
+ when(mNetManager.getNetworkStatsUidDetail(UID_ALL)).thenReturn(detail);
+
+ // also include tethering details, since they are folded into UID
+ when(mNetManager.getNetworkStatsTethering()).thenReturn(tetherStats);
+ }
+
+ private void expectDefaultSettings() throws Exception {
+ expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
+ }
+
+ private void expectSettings(long persistBytes, long bucketDuration, long deleteAge)
+ throws Exception {
+ when(mSettings.getPollInterval()).thenReturn(HOUR_IN_MILLIS);
+ when(mSettings.getTimeCacheMaxAge()).thenReturn(DAY_IN_MILLIS);
+ when(mSettings.getSampleEnabled()).thenReturn(true);
+
+ final Config config = new Config(bucketDuration, deleteAge, deleteAge);
+ when(mSettings.getDevConfig()).thenReturn(config);
+ when(mSettings.getXtConfig()).thenReturn(config);
+ when(mSettings.getUidConfig()).thenReturn(config);
+ when(mSettings.getUidTagConfig()).thenReturn(config);
+
+ when(mSettings.getGlobalAlertBytes(anyLong())).thenReturn(MB_IN_BYTES);
+ when(mSettings.getDevPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
+ when(mSettings.getXtPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
+ when(mSettings.getUidPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
+ when(mSettings.getUidTagPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
+ }
+
+ private void expectCurrentTime() throws Exception {
+ when(mTime.forceRefresh()).thenReturn(false);
+ when(mTime.hasCache()).thenReturn(true);
+ when(mTime.currentTimeMillis()).thenReturn(currentTimeMillis());
+ when(mTime.getCacheAge()).thenReturn(0L);
+ when(mTime.getCacheCertainty()).thenReturn(0L);
+ }
+
+ private void expectBandwidthControlCheck() throws Exception {
+ when(mNetManager.isBandwidthControlEnabled()).thenReturn(true);
+ }
+
+ private void assertStatsFilesExist(boolean exist) {
+ final File basePath = new File(mStatsDir, "netstats");
+ if (exist) {
+ assertTrue(basePath.list().length > 0);
+ } else {
+ assertTrue(basePath.list().length == 0);
+ }
+ }
+
+ private static void assertValues(NetworkStats stats, String iface, int uid, int set,
+ int tag, int metered, int roaming, long rxBytes, long rxPackets, long txBytes,
+ long txPackets, int operations) {
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ List<Integer> sets = new ArrayList<>();
+ if (set == SET_DEFAULT || set == SET_ALL) {
+ sets.add(SET_DEFAULT);
+ }
+ if (set == SET_FOREGROUND || set == SET_ALL) {
+ sets.add(SET_FOREGROUND);
+ }
+
+ List<Integer> roamings = new ArrayList<>();
+ if (roaming == ROAMING_NO || roaming == ROAMING_ALL) {
+ roamings.add(ROAMING_NO);
+ }
+ if (roaming == ROAMING_YES || roaming == ROAMING_ALL) {
+ roamings.add(ROAMING_YES);
+ }
+
+ List<Integer> meterings = new ArrayList<>();
+ if (metered == METERED_NO || metered == METERED_ALL) {
+ meterings.add(METERED_NO);
+ }
+ if (metered == METERED_YES || metered == METERED_ALL) {
+ meterings.add(METERED_YES);
+ }
+
+ for (int s : sets) {
+ for (int r : roamings) {
+ for (int m : meterings) {
+ final int i = stats.findIndex(iface, uid, s, tag, m, r);
+ if (i != -1) {
+ entry.add(stats.getValues(i, null));
+ }
+ }
+ }
+ }
+
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ assertEquals("unexpected operations", operations, entry.operations);
+ }
+
+ private static void assertValues(NetworkStatsHistory stats, long start, long end, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, int operations) {
+ final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ assertEquals("unexpected operations", operations, entry.operations);
+ }
+
+ private static NetworkState buildWifiState() {
+ return buildWifiState(false);
+ }
+
+ private static NetworkState buildWifiState(boolean isMetered) {
+ final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0, null, null);
+ info.setDetailedState(DetailedState.CONNECTED, null, null);
+ final LinkProperties prop = new LinkProperties();
+ prop.setInterfaceName(TEST_IFACE);
+ final NetworkCapabilities capabilities = new NetworkCapabilities();
+ if (!isMetered) {
+ capabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+ }
+ return new NetworkState(info, prop, capabilities, null, null, TEST_SSID);
+ }
+
+ private static NetworkState buildMobile3gState(String subscriberId) {
+ return buildMobile3gState(subscriberId, false /* isRoaming */);
+ }
+
+ private static NetworkState buildMobile3gState(String subscriberId, boolean isRoaming) {
+ final NetworkInfo info = new NetworkInfo(
+ TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UMTS, null, null);
+ info.setDetailedState(DetailedState.CONNECTED, null, null);
+ info.setRoaming(isRoaming);
+ final LinkProperties prop = new LinkProperties();
+ prop.setInterfaceName(TEST_IFACE);
+ final NetworkCapabilities capabilities = new NetworkCapabilities();
+ return new NetworkState(info, prop, capabilities, null, subscriberId, null);
+ }
+
+ private static NetworkState buildMobile4gState(String iface) {
+ final NetworkInfo info = new NetworkInfo(TYPE_WIMAX, 0, null, null);
+ info.setDetailedState(DetailedState.CONNECTED, null, null);
+ final LinkProperties prop = new LinkProperties();
+ prop.setInterfaceName(iface);
+ final NetworkCapabilities capabilities = new NetworkCapabilities();
+ return new NetworkState(info, prop, capabilities, null, null, null);
+ }
+
+ private NetworkStats buildEmptyStats() {
+ return new NetworkStats(getElapsedRealtime(), 0);
+ }
+
+ private long getElapsedRealtime() {
+ return mElapsedRealtime;
+ }
+
+ private long startTimeMillis() {
+ return TEST_START;
+ }
+
+ private long currentTimeMillis() {
+ return startTimeMillis() + mElapsedRealtime;
+ }
+
+ private void incrementCurrentTime(long duration) {
+ mElapsedRealtime += duration;
+ }
+
+ private void forcePollAndWaitForIdle() {
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+ // Send dummy message to make sure that any previous message has been handled
+ mHandler.sendMessage(mHandler.obtainMessage(-1));
+ mHandlerThread.waitForIdle(WAIT_TIMEOUT);
+ }
+
+ static class LatchedHandler extends Handler {
+ private final ConditionVariable mCv;
+ int mLastMessageType = INVALID_TYPE;
+
+ LatchedHandler(Looper looper, ConditionVariable cv) {
+ super(looper);
+ mCv = cv;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ mLastMessageType = msg.what;
+ mCv.open();
+ super.handleMessage(msg);
+ }
+ }
+
+ /**
+ * A subclass of HandlerThread that allows callers to wait for it to become idle. waitForIdle
+ * will return immediately if the handler is already idle.
+ */
+ static class IdleableHandlerThread extends HandlerThread {
+ private IdleHandler mIdleHandler;
+
+ public IdleableHandlerThread(String name) {
+ super(name);
+ }
+
+ public void waitForIdle(long timeoutMs) {
+ final ConditionVariable cv = new ConditionVariable();
+ final MessageQueue queue = getLooper().getQueue();
+
+ synchronized (queue) {
+ if (queue.isIdle()) {
+ return;
+ }
+
+ assertNull("BUG: only one idle handler allowed", mIdleHandler);
+ mIdleHandler = new IdleHandler() {
+ public boolean queueIdle() {
+ cv.open();
+ mIdleHandler = null;
+ return false; // Remove the handler.
+ }
+ };
+ queue.addIdleHandler(mIdleHandler);
+ }
+
+ if (!cv.block(timeoutMs)) {
+ fail("HandlerThread " + getName() + " did not become idle after " + timeoutMs
+ + " ms");
+ queue.removeIdleHandler(mIdleHandler);
+ }
+ }
+ }
+
+}
diff --git a/services/tests/servicestests/res/raw/netstats_uid_v4 b/tests/net/res/raw/netstats_uid_v4
similarity index 100%
rename from services/tests/servicestests/res/raw/netstats_uid_v4
rename to tests/net/res/raw/netstats_uid_v4
Binary files differ
diff --git a/services/tests/servicestests/res/raw/netstats_v1 b/tests/net/res/raw/netstats_v1
similarity index 100%
rename from services/tests/servicestests/res/raw/netstats_v1
rename to tests/net/res/raw/netstats_v1
Binary files differ
diff --git a/tests/radio/Android.mk b/tests/radio/Android.mk
new file mode 100644
index 0000000..dc55d0b
--- /dev/null
+++ b/tests/radio/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := RadioTests
+
+LOCAL_MODULE_TAGS := tests
+# TODO(b/13282254): uncomment when b/13282254 is fixed
+# LOCAL_SDK_VERSION := current
+
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util android-support-test testng
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
diff --git a/tests/radio/AndroidManifest.xml b/tests/radio/AndroidManifest.xml
new file mode 100644
index 0000000..150edbf
--- /dev/null
+++ b/tests/radio/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.hardware.radio.tests">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.hardware.radio.tests"
+ android:label="Tests for broadcast radio API" >
+ </instrumentation>
+</manifest>
diff --git a/tests/radio/src/android/hardware/radio/tests/RadioTest.java b/tests/radio/src/android/hardware/radio/tests/RadioTest.java
new file mode 100644
index 0000000..47e104c
--- /dev/null
+++ b/tests/radio/src/android/hardware/radio/tests/RadioTest.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.radio.tests;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioTuner;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.*;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.testng.Assert.assertThrows;
+
+/**
+ * A test for broadcast radio API.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RadioTest {
+ private static final String TAG = "RadioTest";
+
+ public final Context mContext = InstrumentationRegistry.getContext();
+
+ private final int kConfigCallbackTimeoutMs = 10000;
+ private final int kCancelTimeoutMs = 1000;
+ private final int kTuneCallbackTimeoutMs = 30000;
+
+ private RadioManager mRadioManager;
+ private RadioTuner mRadioTuner;
+ private RadioManager.ModuleProperties mModule;
+ private final List<RadioManager.ModuleProperties> mModules = new ArrayList<>();
+ @Mock private RadioTuner.Callback mCallback;
+
+ RadioManager.AmBandDescriptor mAmBandDescriptor;
+ RadioManager.FmBandDescriptor mFmBandDescriptor;
+
+ RadioManager.BandConfig mAmBandConfig;
+ RadioManager.BandConfig mFmBandConfig;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ // check if radio is supported and skip the test if it's not
+ PackageManager packageManager = mContext.getPackageManager();
+ boolean isRadioSupported = packageManager.hasSystemFeature(PackageManager.FEATURE_RADIO);
+ assumeTrue(isRadioSupported);
+
+ mRadioManager = (RadioManager)mContext.getSystemService(Context.RADIO_SERVICE);
+ assertNotNull(mRadioManager);
+
+ int status = mRadioManager.listModules(mModules);
+ assertEquals(RadioManager.STATUS_OK, status);
+ assertFalse(mModules.isEmpty());
+ }
+
+ @After
+ public void tearDown() {
+ mRadioManager = null;
+ mModules.clear();
+ if (mRadioTuner != null) {
+ mRadioTuner.close();
+ mRadioTuner = null;
+ }
+ verifyNoMoreInteractions(mCallback);
+ }
+
+ private void openTuner() {
+ openTuner(true);
+ }
+
+ private void resetCallback() {
+ verifyNoMoreInteractions(mCallback);
+ Mockito.reset(mCallback);
+ }
+
+ private void openTuner(boolean withAudio) {
+ assertNull(mRadioTuner);
+
+ // find FM band and build its config
+ mModule = mModules.get(0);
+ for (RadioManager.BandDescriptor band : mModule.getBands()) {
+ if (band.getType() == RadioManager.BAND_AM) {
+ mAmBandDescriptor = (RadioManager.AmBandDescriptor)band;
+ }
+ if (band.getType() == RadioManager.BAND_FM) {
+ mFmBandDescriptor = (RadioManager.FmBandDescriptor)band;
+ }
+ }
+ assertNotNull(mAmBandDescriptor);
+ assertNotNull(mFmBandDescriptor);
+ mAmBandConfig = new RadioManager.AmBandConfig.Builder(mAmBandDescriptor).build();
+ mFmBandConfig = new RadioManager.FmBandConfig.Builder(mFmBandDescriptor).build();
+
+ mRadioTuner = mRadioManager.openTuner(mModule.getId(),
+ mFmBandConfig, withAudio, mCallback, null);
+ assertNotNull(mRadioTuner);
+ verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any());
+ resetCallback();
+ }
+
+ private void checkAntenna() {
+ boolean isConnected = mRadioTuner.isAntennaConnected();
+ assertTrue(isConnected);
+ }
+
+ @Test
+ public void testOpenTuner() {
+ openTuner();
+ }
+
+ @Test
+ public void testReopenTuner() throws Throwable {
+ openTuner();
+ mRadioTuner.close();
+ mRadioTuner = null;
+ Thread.sleep(100); // TODO(b/36122635): force reopen
+ openTuner();
+ }
+
+ @Test
+ public void testDoubleClose() {
+ openTuner();
+ mRadioTuner.close();
+ mRadioTuner.close();
+ }
+
+ @Test
+ public void testUseAfterClose() {
+ openTuner();
+ mRadioTuner.close();
+ int ret = mRadioTuner.cancel();
+ assertEquals(RadioManager.STATUS_INVALID_OPERATION, ret);
+ }
+
+ @Test
+ public void testSetAndGetConfiguration() {
+ openTuner();
+
+ // set
+ int ret = mRadioTuner.setConfiguration(mAmBandConfig);
+ assertEquals(RadioManager.STATUS_OK, ret);
+ verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any());
+
+ // get
+ RadioManager.BandConfig[] config = new RadioManager.BandConfig[1];
+ ret = mRadioTuner.getConfiguration(config);
+ assertEquals(RadioManager.STATUS_OK, ret);
+
+ assertEquals(mAmBandConfig, config[0]);
+ }
+
+ @Test
+ public void testSetBadConfiguration() throws Throwable {
+ openTuner();
+
+ // set bad config
+ Constructor<RadioManager.AmBandConfig> configConstr =
+ RadioManager.AmBandConfig.class.getDeclaredConstructor(
+ int.class, int.class, int.class, int.class, int.class, boolean.class);
+ configConstr.setAccessible(true);
+ RadioManager.AmBandConfig badConfig = configConstr.newInstance(
+ 0 /*region*/, RadioManager.BAND_AM /*type*/,
+ 10000 /*lowerLimit*/, 1 /*upperLimit*/, 100 /*spacing*/, false /*stereo*/);
+ int ret = mRadioTuner.setConfiguration(badConfig);
+ assertEquals(RadioManager.STATUS_BAD_VALUE, ret);
+ verify(mCallback, never()).onConfigurationChanged(any());
+
+ // set null config
+ ret = mRadioTuner.setConfiguration(null);
+ assertEquals(RadioManager.STATUS_BAD_VALUE, ret);
+ verify(mCallback, never()).onConfigurationChanged(any());
+
+ // setting good config should recover
+ ret = mRadioTuner.setConfiguration(mAmBandConfig);
+ assertEquals(RadioManager.STATUS_OK, ret);
+ verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any());
+ }
+
+ @Test
+ public void testMute() {
+ openTuner();
+
+ boolean isMuted = mRadioTuner.getMute();
+ assertFalse(isMuted);
+
+ int ret = mRadioTuner.setMute(true);
+ assertEquals(RadioManager.STATUS_OK, ret);
+ isMuted = mRadioTuner.getMute();
+ assertTrue(isMuted);
+
+ ret = mRadioTuner.setMute(false);
+ assertEquals(RadioManager.STATUS_OK, ret);
+ isMuted = mRadioTuner.getMute();
+ assertFalse(isMuted);
+ }
+
+ @Test
+ public void testMuteNoAudio() {
+ openTuner(false);
+
+ int ret = mRadioTuner.setMute(false);
+ assertEquals(RadioManager.STATUS_ERROR, ret);
+
+ boolean isMuted = mRadioTuner.getMute();
+ assertTrue(isMuted);
+ }
+
+ @Test
+ public void testStep() {
+ openTuner();
+ checkAntenna();
+
+ int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, true);
+ assertEquals(RadioManager.STATUS_OK, ret);
+ verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any());
+
+ resetCallback();
+
+ ret = mRadioTuner.step(RadioTuner.DIRECTION_UP, false);
+ assertEquals(RadioManager.STATUS_OK, ret);
+ verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any());
+ }
+
+ @Test
+ public void testTuneAndGetPI() {
+ openTuner();
+ checkAntenna();
+
+ int channel = mFmBandConfig.getLowerLimit() + mFmBandConfig.getSpacing();
+
+ // test tune
+ int ret = mRadioTuner.tune(channel, 0);
+ assertEquals(RadioManager.STATUS_OK, ret);
+ ArgumentCaptor<RadioManager.ProgramInfo> infoc =
+ ArgumentCaptor.forClass(RadioManager.ProgramInfo.class);
+ verify(mCallback, timeout(kTuneCallbackTimeoutMs))
+ .onProgramInfoChanged(infoc.capture());
+ assertEquals(channel, infoc.getValue().getChannel());
+
+ // test getProgramInformation
+ RadioManager.ProgramInfo[] info = new RadioManager.ProgramInfo[1];
+ ret = mRadioTuner.getProgramInformation(info);
+ assertEquals(RadioManager.STATUS_OK, ret);
+ assertNotNull(info[0]);
+ assertEquals(channel, info[0].getChannel());
+ }
+
+ @Test
+ public void testDummyCancel() {
+ openTuner();
+
+ int ret = mRadioTuner.cancel();
+ assertEquals(RadioManager.STATUS_OK, ret);
+ }
+
+ @Test
+ public void testLateCancel() {
+ openTuner();
+ checkAntenna();
+
+ int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, false);
+ assertEquals(RadioManager.STATUS_OK, ret);
+ verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any());
+
+ int cancelRet = mRadioTuner.cancel();
+ assertEquals(RadioManager.STATUS_OK, cancelRet);
+ }
+
+ @Test
+ public void testScanAndCancel() {
+ openTuner();
+ checkAntenna();
+
+ /* There is a possible race condition between scan and cancel commands - the scan may finish
+ * before cancel command is issued. Thus we accept both outcomes in this test.
+ */
+ int scanRet = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, true);
+ int cancelRet = mRadioTuner.cancel();
+
+ assertEquals(RadioManager.STATUS_OK, scanRet);
+ assertEquals(RadioManager.STATUS_OK, cancelRet);
+
+ verify(mCallback, after(kCancelTimeoutMs).atMost(1)).onError(RadioTuner.ERROR_CANCELLED);
+ verify(mCallback, atMost(1)).onProgramInfoChanged(any());
+ }
+
+ @Test
+ public void testStartBackgroundScan() {
+ openTuner();
+ checkAntenna();
+
+ boolean ret = mRadioTuner.startBackgroundScan();
+ boolean isSupported = mModule.isBackgroundScanningSupported();
+ assertEquals(isSupported, ret);
+ }
+
+ @Test
+ public void testGetProgramList() {
+ openTuner();
+ checkAntenna();
+
+ try {
+ List<RadioManager.ProgramInfo> list = mRadioTuner.getProgramList(null);
+ assertNotNull(list);
+ } catch (IllegalStateException e) {
+ // the list may or may not be ready at this point
+ Log.i(TAG, "Background list is not ready");
+ }
+ }
+
+ @Test
+ public void testForcedAnalog() {
+ openTuner();
+
+ boolean isSupported = true;
+ boolean isForced;
+ try {
+ isForced = mRadioTuner.isAnalogForced();
+ assertFalse(isForced);
+ } catch (IllegalStateException ex) {
+ Log.i(TAG, "Forced analog switch is not supported by this tuner");
+ isSupported = false;
+ }
+
+ if (isSupported) {
+ mRadioTuner.setAnalogForced(true);
+ isForced = mRadioTuner.isAnalogForced();
+ assertTrue(isForced);
+
+ mRadioTuner.setAnalogForced(false);
+ isForced = mRadioTuner.isAnalogForced();
+ assertFalse(isForced);
+ } else {
+ assertThrows(IllegalStateException.class, () -> mRadioTuner.setAnalogForced(true));
+ }
+ }
+}
diff --git a/tests/testables/Android.mk b/tests/testables/Android.mk
index 759bc35..0e36981 100644
--- a/tests/testables/Android.mk
+++ b/tests/testables/Android.mk
@@ -31,3 +31,5 @@
LOCAL_JAVA_LIBRARIES := android.test.runner
include $(BUILD_STATIC_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/testables/src/android/testing/AndroidTestingRunner.java b/tests/testables/src/android/testing/AndroidTestingRunner.java
index a425f70..cf5d4cf 100644
--- a/tests/testables/src/android/testing/AndroidTestingRunner.java
+++ b/tests/testables/src/android/testing/AndroidTestingRunner.java
@@ -35,6 +35,8 @@
/**
* A runner with support for extra annotations provided by the Testables library.
+ * @see UiThreadTest
+ * @see TestableLooper.RunWithLooper
*/
public class AndroidTestingRunner extends BlockJUnit4ClassRunner {
diff --git a/tests/testables/src/android/testing/BaseFragmentTest.java b/tests/testables/src/android/testing/BaseFragmentTest.java
index 32ee091..5cedbdf 100644
--- a/tests/testables/src/android/testing/BaseFragmentTest.java
+++ b/tests/testables/src/android/testing/BaseFragmentTest.java
@@ -80,6 +80,10 @@
});
}
+ /**
+ * Allows tests to sub-class TestableContext if they want to provide any extended functionality
+ * or provide a {@link LeakCheck} to the TestableContext upon instantiation.
+ */
protected TestableContext getContext() {
return new TestableContext(InstrumentationRegistry.getContext());
}
diff --git a/tests/testables/src/android/testing/LeakCheck.java b/tests/testables/src/android/testing/LeakCheck.java
index 8daaa8f..949215b 100644
--- a/tests/testables/src/android/testing/LeakCheck.java
+++ b/tests/testables/src/android/testing/LeakCheck.java
@@ -14,6 +14,7 @@
package android.testing;
+import android.content.Context;
import android.util.ArrayMap;
import android.util.Log;
@@ -28,6 +29,35 @@
import java.util.List;
import java.util.Map;
+/**
+ * Utility for dealing with the facts of Lifecycle. Creates trackers to check that for every
+ * call to registerX, addX, bindX, a corresponding call to unregisterX, removeX, and unbindX
+ * is performed. This should be applied to a test as a {@link org.junit.rules.TestRule}
+ * and will only check for leaks on successful tests.
+ * <p>
+ * Example that will catch an allocation and fail:
+ * <pre class="prettyprint">
+ * public class LeakCheckTest {
+ * @Rule public LeakCheck mLeakChecker = new LeakCheck();
+ *
+ * @Test
+ * public void testLeak() {
+ * Context context = new ContextWrapper(...) {
+ * public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ * mLeakChecker.getTracker("receivers").addAllocation(new Throwable());
+ * }
+ * public void unregisterReceiver(BroadcastReceiver receiver) {
+ * mLeakChecker.getTracker("receivers").clearAllocations();
+ * }
+ * };
+ * context.registerReceiver(...);
+ * }
+ * }
+ * </pre>
+ *
+ * Note: {@link TestableContext} supports leak tracking when using
+ * {@link TestableContext#TestableContext(Context, LeakCheck)}.
+ */
public class LeakCheck extends TestWatcher {
private final Map<String, Tracker> mTrackers = new HashMap<>();
@@ -40,6 +70,13 @@
verify();
}
+ /**
+ * Acquire a {@link Tracker}. Gets a tracker for the specified tag, creating one if necessary.
+ * There should be one tracker for each pair of add/remove callbacks (e.g. one tracker for
+ * registerReceiver/unregisterReceiver).
+ *
+ * @param tag Unique tag to use for this set of allocation tracking.
+ */
public Tracker getTracker(String tag) {
Tracker t = mTrackers.get(tag);
if (t == null) {
@@ -49,10 +86,13 @@
return t;
}
- public void verify() {
+ private void verify() {
mTrackers.values().forEach(Tracker::verify);
}
+ /**
+ * Holds allocations associated with a specific callback (such as a BroadcastReceiver).
+ */
public static class LeakInfo {
private static final String TAG = "LeakInfo";
private List<Throwable> mThrowables = new ArrayList<>();
@@ -60,11 +100,20 @@
LeakInfo() {
}
+ /**
+ * Should be called once for each callback/listener added. addAllocation may be
+ * called several times, but it only takes one clearAllocations call to remove all
+ * of them.
+ */
public void addAllocation(Throwable t) {
// TODO: Drop off the first element in the stack trace here to have a cleaner stack.
mThrowables.add(t);
}
+ /**
+ * Should be called when the callback/listener has been removed. One call to
+ * clearAllocations will counteract any number of calls to addAllocation.
+ */
public void clearAllocations() {
mThrowables.clear();
}
@@ -82,9 +131,16 @@
}
}
+ /**
+ * Tracks allocations related to a specific tag or method(s).
+ * @see #getTracker(String)
+ */
public static class Tracker {
private Map<Object, LeakInfo> mObjects = new ArrayMap<>();
+ private Tracker() {
+ }
+
public LeakInfo getLeakInfo(Object object) {
LeakInfo leakInfo = mObjects.get(object);
if (leakInfo == null) {
diff --git a/tests/testables/src/android/testing/TestableContentResolver.java b/tests/testables/src/android/testing/TestableContentResolver.java
index bfafbe0..0850916 100644
--- a/tests/testables/src/android/testing/TestableContentResolver.java
+++ b/tests/testables/src/android/testing/TestableContentResolver.java
@@ -27,7 +27,11 @@
import java.util.Map;
/**
- * Alternative to a MockContentResolver that falls back to real providers.
+ * A version of ContentResolver that allows easy mocking of providers.
+ * By default it acts as a normal ContentResolver and returns all the
+ * same providers.
+ * @see #addProvider(String, ContentProvider)
+ * @see #setFallbackToExisting(boolean)
*/
public class TestableContentResolver extends ContentResolver {
diff --git a/tests/testables/src/android/testing/TestableContext.java b/tests/testables/src/android/testing/TestableContext.java
index 630a287..498d517 100644
--- a/tests/testables/src/android/testing/TestableContext.java
+++ b/tests/testables/src/android/testing/TestableContext.java
@@ -43,6 +43,7 @@
* <ul>
* <li>System services can be mocked out with {@link #addMockSystemService}</li>
* <li>Service binding can be mocked out with {@link #addMockService}</li>
+ * <li>Resources can be mocked out using {@link #getOrCreateTestableResources()}</li>
* <li>Settings support {@link TestableSettingsProvider}</li>
* <li>Has support for {@link LeakCheck} for services and receivers</li>
* </ul>
@@ -50,10 +51,8 @@
* <p>TestableContext should be defined as a rule on your test so it can clean up after itself.
* Like the following:</p>
* <pre class="prettyprint">
- * {@literal
- * @Rule
+ * @Rule
* private final TestableContext mContext = new TestableContext(InstrumentationRegister.getContext());
- * }
* </pre>
*/
public class TestableContext extends ContextWrapper implements TestRule {
@@ -69,6 +68,7 @@
private LeakCheck.Tracker mReceiver;
private LeakCheck.Tracker mService;
private LeakCheck.Tracker mComponent;
+ private TestableResources mTestableResources;
public TestableContext(Context base) {
this(base, null);
@@ -98,25 +98,59 @@
return super.getPackageManager();
}
- @Override
- public Resources getResources() {
- return super.getResources();
+ /**
+ * Makes sure the resources being returned by this TestableContext are a version of
+ * TestableResources.
+ * @see #getResources()
+ */
+ public void ensureTestableResources() {
+ if (mTestableResources == null) {
+ mTestableResources = new TestableResources(super.getResources());
+ }
}
+ /**
+ * Get (and create if necessary) {@link TestableResources} for this TestableContext.
+ */
+ public TestableResources getOrCreateTestableResources() {
+ ensureTestableResources();
+ return mTestableResources;
+ }
+
+ /**
+ * Returns a Resources instance for the test.
+ *
+ * By default this returns the same resources object that would come from the
+ * {@link ContextWrapper}, but if {@link #ensureTestableResources()} or
+ * {@link #getOrCreateTestableResources()} has been called, it will return resources gotten from
+ * {@link TestableResources}.
+ */
+ @Override
+ public Resources getResources() {
+ return mTestableResources != null ? mTestableResources.getResources()
+ : super.getResources();
+ }
+
+ /**
+ * @see #getSystemService(String)
+ */
public <T> void addMockSystemService(Class<T> service, T mock) {
addMockSystemService(getSystemServiceName(service), mock);
}
+ /**
+ * @see #getSystemService(String)
+ */
public void addMockSystemService(String name, Object service) {
if (mMockSystemServices == null) mMockSystemServices = new ArrayMap<>();
mMockSystemServices.put(name, service);
}
- public void addMockService(ComponentName component, IBinder service) {
- if (mMockServices == null) mMockServices = new ArrayMap<>();
- mMockServices.put(component, service);
- }
-
+ /**
+ * If a matching mock service has been added through {@link #addMockSystemService} then
+ * that will be returned, otherwise the real service will be acquired from the base
+ * context.
+ */
@Override
public Object getSystemService(String name) {
if (mMockSystemServices != null && mMockSystemServices.containsKey(name)) {
@@ -137,6 +171,10 @@
return mTestableContentResolver;
}
+ /**
+ * Will always return itself for a TestableContext to ensure the testable effects extend
+ * to the application context.
+ */
@Override
public Context getApplicationContext() {
// Return this so its always a TestableContext.
@@ -170,6 +208,24 @@
super.unregisterReceiver(receiver);
}
+ /**
+ * Adds a mock service to be connected to by a bindService call.
+ * <p>
+ * Normally a TestableContext will pass through all bind requests to the base context
+ * but when addMockService has been called for a ComponentName being bound, then
+ * TestableContext will immediately trigger a {@link ServiceConnection#onServiceConnected}
+ * with the specified service, and will call {@link ServiceConnection#onServiceDisconnected}
+ * when the service is unbound.
+ * </p>
+ */
+ public void addMockService(ComponentName component, IBinder service) {
+ if (mMockServices == null) mMockServices = new ArrayMap<>();
+ mMockServices.put(component, service);
+ }
+
+ /**
+ * @see #addMockService(ComponentName, IBinder)
+ */
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
@@ -177,6 +233,9 @@
return super.bindService(service, conn, flags);
}
+ /**
+ * @see #addMockService(ComponentName, IBinder)
+ */
@Override
public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
Handler handler, UserHandle user) {
@@ -185,6 +244,9 @@
return super.bindServiceAsUser(service, conn, flags, handler, user);
}
+ /**
+ * @see #addMockService(ComponentName, IBinder)
+ */
@Override
public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
UserHandle user) {
@@ -203,6 +265,9 @@
return false;
}
+ /**
+ * @see #addMockService(ComponentName, IBinder)
+ */
@Override
public void unbindService(ServiceConnection conn) {
if (mService != null) mService.getLeakInfo(conn).clearAllocations();
@@ -214,6 +279,13 @@
super.unbindService(conn);
}
+ /**
+ * Check if the TestableContext has a mock binding for a specified component. Will return
+ * true between {@link ServiceConnection#onServiceConnected} and
+ * {@link ServiceConnection#onServiceDisconnected} callbacks for a mock service.
+ *
+ * @see #addMockService(ComponentName, IBinder)
+ */
public boolean isBound(ComponentName component) {
return mActiveServices != null && mActiveServices.containsValue(component);
}
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 9eddc51..f6c3cb3 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -33,16 +33,15 @@
import java.util.Map;
/**
- * Creates a looper on the current thread with control over if/when messages are
- * executed. Warning: This class works through some reflection and may break/need
- * to be updated from time to time.
+ * This is a wrapper around {@link TestLooperManager} to make it easier to manage
+ * and provide an easy annotation for use with tests.
+ *
+ * @see TestableLooperTest TestableLooperTest for examples.
*/
public class TestableLooper {
private Looper mLooper;
private MessageQueue mQueue;
- private boolean mMain;
- private Object mOriginalMain;
private MessageHandler mMessageHandler;
private Handler mHandler;
@@ -72,35 +71,20 @@
mHandler = new Handler(mLooper);
}
- public void setAsMainLooper() throws NoSuchFieldException, IllegalAccessException {
- mMain = true;
- setAsMainInt();
- }
-
- private void setAsMainInt() throws NoSuchFieldException, IllegalAccessException {
- Field field = mLooper.getClass().getDeclaredField("sMainLooper");
- field.setAccessible(true);
- if (mOriginalMain == null) {
- mOriginalMain = field.get(null);
- }
- field.set(null, mLooper);
- }
-
/**
- * Must be called if setAsMainLooper is called to restore the main looper when the
- * test is complete, otherwise the main looper will not be available for any subsequent
- * tests.
+ * Must be called to release the looper when the test is complete, otherwise
+ * the looper will not be available for any subsequent tests. This is
+ * automatically handled for tests using {@link RunWithLooper}.
*/
public void destroy() throws NoSuchFieldException, IllegalAccessException {
mQueueWrapper.release();
- if (mMain && mOriginalMain != null) {
- Field field = mLooper.getClass().getDeclaredField("sMainLooper");
- field.setAccessible(true);
- field.set(null, mOriginalMain);
- mOriginalMain = null;
- }
}
+ /**
+ * Sets a callback for all messages processed on this TestableLooper.
+ *
+ * @see {@link MessageHandler}
+ */
public void setMessageHandler(MessageHandler handler) {
mMessageHandler = handler;
}
@@ -119,6 +103,9 @@
return num;
}
+ /**
+ * Process messages in the queue until no more are found.
+ */
public void processAllMessages() {
while (processQueuedMessages() != 0) ;
}
@@ -183,6 +170,11 @@
void run() throws Exception;
}
+ /**
+ * Annotation that tells the {@link AndroidTestingRunner} to create a TestableLooper and
+ * run this test/class on that thread. The {@link TestableLooper} can be acquired using
+ * {@link #get(Object)}.
+ */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RunWithLooper {
@@ -206,11 +198,15 @@
private static final Map<Object, TestableLooper> sLoopers = new ArrayMap<>();
+ /**
+ * For use with {@link RunWithLooper}, used to get the TestableLooper that was
+ * automatically created for this test.
+ */
public static TestableLooper get(Object test) {
return sLoopers.get(test);
}
- public static class LooperFrameworkMethod extends FrameworkMethod {
+ static class LooperFrameworkMethod extends FrameworkMethod {
private HandlerThread mHandlerThread;
private final TestableLooper mTestableLooper;
@@ -319,6 +315,11 @@
}
}
+ /**
+ * Callback to control the execution of messages on the looper, when set with
+ * {@link #setMessageHandler(MessageHandler)} then {@link #onMessageHandled(Message)}
+ * will get called back for every message processed on the {@link TestableLooper}.
+ */
public interface MessageHandler {
/**
* Return true to have the message executed and delivered to target.
diff --git a/tests/testables/src/android/testing/TestableResources.java b/tests/testables/src/android/testing/TestableResources.java
new file mode 100644
index 0000000..a2fa95d
--- /dev/null
+++ b/tests/testables/src/android/testing/TestableResources.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package android.testing;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.withSettings;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+import android.util.SparseArray;
+
+import org.mockito.invocation.InvocationOnMock;
+
+/**
+ * Provides a version of Resources that defaults to all existing resources, but can have ids
+ * changed to return specific values.
+ * <p>
+ * TestableResources are lazily initialized, be sure to call
+ * {@link TestableContext#ensureTestableResources} before your tested code has an opportunity
+ * to cache {@link Context#getResources}.
+ * </p>
+ */
+public class TestableResources {
+
+ private static final String TAG = "TestableResources";
+ private final Resources mResources;
+ private final SparseArray<Object> mOverrides = new SparseArray<>();
+
+ TestableResources(Resources realResources) {
+ mResources = mock(Resources.class, withSettings()
+ .spiedInstance(realResources)
+ .defaultAnswer(this::answer));
+ }
+
+ /**
+ * Gets the implementation of Resources that will return overridden values when called.
+ */
+ public Resources getResources() {
+ return mResources;
+ }
+
+ /**
+ * Sets the return value for the specified resource id.
+ * <p>
+ * Since resource ids are unique there is a single addOverride that will override the value
+ * whenever it is gotten regardless of which method is used (i.e. getColor or getDrawable).
+ * </p>
+ * @param id The resource id to be overridden
+ * @param value The value of the resource, null to cause a {@link Resources.NotFoundException}
+ * when gotten.
+ */
+ public void addOverride(int id, Object value) {
+ mOverrides.put(id, value);
+ }
+
+ /**
+ * Removes the override for the specified id.
+ * <p>
+ * This should be called over addOverride(id, null), because specifying a null value will
+ * cause a {@link Resources.NotFoundException} whereas removing the override will actually
+ * switch back to returning the default/real value of the resource.
+ * </p>
+ * @param id
+ */
+ public void removeOverride(int id) {
+ mOverrides.remove(id);
+ }
+
+ private Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ try {
+ int id = invocationOnMock.getArgument(0);
+ int index = mOverrides.indexOfKey(id);
+ if (index >= 0) {
+ Object value = mOverrides.valueAt(index);
+ if (value == null) throw new Resources.NotFoundException();
+ return value;
+ }
+ } catch (Resources.NotFoundException e) {
+ // Let through NotFoundException.
+ throw e;
+ } catch (Throwable t) {
+ // Generic catching for the many things that can go wrong, fall back to
+ // the real implementation.
+ Log.i(TAG, "Falling back to default resources call " + t);
+ }
+ return invocationOnMock.callRealMethod();
+ }
+}
diff --git a/tests/testables/src/android/testing/UiThreadTest.java b/tests/testables/src/android/testing/UiThreadTest.java
index e40e1d7..32a5824 100644
--- a/tests/testables/src/android/testing/UiThreadTest.java
+++ b/tests/testables/src/android/testing/UiThreadTest.java
@@ -20,8 +20,8 @@
import java.lang.annotation.Target;
/**
- * When applied to a class, all tests, befores, and afters will behave as if
- * they have @UiThreadTest applied to them.
+ * When applied to a class, all {@link org.junit.Test}s, {@link org.junit.After}s, and
+ * {@link org.junit.Before} will behave as if they have @UiThreadTest applied to them.
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
diff --git a/tests/testables/src/android/testing/ViewUtils.java b/tests/testables/src/android/testing/ViewUtils.java
index fca44ae..1c6016e 100644
--- a/tests/testables/src/android/testing/ViewUtils.java
+++ b/tests/testables/src/android/testing/ViewUtils.java
@@ -21,8 +21,16 @@
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
+/**
+ * Utilities to make testing views easier.
+ */
public class ViewUtils {
+ /**
+ * Causes the view (and its children) to have {@link View#onAttachedToWindow()} called.
+ *
+ * This is currently done by adding the view to a window.
+ */
public static void attachView(View view) {
// Make sure hardware acceleration isn't turned on.
view.getContext().getApplicationInfo().flags &=
@@ -34,6 +42,11 @@
view.getContext().getSystemService(WindowManager.class).addView(view, lp);
}
+ /**
+ * Causes the view (and its children) to have {@link View#onDetachedFromWindow()} called.
+ *
+ * This is currently done by removing the view from a window.
+ */
public static void detachView(View view) {
view.getContext().getSystemService(WindowManager.class).removeViewImmediate(view);
}
diff --git a/tests/testables/tests/Android.mk b/tests/testables/tests/Android.mk
index a123d80..16fe535 100644
--- a/tests/testables/tests/Android.mk
+++ b/tests/testables/tests/Android.mk
@@ -18,7 +18,7 @@
LOCAL_USE_AAPT2 := true
LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := TestablesTest
+LOCAL_PACKAGE_NAME := TestablesTests
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
$(call all-Iaidl-files-under, src)
diff --git a/tests/testables/tests/res/values/strings.xml b/tests/testables/tests/res/values/strings.xml
new file mode 100644
index 0000000..1ad0ade
--- /dev/null
+++ b/tests/testables/tests/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2009, 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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="test_string">This is a test string</string>
+</resources>
diff --git a/tests/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java
index 12f1d0a..13e72ba 100644
--- a/tests/testables/tests/src/android/testing/TestableLooperTest.java
+++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java
@@ -31,9 +31,11 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableLooper.MessageHandler;
import android.testing.TestableLooper.RunWithLooper;
+@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class TestableLooperTest {
diff --git a/tests/testables/tests/src/android/testing/TestableResourcesTest.java b/tests/testables/tests/src/android/testing/TestableResourcesTest.java
new file mode 100644
index 0000000..dc7cf95
--- /dev/null
+++ b/tests/testables/tests/src/android/testing/TestableResourcesTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package android.testing;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.content.res.Resources;
+import android.support.test.InstrumentationRegistry;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.testables.R;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class TestableResourcesTest {
+
+ @Rule
+ public TestableContext mContext = new TestableContext(InstrumentationRegistry.getContext());
+
+ @Test
+ public void testLazyInit() {
+ Resources before = mContext.getResources();
+ mContext.ensureTestableResources();
+ Resources after = mContext.getResources();
+ assertNotEquals(before, after);
+ }
+
+ @Test
+ public void testAddingResource() {
+ final int nonExistentId = 3; // Ids don't go this low.
+
+ try {
+ mContext.getColor(nonExistentId);
+ fail("Should throw NotFoundException");
+ } catch (Resources.NotFoundException e) {
+ }
+ mContext.getOrCreateTestableResources().addOverride(nonExistentId, 0xffffff);
+
+ assertEquals(0xffffff, mContext.getColor(nonExistentId));
+ }
+
+ @Test
+ public void testClearingResource() {
+ final int nonExistentId = 3; // Ids don't go this low.
+
+ mContext.getOrCreateTestableResources().addOverride(nonExistentId, 0xffffff);
+ assertEquals(0xffffff, mContext.getColor(nonExistentId));
+ mContext.getOrCreateTestableResources().removeOverride(nonExistentId);
+ try {
+ mContext.getColor(nonExistentId);
+ fail("Should throw NotFoundException");
+ } catch (Resources.NotFoundException e) {
+ }
+ }
+
+ @Test
+ public void testOverrideExisting() {
+ int existentId = R.string.test_string;
+
+ assertNotNull(mContext.getString(existentId));
+ mContext.getOrCreateTestableResources().addOverride(existentId, "Other strings");
+
+ assertEquals("Other strings", mContext.getString(existentId));
+ }
+
+ @Test(expected = Resources.NotFoundException.class)
+ public void testNonExistentException() {
+ int existentId = R.string.test_string;
+
+ assertNotNull(mContext.getString(existentId));
+ mContext.getOrCreateTestableResources().addOverride(existentId, null);
+
+ assertNull(mContext.getString(existentId));
+ }
+}
diff --git a/tests/testables/tests/src/android/testing/TestableSettingsProviderTest.java b/tests/testables/tests/src/android/testing/TestableSettingsProviderTest.java
index 0e2cc57..ac61deb 100644
--- a/tests/testables/tests/src/android/testing/TestableSettingsProviderTest.java
+++ b/tests/testables/tests/src/android/testing/TestableSettingsProviderTest.java
@@ -20,6 +20,7 @@
import android.provider.Settings.Secure;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -27,6 +28,7 @@
import static org.junit.Assert.*;
+@SmallTest
@RunWith(AndroidJUnit4.class)
public class TestableSettingsProviderTest {
diff --git a/tests/utils/testutils/java/com/android/internal/util/test/FakeSettingsProvider.java b/tests/utils/testutils/java/com/android/internal/util/test/FakeSettingsProvider.java
index 8ca849b..e482708 100644
--- a/tests/utils/testutils/java/com/android/internal/util/test/FakeSettingsProvider.java
+++ b/tests/utils/testutils/java/com/android/internal/util/test/FakeSettingsProvider.java
@@ -54,7 +54,8 @@
* Note that this class cannot be used in the same process as real settings. This is because it
* works by passing an alternate ContentResolver to Settings operations. Unfortunately, the Settings
* class only fetches the content provider from the passed-in ContentResolver the first time it's
- * used, and after that stores it in a per-process static.
+ * used, and after that stores it in a per-process static. If this needs to be used in this case,
+ * then call {@link #clearSettingsProvider()} before and after using this.
*
* TODO: evaluate implementing settings change notifications. This would require:
*
@@ -90,6 +91,15 @@
}
}
+ /**
+ * This needs to be called before and after using the FakeSettingsProvider class.
+ */
+ public static void clearSettingsProvider() {
+ Settings.Secure.clearProviderForTest();
+ Settings.Global.clearProviderForTest();
+ Settings.System.clearProviderForTest();
+ }
+
public Bundle call(String method, String arg, Bundle extras) {
// Methods are "GET_system", "GET_global", "PUT_secure", etc.
String[] commands = method.split("_", 2);
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index eff8283..1dc47f8 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -84,6 +84,7 @@
"compile/PseudolocaleGenerator.cpp",
"compile/Pseudolocalizer.cpp",
"compile/XmlIdCollector.cpp",
+ "configuration/ConfigurationParser.cpp",
"filter/ConfigFilter.cpp",
"flatten/Archive.cpp",
"flatten/TableFlattener.cpp",
@@ -159,8 +160,14 @@
// ==========================================================
cc_test_host {
name: "aapt2_tests",
- srcs: ["test/Common.cpp", "**/*_test.cpp"],
- static_libs: ["libaapt2", "libgmock"],
+ srcs: [
+ "test/Common.cpp",
+ "**/*_test.cpp",
+ ],
+ static_libs: [
+ "libaapt2",
+ "libgmock",
+ ],
defaults: ["aapt_defaults"],
}
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 9503b56..9a37913 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -941,7 +941,7 @@
}
struct SymbolComparator {
- bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
+ bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) const {
return a.symbol.name.value() < b.symbol.name.value();
}
};
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 5352ca8..c6382b1 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -843,4 +843,9 @@
EXPECT_THAT(*str->value, Eq(""));
}
+TEST_F(ResourceParserTest, ParsePlatformIndependentNewline) {
+ std::string input = R"(<string name="foo">%1$s %n %2$s</string>)";
+ ASSERT_TRUE(TestParse(input));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index e09a3979..b64cd8c 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -145,7 +145,7 @@
const std::string& root_dir = options.res_dir.value();
std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
if (!d) {
- context->GetDiagnostics()->Error(DiagMessage()
+ context->GetDiagnostics()->Error(DiagMessage(root_dir) << "failed to open directory: "
<< android::base::SystemErrorCodeToString(errno));
return false;
}
@@ -164,7 +164,7 @@
std::unique_ptr<DIR, decltype(closedir)*> subdir(opendir(prefix_path.data()), closedir);
if (!subdir) {
- context->GetDiagnostics()->Error(DiagMessage()
+ context->GetDiagnostics()->Error(DiagMessage(prefix_path) << "failed to open directory: "
<< android::base::SystemErrorCodeToString(errno));
return false;
}
@@ -180,7 +180,7 @@
std::string err_str;
Maybe<ResourcePathData> path_data = ExtractResourcePathData(full_path, &err_str);
if (!path_data) {
- context->GetDiagnostics()->Error(DiagMessage() << err_str);
+ context->GetDiagnostics()->Error(DiagMessage(full_path) << err_str);
return false;
}
@@ -198,6 +198,7 @@
std::ifstream fin(path_data.source.path, std::ifstream::binary);
if (!fin) {
context->GetDiagnostics()->Error(DiagMessage(path_data.source)
+ << "failed to open file: "
<< android::base::SystemErrorCodeToString(errno));
return false;
}
@@ -395,6 +396,7 @@
std::ifstream fin(path_data.source.path, std::ifstream::binary);
if (!fin) {
context->GetDiagnostics()->Error(DiagMessage(path_data.source)
+ << "failed to open file: "
<< android::base::SystemErrorCodeToString(errno));
return false;
}
@@ -481,6 +483,7 @@
if (!android::base::ReadFileToString(path_data.source.path, &content,
true /*follow_symlinks*/)) {
context->GetDiagnostics()->Error(DiagMessage(path_data.source)
+ << "failed to open file: "
<< android::base::SystemErrorCodeToString(errno));
return false;
}
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 57c4574..aa94723 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -31,21 +31,22 @@
namespace aapt {
-void DumpCompiledFile(const pb::CompiledFile& pb_file, const void* data, size_t len,
+bool DumpCompiledFile(const pb::CompiledFile& pb_file, const void* data, size_t len,
const Source& source, IAaptContext* context) {
std::unique_ptr<ResourceFile> file =
DeserializeCompiledFileFromPb(pb_file, source, context->GetDiagnostics());
if (!file) {
context->GetDiagnostics()->Warn(DiagMessage() << "failed to read compiled file");
- return;
+ return false;
}
std::cout << "Resource: " << file->name << "\n"
<< "Config: " << file->config << "\n"
<< "Source: " << file->source << "\n";
+ return true;
}
-void TryDumpFile(IAaptContext* context, const std::string& file_path) {
+bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
std::unique_ptr<ResourceTable> table;
std::string err;
@@ -57,18 +58,18 @@
if (!data) {
context->GetDiagnostics()->Error(DiagMessage(file_path)
<< "failed to open resources.arsc.flat");
- return;
+ return false;
}
pb::ResourceTable pb_table;
if (!pb_table.ParseFromArray(data->data(), data->size())) {
context->GetDiagnostics()->Error(DiagMessage(file_path) << "invalid resources.arsc.flat");
- return;
+ return false;
}
table = DeserializeTableFromPb(pb_table, Source(file_path), context->GetDiagnostics());
if (!table) {
- return;
+ return false;
}
}
@@ -79,14 +80,14 @@
if (!data) {
context->GetDiagnostics()->Error(DiagMessage(file_path)
<< "failed to open resources.arsc");
- return;
+ return false;
}
table = util::make_unique<ResourceTable>();
BinaryResourceParser parser(context, table.get(), Source(file_path), data->data(),
data->size());
if (!parser.Parse()) {
- return;
+ return false;
}
}
}
@@ -96,7 +97,7 @@
Maybe<android::FileMap> file = file::MmapPath(file_path, &err);
if (!file) {
context->GetDiagnostics()->Error(DiagMessage(file_path) << err);
- return;
+ return false;
}
android::FileMap* file_map = &file.value();
@@ -113,24 +114,26 @@
uint32_t num_files = 0;
if (!input.ReadLittleEndian32(&num_files)) {
- return;
+ return false;
}
for (uint32_t i = 0; i < num_files; i++) {
pb::CompiledFile compiled_file;
if (!input.ReadCompiledFile(&compiled_file)) {
context->GetDiagnostics()->Warn(DiagMessage() << "failed to read compiled file");
- return;
+ return false;
}
uint64_t offset, len;
if (!input.ReadDataMetaData(&offset, &len)) {
context->GetDiagnostics()->Warn(DiagMessage() << "failed to read meta data");
- return;
+ return false;
}
const void* data = static_cast<const uint8_t*>(file_map->getDataPtr()) + offset;
- DumpCompiledFile(compiled_file, data, len, Source(file_path), context);
+ if (!DumpCompiledFile(compiled_file, data, len, Source(file_path), context)) {
+ return false;
+ }
}
}
}
@@ -140,6 +143,8 @@
options.show_sources = true;
Debug::PrintTable(table.get(), options);
}
+
+ return true;
}
class DumpContext : public IAaptContext {
@@ -203,8 +208,11 @@
context.SetVerbose(verbose);
for (const std::string& arg : flags.GetArgs()) {
- TryDumpFile(&context, arg);
+ if (!TryDumpFile(&context, arg)) {
+ return 1;
+ }
}
+
return 0;
}
diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp
index 7073c13..5af91fd 100644
--- a/tools/aapt2/compile/PngChunkFilter.cpp
+++ b/tools/aapt2/compile/PngChunkFilter.cpp
@@ -75,7 +75,7 @@
window_start_ = 0;
window_end_ = kPngSignatureSize;
} else {
- error_msg_ = "PNG does not start with PNG signature";
+ error_msg_ = "file does not start with PNG signature";
}
}
diff --git a/tools/aapt2/compile/PngCrunch.cpp b/tools/aapt2/compile/PngCrunch.cpp
index 42443d8..0346a19 100644
--- a/tools/aapt2/compile/PngCrunch.cpp
+++ b/tools/aapt2/compile/PngCrunch.cpp
@@ -142,19 +142,24 @@
}
std::unique_ptr<Image> ReadPng(IAaptContext* context, const Source& source, io::InputStream* in) {
+ // Create a diagnostics that has the source information encoded.
+ SourcePathDiagnostics source_diag(source, context->GetDiagnostics());
+
// Read the first 8 bytes of the file looking for the PNG signature.
// Bail early if it does not match.
const png_byte* signature;
size_t buffer_size;
if (!in->Next((const void**)&signature, &buffer_size)) {
- context->GetDiagnostics()->Error(DiagMessage()
- << android::base::SystemErrorCodeToString(errno));
+ if (in->HadError()) {
+ source_diag.Error(DiagMessage() << "failed to read PNG signature: " << in->GetError());
+ } else {
+ source_diag.Error(DiagMessage() << "not enough data for PNG signature");
+ }
return {};
}
if (buffer_size < kPngSignatureSize || png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
- context->GetDiagnostics()->Error(DiagMessage()
- << "file signature does not match PNG signature");
+ source_diag.Error(DiagMessage() << "file signature does not match PNG signature");
return {};
}
@@ -166,21 +171,18 @@
// version of libpng.
png_structp read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (read_ptr == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng read png_struct");
+ source_diag.Error(DiagMessage() << "failed to create libpng read png_struct");
return {};
}
// Create and initialize the memory for image header and data.
png_infop info_ptr = png_create_info_struct(read_ptr);
if (info_ptr == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng read png_info");
+ source_diag.Error(DiagMessage() << "failed to create libpng read png_info");
png_destroy_read_struct(&read_ptr, nullptr, nullptr);
return {};
}
- // Create a diagnostics that has the source information encoded.
- SourcePathDiagnostics source_diag(source, context->GetDiagnostics());
-
// Automatically release PNG resources at end of scope.
PngReadStructDeleter png_read_deleter(read_ptr, info_ptr);
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
new file mode 100644
index 0000000..89618d3
--- /dev/null
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "configuration/ConfigurationParser.h"
+
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <utility>
+
+#include <android-base/logging.h>
+
+#include "ConfigDescription.h"
+#include "Diagnostics.h"
+#include "util/Util.h"
+#include "xml/XmlActionExecutor.h"
+#include "xml/XmlDom.h"
+#include "xml/XmlUtil.h"
+
+namespace aapt {
+
+namespace {
+
+using ::aapt::configuration::Abi;
+using ::aapt::configuration::AndroidManifest;
+using ::aapt::configuration::AndroidSdk;
+using ::aapt::configuration::Artifact;
+using ::aapt::configuration::Configuration;
+using ::aapt::configuration::GlTexture;
+using ::aapt::configuration::Group;
+using ::aapt::configuration::Locale;
+using ::aapt::util::TrimWhitespace;
+using ::aapt::xml::Element;
+using ::aapt::xml::FindRootElement;
+using ::aapt::xml::NodeCast;
+using ::aapt::xml::XmlActionExecutor;
+using ::aapt::xml::XmlActionExecutorPolicy;
+using ::aapt::xml::XmlNodeAction;
+
+const std::unordered_map<std::string, Abi> kAbiMap = {
+ {"armeabi", Abi::kArmeV6},
+ {"armeabi-v7a", Abi::kArmV7a},
+ {"arm64-v8a", Abi::kArm64V8a},
+ {"x86", Abi::kX86},
+ {"x86_64", Abi::kX86_64},
+ {"mips", Abi::kMips},
+ {"mips64", Abi::kMips64},
+ {"universal", Abi::kUniversal},
+};
+
+constexpr const char* kAaptXmlNs = "http://schemas.android.com/tools/aapt";
+
+/** A default noop diagnostics context. */
+class NoopDiagnostics : public IDiagnostics {
+ public:
+ void Log(Level level, DiagMessageActual& actualMsg) override {}
+};
+NoopDiagnostics noop_;
+
+std::string GetLabel(const Element* element, IDiagnostics* diag) {
+ std::string label;
+ for (const auto& attr : element->attributes) {
+ if (attr.name == "label") {
+ label = attr.value;
+ break;
+ }
+ }
+
+ if (label.empty()) {
+ diag->Error(DiagMessage() << "No label found for element " << element->name);
+ }
+ return label;
+}
+
+/** XML node visitor that removes all of the namespace URIs from the node and all children. */
+class NamespaceVisitor : public xml::Visitor {
+ public:
+ void Visit(xml::Element* node) override {
+ node->namespace_uri.clear();
+ VisitChildren(node);
+ }
+};
+
+} // namespace
+
+ConfigurationParser::ConfigurationParser(std::string contents)
+ : contents_(std::move(contents)),
+ diag_(&noop_) {
+}
+
+Maybe<Configuration> ConfigurationParser::Parse() {
+ std::istringstream in(contents_);
+
+ auto doc = xml::Inflate(&in, diag_, Source("config.xml"));
+ if (!doc) {
+ return {};
+ }
+
+ // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
+ auto* root = FindRootElement(doc.get());
+ if (root == nullptr) {
+ diag_->Error(DiagMessage() << "Could not find the root element in the XML document");
+ return {};
+ }
+
+ std::string& xml_ns = root->namespace_uri;
+ if (!xml_ns.empty()) {
+ if (xml_ns != kAaptXmlNs) {
+ diag_->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns);
+ return {};
+ }
+
+ xml_ns.clear();
+ NamespaceVisitor visitor;
+ root->Accept(&visitor);
+ }
+
+ XmlActionExecutor executor;
+ XmlNodeAction& root_action = executor["post-process"];
+ XmlNodeAction& artifacts_action = root_action["artifacts"];
+ XmlNodeAction& groups_action = root_action["groups"];
+
+ Configuration config;
+
+ // Helper to bind a static method to an action handler in the DOM executor.
+ auto bind_handler = [&config](std::function<bool(Configuration*, Element*, IDiagnostics*)> h)
+ -> XmlNodeAction::ActionFuncWithDiag {
+ return std::bind(h, &config, std::placeholders::_1, std::placeholders::_2);
+ };
+
+ // Parse the artifact elements.
+ artifacts_action["artifact"].Action(bind_handler(artifact_handler_));
+ artifacts_action["artifact-format"].Action(bind_handler(artifact_format_handler_));
+
+ // Parse the different configuration groups.
+ groups_action["abi-group"].Action(bind_handler(abi_group_handler_));
+ groups_action["screen-density-group"].Action(bind_handler(screen_density_group_handler_));
+ groups_action["locale-group"].Action(bind_handler(locale_group_handler_));
+ groups_action["android-sdk-group"].Action(bind_handler(android_sdk_group_handler_));
+ groups_action["gl-texture-group"].Action(bind_handler(gl_texture_group_handler_));
+ groups_action["device-feature-group"].Action(bind_handler(device_feature_group_handler_));
+
+ if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag_, doc.get())) {
+ diag_->Error(DiagMessage() << "Could not process XML document");
+ return {};
+ }
+
+ return {config};
+}
+
+ConfigurationParser::ActionHandler ConfigurationParser::artifact_handler_ =
+ [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
+ Artifact artifact{};
+ for (const auto& attr : root_element->attributes) {
+ if (attr.name == "name") {
+ artifact.name = attr.value;
+ } else if (attr.name == "abi-group") {
+ artifact.abi_group = {attr.value};
+ } else if (attr.name == "screen-density-group") {
+ artifact.screen_density_group = {attr.value};
+ } else if (attr.name == "locale-group") {
+ artifact.locale_group = {attr.value};
+ } else if (attr.name == "android-sdk-group") {
+ artifact.android_sdk_group = {attr.value};
+ } else if (attr.name == "gl-texture-group") {
+ artifact.gl_texture_group = {attr.value};
+ } else if (attr.name == "device-feature-group") {
+ artifact.device_feature_group = {attr.value};
+ } else {
+ diag->Note(
+ DiagMessage() << "Unknown artifact attribute: " << attr.name << " = " << attr.value);
+ }
+ }
+ config->artifacts[artifact.name] = artifact;
+ return true;
+ };
+
+ConfigurationParser::ActionHandler ConfigurationParser::artifact_format_handler_ =
+ [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
+ for (auto& node : root_element->children) {
+ xml::Text* t;
+ if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
+ config->artifact_format = TrimWhitespace(t->text).to_string();
+ break;
+ }
+ }
+ return true;
+ };
+
+ConfigurationParser::ActionHandler ConfigurationParser::abi_group_handler_ =
+ [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
+ std::string label = GetLabel(root_element, diag);
+ if (label.empty()) {
+ return false;
+ }
+
+ auto& group = config->abi_groups[label];
+ bool valid = true;
+
+ for (auto* child : root_element->GetChildElements()) {
+ if (child->name != "abi") {
+ diag->Error(
+ DiagMessage() << "Unexpected element in ABI group: " << child->name);
+ valid = false;
+ } else {
+ for (auto& node : child->children) {
+ xml::Text* t;
+ if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
+ group.push_back(kAbiMap.at(TrimWhitespace(t->text).to_string()));
+ break;
+ }
+ }
+ }
+ }
+
+ return valid;
+ };
+
+ConfigurationParser::ActionHandler ConfigurationParser::screen_density_group_handler_ =
+ [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
+ std::string label = GetLabel(root_element, diag);
+ if (label.empty()) {
+ return false;
+ }
+
+ auto& group = config->screen_density_groups[label];
+ bool valid = true;
+
+ for (auto* child : root_element->GetChildElements()) {
+ if (child->name != "screen-density") {
+ diag->Error(
+ DiagMessage() << "Unexpected root_element in screen density group: "
+ << child->name);
+ valid = false;
+ } else {
+ for (auto& node : child->children) {
+ xml::Text* t;
+ if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
+ ConfigDescription config_descriptor;
+ const android::StringPiece& text = TrimWhitespace(t->text);
+ if (ConfigDescription::Parse(text, &config_descriptor)) {
+ // Copy the density with the minimum SDK version stripped out.
+ group.push_back(config_descriptor.CopyWithoutSdkVersion());
+ } else {
+ diag->Error(
+ DiagMessage() << "Could not parse config descriptor for screen-density: "
+ << text);
+ valid = false;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ return valid;
+ };
+
+ConfigurationParser::ActionHandler ConfigurationParser::locale_group_handler_ =
+ [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
+ std::string label = GetLabel(root_element, diag);
+ if (label.empty()) {
+ return false;
+ }
+
+ auto& group = config->locale_groups[label];
+ bool valid = true;
+
+ for (auto* child : root_element->GetChildElements()) {
+ if (child->name != "locale") {
+ diag->Error(
+ DiagMessage() << "Unexpected root_element in screen density group: "
+ << child->name);
+ valid = false;
+ } else {
+ Locale entry;
+ for (const auto& attr : child->attributes) {
+ if (attr.name == "lang") {
+ entry.lang = {attr.value};
+ } else if (attr.name == "region") {
+ entry.region = {attr.value};
+ } else {
+ diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name
+ << " = " << attr.value);
+ }
+ }
+ group.push_back(entry);
+ }
+ }
+
+ return valid;
+ };
+
+ConfigurationParser::ActionHandler ConfigurationParser::android_sdk_group_handler_ =
+ [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
+ std::string label = GetLabel(root_element, diag);
+ if (label.empty()) {
+ return false;
+ }
+
+ auto& group = config->android_sdk_groups[label];
+ bool valid = true;
+
+ for (auto* child : root_element->GetChildElements()) {
+ if (child->name != "android-sdk") {
+ diag->Error(
+ DiagMessage() << "Unexpected root_element in ABI group: " << child->name);
+ valid = false;
+ } else {
+ AndroidSdk entry;
+ for (const auto& attr : child->attributes) {
+ if (attr.name == "minSdkVersion") {
+ entry.min_sdk_version = {attr.value};
+ } else if (attr.name == "targetSdkVersion") {
+ entry.target_sdk_version = {attr.value};
+ } else if (attr.name == "maxSdkVersion") {
+ entry.max_sdk_version = {attr.value};
+ } else {
+ diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name
+ << " = " << attr.value);
+ }
+ }
+
+ // TODO: Fill in the manifest details when they are finalised.
+ for (auto node : child->GetChildElements()) {
+ if (node->name == "manifest") {
+ if (entry.manifest) {
+ diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates.");
+ continue;
+ }
+ entry.manifest = {AndroidManifest()};
+ }
+ }
+
+ group.push_back(entry);
+ }
+ }
+
+ return valid;
+ };
+
+ConfigurationParser::ActionHandler ConfigurationParser::gl_texture_group_handler_ =
+ [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
+ std::string label = GetLabel(root_element, diag);
+ if (label.empty()) {
+ return false;
+ }
+
+ auto& group = config->gl_texture_groups[label];
+ bool valid = true;
+
+ GlTexture result;
+ for (auto* child : root_element->GetChildElements()) {
+ if (child->name != "gl-texture") {
+ diag->Error(
+ DiagMessage() << "Unexpected element in GL texture group: "
+ << child->name);
+ valid = false;
+ } else {
+ for (const auto& attr : child->attributes) {
+ if (attr.name == "name") {
+ result.name = attr.value;
+ break;
+ }
+ }
+
+ for (auto* element : child->GetChildElements()) {
+ if (element->name != "texture-path") {
+ diag->Error(
+ DiagMessage() << "Unexpected element in gl-texture element: "
+ << child->name);
+ valid = false;
+ continue;
+ }
+ for (auto& node : element->children) {
+ xml::Text* t;
+ if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
+ result.texture_paths.push_back(TrimWhitespace(t->text).to_string());
+ }
+ }
+ }
+ }
+ group.push_back(result);
+ }
+
+ return valid;
+ };
+
+ConfigurationParser::ActionHandler ConfigurationParser::device_feature_group_handler_ =
+ [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool {
+ std::string label = GetLabel(root_element, diag);
+ if (label.empty()) {
+ return false;
+ }
+
+ auto& group = config->device_feature_groups[label];
+ bool valid = true;
+
+ for (auto* child : root_element->GetChildElements()) {
+ if (child->name != "supports-feature") {
+ diag->Error(
+ DiagMessage() << "Unexpected root_element in device feature group: "
+ << child->name);
+ valid = false;
+ } else {
+ for (auto& node : child->children) {
+ xml::Text* t;
+ if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
+ group.push_back(TrimWhitespace(t->text).to_string());
+ break;
+ }
+ }
+ }
+ }
+
+ return valid;
+ };
+
+} // namespace aapt
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
new file mode 100644
index 0000000..0fb2f71
--- /dev/null
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -0,0 +1,216 @@
+/*
+ * 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.
+ */
+
+#ifndef AAPT2_CONFIGURATION_H
+#define AAPT2_CONFIGURATION_H
+
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include <ConfigDescription.h>
+
+#include "util/Maybe.h"
+
+namespace aapt {
+
+namespace configuration {
+
+/** A mapping of group labels to group of configuration items. */
+template<class T>
+using Group = std::unordered_map<std::string, std::vector<T>>;
+
+/** Output artifact configuration options. */
+struct Artifact {
+ /** Name to use for output of processing foo.apk -> foo.<name>.apk. */
+ std::string name;
+ /** If present, uses the ABI group with this name. */
+ Maybe<std::string> abi_group;
+ /** If present, uses the screen density group with this name. */
+ Maybe<std::string> screen_density_group;
+ /** If present, uses the locale group with this name. */
+ Maybe<std::string> locale_group;
+ /** If present, uses the Android SDK group with this name. */
+ Maybe<std::string> android_sdk_group;
+ /** If present, uses the device feature group with this name. */
+ Maybe<std::string> device_feature_group;
+ /** If present, uses the OpenGL texture group with this name. */
+ Maybe<std::string> gl_texture_group;
+};
+
+/** Enumeration of currently supported ABIs. */
+enum class Abi {
+ kArmeV6,
+ kArmV7a,
+ kArm64V8a,
+ kX86,
+ kX86_64,
+ kMips,
+ kMips64,
+ kUniversal
+};
+
+/**
+ * Represents an individual locale. When a locale is included, it must be
+ * declared from least specific to most specific, as a region does not make
+ * sense without a language. If neither the language or region are specified it
+ * acts as a special case for catch all. This can allow all locales to be kept,
+ * or compressed.
+ */
+struct Locale {
+ /** The ISO<?> standard locale language code. */
+ Maybe<std::string> lang;
+ /** The ISO<?> standard locale region code. */
+ Maybe<std::string> region;
+
+ inline friend bool operator==(const Locale& lhs, const Locale& rhs) {
+ return lhs.lang == rhs.lang && lhs.region == rhs.region;
+ }
+};
+
+// TODO: Encapsulate manifest modifications from the configuration file.
+struct AndroidManifest {
+ inline friend bool operator==(const AndroidManifest& lhs, const AndroidManifest& rhs) {
+ return true; // nothing to compare yet.
+ }
+};
+
+struct AndroidSdk {
+ Maybe<std::string> min_sdk_version;
+ Maybe<std::string> target_sdk_version;
+ Maybe<std::string> max_sdk_version;
+ Maybe<AndroidManifest> manifest;
+
+ inline friend bool operator==(const AndroidSdk& lhs, const AndroidSdk& rhs) {
+ return lhs.min_sdk_version == rhs.min_sdk_version &&
+ lhs.target_sdk_version == rhs.target_sdk_version &&
+ lhs.max_sdk_version == rhs.max_sdk_version &&
+ lhs.manifest == rhs.manifest;
+ }
+};
+
+// TODO: Make device features more than just an arbitrary string?
+using DeviceFeature = std::string;
+
+/** Represents a mapping of texture paths to a GL texture format. */
+struct GlTexture {
+ std::string name;
+ std::vector<std::string> texture_paths;
+
+ inline friend bool operator==(const GlTexture& lhs, const GlTexture& rhs) {
+ return lhs.name == rhs.name && lhs.texture_paths == rhs.texture_paths;
+ }
+};
+
+/**
+ * AAPT2 XML configuration binary representation.
+ */
+struct Configuration {
+ std::unordered_map<std::string, Artifact> artifacts;
+ Maybe<std::string> artifact_format;
+
+ Group<Abi> abi_groups;
+ Group<ConfigDescription> screen_density_groups;
+ Group<Locale> locale_groups;
+ Group<AndroidSdk> android_sdk_groups;
+ Group<DeviceFeature> device_feature_groups;
+ Group<GlTexture> gl_texture_groups;
+};
+
+} // namespace configuration
+
+// Forward declaration of classes used in the API.
+struct IDiagnostics;
+namespace xml {
+class Element;
+}
+
+/**
+ * XML configuration file parser for the split and optimize commands.
+ */
+class ConfigurationParser {
+ public:
+ /** Returns a ConfigurationParser for the configuration in the provided file contents. */
+ static ConfigurationParser ForContents(const std::string& contents) {
+ ConfigurationParser parser{contents};
+ return parser;
+ }
+
+ /** Returns a ConfigurationParser for the file located at the provided path. */
+ static ConfigurationParser ForPath(const std::string& path) {
+ // TODO: Read XML file into memory.
+ return ForContents(path);
+ }
+
+ /** Sets the diagnostics context to use when parsing. */
+ ConfigurationParser& WithDiagnostics(IDiagnostics* diagnostics) {
+ diag_ = diagnostics;
+ return *this;
+ }
+
+ /**
+ * Parses the configuration file and returns the results. If the configuration could not be parsed
+ * the result is empty and any errors will be displayed with the provided diagnostics context.
+ */
+ Maybe<configuration::Configuration> Parse();
+
+ protected:
+ /**
+ * Instantiates a new ConfigurationParser with the provided configuration file and a no-op
+ * diagnostics context. The default diagnostics context can be overridden with a call to
+ * WithDiagnostics(IDiagnostics *).
+ */
+ explicit ConfigurationParser(std::string contents);
+
+ /** Returns the current diagnostics context to any subclasses. */
+ IDiagnostics* diagnostics() {
+ return diag_;
+ }
+
+ /**
+ * An ActionHandler for processing XML elements in the XmlActionExecutor. Returns true if the
+ * element was successfully processed, otherwise returns false.
+ */
+ using ActionHandler = std::function<bool(configuration::Configuration* config,
+ xml::Element* element,
+ IDiagnostics* diag)>;
+
+ /** Handler for <artifact> tags. */
+ static ActionHandler artifact_handler_;
+ /** Handler for <artifact-format> tags. */
+ static ActionHandler artifact_format_handler_;
+ /** Handler for <abi-group> tags. */
+ static ActionHandler abi_group_handler_;
+ /** Handler for <screen-density-group> tags. */
+ static ActionHandler screen_density_group_handler_;
+ /** Handler for <locale-group> tags. */
+ static ActionHandler locale_group_handler_;
+ /** Handler for <android-sdk-group> tags. */
+ static ActionHandler android_sdk_group_handler_;
+ /** Handler for <gl-texture-group> tags. */
+ static ActionHandler gl_texture_group_handler_;
+ /** Handler for <device-feature-group> tags. */
+ static ActionHandler device_feature_group_handler_;
+
+ private:
+ /** The contents of the configuration file to parse. */
+ const std::string contents_;
+ /** The diagnostics context to send messages to. */
+ IDiagnostics* diag_;
+};
+
+} // namespace aapt
+
+#endif //AAPT2_CONFIGURATION_H
diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp
new file mode 100644
index 0000000..72a97b2
--- /dev/null
+++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "configuration/ConfigurationParser.h"
+
+#include <string>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "androidfw/ResourceTypes.h"
+
+#include "test/Test.h"
+#include "xml/XmlDom.h"
+
+namespace aapt {
+namespace {
+
+using android::ResTable_config;
+using configuration::Abi;
+using configuration::AndroidSdk;
+using configuration::Configuration;
+using configuration::DeviceFeature;
+using configuration::GlTexture;
+using configuration::Locale;
+using configuration::AndroidManifest;
+using testing::ElementsAre;
+using xml::Element;
+using xml::NodeCast;
+
+constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?>
+<post-process xmlns="http://schemas.android.com/tools/aapt">
+ <groups>
+ <abi-group label="arm">
+ <abi>armeabi-v7a</abi>
+ <abi>arm64-v8a</abi>
+ </abi-group>
+ <abi-group label="other">
+ <abi>x86</abi>
+ <abi>mips</abi>
+ </abi-group>
+ <screen-density-group label="large">
+ <screen-density>xhdpi</screen-density>
+ <screen-density>xxhdpi</screen-density>
+ <screen-density>xxxhdpi</screen-density>
+ </screen-density-group>
+ <screen-density-group label="alldpi">
+ <screen-density>ldpi</screen-density>
+ <screen-density>mdpi</screen-density>
+ <screen-density>hdpi</screen-density>
+ <screen-density>xhdpi</screen-density>
+ <screen-density>xxhdpi</screen-density>
+ <screen-density>xxxhdpi</screen-density>
+ </screen-density-group>
+ <locale-group label="europe">
+ <locale lang="en"/>
+ <locale lang="es"/>
+ <locale lang="fr"/>
+ <locale lang="de"/>
+ </locale-group>
+ <locale-group label="north-america">
+ <locale lang="en"/>
+ <locale lang="es" region="MX"/>
+ <locale lang="fr" region="CA"/>
+ </locale-group>
+ <locale-group label="all">
+ <locale/>
+ </locale-group>
+ <android-sdk-group label="19">
+ <android-sdk
+ minSdkVersion="19"
+ targetSdkVersion="24"
+ maxSdkVersion="25">
+ <manifest>
+ <!--- manifest additions here XSLT? TODO -->
+ </manifest>
+ </android-sdk>
+ </android-sdk-group>
+ <gl-texture-group label="dxt1">
+ <gl-texture name="GL_EXT_texture_compression_dxt1">
+ <texture-path>assets/dxt1/*</texture-path>
+ </gl-texture>
+ </gl-texture-group>
+ <device-feature-group label="low-latency">
+ <supports-feature>android.hardware.audio.low_latency</supports-feature>
+ </device-feature-group>
+ </groups>
+ <artifacts>
+ <artifact-format>
+ ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
+ </artifact-format>
+ <artifact
+ name="art1"
+ abi-group="arm"
+ screen-density-group="large"
+ locale-group="europe"
+ android-sdk-group="19"
+ gl-texture-group="dxt1"
+ device-feature-group="low-latency"/>
+ <artifact
+ name="art2"
+ abi-group="other"
+ screen-density-group="alldpi"
+ locale-group="north-america"
+ android-sdk-group="19"
+ gl-texture-group="dxt1"
+ device-feature-group="low-latency"/>
+ </artifacts>
+</post-process>
+)";
+
+class ConfigurationParserTest : public ConfigurationParser, public ::testing::Test {
+ public:
+ ConfigurationParserTest() : ConfigurationParser("") {}
+
+ protected:
+ StdErrDiagnostics diag_;
+};
+
+TEST_F(ConfigurationParserTest, ValidateFile) {
+ auto parser = ConfigurationParser::ForContents(kValidConfig).WithDiagnostics(&diag_);
+ auto result = parser.Parse();
+ ASSERT_TRUE(result);
+ Configuration& value = result.value();
+ EXPECT_EQ(2ul, value.artifacts.size());
+ ASSERT_TRUE(value.artifact_format);
+ EXPECT_EQ(
+ "${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release",
+ value.artifact_format.value()
+ );
+
+ EXPECT_EQ(2ul, value.abi_groups.size());
+ EXPECT_EQ(2ul, value.abi_groups["arm"].size());
+ EXPECT_EQ(2ul, value.abi_groups["other"].size());
+
+ EXPECT_EQ(2ul, value.screen_density_groups.size());
+ EXPECT_EQ(3ul, value.screen_density_groups["large"].size());
+ EXPECT_EQ(6ul, value.screen_density_groups["alldpi"].size());
+
+ EXPECT_EQ(3ul, value.locale_groups.size());
+ EXPECT_EQ(4ul, value.locale_groups["europe"].size());
+ EXPECT_EQ(3ul, value.locale_groups["north-america"].size());
+ EXPECT_EQ(1ul, value.locale_groups["all"].size());
+
+ EXPECT_EQ(1ul, value.android_sdk_groups.size());
+ EXPECT_EQ(1ul, value.android_sdk_groups["19"].size());
+
+ EXPECT_EQ(1ul, value.gl_texture_groups.size());
+ EXPECT_EQ(1ul, value.gl_texture_groups["dxt1"].size());
+
+ EXPECT_EQ(1ul, value.device_feature_groups.size());
+ EXPECT_EQ(1ul, value.device_feature_groups["low-latency"].size());
+}
+
+TEST_F(ConfigurationParserTest, InvalidNamespace) {
+ constexpr const char* invalid_ns = R"(<?xml version="1.0" encoding="utf-8" ?>
+ <post-process xmlns="http://schemas.android.com/tools/another-unknown-tool" />)";
+
+ auto result = ConfigurationParser::ForContents(invalid_ns).Parse();
+ ASSERT_FALSE(result);
+}
+
+TEST_F(ConfigurationParserTest, ArtifactAction) {
+ static constexpr const char* xml = R"xml(
+ <artifact
+ abi-group="arm"
+ screen-density-group="large"
+ locale-group="europe"
+ android-sdk-group="19"
+ gl-texture-group="dxt1"
+ device-feature-group="low-latency"/>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ Configuration config;
+ bool ok = artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ EXPECT_EQ(1ul, config.artifacts.size());
+
+ auto& artifact = config.artifacts.begin()->second;
+ EXPECT_EQ("", artifact.name); // TODO: make this fail.
+ EXPECT_EQ("arm", artifact.abi_group.value());
+ EXPECT_EQ("large", artifact.screen_density_group.value());
+ EXPECT_EQ("europe", artifact.locale_group.value());
+ EXPECT_EQ("19", artifact.android_sdk_group.value());
+ EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
+ EXPECT_EQ("low-latency", artifact.device_feature_group.value());
+}
+
+TEST_F(ConfigurationParserTest, ArtifactFormatAction) {
+ static constexpr const char* xml = R"xml(
+ <artifact-format>
+ ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
+ </artifact-format>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ Configuration config;
+ bool ok = artifact_format_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+ ASSERT_TRUE(config.artifact_format);
+ EXPECT_EQ(
+ "${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release",
+ static_cast<std::string>(config.artifact_format.value())
+ );
+}
+
+TEST_F(ConfigurationParserTest, AbiGroupAction) {
+ static constexpr const char* xml = R"xml(
+ <abi-group label="arm">
+ <!-- First comment. -->
+ <abi>
+ armeabi-v7a
+ </abi>
+ <!-- Another comment. -->
+ <abi>arm64-v8a</abi>
+ </abi-group>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ Configuration config;
+ bool ok = abi_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ EXPECT_EQ(1ul, config.abi_groups.size());
+ ASSERT_EQ(1u, config.abi_groups.count("arm"));
+
+ auto& out = config.abi_groups["arm"];
+ ASSERT_THAT(out, ElementsAre(Abi::kArmV7a, Abi::kArm64V8a));
+}
+
+TEST_F(ConfigurationParserTest, ScreenDensityGroupAction) {
+ static constexpr const char* xml = R"xml(
+ <screen-density-group label="large">
+ <screen-density>xhdpi</screen-density>
+ <screen-density>
+ xxhdpi
+ </screen-density>
+ <screen-density>xxxhdpi</screen-density>
+ </screen-density-group>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ Configuration config;
+ bool ok =
+ screen_density_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ EXPECT_EQ(1ul, config.screen_density_groups.size());
+ ASSERT_EQ(1u, config.screen_density_groups.count("large"));
+
+ ConfigDescription xhdpi;
+ xhdpi.density = ResTable_config::DENSITY_XHIGH;
+ ConfigDescription xxhdpi;
+ xxhdpi.density = ResTable_config::DENSITY_XXHIGH;
+ ConfigDescription xxxhdpi;
+ xxxhdpi.density = ResTable_config::DENSITY_XXXHIGH;
+
+ auto& out = config.screen_density_groups["large"];
+ ASSERT_THAT(out, ElementsAre(xhdpi, xxhdpi, xxxhdpi));
+}
+
+TEST_F(ConfigurationParserTest, LocaleGroupAction) {
+ static constexpr const char* xml = R"xml(
+ <locale-group label="europe">
+ <locale lang="en"/>
+ <locale lang="es"/>
+ <locale lang="fr"/>
+ <locale lang="de"/>
+ </locale-group>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ Configuration config;
+ bool ok = locale_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ ASSERT_EQ(1ul, config.locale_groups.size());
+ ASSERT_EQ(1u, config.locale_groups.count("europe"));
+
+ auto& out = config.locale_groups["europe"];
+
+ Locale en;
+ en.lang = std::string("en");
+ Locale es;
+ es.lang = std::string("es");
+ Locale fr;
+ fr.lang = std::string("fr");
+ Locale de;
+ de.lang = std::string("de");
+
+ ASSERT_THAT(out, ElementsAre(en, es, fr, de));
+}
+
+TEST_F(ConfigurationParserTest, AndroidSdkGroupAction) {
+ static constexpr const char* xml = R"xml(
+ <android-sdk-group label="19">
+ <android-sdk
+ minSdkVersion="19"
+ targetSdkVersion="24"
+ maxSdkVersion="25">
+ <manifest>
+ <!--- manifest additions here XSLT? TODO -->
+ </manifest>
+ </android-sdk>
+ </android-sdk-group>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ Configuration config;
+ bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ ASSERT_EQ(1ul, config.android_sdk_groups.size());
+ ASSERT_EQ(1u, config.android_sdk_groups.count("19"));
+
+ auto& out = config.android_sdk_groups["19"];
+
+ AndroidSdk sdk;
+ sdk.min_sdk_version = std::string("19");
+ sdk.target_sdk_version = std::string("24");
+ sdk.max_sdk_version = std::string("25");
+ sdk.manifest = AndroidManifest();
+
+ ASSERT_EQ(1ul, out.size());
+ ASSERT_EQ(sdk, out[0]);
+}
+
+TEST_F(ConfigurationParserTest, GlTextureGroupAction) {
+ static constexpr const char* xml = R"xml(
+ <gl-texture-group label="dxt1">
+ <gl-texture name="GL_EXT_texture_compression_dxt1">
+ <texture-path>assets/dxt1/main/*</texture-path>
+ <texture-path>
+ assets/dxt1/test/*
+ </texture-path>
+ </gl-texture>
+ </gl-texture-group>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ Configuration config;
+ bool ok = gl_texture_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ EXPECT_EQ(1ul, config.gl_texture_groups.size());
+ ASSERT_EQ(1u, config.gl_texture_groups.count("dxt1"));
+
+ auto& out = config.gl_texture_groups["dxt1"];
+
+ GlTexture texture{
+ std::string("GL_EXT_texture_compression_dxt1"),
+ {"assets/dxt1/main/*", "assets/dxt1/test/*"}
+ };
+
+ ASSERT_EQ(1ul, out.size());
+ ASSERT_EQ(texture, out[0]);
+}
+
+TEST_F(ConfigurationParserTest, DeviceFeatureGroupAction) {
+ static constexpr const char* xml = R"xml(
+ <device-feature-group label="low-latency">
+ <supports-feature>android.hardware.audio.low_latency</supports-feature>
+ <supports-feature>
+ android.hardware.audio.pro
+ </supports-feature>
+ </device-feature-group>)xml";
+
+ auto doc = test::BuildXmlDom(xml);
+
+ Configuration config;
+ bool ok
+ = device_feature_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+ ASSERT_TRUE(ok);
+
+ EXPECT_EQ(1ul, config.device_feature_groups.size());
+ ASSERT_EQ(1u, config.device_feature_groups.count("low-latency"));
+
+ auto& out = config.device_feature_groups["low-latency"];
+
+ DeviceFeature low_latency = "android.hardware.audio.low_latency";
+ DeviceFeature pro = "android.hardware.audio.pro";
+ ASSERT_THAT(out, ElementsAre(low_latency, pro));
+}
+
+} // namespace
+} // namespace aapt
diff --git a/tools/aapt2/configuration/aapt2.xsd b/tools/aapt2/configuration/aapt2.xsd
new file mode 100644
index 0000000..47bf99e
--- /dev/null
+++ b/tools/aapt2/configuration/aapt2.xsd
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ elementFormDefault="qualified"
+ xmlns="http://schemas.android.com/tools/aapt2"
+ targetNamespace="http://schemas.android.com/tools/aapt2">
+
+ <xsd:element name="post-process">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="groups" type="groups"/>
+ <xsd:element name="artifacts" type="artifacts"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <xsd:complexType name="groups">
+ <xsd:sequence>
+ <xsd:element name="abi-group" type="abi-group" maxOccurs="unbounded"/>
+ <xsd:element name="screen-density-group" type="screen-density-group" maxOccurs="unbounded"/>
+ <xsd:element name="locale-group" type="locale-group" maxOccurs="unbounded"/>
+ <xsd:element name="android-sdk-group" type="android-sdk-group" maxOccurs="unbounded"/>
+ <xsd:element
+ name="gl-texture-group"
+ type="gl-texture-group"
+ maxOccurs="unbounded"/>
+ <xsd:element name="device-feature-group" type="device-feature-group" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="artifacts">
+ <xsd:sequence>
+ <xsd:element name="artifact-format" type="xsd:string"/>
+ <xsd:element name="artifact" type="artifact" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="generate-all" type="xsd:boolean"/>
+ </xsd:complexType>
+
+ <!-- Groups output artifacts together by dimension labels. -->
+ <xsd:complexType name="artifact">
+ <xsd:attribute name="name" type="xsd:string"/>
+ <xsd:attribute name="abi-group" type="xsd:string"/>
+ <xsd:attribute name="android-sdk-group" type="xsd:string"/>
+ <xsd:attribute name="device-feature-group" type="xsd:string"/>
+ <xsd:attribute name="gl-texture-group" type="xsd:string"/>
+ <xsd:attribute name="screen-density-group" type="xsd:string"/>
+ <xsd:attribute name="locale-group" type="xsd:string"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="gl-texture-group">
+ <xsd:sequence>
+ <xsd:element name="gl-texture" type="gl-texture" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="gl-texture">
+ <xsd:sequence>
+ <xsd:element name="texture-path" type="xsd:string" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="device-feature-group">
+ <xsd:sequence>
+ <xsd:element name="supports-feature" type="xsd:string" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="abi-group">
+ <xsd:sequence>
+ <xsd:element name="abi" type="abi-name" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+
+ <xsd:simpleType name="abi-name">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="armeabi"/>
+ <xsd:enumeration value="armeabi-v7a"/>
+ <xsd:enumeration value="arm64-v8a"/>
+ <xsd:enumeration value="x86"/>
+ <xsd:enumeration value="x86_64"/>
+ <xsd:enumeration value="mips"/>
+ <xsd:enumeration value="mips64"/>
+ <xsd:enumeration value="fat"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="screen-density-group">
+ <xsd:sequence>
+ <xsd:element name="screen-density" type="screen-density" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+
+ <xsd:simpleType name="screen-density">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="alldpi"/>
+ <xsd:enumeration value="ldpi"/>
+ <xsd:enumeration value="mdpi"/>
+ <xsd:enumeration value="hdpi"/>
+ <xsd:enumeration value="xhdpi"/>
+ <xsd:enumeration value="xxhdpi"/>
+ <xsd:enumeration value="xxxhdpi"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="android-sdk-group">
+ <xsd:sequence>
+ <xsd:element name="android-sdk" type="android-sdk" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="android-sdk">
+ <!-- TODO(safarmer): Add permissions to add/remove. -->
+ <!-- TODO(safarmer): Add option for uncompressed native libs. -->
+ <xsd:sequence>
+ <xsd:element name="manifest" type="manifest"/>
+ </xsd:sequence>
+ <xsd:attribute name="minSdkVersion" type="xsd:integer"/>
+ <xsd:attribute name="targetSdkVersion" type="xsd:integer"/>
+ <xsd:attribute name="maxSdkVersion" type="xsd:integer"/>
+ </xsd:complexType>
+
+ <!-- TODO(safarmer): Figure out the best way to handle manifest updates. -->
+ <xsd:simpleType name="manifest">
+ <xsd:restriction base="xsd:string"/>
+ </xsd:simpleType>
+
+ <xsd:complexType name="locale-group">
+ <xsd:sequence>
+ <xsd:element name="locale" type="locale" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="label" type="xsd:string" use="optional"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="locale">
+ <xsd:attribute name="lang" type="xsd:string"/>
+ <xsd:attribute name="region" type="xsd:string"/>
+ <xsd:attribute name="compressed" type="xsd:boolean"/>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/tools/aapt2/configuration/example/config.xml b/tools/aapt2/configuration/example/config.xml
new file mode 100644
index 0000000..a8360f8
--- /dev/null
+++ b/tools/aapt2/configuration/example/config.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<post-process xmlns="http://schemas.android.com/tools/aapt2">
+ <groups>
+ <abi-group label="arm">
+ <abi>armeabi-v7a</abi>
+ <abi>arm64-v8a</abi>
+ </abi-group>
+
+ <abi-group label="other">
+ <abi>x86</abi>
+ <abi>mips</abi>
+ </abi-group>
+
+ <screen-density-group label="large">
+ <screen-density>xhdpi</screen-density>
+ <screen-density>xxhdpi</screen-density>
+ <screen-density>xxxhdpi</screen-density>
+ </screen-density-group>
+
+ <screen-density-group label="alldpi">
+ <screen-density>ldpi</screen-density>
+ <screen-density>mdpi</screen-density>
+ <screen-density>hdpi</screen-density>
+ <screen-density>xhdpi</screen-density>
+ <screen-density>xxhdpi</screen-density>
+ <screen-density>xxxhdpi</screen-density>
+ </screen-density-group>
+
+ <locale-group label="europe">
+ <locale lang="en"/>
+ <locale lang="es"/>
+ <locale lang="fr"/>
+ <locale lang="de" compressed="true"/>
+ </locale-group>
+
+ <locale-group label="north-america">
+ <locale lang="en"/>
+ <locale lang="es" region="MX"/>
+ <locale lang="fr" region="CA" compressed="true"/>
+ </locale-group>
+
+ <locale-group label="all">
+ <locale compressed="true"/>
+ </locale-group>
+
+ <android-sdk-group label="19">
+ <android-sdk
+ minSdkVersion="19"
+ targetSdkVersion="24"
+ maxSdkVersion="25">
+ <manifest>
+ <!--- manifest additions here XSLT? TODO -->
+ </manifest>
+ </android-sdk>
+ </android-sdk-group>
+
+ <gl-texture-group label="dxt1">
+ <gl-texture name="GL_EXT_texture_compression_dxt1">
+ <texture-path>assets/dxt1/*</texture-path>
+ </gl-texture>
+ </gl-texture-group>
+
+ <device-feature-group label="low-latency">
+ <supports-feature>android.hardware.audio.low_latency</supports-feature>
+ </device-feature-group>
+ </groups>
+
+ <artifacts>
+ <artifact-format>
+ ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
+ </artifact-format>
+
+ <artifact
+ abi-group="arm"
+ screen-density-group="large"
+ locale-group="europe"
+ android-sdk-group="19"
+ gl-texture-group="dxt1"
+ device-feature-group="low-latency"/>
+
+ <artifact
+ abi-group="other"
+ screen-density-group="alldpi"
+ locale-group="north-america"
+ android-sdk-group="19"
+ gl-texture-group="dxt1"
+ device-feature-group="low-latency"/>
+
+ </artifacts>
+</post-process>
diff --git a/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml b/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml
index 1a4067f..a5f202d 100644
--- a/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml
+++ b/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml
@@ -19,4 +19,11 @@
<uses-sdk android:minSdkVersion="21" />
<uses-permission-sdk-23 android:name="android.permission.TEST" android:maxSdkVersion="22" />
+
+ <application>
+ <activity android:name=".MyActivity">
+ <layout android:defaultHeight="500dp"
+ android:defaultWidth="600dp" />
+ </activity>
+ </application>
</manifest>
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 53c66a6..99fd95b 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -326,7 +326,10 @@
uses_static_library_action.Action(RequiredAndroidAttribute("certDigest"));
application_action["meta-data"] = meta_data_action;
+
application_action["activity"] = component_action;
+ application_action["activity"]["layout"];
+
application_action["activity-alias"] = component_action;
application_action["service"] = component_action;
application_action["receiver"] = component_action;
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index cf22322..28e952e 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -203,7 +203,7 @@
if (*c == '%' && c + 1 < end) {
c++;
- if (*c == '%') {
+ if (*c == '%' || *c == 'n') {
c++;
continue;
}
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index 53501f9..77c1c24 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -429,7 +429,8 @@
if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
- if " final class " not in clazz.raw:
+ if ((" final class " not in clazz.raw) and
+ (" final deprecated class " not in clazz.raw)):
error(clazz, None, "FW8", "Parcelable classes must be final")
diff --git a/tools/fonts/add_additional_fonts.py b/tools/fonts/add_additional_fonts.py
new file mode 100644
index 0000000..bf4af2b
--- /dev/null
+++ b/tools/fonts/add_additional_fonts.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+import sys
+
+def main(argv):
+ original_file = 'frameworks/base/data/fonts/fonts.xml'
+
+ if len(argv) == 3:
+ output_file_path = argv[1]
+ override_file_path = argv[2]
+ else:
+ raise ValueError("Wrong number of arguments %s" % len(argv))
+
+ fallbackPlaceholderFound = False
+ with open(original_file, 'r') as input_file:
+ with open(output_file_path, 'w') as output_file:
+ for line in input_file:
+ # If we've found the spot to add additional fonts, add them.
+ if line.strip() == '<!-- fallback fonts -->':
+ fallbackPlaceholderFound = True
+ with open(override_file_path) as override_file:
+ for override_line in override_file:
+ output_file.write(override_line)
+ output_file.write(line)
+ if not fallbackPlaceholderFound:
+ raise ValueError('<!-- fallback fonts --> not found in source file: %s' % original_file)
+
+if __name__ == '__main__':
+ main(sys.argv)
diff --git a/tools/fonts/fontchain_lint.py b/tools/fonts/fontchain_lint.py
index c0475ee..c6ad4c2 100755
--- a/tools/fonts/fontchain_lint.py
+++ b/tools/fonts/fontchain_lint.py
@@ -3,7 +3,6 @@
import collections
import copy
import glob
-import itertools
from os import path
import sys
from xml.etree import ElementTree
@@ -574,8 +573,8 @@
all_sequences.add(sequence)
sequence_pieces.update(sequence)
if _emoji_sequences.get(sequence, None) == 'Emoji_Tag_Sequence':
- # Add reverse of all emoji ZWJ sequences, which are added to the fonts
- # as a workaround to get the sequences work in RTL text.
+ # Add reverse of all emoji ZWJ sequences, which are added to the
+ # fonts as a workaround to get the sequences work in RTL text.
# TODO: test if these are actually needed by Minikin/HarfBuzz.
reversed_seq = reverse_emoji(sequence)
all_sequences.add(reversed_seq)
@@ -630,12 +629,26 @@
if record.name in ['sans-serif', 'sans-serif-condensed']:
font = open_font(record.font)
assert font['head'].yMax == 2163 and font['head'].yMin == -555, (
- 'yMax and yMin of %s do not match expected values.' % (record.font,))
+ 'yMax and yMin of %s do not match expected values.' % (
+ record.font,))
- if record.name in ['sans-serif', 'sans-serif-condensed', 'serif', 'monospace']:
+ if record.name in ['sans-serif', 'sans-serif-condensed',
+ 'serif', 'monospace']:
font = open_font(record.font)
- assert font['hhea'].ascent == 1900 and font['hhea'].descent == -500, (
- 'ascent and descent of %s do not match expected values.' % (record.font,))
+ assert (font['hhea'].ascent == 1900 and
+ font['hhea'].descent == -500), (
+ 'ascent and descent of %s do not match expected '
+ 'values.' % (record.font,))
+
+
+def check_cjk_punctuation():
+ cjk_scripts = {'Hans', 'Hant', 'Jpan', 'Kore'}
+ cjk_punctuation = range(0x3000, 0x301F + 1)
+ for record in _fallback_chain:
+ if record.scripts.intersection(cjk_scripts):
+ # CJK font seen. Stop checking the rest of the fonts.
+ break
+ assert_font_supports_none_of_chars(record.font, cjk_punctuation)
def main():
@@ -651,6 +664,8 @@
hyphens_dir = path.join(target_out, 'usr', 'hyphen-data')
check_hyphens(hyphens_dir)
+ check_cjk_punctuation()
+
check_emoji = sys.argv[2]
if check_emoji == 'true':
ucd_path = sys.argv[3]
diff --git a/tools/incident_report/main.cpp b/tools/incident_report/main.cpp
index 884b8e4..1d8809f6 100644
--- a/tools/incident_report/main.cpp
+++ b/tools/incident_report/main.cpp
@@ -355,6 +355,7 @@
args[argpos++] = NULL;
execvp(args[0], (char*const*)args);
fprintf(stderr, "execvp failed: %s\n", strerror(errno));
+ free(args);
return 1;
} else {
// parent
@@ -400,6 +401,7 @@
}
}
+ free(buffer);
return 0;
}
diff --git a/tools/layoutlib/.gitignore b/tools/layoutlib/.gitignore
deleted file mode 100644
index 819103d..0000000
--- a/tools/layoutlib/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-bin
-/.idea/workspace.xml
-/out
-/bridge/out
diff --git a/tools/layoutlib/.idea/.name b/tools/layoutlib/.idea/.name
deleted file mode 100644
index 10eb5c1..0000000
--- a/tools/layoutlib/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-layoutlib
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/artifacts/studio_android_widgets_jar.xml b/tools/layoutlib/.idea/artifacts/studio_android_widgets_jar.xml
deleted file mode 100644
index 0450be3..0000000
--- a/tools/layoutlib/.idea/artifacts/studio_android_widgets_jar.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<component name="ArtifactManager">
- <artifact type="jar" name="studio-android-widgets:jar">
- <output-path>$PROJECT_DIR$/out/artifacts/studio_android_widgets_jar</output-path>
- <root id="archive" name="studio-android-widgets.jar">
- <element id="module-output" name="studio-android-widgets" />
- </root>
- </artifact>
-</component>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/artifacts/studio_android_widgets_src_jar.xml b/tools/layoutlib/.idea/artifacts/studio_android_widgets_src_jar.xml
deleted file mode 100644
index a844ca3..0000000
--- a/tools/layoutlib/.idea/artifacts/studio_android_widgets_src_jar.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<component name="ArtifactManager">
- <artifact type="jar" name="studio-android-widgets-src:jar">
- <output-path>$PROJECT_DIR$/out/artifacts/studio_android_widgets_src_jar</output-path>
- <root id="archive" name="studio-android-widgets-src.jar">
- <element id="dir-copy" path="$PROJECT_DIR$/studio-custom-widgets/src" />
- </root>
- </artifact>
-</component>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/codeStyleSettings.xml b/tools/layoutlib/.idea/codeStyleSettings.xml
deleted file mode 100644
index ac90d1e..0000000
--- a/tools/layoutlib/.idea/codeStyleSettings.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="ProjectCodeStyleSettingsManager">
- <option name="PER_PROJECT_SETTINGS">
- <value>
- <option name="FIELD_NAME_PREFIX" value="m" />
- <option name="STATIC_FIELD_NAME_PREFIX" value="s" />
- <option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
- <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
- <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
- <option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
- <value />
- </option>
- <option name="IMPORT_LAYOUT_TABLE">
- <value>
- <package name="com.android" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="org" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="android" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="java" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="" withSubpackages="true" static="false" />
- <emptyLine />
- <package name="" withSubpackages="true" static="true" />
- </value>
- </option>
- <option name="RIGHT_MARGIN" value="100" />
- <option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
- <option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
- <option name="JD_ADD_BLANK_AFTER_PARM_COMMENTS" value="true" />
- <option name="JD_ADD_BLANK_AFTER_RETURN" value="true" />
- <option name="JD_DO_NOT_WRAP_ONE_LINE_COMMENTS" value="true" />
- <option name="WRAP_COMMENTS" value="true" />
- <JavaCodeStyleSettings>
- <option name="CLASS_NAMES_IN_JAVADOC" value="3" />
- </JavaCodeStyleSettings>
- <XML>
- <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
- </XML>
- <codeStyleSettings language="JAVA">
- <option name="KEEP_LINE_BREAKS" value="false" />
- <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
- <option name="CALL_PARAMETERS_WRAP" value="1" />
- <option name="METHOD_PARAMETERS_WRAP" value="1" />
- <option name="THROWS_LIST_WRAP" value="1" />
- <option name="EXTENDS_KEYWORD_WRAP" value="1" />
- <option name="THROWS_KEYWORD_WRAP" value="1" />
- <option name="BINARY_OPERATION_WRAP" value="1" />
- <option name="TERNARY_OPERATION_WRAP" value="1" />
- <option name="ARRAY_INITIALIZER_WRAP" value="1" />
- <option name="ASSIGNMENT_WRAP" value="1" />
- <option name="ASSERT_STATEMENT_WRAP" value="1" />
- <option name="IF_BRACE_FORCE" value="3" />
- <option name="DOWHILE_BRACE_FORCE" value="3" />
- <option name="WHILE_BRACE_FORCE" value="3" />
- <option name="FOR_BRACE_FORCE" value="3" />
- <option name="WRAP_LONG_LINES" value="true" />
- <arrangement>
- <groups>
- <group>
- <type>GETTERS_AND_SETTERS</type>
- <order>KEEP</order>
- </group>
- <group>
- <type>OVERRIDDEN_METHODS</type>
- <order>KEEP</order>
- </group>
- </groups>
- </arrangement>
- </codeStyleSettings>
- <codeStyleSettings language="XML">
- <indentOptions>
- <option name="CONTINUATION_INDENT_SIZE" value="4" />
- </indentOptions>
- </codeStyleSettings>
- </value>
- </option>
- <option name="USE_PER_PROJECT_SETTINGS" value="true" />
- </component>
-</project>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/compiler.xml b/tools/layoutlib/.idea/compiler.xml
deleted file mode 100644
index 35961a2..0000000
--- a/tools/layoutlib/.idea/compiler.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="CompilerConfiguration">
- <option name="DEFAULT_COMPILER" value="Javac" />
- <excludeFromCompile>
- <directory url="file://$PROJECT_DIR$/create/tests/mock_data" includeSubdirectories="true" />
- </excludeFromCompile>
- <resourceExtensions />
- <wildcardResourcePatterns>
- <entry name="!?*.java" />
- <entry name="!?*.form" />
- <entry name="!?*.class" />
- <entry name="!?*.groovy" />
- <entry name="!?*.scala" />
- <entry name="!?*.flex" />
- <entry name="!?*.kt" />
- <entry name="!?*.clj" />
- </wildcardResourcePatterns>
- <annotationProcessing>
- <profile default="true" name="Default" enabled="false">
- <processorPath useClasspath="true" />
- </profile>
- </annotationProcessing>
- </component>
-</project>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/copyright/Android.xml b/tools/layoutlib/.idea/copyright/Android.xml
deleted file mode 100644
index d81d75d..0000000
--- a/tools/layoutlib/.idea/copyright/Android.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<component name="CopyrightManager">
- <copyright>
- <option name="notice" value="Copyright (C) &#36;today.year 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." />
- <option name="keyword" value="Copyright" />
- <option name="allowReplaceKeyword" value="" />
- <option name="myName" value="Android" />
- <option name="myLocal" value="true" />
- </copyright>
-</component>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/copyright/profiles_settings.xml b/tools/layoutlib/.idea/copyright/profiles_settings.xml
deleted file mode 100644
index 20145de..0000000
--- a/tools/layoutlib/.idea/copyright/profiles_settings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<component name="CopyrightManager">
- <settings default="Android" />
-</component>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/encodings.xml b/tools/layoutlib/.idea/encodings.xml
deleted file mode 100644
index f758959..0000000
--- a/tools/layoutlib/.idea/encodings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false">
- <file url="PROJECT" charset="UTF-8" />
- </component>
-</project>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 74fa549..0000000
--- a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<component name="InspectionProjectProfileManager">
- <profile version="1.0" is_locked="false">
- <option name="myName" value="Project Default" />
- <option name="myLocal" value="false" />
- <inspection_tool class="DefaultFileTemplate" enabled="false" level="WARNING" enabled_by_default="false">
- <option name="CHECK_FILE_HEADER" value="true" />
- <option name="CHECK_TRY_CATCH_SECTION" value="true" />
- <option name="CHECK_METHOD_BODY" value="true" />
- </inspection_tool>
- <inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
- <option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" />
- <option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
- </inspection_tool>
- <inspection_tool class="WeakerAccess" enabled="true" level="WARNING" enabled_by_default="true">
- <option name="SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS" value="false" />
- <option name="SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES" value="false" />
- <option name="SUGGEST_PRIVATE_FOR_INNERS" value="true" />
- </inspection_tool>
- </profile>
-</component>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/inspectionProfiles/profiles_settings.xml b/tools/layoutlib/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 3b31283..0000000
--- a/tools/layoutlib/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<component name="InspectionProjectProfileManager">
- <settings>
- <option name="PROJECT_PROFILE" value="Project Default" />
- <option name="USE_PROJECT_PROFILE" value="true" />
- <version value="1.0" />
- </settings>
-</component>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/libraries/framework_jar.xml b/tools/layoutlib/.idea/libraries/framework_jar.xml
deleted file mode 100644
index 6695a36..0000000
--- a/tools/layoutlib/.idea/libraries/framework_jar.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<component name="libraryTable">
- <library name="framework.jar">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../../../out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="file://$PROJECT_DIR$/../../core/java" />
- <root url="file://$PROJECT_DIR$/../../graphics/java" />
- <root url="file://$PROJECT_DIR$/../../../../libcore/luni/src/main/java" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/libraries/junit.xml b/tools/layoutlib/.idea/libraries/junit.xml
deleted file mode 100644
index ba46ebf..0000000
--- a/tools/layoutlib/.idea/libraries/junit.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="junit">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../../../out/host/common/obj/JAVA_LIBRARIES/junit-host_intermediates/javalib.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="file://$PROJECT_DIR$/../../../../external/junit/src" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/libraries/layoutlib_api_prebuilt.xml b/tools/layoutlib/.idea/libraries/layoutlib_api_prebuilt.xml
deleted file mode 100644
index a873600..0000000
--- a/tools/layoutlib/.idea/libraries/layoutlib_api_prebuilt.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<component name="libraryTable">
- <library name="layoutlib_api-prebuilt">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/layoutlib_api/layoutlib_api-prebuilt.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/layoutlib_api/layoutlib_api-sources.jar!/" />
- </SOURCES>
- </library>
-</component>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/libraries/mockito.xml b/tools/layoutlib/.idea/libraries/mockito.xml
deleted file mode 100644
index 032963e..0000000
--- a/tools/layoutlib/.idea/libraries/mockito.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<component name="libraryTable">
- <library name="mockito">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../../../out/host/common/obj/JAVA_LIBRARIES/mockito-host_intermediates/javalib.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
-</component>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/libraries/objenesis.xml b/tools/layoutlib/.idea/libraries/objenesis.xml
deleted file mode 100644
index 1484de5..0000000
--- a/tools/layoutlib/.idea/libraries/objenesis.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<component name="libraryTable">
- <library name="objenesis">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../../../out/host/common/obj/JAVA_LIBRARIES/objenesis-host_intermediates/javalib.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
-</component>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/misc.xml b/tools/layoutlib/.idea/misc.xml
deleted file mode 100644
index 44b47f2..0000000
--- a/tools/layoutlib/.idea/misc.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="EntryPointsManager">
- <entry_points version="2.0" />
- <list size="1">
- <item index="0" class="java.lang.String" itemvalue="com.android.tools.layoutlib.annotations.LayoutlibDelegate" />
- </list>
- </component>
- <component name="FrameworkDetectionExcludesConfiguration">
- <type id="android" />
- </component>
- <component name="NullableNotNullManager">
- <option name="myDefaultNullable" value="android.annotation.Nullable" />
- <option name="myDefaultNotNull" value="android.annotation.NonNull" />
- <option name="myNullables">
- <value>
- <list size="6">
- <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
- <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
- <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
- <item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
- <item index="4" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
- <item index="5" class="java.lang.String" itemvalue="android.annotation.Nullable" />
- </list>
- </value>
- </option>
- <option name="myNotNulls">
- <value>
- <list size="6">
- <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
- <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
- <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
- <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
- <item index="4" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
- <item index="5" class="java.lang.String" itemvalue="android.annotation.NonNull" />
- </list>
- </value>
- </option>
- </component>
- <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
- <output url="file://$PROJECT_DIR$/out" />
- </component>
-</project>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/modules.xml b/tools/layoutlib/.idea/modules.xml
deleted file mode 100644
index 6ffc1cc..0000000
--- a/tools/layoutlib/.idea/modules.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="ProjectModuleManager">
- <modules>
- <module fileurl="file://$PROJECT_DIR$/bridge/bridge.iml" filepath="$PROJECT_DIR$/bridge/bridge.iml" />
- <module fileurl="file://$PROJECT_DIR$/create/create.iml" filepath="$PROJECT_DIR$/create/create.iml" />
- <module fileurl="file://$PROJECT_DIR$/legacy/legacy.iml" filepath="$PROJECT_DIR$/legacy/legacy.iml" />
- <module fileurl="file://$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" filepath="$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" />
- </modules>
- </component>
-</project>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/runConfigurations/All_in_bridge.xml b/tools/layoutlib/.idea/runConfigurations/All_in_bridge.xml
deleted file mode 100644
index 0b22717..0000000
--- a/tools/layoutlib/.idea/runConfigurations/All_in_bridge.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<component name="ProjectRunConfigurationManager">
- <configuration default="false" name="All in bridge" type="JUnit" factoryName="JUnit" singleton="true">
- <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
- <module name="bridge" />
- <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
- <option name="ALTERNATIVE_JRE_PATH" value="" />
- <option name="PACKAGE_NAME" value="" />
- <option name="MAIN_CLASS_NAME" value="" />
- <option name="METHOD_NAME" value="" />
- <option name="TEST_OBJECT" value="package" />
- <option name="VM_PARAMETERS" value="-ea" />
- <option name="PARAMETERS" value="" />
- <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
- <option name="ENV_VARIABLES" />
- <option name="PASS_PARENT_ENVS" value="true" />
- <option name="TEST_SEARCH_SCOPE">
- <value defaultName="singleModule" />
- </option>
- <envs />
- <patterns />
- <RunnerSettings RunnerId="Run" />
- <ConfigurationWrapper RunnerId="Run" />
- <method />
- </configuration>
-</component>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/runConfigurations/All_in_create.xml b/tools/layoutlib/.idea/runConfigurations/All_in_create.xml
deleted file mode 100644
index b9cd419d..0000000
--- a/tools/layoutlib/.idea/runConfigurations/All_in_create.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<component name="ProjectRunConfigurationManager">
- <configuration default="false" name="All in create" type="JUnit" factoryName="JUnit" singleton="false" nameIsGenerated="true">
- <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
- <module name="create" />
- <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
- <option name="ALTERNATIVE_JRE_PATH" value="" />
- <option name="PACKAGE_NAME" value="" />
- <option name="MAIN_CLASS_NAME" value="" />
- <option name="METHOD_NAME" value="" />
- <option name="TEST_OBJECT" value="package" />
- <option name="VM_PARAMETERS" value="-ea" />
- <option name="PARAMETERS" value="" />
- <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
- <option name="ENV_VARIABLES" />
- <option name="PASS_PARENT_ENVS" value="true" />
- <option name="TEST_SEARCH_SCOPE">
- <value defaultName="singleModule" />
- </option>
- <envs />
- <patterns />
- <RunnerSettings RunnerId="Debug">
- <option name="DEBUG_PORT" value="" />
- <option name="TRANSPORT" value="0" />
- <option name="LOCAL" value="true" />
- </RunnerSettings>
- <RunnerSettings RunnerId="Run" />
- <ConfigurationWrapper RunnerId="Debug" />
- <ConfigurationWrapper RunnerId="Run" />
- <method />
- </configuration>
-</component>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml b/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml
deleted file mode 100644
index b402849..0000000
--- a/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<component name="ProjectRunConfigurationManager">
- <configuration default="false" name="Bridge quick" type="JUnit" factoryName="JUnit">
- <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
- <module name="bridge" />
- <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
- <option name="ALTERNATIVE_JRE_PATH" value="" />
- <option name="PACKAGE_NAME" />
- <option name="MAIN_CLASS_NAME" value="" />
- <option name="METHOD_NAME" value="" />
- <option name="TEST_OBJECT" value="pattern" />
- <option name="VM_PARAMETERS" value="-ea" />
- <option name="PARAMETERS" value="" />
- <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
- <option name="ENV_VARIABLES" />
- <option name="PASS_PARENT_ENVS" value="true" />
- <option name="TEST_SEARCH_SCOPE">
- <value defaultName="singleModule" />
- </option>
- <envs />
- <patterns>
- <pattern testClass="com.android.layoutlib.bridge.TestDelegates" />
- <pattern testClass="android.graphics.Matrix_DelegateTest" />
- <pattern testClass="com.android.layoutlib.bridge.android.BridgeXmlBlockParserTest" />
- </patterns>
- <RunnerSettings RunnerId="Run" />
- <ConfigurationWrapper RunnerId="Run" />
- <method />
- </configuration>
-</component>
diff --git a/tools/layoutlib/.idea/runConfigurations/Create.xml b/tools/layoutlib/.idea/runConfigurations/Create.xml
deleted file mode 100644
index 536a23f..0000000
--- a/tools/layoutlib/.idea/runConfigurations/Create.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<component name="ProjectRunConfigurationManager">
- <configuration default="false" name="Create" type="Application" factoryName="Application" singleton="true">
- <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
- <option name="MAIN_CLASS_NAME" value="com.android.tools.layoutlib.create.Main" />
- <option name="VM_PARAMETERS" value="-ea" />
- <option name="PROGRAM_PARAMETERS" value="out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar out/target/common/obj/JAVA_LIBRARIES/core-libart_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar out/host/common/obj/JAVA_LIBRARIES/icu4j-icudata-host-jarjar_intermediates/classes-jarjar.jar out/host/common/obj/JAVA_LIBRARIES/icu4j-icutzdata-host-jarjar_intermediates/classes-jarjar.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/javalib.jar" />
- <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/../../../../" />
- <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
- <option name="ALTERNATIVE_JRE_PATH" value="" />
- <option name="ENABLE_SWING_INSPECTOR" value="false" />
- <option name="ENV_VARIABLES" />
- <option name="PASS_PARENT_ENVS" value="true" />
- <module name="create" />
- <envs />
- <RunnerSettings RunnerId="Debug">
- <option name="DEBUG_PORT" value="" />
- <option name="TRANSPORT" value="0" />
- <option name="LOCAL" value="true" />
- </RunnerSettings>
- <RunnerSettings RunnerId="Run" />
- <ConfigurationWrapper RunnerId="Debug" />
- <ConfigurationWrapper RunnerId="Run" />
- <method />
- </configuration>
-</component>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/scopes/scope_settings.xml b/tools/layoutlib/.idea/scopes/scope_settings.xml
deleted file mode 100644
index 922003b..0000000
--- a/tools/layoutlib/.idea/scopes/scope_settings.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<component name="DependencyValidationManager">
- <state>
- <option name="SKIP_IMPORT_STATEMENTS" value="false" />
- </state>
-</component>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/uiDesigner.xml b/tools/layoutlib/.idea/uiDesigner.xml
deleted file mode 100644
index 3b00020..0000000
--- a/tools/layoutlib/.idea/uiDesigner.xml
+++ /dev/null
@@ -1,125 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="Palette2">
- <group name="Swing">
- <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
- <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
- </item>
- <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
- <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
- </item>
- <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
- <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
- </item>
- <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
- <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
- </item>
- <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
- <initial-values>
- <property name="text" value="Button" />
- </initial-values>
- </item>
- <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
- <initial-values>
- <property name="text" value="RadioButton" />
- </initial-values>
- </item>
- <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
- <initial-values>
- <property name="text" value="CheckBox" />
- </initial-values>
- </item>
- <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
- <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
- <initial-values>
- <property name="text" value="Label" />
- </initial-values>
- </item>
- <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
- <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
- <preferred-size width="150" height="-1" />
- </default-constraints>
- </item>
- <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
- <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
- <preferred-size width="150" height="-1" />
- </default-constraints>
- </item>
- <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
- <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
- <preferred-size width="150" height="-1" />
- </default-constraints>
- </item>
- <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
- <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
- <preferred-size width="150" height="50" />
- </default-constraints>
- </item>
- <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
- <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
- <preferred-size width="150" height="50" />
- </default-constraints>
- </item>
- <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
- <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
- <preferred-size width="150" height="50" />
- </default-constraints>
- </item>
- <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
- <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
- </item>
- <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
- <preferred-size width="150" height="50" />
- </default-constraints>
- </item>
- <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
- <preferred-size width="150" height="50" />
- </default-constraints>
- </item>
- <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
- <preferred-size width="150" height="50" />
- </default-constraints>
- </item>
- <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
- <preferred-size width="200" height="200" />
- </default-constraints>
- </item>
- <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
- <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
- <preferred-size width="200" height="200" />
- </default-constraints>
- </item>
- <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
- <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
- </item>
- <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
- </item>
- <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
- <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
- </item>
- <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
- </item>
- <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
- <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
- <preferred-size width="-1" height="20" />
- </default-constraints>
- </item>
- <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
- <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
- </item>
- <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
- <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
- </item>
- </group>
- </component>
-</project>
-
diff --git a/tools/layoutlib/.idea/vcs.xml b/tools/layoutlib/.idea/vcs.xml
deleted file mode 100644
index 9ab281a..0000000
--- a/tools/layoutlib/.idea/vcs.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="VcsDirectoryMappings">
- <mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
- </component>
-</project>
-
diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk
deleted file mode 100644
index 29c933a..0000000
--- a/tools/layoutlib/Android.mk
+++ /dev/null
@@ -1,82 +0,0 @@
-#
-# Copyright (C) 2008 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 := $(my-dir)
-include $(CLEAR_VARS)
-
-#
-# Define rules to build temp_layoutlib.jar, which contains a subset of
-# the classes in framework.jar. The layoutlib_create tool is used to
-# transform the framework jar into the temp_layoutlib jar.
-#
-
-# We need to process the framework classes.jar file, but we can't
-# depend directly on it (private vars won't be inherited correctly).
-# So, we depend on framework's BUILT file.
-built_framework_dep := $(call java-lib-deps,framework)
-built_framework_classes := $(call java-lib-files,framework)
-
-built_oj_dep := $(call java-lib-deps,core-oj)
-built_oj_classes := $(call java-lib-files,core-oj)
-
-built_core_dep := $(call java-lib-deps,core-libart)
-built_core_classes := $(call java-lib-files,core-libart)
-
-built_ext_dep := $(call java-lib-deps,ext)
-built_ext_classes := $(call java-lib-files,ext)
-
-built_icudata_dep := $(call java-lib-deps,icu4j-icudata-host-jarjar,HOST)
-built_icutzdata_dep := $(call java-lib-deps,icu4j-icutzdata-host-jarjar,HOST)
-
-built_layoutlib_create_jar := $(call java-lib-files,layoutlib_create,HOST)
-
-# This is mostly a copy of config/host_java_library.mk
-LOCAL_MODULE := temp_layoutlib
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-LOCAL_MODULE_SUFFIX := $(COMMON_JAVA_PACKAGE_SUFFIX)
-LOCAL_IS_HOST_MODULE := true
-LOCAL_BUILT_MODULE_STEM := classes.jar
-
-#######################################
-include $(BUILD_SYSTEM)/base_rules.mk
-#######################################
-
-$(LOCAL_BUILT_MODULE): $(built_oj_dep) \
- $(built_core_dep) \
- $(built_framework_dep) \
- $(built_ext_dep) \
- $(built_icudata_dep) \
- $(built_icutzdata_dep) \
- $(built_layoutlib_create_jar)
- $(hide) echo "host layoutlib_create: $@"
- $(hide) mkdir -p $(dir $@)
- $(hide) rm -f $@
- $(hide) ls -l $(built_framework_classes)
- $(hide) java -ea -jar $(built_layoutlib_create_jar) \
- $@ \
- $(built_oj_classes) \
- $(built_core_classes) \
- $(built_framework_classes) \
- $(built_ext_classes) \
- $(built_icudata_dep) \
- $(built_icutzdata_dep) \
- $(built_ext_data)
- $(hide) ls -l $(built_framework_classes)
-
-
-#
-# Include the subdir makefiles.
-#
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/layoutlib/README b/tools/layoutlib/README
deleted file mode 100644
index 0fea9bd..0000000
--- a/tools/layoutlib/README
+++ /dev/null
@@ -1,4 +0,0 @@
-Layoutlib is a custom version of the android View framework designed to run inside Eclipse.
-The goal of the library is to provide layout rendering in Eclipse that are very very close to their rendering on devices.
-
-None of the com.android.* or android.* classes in layoutlib run on devices.
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/.classpath b/tools/layoutlib/bridge/.classpath
deleted file mode 100644
index 9c4160c..0000000
--- a/tools/layoutlib/bridge/.classpath
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
- <classpathentry excluding="org/kxml2/io/" kind="src" path="src"/>
- <classpathentry kind="src" path="tests/src"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/layoutlib_api/layoutlib_api-prebuilt.jar" sourcepath="/ANDROID_SRC/tools/base/layoutlib-api/src/main"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_PLAT_SRC/dalvik/libcore/xml/src/main/java"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar" sourcepath="/ANDROID_PLAT_SRC/frameworks/base"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/ninepatch/ninepatch-prebuilt.jar"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/tools-common/tools-common-prebuilt.jar"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/icu4j/icu4j.jar"/>
- <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/sdk-common/sdk-common.jar"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/out/host/common/obj/JAVA_LIBRARIES/guavalib_intermediates/javalib.jar"/>
- <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/tools/layoutlib/bridge/.project b/tools/layoutlib/bridge/.project
deleted file mode 100644
index e36e71b..0000000
--- a/tools/layoutlib/bridge/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>layoutlib_bridge</name>
- <comment></comment>
- <projects>
- </projects>
- <buildSpec>
- <buildCommand>
- <name>org.eclipse.jdt.core.javabuilder</name>
- <arguments>
- </arguments>
- </buildCommand>
- </buildSpec>
- <natures>
- <nature>org.eclipse.jdt.core.javanature</nature>
- </natures>
-</projectDescription>
diff --git a/tools/layoutlib/bridge/.settings/README.txt b/tools/layoutlib/bridge/.settings/README.txt
deleted file mode 100644
index 9120b20..0000000
--- a/tools/layoutlib/bridge/.settings/README.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-Copy this in eclipse project as a .settings folder at the root.
-This ensure proper compilation compliance and warning/error levels.
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/.settings/org.eclipse.jdt.core.prefs b/tools/layoutlib/bridge/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 5381a0e..0000000
--- a/tools/layoutlib/bridge/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,93 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.annotation.nonnull=com.android.annotations.NonNull
-org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=com.android.annotations.NonNullByDefault
-org.eclipse.jdt.core.compiler.annotation.nonnullisdefault=disabled
-org.eclipse.jdt.core.compiler.annotation.nullable=com.android.annotations.Nullable
-org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
-org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
-org.eclipse.jdt.core.compiler.problem.deadCode=warning
-org.eclipse.jdt.core.compiler.problem.deprecation=warning
-org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
-org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
-org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
-org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
-org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
-org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
-org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
-org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
-org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
-org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
-org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
-org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
-org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
-org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
-org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
-org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
-org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
-org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
-org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
-org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
-org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
-org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
-org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
-org.eclipse.jdt.core.compiler.problem.nullReference=error
-org.eclipse.jdt.core.compiler.problem.nullSpecInsufficientInfo=warning
-org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
-org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
-org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
-org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
-org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
-org.eclipse.jdt.core.compiler.problem.potentialNullSpecViolation=error
-org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning
-org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
-org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
-org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
-org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
-org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
-org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
-org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
-org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
-org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
-org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
-org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
-org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
-org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
-org.eclipse.jdt.core.compiler.problem.unclosedCloseable=error
-org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
-org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
-org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
-org.eclipse.jdt.core.compiler.problem.unusedImport=warning
-org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
-org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
-org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
-org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
-org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
-org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/tools/layoutlib/bridge/Android.mk b/tools/layoutlib/bridge/Android.mk
deleted file mode 100644
index 3dd8002..0000000
--- a/tools/layoutlib/bridge/Android.mk
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# Copyright (C) 2008 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 := $(call all-java-files-under,src)
-LOCAL_JAVA_RESOURCE_DIRS := resources
-
-LOCAL_JAVA_LIBRARIES := \
- layoutlib_api-prebuilt \
- tools-common-prebuilt
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- temp_layoutlib \
- ninepatch-prebuilt
-
-LOCAL_MODULE := layoutlib
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-# Build all sub-directories
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/tools/layoutlib/bridge/bridge.iml b/tools/layoutlib/bridge/bridge.iml
deleted file mode 100644
index 85ec3eb..0000000
--- a/tools/layoutlib/bridge/bridge.iml
+++ /dev/null
@@ -1,92 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
- <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/tests/res" type="java-test-resource" />
- <sourceFolder url="file://$MODULE_DIR$/tests/src" isTestSource="true" />
- <sourceFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/src/main/myapplication.widgets" isTestSource="true" />
- <excludeFolder url="file://$MODULE_DIR$/.settings" />
- <excludeFolder url="file://$MODULE_DIR$/bin" />
- <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/.gradle" />
- <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/.idea" />
- <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/generated" />
- <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/assets" />
- <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/dependency-cache" />
- <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/incremental" />
- <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/libs" />
- <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/manifests" />
- <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/res" />
- <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/rs" />
- <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/symbols" />
- <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/gradle" />
- </content>
- <orderEntry type="inheritedJdk" />
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="library" name="layoutlib_api-prebuilt" level="project" />
- <orderEntry type="module-library">
- <library name="ninepatch-prebuilt">
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/ninepatch/ninepatch-prebuilt.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="file://$ANDROID_SRC$/tools/base/ninepatch/src/main/java" />
- </SOURCES>
- </library>
- </orderEntry>
- <orderEntry type="module-library">
- <library name="tools-common-prebuilt">
- <ANNOTATIONS>
- <root url="file://$MODULE_DIR$/.." />
- </ANNOTATIONS>
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/tools-common/tools-common-prebuilt.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/tools-common/tools-common-prebuilt-sources.jar!/" />
- </SOURCES>
- </library>
- </orderEntry>
- <orderEntry type="library" name="framework.jar" level="project" />
- <orderEntry type="module-library" scope="TEST">
- <library name="kxml2-2.3.0">
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/kxml2/kxml2-2.3.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="file://$MODULE_DIR$/../../../../../libcore/xml/src/main/java" />
- </SOURCES>
- </library>
- </orderEntry>
- <orderEntry type="module-library" scope="TEST">
- <library name="guava">
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../../prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$MODULE_DIR$/../../../../../prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0-sources.jar!/" />
- </SOURCES>
- </library>
- </orderEntry>
- <orderEntry type="module-library" scope="TEST">
- <library name="sdk-common">
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/sdk-common/sdk-common.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/sdk-common/sdk-common-sources.jar!/" />
- </SOURCES>
- </library>
- </orderEntry>
- <orderEntry type="library" scope="TEST" name="junit" level="project" />
- <orderEntry type="library" scope="TEST" name="mockito" level="project" />
- <orderEntry type="library" scope="TEST" name="objenesis" level="project" />
- </component>
-</module>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/resources/bars/README b/tools/layoutlib/bridge/resources/bars/README
deleted file mode 100644
index c84ef80..0000000
--- a/tools/layoutlib/bridge/resources/bars/README
+++ /dev/null
@@ -1,8 +0,0 @@
-The directory contains the resources for StatusBar and Navigation Bar.
-
-The resources are not arranged as per the standard resources configuration.
-They are stored per API. However, to prevent duplication of resources, each API
-resource directory is used as a backup for all earlier API levels.
-
-For example, for the back icon for ICS, we search first in v18, where we don't
-find it, and then in v19.
diff --git a/tools/layoutlib/bridge/resources/bars/navigation_bar.xml b/tools/layoutlib/bridge/resources/bars/navigation_bar.xml
deleted file mode 100644
index 55bd1d2..0000000
--- a/tools/layoutlib/bridge/resources/bars/navigation_bar.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2015 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.
- -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <View
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="invisible"/>
- <ImageView
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:scaleType="centerInside"/>
- <View
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_weight="1"
- android:visibility="invisible"/>
- <ImageView
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:scaleType="centerInside"/>
- <View
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_weight="1"
- android:visibility="invisible"/>
- <ImageView
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:scaleType="centerInside"/>
- <View
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="invisible"/>
-</merge>
diff --git a/tools/layoutlib/bridge/resources/bars/navigation_bar600dp.xml b/tools/layoutlib/bridge/resources/bars/navigation_bar600dp.xml
deleted file mode 100644
index e208a0d..0000000
--- a/tools/layoutlib/bridge/resources/bars/navigation_bar600dp.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2015 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.
- -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <View
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:visibility="invisible"/>
- <ImageView
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:scaleType="centerInside"/>
- <View
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:visibility="invisible"/>
- <ImageView
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:scaleType="centerInside"/>
- <View
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:visibility="invisible"/>
- <ImageView
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:scaleType="centerInside"/>
- <View
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:visibility="invisible"/>
-</merge>
diff --git a/tools/layoutlib/bridge/resources/bars/status_bar.xml b/tools/layoutlib/bridge/resources/bars/status_bar.xml
deleted file mode 100644
index 04571e1..0000000
--- a/tools/layoutlib/bridge/resources/bars/status_bar.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"/>
- <!-- The exact size of the wifi icon is specified in order to scale it properly.
- Without scaling, it appeared huge. This is currently, 70% of the actual size. -->
- <ImageView
- android:layout_height="22.4dp"
- android:layout_width="20.65dp"
- android:layout_marginTop="1dp"/>
- <ImageView
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_marginLeft="3dp"
- android:layout_marginRight="5dp"
- android:layout_marginTop="4dp"/>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="4dp"
- android:layout_marginRight="5dp"
- android:gravity="center_vertical"
- android:textSize="16dp"
- android:fontFamily="sans-serif-medium"/>
-</merge>
diff --git a/tools/layoutlib/bridge/resources/bars/title_bar.xml b/tools/layoutlib/bridge/resources/bars/title_bar.xml
deleted file mode 100644
index 76d78d9..0000000
--- a/tools/layoutlib/bridge/resources/bars/title_bar.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-</merge>
diff --git a/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_battery_100.png
deleted file mode 100644
index c920ec4..0000000
--- a/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_battery_100.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_wifi_signal_4_fully.png
deleted file mode 100644
index 6248cfd..0000000
--- a/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_wifi_signal_4_fully.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_battery_100.png
deleted file mode 100644
index 943332e..0000000
--- a/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_battery_100.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_wifi_signal_4_fully.png
deleted file mode 100644
index 441de0c..0000000
--- a/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_wifi_signal_4_fully.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_battery_100.png
deleted file mode 100644
index 36c61e1..0000000
--- a/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_battery_100.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_wifi_signal_4_fully.png
deleted file mode 100644
index 459a1a2..0000000
--- a/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_wifi_signal_4_fully.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_back.png
deleted file mode 100644
index 84e6bc8..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_back.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_home.png
deleted file mode 100644
index 38e4f45..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_home.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_recent.png
deleted file mode 100644
index bf9f300..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_recent.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/hdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v19/hdpi/stat_sys_wifi_signal_4_fully.png
deleted file mode 100644
index 6248cfd..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/hdpi/stat_sys_wifi_signal_4_fully.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/ldrtl-hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-hdpi/ic_sysbar_back.png
deleted file mode 100644
index 782ebfe..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/ldrtl-hdpi/ic_sysbar_back.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/ldrtl-hdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-hdpi/ic_sysbar_recent.png
deleted file mode 100644
index 677b471..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/ldrtl-hdpi/ic_sysbar_recent.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/ldrtl-mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-mdpi/ic_sysbar_back.png
deleted file mode 100644
index a1b8062..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/ldrtl-mdpi/ic_sysbar_back.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/ldrtl-mdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-mdpi/ic_sysbar_recent.png
deleted file mode 100644
index fcdbefe..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/ldrtl-mdpi/ic_sysbar_recent.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/ldrtl-xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-xhdpi/ic_sysbar_back.png
deleted file mode 100644
index 633d864..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/ldrtl-xhdpi/ic_sysbar_back.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/ldrtl-xhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-xhdpi/ic_sysbar_recent.png
deleted file mode 100644
index 4665e2a..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/ldrtl-xhdpi/ic_sysbar_recent.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_back.png
deleted file mode 100644
index a00bc5b..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_back.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_home.png
deleted file mode 100644
index dc3183b..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_home.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_recent.png
deleted file mode 100644
index b07f611..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_recent.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/mdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v19/mdpi/stat_sys_wifi_signal_4_fully.png
deleted file mode 100644
index 441de0c..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/mdpi/stat_sys_wifi_signal_4_fully.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_back.png
deleted file mode 100644
index bd60cd6..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_back.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_home.png
deleted file mode 100644
index c5bc5c9..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_home.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_recent.png
deleted file mode 100644
index f621d9c..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_recent.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/xhdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/stat_sys_wifi_signal_4_fully.png
deleted file mode 100644
index 459a1a2..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/xhdpi/stat_sys_wifi_signal_4_fully.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_back.png
deleted file mode 100644
index 79cfcee..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_back.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_home.png
deleted file mode 100644
index 64f6a22..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_home.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_recent.png
deleted file mode 100644
index 6e0b071..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_recent.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/stat_sys_wifi_signal_4_fully.png
deleted file mode 100644
index 494b005..0000000
--- a/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/stat_sys_wifi_signal_4_fully.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_back.png
deleted file mode 100644
index d2760bb..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_back.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_home.png
deleted file mode 100644
index df43e21..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_home.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_recent.png
deleted file mode 100644
index 6fab1d6..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_recent.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/hdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v21/hdpi/stat_sys_battery_100.png
deleted file mode 100644
index f17189a..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/hdpi/stat_sys_battery_100.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-hdpi/ic_sysbar_back.png
deleted file mode 100644
index 2fcfdde..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-hdpi/ic_sysbar_back.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-mdpi/ic_sysbar_back.png
deleted file mode 100644
index 48708a5..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-mdpi/ic_sysbar_back.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xhdpi/ic_sysbar_back.png
deleted file mode 100644
index 3d73184..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xhdpi/ic_sysbar_back.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xxhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xxhdpi/ic_sysbar_back.png
deleted file mode 100644
index 786935d..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xxhdpi/ic_sysbar_back.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_back.png
deleted file mode 100644
index 1d8c3af..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_back.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_home.png
deleted file mode 100644
index 66de0ec..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_home.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_recent.png
deleted file mode 100644
index 30c65f5..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_recent.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/mdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v21/mdpi/stat_sys_battery_100.png
deleted file mode 100644
index 2a9757d..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/mdpi/stat_sys_battery_100.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_back.png
deleted file mode 100644
index a356285..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_back.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_home.png
deleted file mode 100644
index ba2d0b2..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_home.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_recent.png
deleted file mode 100644
index 94a74b1..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_recent.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_battery_100.png
deleted file mode 100644
index 555bcd9..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_battery_100.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_wifi_signal_4_fully.xml b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_wifi_signal_4_fully.xml
deleted file mode 100644
index 0498b6c..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_wifi_signal_4_fully.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32.0dp"
- android:height="29.5dp"
- android:viewportWidth="26.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M13.000000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/>
-</vector>
diff --git a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_back.png
deleted file mode 100644
index 29da099..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_back.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_home.png
deleted file mode 100644
index 59b32f2..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_home.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_recent.png
deleted file mode 100644
index ba66d27..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_recent.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/stat_sys_battery_100.png
deleted file mode 100644
index 6474aad..0000000
--- a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/stat_sys_battery_100.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_battery_100.png
deleted file mode 100644
index 754cdf6..0000000
--- a/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_battery_100.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_wifi_signal_4_fully.png
deleted file mode 100644
index b5326d2..0000000
--- a/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_wifi_signal_4_fully.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v9/ldpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v9/ldpi/stat_sys_battery_100.png
deleted file mode 100644
index 7023ea7..0000000
--- a/tools/layoutlib/bridge/resources/bars/v9/ldpi/stat_sys_battery_100.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_battery_100.png
deleted file mode 100644
index 17a955d..0000000
--- a/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_battery_100.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_wifi_signal_4_fully.png
deleted file mode 100644
index 19165ab..0000000
--- a/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_wifi_signal_4_fully.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-b.png b/tools/layoutlib/bridge/resources/icons/shadow-b.png
deleted file mode 100644
index 68f4f4b..0000000
--- a/tools/layoutlib/bridge/resources/icons/shadow-b.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-bl.png b/tools/layoutlib/bridge/resources/icons/shadow-bl.png
deleted file mode 100644
index ee7dbe8..0000000
--- a/tools/layoutlib/bridge/resources/icons/shadow-bl.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-br.png b/tools/layoutlib/bridge/resources/icons/shadow-br.png
deleted file mode 100644
index c45ad77..0000000
--- a/tools/layoutlib/bridge/resources/icons/shadow-br.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-l.png b/tools/layoutlib/bridge/resources/icons/shadow-l.png
deleted file mode 100644
index 77d0bd0..0000000
--- a/tools/layoutlib/bridge/resources/icons/shadow-l.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-r.png b/tools/layoutlib/bridge/resources/icons/shadow-r.png
deleted file mode 100644
index 4af7a33..0000000
--- a/tools/layoutlib/bridge/resources/icons/shadow-r.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-tl.png b/tools/layoutlib/bridge/resources/icons/shadow-tl.png
deleted file mode 100644
index 424fb36..0000000
--- a/tools/layoutlib/bridge/resources/icons/shadow-tl.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-tr.png b/tools/layoutlib/bridge/resources/icons/shadow-tr.png
deleted file mode 100644
index 1fd0c77..0000000
--- a/tools/layoutlib/bridge/resources/icons/shadow-tr.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-b.png b/tools/layoutlib/bridge/resources/icons/shadow2-b.png
deleted file mode 100644
index 963973e..0000000
--- a/tools/layoutlib/bridge/resources/icons/shadow2-b.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-bl.png b/tools/layoutlib/bridge/resources/icons/shadow2-bl.png
deleted file mode 100644
index 7612487..0000000
--- a/tools/layoutlib/bridge/resources/icons/shadow2-bl.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-br.png b/tools/layoutlib/bridge/resources/icons/shadow2-br.png
deleted file mode 100644
index 8e20252..0000000
--- a/tools/layoutlib/bridge/resources/icons/shadow2-br.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-l.png b/tools/layoutlib/bridge/resources/icons/shadow2-l.png
deleted file mode 100644
index 2db18a0..0000000
--- a/tools/layoutlib/bridge/resources/icons/shadow2-l.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-r.png b/tools/layoutlib/bridge/resources/icons/shadow2-r.png
deleted file mode 100644
index 8e026f1..0000000
--- a/tools/layoutlib/bridge/resources/icons/shadow2-r.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-tl.png b/tools/layoutlib/bridge/resources/icons/shadow2-tl.png
deleted file mode 100644
index a8045ed..0000000
--- a/tools/layoutlib/bridge/resources/icons/shadow2-tl.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-tr.png b/tools/layoutlib/bridge/resources/icons/shadow2-tr.png
deleted file mode 100644
index 590373c..0000000
--- a/tools/layoutlib/bridge/resources/icons/shadow2-tr.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/src/android/animation/AnimationThread.java b/tools/layoutlib/bridge/src/android/animation/AnimationThread.java
deleted file mode 100644
index ce2aec7..0000000
--- a/tools/layoutlib/bridge/src/android/animation/AnimationThread.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2010 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.animation;
-
-import com.android.ide.common.rendering.api.IAnimationListener;
-import com.android.ide.common.rendering.api.RenderSession;
-import com.android.ide.common.rendering.api.Result;
-import com.android.ide.common.rendering.api.Result.Status;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.RenderSessionImpl;
-
-import android.os.Handler;
-import android.os.Handler_Delegate;
-import android.os.Message;
-
-import java.util.PriorityQueue;
-import java.util.Queue;
-
-/**
- * Abstract animation thread.
- * <p/>
- * This does not actually start an animation, instead it fakes a looper that will play whatever
- * animation is sending messages to its own {@link Handler}.
- * <p/>
- * Classes should implement {@link #preAnimation()} and {@link #postAnimation()}.
- * <p/>
- * If {@link #preAnimation()} does not start an animation somehow then the thread doesn't do
- * anything.
- *
- */
-public abstract class AnimationThread extends Thread {
-
- private static class MessageBundle implements Comparable<MessageBundle> {
- final Handler mTarget;
- final Message mMessage;
- final long mUptimeMillis;
-
- MessageBundle(Handler target, Message message, long uptimeMillis) {
- mTarget = target;
- mMessage = message;
- mUptimeMillis = uptimeMillis;
- }
-
- @Override
- public int compareTo(MessageBundle bundle) {
- if (mUptimeMillis < bundle.mUptimeMillis) {
- return -1;
- }
- return 1;
- }
- }
-
- private final RenderSessionImpl mSession;
-
- private Queue<MessageBundle> mQueue = new PriorityQueue<MessageBundle>();
- private final IAnimationListener mListener;
-
- public AnimationThread(RenderSessionImpl scene, String threadName,
- IAnimationListener listener) {
- super(threadName);
- mSession = scene;
- mListener = listener;
- }
-
- public abstract Result preAnimation();
- public abstract void postAnimation();
-
- @Override
- public void run() {
- Bridge.prepareThread();
- try {
- /* FIXME: The ANIMATION_FRAME message no longer exists. Instead, the
- * animation timing loop is completely based on a Choreographer objects
- * that schedules animation and drawing frames. The animation handler is
- * no longer even a handler; it is just a Runnable enqueued on the Choreographer.
- Handler_Delegate.setCallback(new IHandlerCallback() {
- @Override
- public void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) {
- if (msg.what == ValueAnimator.ANIMATION_START ||
- msg.what == ValueAnimator.ANIMATION_FRAME) {
- mQueue.add(new MessageBundle(handler, msg, uptimeMillis));
- } else {
- // just ignore.
- }
- }
- });
- */
-
- // call out to the pre-animation work, which should start an animation or more.
- Result result = preAnimation();
- if (result.isSuccess() == false) {
- mListener.done(result);
- }
-
- // loop the animation
- RenderSession session = mSession.getSession();
- do {
- // check early.
- if (mListener.isCanceled()) {
- break;
- }
-
- // get the next message.
- MessageBundle bundle = mQueue.poll();
- if (bundle == null) {
- break;
- }
-
- // sleep enough for this bundle to be on time
- long currentTime = System.currentTimeMillis();
- if (currentTime < bundle.mUptimeMillis) {
- try {
- sleep(bundle.mUptimeMillis - currentTime);
- } catch (InterruptedException e) {
- // FIXME log/do something/sleep again?
- e.printStackTrace();
- }
- }
-
- // check after sleeping.
- if (mListener.isCanceled()) {
- break;
- }
-
- // ready to do the work, acquire the scene.
- result = mSession.acquire(250);
- if (result.isSuccess() == false) {
- mListener.done(result);
- return;
- }
-
- // process the bundle. If the animation is not finished, this will enqueue
- // the next message, so mQueue will have another one.
- try {
- // check after acquiring in case it took a while.
- if (mListener.isCanceled()) {
- break;
- }
-
- bundle.mTarget.handleMessage(bundle.mMessage);
- if (mSession.render(false /*freshRender*/).isSuccess()) {
- mListener.onNewFrame(session);
- }
- } finally {
- mSession.release();
- }
- } while (mListener.isCanceled() == false && mQueue.size() > 0);
-
- mListener.done(Status.SUCCESS.createResult());
-
- } catch (Throwable throwable) {
- // can't use Bridge.getLog() as the exception might be thrown outside
- // of an acquire/release block.
- mListener.done(Status.ERROR_UNKNOWN.createResult("Error playing animation", throwable));
-
- } finally {
- postAnimation();
- Handler_Delegate.setCallback(null);
- Bridge.cleanupThread();
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
deleted file mode 100644
index 28a489a..0000000
--- a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2010 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.animation;
-
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Delegate implementing the native methods of android.animation.PropertyValuesHolder
- *
- * Through the layoutlib_create tool, the original native methods of PropertyValuesHolder have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
- * around to map int to instance of the delegate.
- *
- * The main goal of this class' methods are to provide a native way to access setters and getters
- * on some object. We override these methods to use reflection since the original reflection
- * implementation of the PropertyValuesHolder won't be able to access protected methods.
- *
- */
-/*package*/
-@SuppressWarnings("unused")
-class PropertyValuesHolder_Delegate {
- // This code is copied from android.animation.PropertyValuesHolder and must be kept in sync
- // We try several different types when searching for appropriate setter/getter functions.
- // The caller may have supplied values in a type that does not match the setter/getter
- // functions (such as the integers 0 and 1 to represent floating point values for alpha).
- // Also, the use of generics in constructors means that we end up with the Object versions
- // of primitive types (Float vs. float). But most likely, the setter/getter functions
- // will take primitive types instead.
- // So we supply an ordered array of other types to try before giving up.
- private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class,
- Double.class, Integer.class};
- private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class,
- Float.class, Double.class};
-
- private static final Object sMethodIndexLock = new Object();
- private static final Map<Long, Method> ID_TO_METHOD = new HashMap<Long, Method>();
- private static final Map<String, Long> METHOD_NAME_TO_ID = new HashMap<String, Long>();
- private static long sNextId = 1;
-
- private static long registerMethod(Class<?> targetClass, String methodName, Class[] types,
- int nArgs) {
- // Encode the number of arguments in the method name
- String methodIndexName = String.format("%1$s.%2$s#%3$d", targetClass.getSimpleName(),
- methodName, nArgs);
- synchronized (sMethodIndexLock) {
- Long methodId = METHOD_NAME_TO_ID.get(methodIndexName);
-
- if (methodId != null) {
- // The method was already registered
- return methodId;
- }
-
- Class[] args = new Class[nArgs];
- Method method = null;
- for (Class typeVariant : types) {
- for (int i = 0; i < nArgs; i++) {
- args[i] = typeVariant;
- }
- try {
- method = targetClass.getDeclaredMethod(methodName, args);
- } catch (NoSuchMethodException ignore) {
- }
- }
-
- if (method != null) {
- methodId = sNextId++;
- ID_TO_METHOD.put(methodId, method);
- METHOD_NAME_TO_ID.put(methodIndexName, methodId);
-
- return methodId;
- }
- }
-
- // Method not found
- return 0;
- }
-
- private static void callMethod(Object target, long methodID, Object... args) {
- Method method = ID_TO_METHOD.get(methodID);
- assert method != null;
-
- try {
- method.setAccessible(true);
- method.invoke(target, args);
- } catch (IllegalAccessException e) {
- Bridge.getLog().error(null, "Unable to update property during animation", e, null);
- } catch (InvocationTargetException e) {
- Bridge.getLog().error(null, "Unable to update property during animation", e, null);
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static long nGetIntMethod(Class<?> targetClass, String methodName) {
- return nGetMultipleIntMethod(targetClass, methodName, 1);
- }
-
- @LayoutlibDelegate
- /*package*/ static long nGetFloatMethod(Class<?> targetClass, String methodName) {
- return nGetMultipleFloatMethod(targetClass, methodName, 1);
- }
-
- @LayoutlibDelegate
- /*package*/ static long nGetMultipleIntMethod(Class<?> targetClass, String methodName,
- int numParams) {
- return registerMethod(targetClass, methodName, INTEGER_VARIANTS, numParams);
- }
-
- @LayoutlibDelegate
- /*package*/ static long nGetMultipleFloatMethod(Class<?> targetClass, String methodName,
- int numParams) {
- return registerMethod(targetClass, methodName, FLOAT_VARIANTS, numParams);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nCallIntMethod(Object target, long methodID, int arg) {
- callMethod(target, methodID, arg);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nCallFloatMethod(Object target, long methodID, float arg) {
- callMethod(target, methodID, arg);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nCallTwoIntMethod(Object target, long methodID, int arg1,
- int arg2) {
- callMethod(target, methodID, arg1, arg2);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nCallFourIntMethod(Object target, long methodID, int arg1,
- int arg2, int arg3, int arg4) {
- callMethod(target, methodID, arg1, arg2, arg3, arg4);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nCallMultipleIntMethod(Object target, long methodID,
- int[] args) {
- assert args != null;
-
- // Box parameters
- Object[] params = new Object[args.length];
- for (int i = 0; i < args.length; i++) {
- params[i] = args;
- }
- callMethod(target, methodID, params);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nCallTwoFloatMethod(Object target, long methodID, float arg1,
- float arg2) {
- callMethod(target, methodID, arg1, arg2);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nCallFourFloatMethod(Object target, long methodID, float arg1,
- float arg2, float arg3, float arg4) {
- callMethod(target, methodID, arg1, arg2, arg3, arg4);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nCallMultipleFloatMethod(Object target, long methodID,
- float[] args) {
- assert args != null;
-
- // Box parameters
- Object[] params = new Object[args.length];
- for (int i = 0; i < args.length; i++) {
- params[i] = args;
- }
- callMethod(target, methodID, params);
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java b/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java
deleted file mode 100644
index f7654ce..0000000
--- a/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2010 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;
-
-import com.android.ide.common.rendering.api.LayoutlibCallback;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.content.Context;
-import android.os.Bundle;
-
-/**
- * Delegate used to provide new implementation of a select few methods of {@link Fragment}
- *
- * Through the layoutlib_create tool, the original methods of Fragment have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * The methods being re-implemented are the ones responsible for instantiating Fragment objects.
- * Because the classes of these objects are found in the project, these methods need access to
- * {@link LayoutlibCallback} object. They are however static methods, so the callback is set
- * before the inflation through {@link #setLayoutlibCallback(LayoutlibCallback)}.
- */
-public class Fragment_Delegate {
-
- private static LayoutlibCallback sLayoutlibCallback;
-
- /**
- * Sets the current {@link LayoutlibCallback} to be used to instantiate classes coming
- * from the project being rendered.
- */
- public static void setLayoutlibCallback(LayoutlibCallback layoutlibCallback) {
- sLayoutlibCallback = layoutlibCallback;
- }
-
- /**
- * Like {@link #instantiate(Context, String, Bundle)} but with a null
- * argument Bundle.
- */
- @LayoutlibDelegate
- /*package*/ static Fragment instantiate(Context context, String fname) {
- return instantiate(context, fname, null);
- }
-
- /**
- * Create a new instance of a Fragment with the given class name. This is
- * the same as calling its empty constructor.
- *
- * @param context The calling context being used to instantiate the fragment.
- * This is currently just used to get its ClassLoader.
- * @param fname The class name of the fragment to instantiate.
- * @param args Bundle of arguments to supply to the fragment, which it
- * can retrieve with {@link Fragment#getArguments()}. May be null.
- * @return Returns a new fragment instance.
- * @throws Fragment.InstantiationException If there is a failure in instantiating
- * the given fragment class. This is a runtime exception; it is not
- * normally expected to happen.
- */
- @LayoutlibDelegate
- /*package*/ static Fragment instantiate(Context context, String fname, Bundle args) {
- try {
- if (sLayoutlibCallback != null) {
- Fragment f = (Fragment) sLayoutlibCallback.loadView(fname,
- new Class[0], new Object[0]);
-
- if (args != null) {
- args.setClassLoader(f.getClass().getClassLoader());
- f.mArguments = args;
- }
- return f;
- }
-
- return null;
- } catch (ClassNotFoundException e) {
- throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname
- + ": make sure class name exists, is public, and has an"
- + " empty constructor that is public", e);
- } catch (java.lang.InstantiationException e) {
- throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname
- + ": make sure class name exists, is public, and has an"
- + " empty constructor that is public", e);
- } catch (IllegalAccessException e) {
- throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname
- + ": make sure class name exists, is public, and has an"
- + " empty constructor that is public", e);
- } catch (Exception e) {
- throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname
- + ": make sure class name exists, is public, and has an"
- + " empty constructor that is public", e);
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/app/SystemServiceRegistry_Accessor.java b/tools/layoutlib/bridge/src/android/app/SystemServiceRegistry_Accessor.java
deleted file mode 100644
index 591aee0..0000000
--- a/tools/layoutlib/bridge/src/android/app/SystemServiceRegistry_Accessor.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app;
-
-/**
- * Class to allow accessing {@link SystemServiceRegistry#getSystemServiceName}
- */
-public class SystemServiceRegistry_Accessor {
- /**
- * Gets the name of the system-level service that is represented by the specified class.
- */
- public static String getSystemServiceName(Class<?> serviceClass) {
- return SystemServiceRegistry.getSystemServiceName(serviceClass);
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java
deleted file mode 100644
index b4d5288..0000000
--- a/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2014 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.content.res;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.util.SparseArray;
-
-/**
- * Delegate used to provide implementation of a select few native methods of {@link AssetManager}
- * <p/>
- * Through the layoutlib_create tool, the original native methods of AssetManager have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class AssetManager_Delegate {
-
- @LayoutlibDelegate
- /*package*/ static long newTheme(AssetManager manager) {
- return Resources_Theme_Delegate.getDelegateManager()
- .addNewDelegate(new Resources_Theme_Delegate());
- }
-
- @LayoutlibDelegate
- /*package*/ static void deleteTheme(AssetManager manager, long theme) {
- Resources_Theme_Delegate.getDelegateManager().removeJavaReferenceFor(theme);
- }
-
- @LayoutlibDelegate
- /*package*/ static SparseArray<String> getAssignedPackageIdentifiers(AssetManager manager) {
- return new SparseArray<>();
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java b/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java
deleted file mode 100644
index 2691e56..0000000
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2008 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.content.res;
-
-import com.android.ide.common.rendering.api.AssetRepository;
-import com.android.layoutlib.bridge.Bridge;
-
-public class BridgeAssetManager extends AssetManager {
-
- private AssetRepository mAssetRepository;
-
- /**
- * This initializes the static field {@link AssetManager#sSystem} which is used
- * by methods who get a global asset manager using {@link AssetManager#getSystem()}.
- * <p/>
- * They will end up using our bridge asset manager.
- * <p/>
- * {@link Bridge} calls this method after setting up a new bridge.
- */
- public static AssetManager initSystem() {
- if (!(AssetManager.sSystem instanceof BridgeAssetManager)) {
- // Note that AssetManager() creates a system AssetManager and we override it
- // with our BridgeAssetManager.
- AssetManager.sSystem = new BridgeAssetManager();
- AssetManager.sSystem.makeStringBlocks(null);
- }
- return AssetManager.sSystem;
- }
-
- /**
- * Clears the static {@link AssetManager#sSystem} to make sure we don't leave objects
- * around that would prevent us from unloading the library.
- */
- public static void clearSystem() {
- AssetManager.sSystem = null;
- }
-
- public void setAssetRepository(AssetRepository assetRepository) {
- mAssetRepository = assetRepository;
- }
-
- public AssetRepository getAssetRepository() {
- return mAssetRepository;
- }
-
- public BridgeAssetManager() {
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
deleted file mode 100644
index cda8e6a..0000000
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
+++ /dev/null
@@ -1,991 +0,0 @@
-/*
- * Copyright (C) 2008 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.content.res;
-
-import com.android.ide.common.rendering.api.ArrayResourceValue;
-import com.android.ide.common.rendering.api.AttrResourceValue;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.rendering.api.StyleResourceValue;
-import com.android.internal.util.XmlUtils;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.impl.ResourceHelper;
-import com.android.resources.ResourceType;
-
-import android.annotation.Nullable;
-import android.content.res.Resources.NotFoundException;
-import android.content.res.Resources.Theme;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
-import android.view.LayoutInflater_Delegate;
-import android.view.ViewGroup.LayoutParams;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Map;
-
-import static android.util.TypedValue.TYPE_ATTRIBUTE;
-import static android.util.TypedValue.TYPE_DIMENSION;
-import static android.util.TypedValue.TYPE_FLOAT;
-import static android.util.TypedValue.TYPE_INT_BOOLEAN;
-import static android.util.TypedValue.TYPE_INT_COLOR_ARGB4;
-import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
-import static android.util.TypedValue.TYPE_INT_COLOR_RGB4;
-import static android.util.TypedValue.TYPE_INT_COLOR_RGB8;
-import static android.util.TypedValue.TYPE_INT_DEC;
-import static android.util.TypedValue.TYPE_INT_HEX;
-import static android.util.TypedValue.TYPE_NULL;
-import static android.util.TypedValue.TYPE_REFERENCE;
-import static android.util.TypedValue.TYPE_STRING;
-import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
-import static com.android.SdkConstants.PREFIX_THEME_REF;
-import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_EMPTY;
-import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_NULL;
-import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_UNDEFINED;
-
-/**
- * Custom implementation of TypedArray to handle non compiled resources.
- */
-public final class BridgeTypedArray extends TypedArray {
-
- private final Resources mBridgeResources;
- private final BridgeContext mContext;
- private final boolean mPlatformFile;
-
- private final ResourceValue[] mResourceData;
- private final String[] mNames;
- private final boolean[] mIsFramework;
-
- // Contains ids that are @empty. We still store null in mResourceData for that index, since we
- // want to save on the check against empty, each time a resource value is requested.
- @Nullable
- private int[] mEmptyIds;
-
- public BridgeTypedArray(Resources resources, BridgeContext context, int len,
- boolean platformFile) {
- super(resources);
- mBridgeResources = resources;
- mContext = context;
- mPlatformFile = platformFile;
- mResourceData = new ResourceValue[len];
- mNames = new String[len];
- mIsFramework = new boolean[len];
- }
-
- /**
- * A bridge-specific method that sets a value in the type array
- * @param index the index of the value in the TypedArray
- * @param name the name of the attribute
- * @param isFramework whether the attribute is in the android namespace.
- * @param value the value of the attribute
- */
- public void bridgeSetValue(int index, String name, boolean isFramework, ResourceValue value) {
- mResourceData[index] = value;
- mNames[index] = name;
- mIsFramework[index] = isFramework;
- }
-
- /**
- * Seals the array after all calls to
- * {@link #bridgeSetValue(int, String, boolean, ResourceValue)} have been done.
- * <p/>This allows to compute the list of non default values, permitting
- * {@link #getIndexCount()} to return the proper value.
- */
- public void sealArray() {
- // fills TypedArray.mIndices which is used to implement getIndexCount/getIndexAt
- // first count the array size
- int count = 0;
- ArrayList<Integer> emptyIds = null;
- for (int i = 0; i < mResourceData.length; i++) {
- ResourceValue data = mResourceData[i];
- if (data != null) {
- String dataValue = data.getValue();
- if (REFERENCE_NULL.equals(dataValue) || REFERENCE_UNDEFINED.equals(dataValue)) {
- mResourceData[i] = null;
- } else if (REFERENCE_EMPTY.equals(dataValue)) {
- mResourceData[i] = null;
- if (emptyIds == null) {
- emptyIds = new ArrayList<Integer>(4);
- }
- emptyIds.add(i);
- } else {
- count++;
- }
- }
- }
-
- if (emptyIds != null) {
- mEmptyIds = new int[emptyIds.size()];
- for (int i = 0; i < emptyIds.size(); i++) {
- mEmptyIds[i] = emptyIds.get(i);
- }
- }
-
- // allocate the table with an extra to store the size
- mIndices = new int[count+1];
- mIndices[0] = count;
-
- // fill the array with the indices.
- int index = 1;
- for (int i = 0 ; i < mResourceData.length ; i++) {
- if (mResourceData[i] != null) {
- mIndices[index++] = i;
- }
- }
- }
-
- /**
- * Set the theme to be used for inflating drawables.
- */
- public void setTheme(Theme theme) {
- mTheme = theme;
- }
-
- /**
- * Return the number of values in this array.
- */
- @Override
- public int length() {
- return mResourceData.length;
- }
-
- /**
- * Return the Resources object this array was loaded from.
- */
- @Override
- public Resources getResources() {
- return mBridgeResources;
- }
-
- /**
- * Retrieve the styled string value for the attribute at <var>index</var>.
- *
- * @param index Index of attribute to retrieve.
- *
- * @return CharSequence holding string data. May be styled. Returns
- * null if the attribute is not defined.
- */
- @Override
- public CharSequence getText(int index) {
- // FIXME: handle styled strings!
- return getString(index);
- }
-
- /**
- * Retrieve the string value for the attribute at <var>index</var>.
- *
- * @param index Index of attribute to retrieve.
- *
- * @return String holding string data. Any styling information is
- * removed. Returns null if the attribute is not defined.
- */
- @Override
- public String getString(int index) {
- if (!hasValue(index)) {
- return null;
- }
- // As unfortunate as it is, it's possible to use enums with all attribute formats,
- // not just integers/enums. So, we need to search the enums always. In case
- // enums are used, the returned value is an integer.
- Integer v = resolveEnumAttribute(index);
- return v == null ? mResourceData[index].getValue() : String.valueOf((int) v);
- }
-
- /**
- * Retrieve the boolean value for the attribute at <var>index</var>.
- *
- * @param index Index of attribute to retrieve.
- * @param defValue Value to return if the attribute is not defined.
- *
- * @return Attribute boolean value, or defValue if not defined.
- */
- @Override
- public boolean getBoolean(int index, boolean defValue) {
- String s = getString(index);
- return s == null ? defValue : XmlUtils.convertValueToBoolean(s, defValue);
-
- }
-
- /**
- * Retrieve the integer value for the attribute at <var>index</var>.
- *
- * @param index Index of attribute to retrieve.
- * @param defValue Value to return if the attribute is not defined.
- *
- * @return Attribute int value, or defValue if not defined.
- */
- @Override
- public int getInt(int index, int defValue) {
- String s = getString(index);
- try {
- return convertValueToInt(s, defValue);
- } catch (NumberFormatException e) {
- Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
- String.format("\"%1$s\" in attribute \"%2$s\" is not a valid integer",
- s, mNames[index]),
- null);
- }
- return defValue;
- }
-
- /**
- * Retrieve the float value for the attribute at <var>index</var>.
- *
- * @param index Index of attribute to retrieve.
- *
- * @return Attribute float value, or defValue if not defined..
- */
- @Override
- public float getFloat(int index, float defValue) {
- String s = getString(index);
- try {
- if (s != null) {
- return Float.parseFloat(s);
- }
- } catch (NumberFormatException e) {
- Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
- String.format("\"%1$s\" in attribute \"%2$s\" cannot be converted to float.",
- s, mNames[index]),
- null);
- }
- return defValue;
- }
-
- /**
- * Retrieve the color value for the attribute at <var>index</var>. If
- * the attribute references a color resource holding a complex
- * {@link android.content.res.ColorStateList}, then the default color from
- * the set is returned.
- *
- * @param index Index of attribute to retrieve.
- * @param defValue Value to return if the attribute is not defined or
- * not a resource.
- *
- * @return Attribute color value, or defValue if not defined.
- */
- @Override
- public int getColor(int index, int defValue) {
- if (index < 0 || index >= mResourceData.length) {
- return defValue;
- }
-
- if (mResourceData[index] == null) {
- return defValue;
- }
-
- ColorStateList colorStateList = ResourceHelper.getColorStateList(
- mResourceData[index], mContext);
- if (colorStateList != null) {
- return colorStateList.getDefaultColor();
- }
-
- return defValue;
- }
-
- @Override
- public ColorStateList getColorStateList(int index) {
- if (!hasValue(index)) {
- return null;
- }
-
- return ResourceHelper.getColorStateList(mResourceData[index], mContext);
- }
-
- @Override
- public ComplexColor getComplexColor(int index) {
- if (!hasValue(index)) {
- return null;
- }
-
- return ResourceHelper.getComplexColor(mResourceData[index], mContext);
- }
-
- /**
- * Retrieve the integer value for the attribute at <var>index</var>.
- *
- * @param index Index of attribute to retrieve.
- * @param defValue Value to return if the attribute is not defined or
- * not a resource.
- *
- * @return Attribute integer value, or defValue if not defined.
- */
- @Override
- public int getInteger(int index, int defValue) {
- return getInt(index, defValue);
- }
-
- /**
- * Retrieve a dimensional unit attribute at <var>index</var>. Unit
- * conversions are based on the current {@link DisplayMetrics}
- * associated with the resources this {@link TypedArray} object
- * came from.
- *
- * @param index Index of attribute to retrieve.
- * @param defValue Value to return if the attribute is not defined or
- * not a resource.
- *
- * @return Attribute dimension value multiplied by the appropriate
- * metric, or defValue if not defined.
- *
- * @see #getDimensionPixelOffset
- * @see #getDimensionPixelSize
- */
- @Override
- public float getDimension(int index, float defValue) {
- String s = getString(index);
- if (s == null) {
- return defValue;
- }
- // Check if the value is a magic constant that doesn't require a unit.
- try {
- int i = Integer.parseInt(s);
- if (i == LayoutParams.MATCH_PARENT || i == LayoutParams.WRAP_CONTENT) {
- return i;
- }
- } catch (NumberFormatException ignored) {
- // pass
- }
-
- if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true)) {
- return mValue.getDimension(mBridgeResources.getDisplayMetrics());
- }
-
- return defValue;
- }
-
- /**
- * Retrieve a dimensional unit attribute at <var>index</var> for use
- * as an offset in raw pixels. This is the same as
- * {@link #getDimension}, except the returned value is converted to
- * integer pixels for you. An offset conversion involves simply
- * truncating the base value to an integer.
- *
- * @param index Index of attribute to retrieve.
- * @param defValue Value to return if the attribute is not defined or
- * not a resource.
- *
- * @return Attribute dimension value multiplied by the appropriate
- * metric and truncated to integer pixels, or defValue if not defined.
- *
- * @see #getDimension
- * @see #getDimensionPixelSize
- */
- @Override
- public int getDimensionPixelOffset(int index, int defValue) {
- return (int) getDimension(index, defValue);
- }
-
- /**
- * Retrieve a dimensional unit attribute at <var>index</var> for use
- * as a size in raw pixels. This is the same as
- * {@link #getDimension}, except the returned value is converted to
- * integer pixels for use as a size. A size conversion involves
- * rounding the base value, and ensuring that a non-zero base value
- * is at least one pixel in size.
- *
- * @param index Index of attribute to retrieve.
- * @param defValue Value to return if the attribute is not defined or
- * not a resource.
- *
- * @return Attribute dimension value multiplied by the appropriate
- * metric and truncated to integer pixels, or defValue if not defined.
- *
- * @see #getDimension
- * @see #getDimensionPixelOffset
- */
- @Override
- public int getDimensionPixelSize(int index, int defValue) {
- try {
- return getDimension(index, null);
- } catch (RuntimeException e) {
- String s = getString(index);
-
- if (s != null) {
- // looks like we were unable to resolve the dimension value
- Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
- String.format("\"%1$s\" in attribute \"%2$s\" is not a valid format.",
- s, mNames[index]), null);
- }
-
- return defValue;
- }
- }
-
- /**
- * Special version of {@link #getDimensionPixelSize} for retrieving
- * {@link android.view.ViewGroup}'s layout_width and layout_height
- * attributes. This is only here for performance reasons; applications
- * should use {@link #getDimensionPixelSize}.
- *
- * @param index Index of the attribute to retrieve.
- * @param name Textual name of attribute for error reporting.
- *
- * @return Attribute dimension value multiplied by the appropriate
- * metric and truncated to integer pixels.
- */
- @Override
- public int getLayoutDimension(int index, String name) {
- try {
- // this will throw an exception if not found.
- return getDimension(index, name);
- } catch (RuntimeException e) {
-
- if (LayoutInflater_Delegate.sIsInInclude) {
- throw new RuntimeException("Layout Dimension '" + name + "' not found.");
- }
-
- Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
- "You must supply a " + name + " attribute.", null);
-
- return 0;
- }
- }
-
- @Override
- public int getLayoutDimension(int index, int defValue) {
- return getDimensionPixelSize(index, defValue);
- }
-
- /** @param name attribute name, used for error reporting. */
- private int getDimension(int index, @Nullable String name) {
- String s = getString(index);
- if (s == null) {
- if (name != null) {
- throw new RuntimeException("Attribute '" + name + "' not found");
- }
- throw new RuntimeException();
- }
- // Check if the value is a magic constant that doesn't require a unit.
- try {
- int i = Integer.parseInt(s);
- if (i == LayoutParams.MATCH_PARENT || i == LayoutParams.WRAP_CONTENT) {
- return i;
- }
- } catch (NumberFormatException ignored) {
- // pass
- }
- if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true)) {
- float f = mValue.getDimension(mBridgeResources.getDisplayMetrics());
-
- final int res = (int)(f+0.5f);
- if (res != 0) return res;
- if (f == 0) return 0;
- if (f > 0) return 1;
- }
-
- throw new RuntimeException();
- }
-
- /**
- * Retrieve a fractional unit attribute at <var>index</var>.
- *
- * @param index Index of attribute to retrieve.
- * @param base The base value of this fraction. In other words, a
- * standard fraction is multiplied by this value.
- * @param pbase The parent base value of this fraction. In other
- * words, a parent fraction (nn%p) is multiplied by this
- * value.
- * @param defValue Value to return if the attribute is not defined or
- * not a resource.
- *
- * @return Attribute fractional value multiplied by the appropriate
- * base value, or defValue if not defined.
- */
- @Override
- public float getFraction(int index, int base, int pbase, float defValue) {
- String value = getString(index);
- if (value == null) {
- return defValue;
- }
-
- if (ResourceHelper.parseFloatAttribute(mNames[index], value, mValue, false)) {
- return mValue.getFraction(base, pbase);
- }
-
- // looks like we were unable to resolve the fraction value
- Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
- String.format(
- "\"%1$s\" in attribute \"%2$s\" cannot be converted to a fraction.",
- value, mNames[index]), null);
-
- return defValue;
- }
-
- /**
- * Retrieve the resource identifier for the attribute at
- * <var>index</var>. Note that attribute resource as resolved when
- * the overall {@link TypedArray} object is retrieved. As a
- * result, this function will return the resource identifier of the
- * final resource value that was found, <em>not</em> necessarily the
- * original resource that was specified by the attribute.
- *
- * @param index Index of attribute to retrieve.
- * @param defValue Value to return if the attribute is not defined or
- * not a resource.
- *
- * @return Attribute resource identifier, or defValue if not defined.
- */
- @Override
- public int getResourceId(int index, int defValue) {
- if (index < 0 || index >= mResourceData.length) {
- return defValue;
- }
-
- // get the Resource for this index
- ResourceValue resValue = mResourceData[index];
-
- // no data, return the default value.
- if (resValue == null) {
- return defValue;
- }
-
- // check if this is a style resource
- if (resValue instanceof StyleResourceValue) {
- // get the id that will represent this style.
- return mContext.getDynamicIdByStyle((StyleResourceValue)resValue);
- }
-
- // if the attribute was a reference to a resource, and not a declaration of an id (@+id),
- // then the xml attribute value was "resolved" which leads us to a ResourceValue with a
- // valid getType() and getName() returning a resource name.
- // (and getValue() returning null!). We need to handle this!
- if (resValue.getResourceType() != null) {
- // if this is a framework id
- if (mPlatformFile || resValue.isFramework()) {
- // look for idName in the android R classes
- return mContext.getFrameworkResourceValue(
- resValue.getResourceType(), resValue.getName(), defValue);
- }
-
- // look for idName in the project R class.
- return mContext.getProjectResourceValue(
- resValue.getResourceType(), resValue.getName(), defValue);
- }
-
- // else, try to get the value, and resolve it somehow.
- String value = resValue.getValue();
- if (value == null) {
- return defValue;
- }
- value = value.trim();
-
- // if the value is just an integer, return it.
- try {
- int i = Integer.parseInt(value);
- if (Integer.toString(i).equals(value)) {
- return i;
- }
- } catch (NumberFormatException e) {
- // pass
- }
-
- if (value.startsWith("#")) {
- // this looks like a color, do not try to parse it
- return defValue;
- }
-
- // Handle the @id/<name>, @+id/<name> and @android:id/<name>
- // We need to return the exact value that was compiled (from the various R classes),
- // as these values can be reused internally with calls to findViewById().
- // There's a trick with platform layouts that not use "android:" but their IDs are in
- // fact in the android.R and com.android.internal.R classes.
- // The field mPlatformFile will indicate that all IDs are to be looked up in the android R
- // classes exclusively.
-
- // if this is a reference to an id, find it.
- if (value.startsWith("@id/") || value.startsWith("@+") ||
- value.startsWith("@android:id/")) {
-
- int pos = value.indexOf('/');
- String idName = value.substring(pos + 1);
- boolean create = value.startsWith("@+");
- boolean isFrameworkId =
- mPlatformFile || value.startsWith("@android") || value.startsWith("@+android");
-
- // Look for the idName in project or android R class depending on isPlatform.
- if (create) {
- Integer idValue;
- if (isFrameworkId) {
- idValue = Bridge.getResourceId(ResourceType.ID, idName);
- } else {
- idValue = mContext.getLayoutlibCallback().getResourceId(ResourceType.ID, idName);
- }
- return idValue == null ? defValue : idValue;
- }
- // This calls the same method as in if(create), but doesn't create a dynamic id, if
- // one is not found.
- if (isFrameworkId) {
- return mContext.getFrameworkResourceValue(ResourceType.ID, idName, defValue);
- } else {
- return mContext.getProjectResourceValue(ResourceType.ID, idName, defValue);
- }
- }
- else if (value.startsWith("@aapt:_aapt")) {
- return mContext.getLayoutlibCallback().getResourceId(ResourceType.AAPT, value);
- }
-
- // not a direct id valid reference. First check if it's an enum (this is a corner case
- // for attributes that have a reference|enum type), then fallback to resolve
- // as an ID without prefix.
- Integer enumValue = resolveEnumAttribute(index);
- if (enumValue != null) {
- return enumValue;
- }
-
- // Ok, not an enum, resolve as an ID
- Integer idValue;
-
- if (resValue.isFramework()) {
- idValue = Bridge.getResourceId(resValue.getResourceType(),
- resValue.getName());
- } else {
- idValue = mContext.getLayoutlibCallback().getResourceId(
- resValue.getResourceType(), resValue.getName());
- }
-
- if (idValue != null) {
- return idValue;
- }
-
- Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE,
- String.format(
- "Unable to resolve id \"%1$s\" for attribute \"%2$s\"", value, mNames[index]),
- resValue);
-
- return defValue;
- }
-
- @Override
- public int getThemeAttributeId(int index, int defValue) {
- // TODO: Get the right Theme Attribute ID to enable caching of the drawables.
- return defValue;
- }
-
- /**
- * Retrieve the Drawable for the attribute at <var>index</var>. This
- * gets the resource ID of the selected attribute, and uses
- * {@link Resources#getDrawable Resources.getDrawable} of the owning
- * Resources object to retrieve its Drawable.
- *
- * @param index Index of attribute to retrieve.
- *
- * @return Drawable for the attribute, or null if not defined.
- */
- @Override
- public Drawable getDrawable(int index) {
- if (!hasValue(index)) {
- return null;
- }
-
- ResourceValue value = mResourceData[index];
- return ResourceHelper.getDrawable(value, mContext, mTheme);
- }
-
-
- /**
- * Retrieve the Typeface for the attribute at <var>index</var>.
- * @param index Index of attribute to retrieve.
- *
- * @return Typeface for the attribute, or null if not defined.
- */
- @Override
- public Typeface getFont(int index) {
- if (!hasValue(index)) {
- return null;
- }
-
- ResourceValue value = mResourceData[index];
- return ResourceHelper.getFont(value, mContext, mTheme);
- }
-
- /**
- * Retrieve the CharSequence[] for the attribute at <var>index</var>.
- * This gets the resource ID of the selected attribute, and uses
- * {@link Resources#getTextArray Resources.getTextArray} of the owning
- * Resources object to retrieve its String[].
- *
- * @param index Index of attribute to retrieve.
- *
- * @return CharSequence[] for the attribute, or null if not defined.
- */
- @Override
- public CharSequence[] getTextArray(int index) {
- if (!hasValue(index)) {
- return null;
- }
- ResourceValue resVal = mResourceData[index];
- if (resVal instanceof ArrayResourceValue) {
- ArrayResourceValue array = (ArrayResourceValue) resVal;
- int count = array.getElementCount();
- return count >= 0 ? Resources_Delegate.fillValues(mBridgeResources, array, new CharSequence[count]) :
- null;
- }
- int id = getResourceId(index, 0);
- String resIdMessage = id > 0 ? " (resource id 0x" + Integer.toHexString(id) + ')' : "";
- assert false :
- String.format("%1$s in %2$s%3$s is not a valid array resource.", resVal.getValue(),
- mNames[index], resIdMessage);
-
- return new CharSequence[0];
- }
-
- @Override
- public int[] extractThemeAttrs() {
- // The drawables are always inflated with a Theme and we don't care about caching. So,
- // just return.
- return null;
- }
-
- @Override
- public int getChangingConfigurations() {
- // We don't care about caching. Any change in configuration is a fresh render. So,
- // just return.
- return 0;
- }
-
- /**
- * Retrieve the raw TypedValue for the attribute at <var>index</var>.
- *
- * @param index Index of attribute to retrieve.
- * @param outValue TypedValue object in which to place the attribute's
- * data.
- *
- * @return Returns true if the value was retrieved, else false.
- */
- @Override
- public boolean getValue(int index, TypedValue outValue) {
- String s = getString(index);
- return s != null && ResourceHelper.parseFloatAttribute(mNames[index], s, outValue, false);
- }
-
- @Override
- @SuppressWarnings("ResultOfMethodCallIgnored")
- public int getType(int index) {
- String value = getString(index);
- if (value == null) {
- return TYPE_NULL;
- }
- if (value.startsWith(PREFIX_RESOURCE_REF)) {
- return TYPE_REFERENCE;
- }
- if (value.startsWith(PREFIX_THEME_REF)) {
- return TYPE_ATTRIBUTE;
- }
- try {
- // Don't care about the value. Only called to check if an exception is thrown.
- convertValueToInt(value, 0);
- if (value.startsWith("0x") || value.startsWith("0X")) {
- return TYPE_INT_HEX;
- }
- // is it a color?
- if (value.startsWith("#")) {
- int length = value.length() - 1;
- if (length == 3) { // rgb
- return TYPE_INT_COLOR_RGB4;
- }
- if (length == 4) { // argb
- return TYPE_INT_COLOR_ARGB4;
- }
- if (length == 6) { // rrggbb
- return TYPE_INT_COLOR_RGB8;
- }
- if (length == 8) { // aarrggbb
- return TYPE_INT_COLOR_ARGB8;
- }
- }
- if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
- return TYPE_INT_BOOLEAN;
- }
- return TYPE_INT_DEC;
- } catch (NumberFormatException ignored) {
- try {
- Float.parseFloat(value);
- return TYPE_FLOAT;
- } catch (NumberFormatException ignore) {
- }
- // Might be a dimension.
- if (ResourceHelper.parseFloatAttribute(null, value, new TypedValue(), false)) {
- return TYPE_DIMENSION;
- }
- }
- // TODO: handle fractions.
- return TYPE_STRING;
- }
-
- /**
- * Determines whether there is an attribute at <var>index</var>.
- *
- * @param index Index of attribute to retrieve.
- *
- * @return True if the attribute has a value, false otherwise.
- */
- @Override
- public boolean hasValue(int index) {
- return index >= 0 && index < mResourceData.length && mResourceData[index] != null;
- }
-
- @Override
- public boolean hasValueOrEmpty(int index) {
- return hasValue(index) || index >= 0 && index < mResourceData.length &&
- mEmptyIds != null && Arrays.binarySearch(mEmptyIds, index) >= 0;
- }
-
- /**
- * Retrieve the raw TypedValue for the attribute at <var>index</var>
- * and return a temporary object holding its data. This object is only
- * valid until the next call on to {@link TypedArray}.
- *
- * @param index Index of attribute to retrieve.
- *
- * @return Returns a TypedValue object if the attribute is defined,
- * containing its data; otherwise returns null. (You will not
- * receive a TypedValue whose type is TYPE_NULL.)
- */
- @Override
- public TypedValue peekValue(int index) {
- if (index < 0 || index >= mResourceData.length) {
- return null;
- }
-
- if (getValue(index, mValue)) {
- return mValue;
- }
-
- return null;
- }
-
- /**
- * Returns a message about the parser state suitable for printing error messages.
- */
- @Override
- public String getPositionDescription() {
- return "<internal -- stub if needed>";
- }
-
- /**
- * Give back a previously retrieved TypedArray, for later re-use.
- */
- @Override
- public void recycle() {
- // pass
- }
-
- @Override
- public String toString() {
- return Arrays.toString(mResourceData);
- }
-
- /**
- * Searches for the string in the attributes (flag or enums) and returns the integer.
- * If found, it will return an integer matching the value.
- *
- * @param index Index of attribute to retrieve.
- *
- * @return Attribute int value, or null if not defined.
- */
- private Integer resolveEnumAttribute(int index) {
- // Get the map of attribute-constant -> IntegerValue
- Map<String, Integer> map = null;
- if (mIsFramework[index]) {
- map = Bridge.getEnumValues(mNames[index]);
- } else {
- // get the styleable matching the resolved name
- RenderResources res = mContext.getRenderResources();
- ResourceValue attr = res.getProjectResource(ResourceType.ATTR, mNames[index]);
- if (attr instanceof AttrResourceValue) {
- map = ((AttrResourceValue) attr).getAttributeValues();
- }
- }
-
- if (map != null) {
- // accumulator to store the value of the 1+ constants.
- int result = 0;
- boolean found = false;
-
- // split the value in case this is a mix of several flags.
- String[] keywords = mResourceData[index].getValue().split("\\|");
- for (String keyword : keywords) {
- Integer i = map.get(keyword.trim());
- if (i != null) {
- result |= i;
- found = true;
- }
- // TODO: We should act smartly and log a warning for incorrect keywords. However,
- // this method is currently called even if the resourceValue is not an enum.
- }
- if (found) {
- return result;
- }
- }
-
- return null;
- }
-
- /**
- * Copied from {@link XmlUtils#convertValueToInt(CharSequence, int)}, but adapted to account
- * for aapt, and the fact that host Java VM's Integer.parseInt("XXXXXXXX", 16) cannot handle
- * "XXXXXXXX" > 80000000.
- */
- private static int convertValueToInt(@Nullable String charSeq, int defValue) {
- if (null == charSeq || charSeq.isEmpty())
- return defValue;
-
- int sign = 1;
- int index = 0;
- int len = charSeq.length();
- int base = 10;
-
- if ('-' == charSeq.charAt(0)) {
- sign = -1;
- index++;
- }
-
- if ('0' == charSeq.charAt(index)) {
- // Quick check for a zero by itself
- if (index == (len - 1))
- return 0;
-
- char c = charSeq.charAt(index + 1);
-
- if ('x' == c || 'X' == c) {
- index += 2;
- base = 16;
- } else {
- index++;
- // Leave the base as 10. aapt removes the preceding zero, and thus when framework
- // sees the value, it only gets the decimal value.
- }
- } else if ('#' == charSeq.charAt(index)) {
- return ResourceHelper.getColor(charSeq) * sign;
- } else if ("true".equals(charSeq) || "TRUE".equals(charSeq)) {
- return -1;
- } else if ("false".equals(charSeq) || "FALSE".equals(charSeq)) {
- return 0;
- }
-
- // Use Long, since we want to handle hex ints > 80000000.
- return ((int)Long.parseLong(charSeq.substring(index), base)) * sign;
- }
-
- static TypedArray obtain(Resources res, int len) {
- return new BridgeTypedArray(res, null, len, true);
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/content/res/ComplexColor_Accessor.java b/tools/layoutlib/bridge/src/android/content/res/ComplexColor_Accessor.java
deleted file mode 100644
index 09c0260..0000000
--- a/tools/layoutlib/bridge/src/android/content/res/ComplexColor_Accessor.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.res;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.res.Resources.Theme;
-import android.util.AttributeSet;
-
-import java.io.IOException;
-
-/**
- * Class that provides access to the {@link GradientColor#createFromXmlInner(Resources,
- * XmlPullParser, AttributeSet, Theme)} and {@link ColorStateList#createFromXmlInner(Resources,
- * XmlPullParser, AttributeSet, Theme)} methods
- */
-public class ComplexColor_Accessor {
- public static GradientColor createGradientColorFromXmlInner(@NonNull Resources r,
- @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)
- throws IOException, XmlPullParserException {
- return GradientColor.createFromXmlInner(r, parser, attrs, theme);
- }
-
- public static ColorStateList createColorStateListFromXmlInner(@NonNull Resources r,
- @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)
- throws IOException, XmlPullParserException {
- return ColorStateList.createFromXmlInner(r, parser, attrs, theme);
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
deleted file mode 100644
index c20ee12..0000000
--- a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
+++ /dev/null
@@ -1,1025 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.res;
-
-import com.android.SdkConstants;
-import com.android.ide.common.rendering.api.ArrayResourceValue;
-import com.android.ide.common.rendering.api.DensityBasedResourceValue;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.LayoutlibCallback;
-import com.android.ide.common.rendering.api.PluralsResourceValue;
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.BridgeConstants;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
-import com.android.layoutlib.bridge.impl.ParserFactory;
-import com.android.layoutlib.bridge.impl.ResourceHelper;
-import com.android.layoutlib.bridge.util.NinePatchInputStream;
-import com.android.ninepatch.NinePatch;
-import com.android.resources.ResourceType;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-import com.android.util.Pair;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.res.Resources.NotFoundException;
-import android.content.res.Resources.Theme;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.icu.text.PluralRules;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.LruCache;
-import android.util.TypedValue;
-import android.view.DisplayAdjustments;
-import android.view.ViewGroup.LayoutParams;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.util.Iterator;
-
-@SuppressWarnings("deprecation")
-public class Resources_Delegate {
-
- private static boolean[] mPlatformResourceFlag = new boolean[1];
- // TODO: This cache is cleared every time a render session is disposed. Look into making this
- // more long lived.
- private static LruCache<String, Drawable.ConstantState> sDrawableCache = new LruCache<>(50);
-
- public static Resources initSystem(BridgeContext context,
- AssetManager assets,
- DisplayMetrics metrics,
- Configuration config,
- LayoutlibCallback layoutlibCallback) {
- Resources resources = new Resources(Resources_Delegate.class.getClassLoader());
- resources.setImpl(new ResourcesImpl(assets, metrics, config, new DisplayAdjustments()));
- resources.mContext = context;
- resources.mLayoutlibCallback = layoutlibCallback;
- return Resources.mSystem = resources;
- }
-
- /**
- * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects around that
- * would prevent us from unloading the library.
- */
- public static void disposeSystem() {
- sDrawableCache.evictAll();
- Resources.mSystem.mContext = null;
- Resources.mSystem.mLayoutlibCallback = null;
- Resources.mSystem = null;
- }
-
- public static BridgeTypedArray newTypeArray(Resources resources, int numEntries,
- boolean platformFile) {
- return new BridgeTypedArray(resources, resources.mContext, numEntries, platformFile);
- }
-
- private static Pair<ResourceType, String> getResourceInfo(Resources resources, int id,
- boolean[] platformResFlag_out) {
- // first get the String related to this id in the framework
- Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
-
- // Set the layoutlib callback and context for resources
- if (resources != Resources.mSystem && resources.mLayoutlibCallback == null) {
- resources.mLayoutlibCallback = Resources.mSystem.mLayoutlibCallback;
- resources.mContext = Resources.mSystem.mContext;
- }
-
- if (resourceInfo != null) {
- platformResFlag_out[0] = true;
- return resourceInfo;
- }
-
- // didn't find a match in the framework? look in the project.
- if (resources.mLayoutlibCallback != null) {
- resourceInfo = resources.mLayoutlibCallback.resolveResourceId(id);
-
- if (resourceInfo != null) {
- platformResFlag_out[0] = false;
- return resourceInfo;
- }
- }
- return null;
- }
-
- private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id,
- boolean[] platformResFlag_out) {
- Pair<ResourceType, String> resourceInfo =
- getResourceInfo(resources, id, platformResFlag_out);
-
- if (resourceInfo != null) {
- String attributeName = resourceInfo.getSecond();
- RenderResources renderResources = resources.mContext.getRenderResources();
- ResourceValue value = platformResFlag_out[0] ?
- renderResources.getFrameworkResource(resourceInfo.getFirst(), attributeName) :
- renderResources.getProjectResource(resourceInfo.getFirst(), attributeName);
-
- if (value == null) {
- // Unable to resolve the attribute, just leave the unresolved value
- value = new ResourceValue(resourceInfo.getFirst(), attributeName, attributeName,
- platformResFlag_out[0]);
- }
- return Pair.of(attributeName, value);
- }
-
- return null;
- }
-
- @LayoutlibDelegate
- static Drawable getDrawable(Resources resources, int id) {
- return getDrawable(resources, id, null);
- }
-
- @LayoutlibDelegate
- static Drawable getDrawable(Resources resources, int id, Theme theme) {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
- if (value != null) {
- String key = value.getSecond().getValue();
-
- Drawable.ConstantState constantState = key != null ? sDrawableCache.get(key) : null;
- Drawable drawable;
- if (constantState != null) {
- drawable = constantState.newDrawable(resources, theme);
- } else {
- drawable =
- ResourceHelper.getDrawable(value.getSecond(), resources.mContext, theme);
-
- if (key != null) {
- sDrawableCache.put(key, drawable.getConstantState());
- }
- }
-
- return drawable;
- }
-
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return null;
- }
-
- @LayoutlibDelegate
- static int getColor(Resources resources, int id) {
- return getColor(resources, id, null);
- }
-
- @LayoutlibDelegate
- static int getColor(Resources resources, int id, Theme theme) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (value != null) {
- ResourceValue resourceValue = value.getSecond();
- try {
- return ResourceHelper.getColor(resourceValue.getValue());
- } catch (NumberFormatException e) {
- // Check if the value passed is a file. If it is, mostly likely, user is referencing
- // a color state list from a place where they should reference only a pure color.
- String message;
- if (new File(resourceValue.getValue()).isFile()) {
- String resource = (resourceValue.isFramework() ? "@android:" : "@") + "color/"
- + resourceValue.getName();
- message = "Hexadecimal color expected, found Color State List for " + resource;
- } else {
- message = e.getMessage();
- }
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, message, e, null);
- return 0;
- }
- }
-
- // Suppress possible NPE. getColorStateList will never return null, it will instead
- // throw an exception, but intelliJ can't figure that out
- //noinspection ConstantConditions
- return getColorStateList(resources, id, theme).getDefaultColor();
- }
-
- @LayoutlibDelegate
- static ColorStateList getColorStateList(Resources resources, int id) throws NotFoundException {
- return getColorStateList(resources, id, null);
- }
-
- @LayoutlibDelegate
- static ColorStateList getColorStateList(Resources resources, int id, Theme theme)
- throws NotFoundException {
- Pair<String, ResourceValue> resValue =
- getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (resValue != null) {
- ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(),
- resources.mContext);
- if (stateList != null) {
- return stateList.obtainForTheme(theme);
- }
- }
-
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return null;
- }
-
- @LayoutlibDelegate
- static CharSequence getText(Resources resources, int id, CharSequence def) {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (value != null) {
- ResourceValue resValue = value.getSecond();
-
- assert resValue != null;
- if (resValue != null) {
- String v = resValue.getValue();
- if (v != null) {
- return v;
- }
- }
- }
-
- return def;
- }
-
- @LayoutlibDelegate
- static CharSequence getText(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (value != null) {
- ResourceValue resValue = value.getSecond();
-
- assert resValue != null;
- if (resValue != null) {
- String v = resValue.getValue();
- if (v != null) {
- return v;
- }
- }
- }
-
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return null;
- }
-
- @LayoutlibDelegate
- static CharSequence[] getTextArray(Resources resources, int id) throws NotFoundException {
- ResourceValue resValue = getArrayResourceValue(resources, id);
- if (resValue == null) {
- // Error already logged by getArrayResourceValue.
- return new CharSequence[0];
- } else if (!(resValue instanceof ArrayResourceValue)) {
- return new CharSequence[]{
- resolveReference(resources, resValue.getValue(), resValue.isFramework())};
- }
- ArrayResourceValue arv = ((ArrayResourceValue) resValue);
- return fillValues(resources, arv, new CharSequence[arv.getElementCount()]);
- }
-
- @LayoutlibDelegate
- static String[] getStringArray(Resources resources, int id) throws NotFoundException {
- ResourceValue resValue = getArrayResourceValue(resources, id);
- if (resValue == null) {
- // Error already logged by getArrayResourceValue.
- return new String[0];
- } else if (!(resValue instanceof ArrayResourceValue)) {
- return new String[]{
- resolveReference(resources, resValue.getValue(), resValue.isFramework())};
- }
- ArrayResourceValue arv = ((ArrayResourceValue) resValue);
- return fillValues(resources, arv, new String[arv.getElementCount()]);
- }
-
- /**
- * Resolve each element in resValue and copy them to {@code values}. The values copied are
- * always Strings. The ideal signature for the method should be <T super String>, but java
- * generics don't support it.
- */
- static <T extends CharSequence> T[] fillValues(Resources resources, ArrayResourceValue resValue,
- T[] values) {
- int i = 0;
- for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) {
- @SuppressWarnings("unchecked")
- T s = (T) resolveReference(resources, iterator.next(), resValue.isFramework());
- values[i] = s;
- }
- return values;
- }
-
- @LayoutlibDelegate
- static int[] getIntArray(Resources resources, int id) throws NotFoundException {
- ResourceValue rv = getArrayResourceValue(resources, id);
- if (rv == null) {
- // Error already logged by getArrayResourceValue.
- return new int[0];
- } else if (!(rv instanceof ArrayResourceValue)) {
- // This is an older IDE that can only give us the first element of the array.
- String firstValue = resolveReference(resources, rv.getValue(), rv.isFramework());
- try {
- return new int[]{getInt(firstValue)};
- } catch (NumberFormatException e) {
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
- "Integer resource array contains non-integer value: " +
- firstValue, null);
- return new int[1];
- }
- }
- ArrayResourceValue resValue = ((ArrayResourceValue) rv);
- int[] values = new int[resValue.getElementCount()];
- int i = 0;
- for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) {
- String element = resolveReference(resources, iterator.next(), resValue.isFramework());
- try {
- values[i] = getInt(element);
- } catch (NumberFormatException e) {
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
- "Integer resource array contains non-integer value: " + element, null);
- }
- }
- return values;
- }
-
- /**
- * Try to find the ArrayResourceValue for the given id.
- * <p/>
- * If the ResourceValue found is not of type {@link ResourceType#ARRAY}, the method logs an
- * error and return null. However, if the ResourceValue found has type {@code
- * ResourceType.ARRAY}, but the value is not an instance of {@link ArrayResourceValue}, the
- * method returns the ResourceValue. This happens on older versions of the IDE, which did not
- * parse the array resources properly.
- * <p/>
- *
- * @throws NotFoundException if no resource if found
- */
- @Nullable
- private static ResourceValue getArrayResourceValue(Resources resources, int id)
- throws NotFoundException {
- Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (v != null) {
- ResourceValue resValue = v.getSecond();
-
- assert resValue != null;
- if (resValue != null) {
- final ResourceType type = resValue.getResourceType();
- if (type != ResourceType.ARRAY) {
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
- String.format(
- "Resource with id 0x%1$X is not an array resource, but %2$s",
- id, type == null ? "null" : type.getDisplayName()),
- null);
- return null;
- }
- if (!(resValue instanceof ArrayResourceValue)) {
- Bridge.getLog().warning(LayoutLog.TAG_UNSUPPORTED,
- "Obtaining resource arrays via getTextArray, getStringArray or getIntArray is not fully supported in this version of the IDE.",
- null);
- }
- return resValue;
- }
- }
-
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return null;
- }
-
- @NonNull
- private static String resolveReference(Resources resources, @NonNull String ref,
- boolean forceFrameworkOnly) {
- if (ref.startsWith(SdkConstants.PREFIX_RESOURCE_REF) || ref.startsWith
- (SdkConstants.PREFIX_THEME_REF)) {
- ResourceValue rv =
- resources.mContext.getRenderResources().findResValue(ref, forceFrameworkOnly);
- rv = resources.mContext.getRenderResources().resolveResValue(rv);
- if (rv != null) {
- return rv.getValue();
- }
- }
- // Not a reference.
- return ref;
- }
-
- @LayoutlibDelegate
- static XmlResourceParser getLayout(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (v != null) {
- ResourceValue value = v.getSecond();
- XmlPullParser parser = null;
-
- try {
- // check if the current parser can provide us with a custom parser.
- if (!mPlatformResourceFlag[0]) {
- parser = resources.mLayoutlibCallback.getParser(value);
- }
-
- // create a new one manually if needed.
- if (parser == null) {
- File xml = new File(value.getValue());
- if (xml.isFile()) {
- // we need to create a pull parser around the layout XML file, and then
- // give that to our XmlBlockParser
- parser = ParserFactory.create(xml, true);
- }
- }
-
- if (parser != null) {
- return new BridgeXmlBlockParser(parser, resources.mContext,
- mPlatformResourceFlag[0]);
- }
- } catch (XmlPullParserException e) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Failed to configure parser for " + value.getValue(), e, null /*data*/);
- // we'll return null below.
- } catch (FileNotFoundException e) {
- // this shouldn't happen since we check above.
- }
-
- }
-
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return null;
- }
-
- @LayoutlibDelegate
- static XmlResourceParser getAnimation(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (v != null) {
- ResourceValue value = v.getSecond();
- XmlPullParser parser;
-
- try {
- File xml = new File(value.getValue());
- if (xml.isFile()) {
- // we need to create a pull parser around the layout XML file, and then
- // give that to our XmlBlockParser
- parser = ParserFactory.create(xml);
-
- return new BridgeXmlBlockParser(parser, resources.mContext,
- mPlatformResourceFlag[0]);
- }
- } catch (XmlPullParserException e) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Failed to configure parser for " + value.getValue(), e, null /*data*/);
- // we'll return null below.
- } catch (FileNotFoundException e) {
- // this shouldn't happen since we check above.
- }
-
- }
-
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return null;
- }
-
- @LayoutlibDelegate
- static TypedArray obtainAttributes(Resources resources, AttributeSet set, int[] attrs) {
- return resources.mContext.obtainStyledAttributes(set, attrs);
- }
-
- @LayoutlibDelegate
- static TypedArray obtainAttributes(Resources resources, Resources.Theme theme, AttributeSet
- set, int[] attrs) {
- return Resources.obtainAttributes_Original(resources, theme, set, attrs);
- }
-
- @LayoutlibDelegate
- static TypedArray obtainTypedArray(Resources resources, int id) throws NotFoundException {
- throw new UnsupportedOperationException();
- }
-
- @LayoutlibDelegate
- static float getDimension(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (value != null) {
- ResourceValue resValue = value.getSecond();
-
- assert resValue != null;
- if (resValue != null) {
- String v = resValue.getValue();
- if (v != null) {
- if (v.equals(BridgeConstants.MATCH_PARENT) ||
- v.equals(BridgeConstants.FILL_PARENT)) {
- return LayoutParams.MATCH_PARENT;
- } else if (v.equals(BridgeConstants.WRAP_CONTENT)) {
- return LayoutParams.WRAP_CONTENT;
- }
- TypedValue tmpValue = new TypedValue();
- if (ResourceHelper.parseFloatAttribute(
- value.getFirst(), v, tmpValue, true /*requireUnit*/) &&
- tmpValue.type == TypedValue.TYPE_DIMENSION) {
- return tmpValue.getDimension(resources.getDisplayMetrics());
- }
- }
- }
- }
-
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return 0;
- }
-
- @LayoutlibDelegate
- static int getDimensionPixelOffset(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (value != null) {
- ResourceValue resValue = value.getSecond();
-
- assert resValue != null;
- if (resValue != null) {
- String v = resValue.getValue();
- if (v != null) {
- TypedValue tmpValue = new TypedValue();
- if (ResourceHelper.parseFloatAttribute(
- value.getFirst(), v, tmpValue, true /*requireUnit*/) &&
- tmpValue.type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelOffset(tmpValue.data,
- resources.getDisplayMetrics());
- }
- }
- }
- }
-
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return 0;
- }
-
- @LayoutlibDelegate
- static int getDimensionPixelSize(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (value != null) {
- ResourceValue resValue = value.getSecond();
-
- assert resValue != null;
- if (resValue != null) {
- String v = resValue.getValue();
- if (v != null) {
- TypedValue tmpValue = new TypedValue();
- if (ResourceHelper.parseFloatAttribute(
- value.getFirst(), v, tmpValue, true /*requireUnit*/) &&
- tmpValue.type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelSize(tmpValue.data,
- resources.getDisplayMetrics());
- }
- }
- }
- }
-
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return 0;
- }
-
- @LayoutlibDelegate
- static int getInteger(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (value != null) {
- ResourceValue resValue = value.getSecond();
-
- assert resValue != null;
- if (resValue != null) {
- String v = resValue.getValue();
- if (v != null) {
- try {
- return getInt(v);
- } catch (NumberFormatException e) {
- // return exception below
- }
- }
- }
- }
-
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return 0;
- }
-
- @LayoutlibDelegate
- static boolean getBoolean(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (value != null) {
- ResourceValue resValue = value.getSecond();
-
- if (resValue != null) {
- String v = resValue.getValue();
- if (v != null) {
- return Boolean.parseBoolean(v);
- }
- }
- }
-
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return false;
- }
-
- @LayoutlibDelegate
- static String getResourceEntryName(Resources resources, int resid) throws NotFoundException {
- Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]);
- if (resourceInfo != null) {
- return resourceInfo.getSecond();
- }
- throwException(resid, null);
- return null;
-
- }
-
- @LayoutlibDelegate
- static String getResourceName(Resources resources, int resid) throws NotFoundException {
- boolean[] platformOut = new boolean[1];
- Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut);
- String packageName;
- if (resourceInfo != null) {
- if (platformOut[0]) {
- packageName = SdkConstants.ANDROID_NS_NAME;
- } else {
- packageName = resources.mContext.getPackageName();
- packageName = packageName == null ? SdkConstants.APP_PREFIX : packageName;
- }
- return packageName + ':' + resourceInfo.getFirst().getName() + '/' +
- resourceInfo.getSecond();
- }
- throwException(resid, null);
- return null;
- }
-
- @LayoutlibDelegate
- static String getResourcePackageName(Resources resources, int resid) throws NotFoundException {
- boolean[] platformOut = new boolean[1];
- Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut);
- if (resourceInfo != null) {
- if (platformOut[0]) {
- return SdkConstants.ANDROID_NS_NAME;
- }
- String packageName = resources.mContext.getPackageName();
- return packageName == null ? SdkConstants.APP_PREFIX : packageName;
- }
- throwException(resid, null);
- return null;
- }
-
- @LayoutlibDelegate
- static String getResourceTypeName(Resources resources, int resid) throws NotFoundException {
- Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]);
- if (resourceInfo != null) {
- return resourceInfo.getFirst().getName();
- }
- throwException(resid, null);
- return null;
- }
-
- @LayoutlibDelegate
- static String getString(Resources resources, int id, Object... formatArgs)
- throws NotFoundException {
- String s = getString(resources, id);
- if (s != null) {
- return String.format(s, formatArgs);
-
- }
-
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return null;
- }
-
- @LayoutlibDelegate
- static String getString(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (value != null && value.getSecond().getValue() != null) {
- return value.getSecond().getValue();
- }
-
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return null;
- }
-
- @LayoutlibDelegate
- static String getQuantityString(Resources resources, int id, int quantity) throws
- NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (value != null) {
- if (value.getSecond() instanceof PluralsResourceValue) {
- PluralsResourceValue pluralsResourceValue = (PluralsResourceValue) value.getSecond();
- PluralRules pluralRules = PluralRules.forLocale(resources.getConfiguration().getLocales()
- .get(0));
- String strValue = pluralsResourceValue.getValue(pluralRules.select(quantity));
- if (strValue == null) {
- strValue = pluralsResourceValue.getValue(PluralRules.KEYWORD_OTHER);
- }
-
- return strValue;
- }
- else {
- return value.getSecond().getValue();
- }
- }
-
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return null;
- }
-
- @LayoutlibDelegate
- static String getQuantityString(Resources resources, int id, int quantity, Object... formatArgs)
- throws NotFoundException {
- String raw = getQuantityString(resources, id, quantity);
- return String.format(resources.getConfiguration().getLocales().get(0), raw, formatArgs);
- }
-
- @LayoutlibDelegate
- static CharSequence getQuantityText(Resources resources, int id, int quantity) throws
- NotFoundException {
- return getQuantityString(resources, id, quantity);
- }
-
- @LayoutlibDelegate
- static Typeface getFont(Resources resources, int id) throws
- NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
- if (value != null) {
- return ResourceHelper.getFont(value.getSecond(), resources.mContext, null);
- }
-
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return null;
- }
-
- @LayoutlibDelegate
- static Typeface getFont(Resources resources, TypedValue outValue, int id) throws
- NotFoundException {
- Resources_Delegate.getValue(resources, id, outValue, true);
- if (outValue.string != null) {
- return ResourceHelper.getFont(outValue.string.toString(), resources.mContext, null,
- mPlatformResourceFlag[0]);
- }
-
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return null;
- }
-
- @LayoutlibDelegate
- static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)
- throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (value != null) {
- ResourceValue resVal = value.getSecond();
- String v = resVal.getValue();
-
- if (v != null) {
- if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue,
- false /*requireUnit*/)) {
- return;
- }
- if (resVal instanceof DensityBasedResourceValue) {
- outValue.density =
- ((DensityBasedResourceValue) resVal).getResourceDensity().getDpiValue();
- }
-
- // else it's a string
- outValue.type = TypedValue.TYPE_STRING;
- outValue.string = v;
- return;
- }
- }
-
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
- }
-
- @LayoutlibDelegate
- static void getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs)
- throws NotFoundException {
- throw new UnsupportedOperationException();
- }
-
- @LayoutlibDelegate
- static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (value != null) {
- String v = value.getSecond().getValue();
-
- if (v != null) {
- // check this is a file
- File f = new File(v);
- if (f.isFile()) {
- try {
- XmlPullParser parser = ParserFactory.create(f);
-
- return new BridgeXmlBlockParser(parser, resources.mContext,
- mPlatformResourceFlag[0]);
- } catch (XmlPullParserException e) {
- NotFoundException newE = new NotFoundException();
- newE.initCause(e);
- throw newE;
- } catch (FileNotFoundException e) {
- NotFoundException newE = new NotFoundException();
- newE.initCause(e);
- throw newE;
- }
- }
- }
- }
-
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return null;
- }
-
- @LayoutlibDelegate
- static XmlResourceParser loadXmlResourceParser(Resources resources, int id,
- String type) throws NotFoundException {
- return resources.loadXmlResourceParser_Original(id, type);
- }
-
- @LayoutlibDelegate
- static XmlResourceParser loadXmlResourceParser(Resources resources, String file, int id,
- int assetCookie, String type) throws NotFoundException {
- // even though we know the XML file to load directly, we still need to resolve the
- // id so that we can know if it's a platform or project resource.
- // (mPlatformResouceFlag will get the result and will be used later).
- getResourceValue(resources, id, mPlatformResourceFlag);
-
- File f = new File(file);
- try {
- XmlPullParser parser = ParserFactory.create(f);
-
- return new BridgeXmlBlockParser(parser, resources.mContext, mPlatformResourceFlag[0]);
- } catch (XmlPullParserException e) {
- NotFoundException newE = new NotFoundException();
- newE.initCause(e);
- throw newE;
- } catch (FileNotFoundException e) {
- NotFoundException newE = new NotFoundException();
- newE.initCause(e);
- throw newE;
- }
- }
-
- @LayoutlibDelegate
- static InputStream openRawResource(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (value != null) {
- String path = value.getSecond().getValue();
-
- if (path != null) {
- // check this is a file
- File f = new File(path);
- if (f.isFile()) {
- try {
- // if it's a nine-patch return a custom input stream so that
- // other methods (mainly bitmap factory) can detect it's a 9-patch
- // and actually load it as a 9-patch instead of a normal bitmap
- if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) {
- return new NinePatchInputStream(f);
- }
- return new FileInputStream(f);
- } catch (FileNotFoundException e) {
- NotFoundException newE = new NotFoundException();
- newE.initCause(e);
- throw newE;
- }
- }
- }
- }
-
- // id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
-
- // this is not used since the method above always throws
- return null;
- }
-
- @LayoutlibDelegate
- static InputStream openRawResource(Resources resources, int id, TypedValue value) throws
- NotFoundException {
- getValue(resources, id, value, true);
-
- String path = value.string.toString();
-
- File f = new File(path);
- if (f.isFile()) {
- try {
- // if it's a nine-patch return a custom input stream so that
- // other methods (mainly bitmap factory) can detect it's a 9-patch
- // and actually load it as a 9-patch instead of a normal bitmap
- if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) {
- return new NinePatchInputStream(f);
- }
- return new FileInputStream(f);
- } catch (FileNotFoundException e) {
- NotFoundException exception = new NotFoundException();
- exception.initCause(e);
- throw exception;
- }
- }
-
- throw new NotFoundException();
- }
-
- @LayoutlibDelegate
- static AssetFileDescriptor openRawResourceFd(Resources resources, int id) throws
- NotFoundException {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource
- * type.
- *
- * @param id the id of the resource
- *
- * @throws NotFoundException
- */
- private static void throwException(Resources resources, int id) throws NotFoundException {
- throwException(id, getResourceInfo(resources, id, new boolean[1]));
- }
-
- private static void throwException(int id, @Nullable Pair<ResourceType, String> resourceInfo) {
- String message;
- if (resourceInfo != null) {
- message = String.format(
- "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.",
- resourceInfo.getFirst(), id, resourceInfo.getSecond());
- } else {
- message = String.format("Could not resolve resource value: 0x%1$X.", id);
- }
-
- throw new NotFoundException(message);
- }
-
- private static int getInt(String v) throws NumberFormatException {
- int radix = 10;
- if (v.startsWith("0x")) {
- v = v.substring(2);
- radix = 16;
- } else if (v.startsWith("0")) {
- radix = 8;
- }
- return Integer.parseInt(v, radix);
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java
deleted file mode 100644
index f1e8fc21..0000000
--- a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2011 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.content.res;
-
-import com.android.ide.common.rendering.api.ResourceReference;
-import com.android.ide.common.rendering.api.StyleResourceValue;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.layoutlib.bridge.impl.RenderSessionImpl;
-import com.android.resources.ResourceType;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.annotation.Nullable;
-import android.content.res.Resources.NotFoundException;
-import android.content.res.Resources.Theme;
-import android.content.res.Resources.ThemeKey;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-
-/**
- * Delegate used to provide new implementation of a select few methods of {@link Resources.Theme}
- *
- * Through the layoutlib_create tool, the original methods of Theme have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class Resources_Theme_Delegate {
-
- // ---- delegate manager ----
-
- private static final DelegateManager<Resources_Theme_Delegate> sManager =
- new DelegateManager<Resources_Theme_Delegate>(Resources_Theme_Delegate.class);
-
- public static DelegateManager<Resources_Theme_Delegate> getDelegateManager() {
- return sManager;
- }
-
- // ---- delegate methods. ----
-
- @LayoutlibDelegate
- /*package*/ static TypedArray obtainStyledAttributes(
- Resources thisResources, Theme thisTheme,
- int[] attrs) {
- boolean changed = setupResources(thisTheme);
- BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(attrs);
- ta.setTheme(thisTheme);
- restoreResources(changed);
- return ta;
- }
-
- @LayoutlibDelegate
- /*package*/ static TypedArray obtainStyledAttributes(
- Resources thisResources, Theme thisTheme,
- int resid, int[] attrs)
- throws NotFoundException {
- boolean changed = setupResources(thisTheme);
- BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(resid,
- attrs);
- ta.setTheme(thisTheme);
- restoreResources(changed);
- return ta;
- }
-
- @LayoutlibDelegate
- /*package*/ static TypedArray obtainStyledAttributes(
- Resources thisResources, Theme thisTheme,
- AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
- boolean changed = setupResources(thisTheme);
- BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(set,
- attrs, defStyleAttr, defStyleRes);
- ta.setTheme(thisTheme);
- restoreResources(changed);
- return ta;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean resolveAttribute(
- Resources thisResources, Theme thisTheme,
- int resid, TypedValue outValue,
- boolean resolveRefs) {
- boolean changed = setupResources(thisTheme);
- boolean found = RenderSessionImpl.getCurrentContext().resolveThemeAttribute(resid,
- outValue, resolveRefs);
- restoreResources(changed);
- return found;
- }
-
- @LayoutlibDelegate
- /*package*/ static TypedArray resolveAttributes(Resources thisResources, Theme thisTheme,
- int[] values, int[] attrs) {
- // FIXME
- return null;
- }
-
- // ---- private helper methods ----
-
- private static boolean setupResources(Theme thisTheme) {
- // Key is a space-separated list of theme ids applied that have been merged into the
- // BridgeContext's theme to make thisTheme.
- final ThemeKey key = thisTheme.getKey();
- final int[] resId = key.mResId;
- final boolean[] force = key.mForce;
-
- boolean changed = false;
- for (int i = 0, N = key.mCount; i < N; i++) {
- StyleResourceValue style = resolveStyle(resId[i]);
- if (style != null) {
- RenderSessionImpl.getCurrentContext().getRenderResources().applyStyle(
- style, force[i]);
- changed = true;
- }
-
- }
- return changed;
- }
-
- private static void restoreResources(boolean changed) {
- if (changed) {
- RenderSessionImpl.getCurrentContext().getRenderResources().clearStyles();
- }
- }
-
- @Nullable
- private static StyleResourceValue resolveStyle(int nativeResid) {
- if (nativeResid == 0) {
- return null;
- }
- BridgeContext context = RenderSessionImpl.getCurrentContext();
- ResourceReference theme = context.resolveId(nativeResid);
- if (theme.isFramework()) {
- return (StyleResourceValue) context.getRenderResources()
- .getFrameworkResource(ResourceType.STYLE, theme.getName());
- } else {
- return (StyleResourceValue) context.getRenderResources()
- .getProjectResource(ResourceType.STYLE, theme.getName());
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java
deleted file mode 100644
index faa8852..0000000
--- a/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2011 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.content.res;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.util.TypedValue;
-
-public class TypedArray_Delegate {
-
- @LayoutlibDelegate
- public static boolean getValueAt(TypedArray theTypedArray, int index, TypedValue outValue) {
- // pass
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static TypedArray obtain(Resources res, int len) {
- return BridgeTypedArray.obtain(res, len);
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/BaseCanvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BaseCanvas_Delegate.java
deleted file mode 100644
index cc71053..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/BaseCanvas_Delegate.java
+++ /dev/null
@@ -1,762 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.graphics;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.layoutlib.bridge.impl.GcSnapshot;
-import com.android.layoutlib.bridge.impl.PorterDuffUtility;
-import com.android.ninepatch.NinePatchChunk;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.annotation.Nullable;
-import android.text.TextUtils;
-
-import java.awt.*;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Arc2D;
-import java.awt.geom.Rectangle2D;
-import java.awt.image.BufferedImage;
-import java.awt.image.ColorModel;
-import java.awt.image.DataBuffer;
-
-public class BaseCanvas_Delegate {
- // ---- delegate manager ----
- protected static DelegateManager<BaseCanvas_Delegate> sManager =
- new DelegateManager<>(BaseCanvas_Delegate.class);
-
- // ---- delegate helper data ----
- private final static boolean[] sBoolOut = new boolean[1];
-
-
- // ---- delegate data ----
- protected Bitmap_Delegate mBitmap;
- protected GcSnapshot mSnapshot;
-
- // ---- Public Helper methods ----
-
- protected BaseCanvas_Delegate(Bitmap_Delegate bitmap) {
- mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap);
- }
-
- protected BaseCanvas_Delegate() {
- mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/);
- }
-
- /**
- * Disposes of the {@link Graphics2D} stack.
- */
- protected void dispose() {
- mSnapshot.dispose();
- }
-
- /**
- * Returns the current {@link Graphics2D} used to draw.
- */
- public GcSnapshot getSnapshot() {
- return mSnapshot;
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float left, float top,
- long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity) {
- // get the delegate from the native int.
- Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
- if (bitmapDelegate == null) {
- return;
- }
-
- BufferedImage image = bitmapDelegate.getImage();
- float right = left + image.getWidth();
- float bottom = top + image.getHeight();
-
- drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
- 0, 0, image.getWidth(), image.getHeight(),
- (int)left, (int)top, (int)right, (int)bottom);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float srcLeft, float srcTop,
- float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight,
- float dstBottom, long nativePaintOrZero, int screenDensity, int bitmapDensity) {
- // get the delegate from the native int.
- Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
- if (bitmapDelegate == null) {
- return;
- }
-
- drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, (int) srcLeft, (int) srcTop,
- (int) srcRight, (int) srcBottom, (int) dstLeft, (int) dstTop, (int) dstRight,
- (int) dstBottom);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawBitmap(long nativeCanvas, int[] colors, int offset, int stride,
- final float x, final float y, int width, int height, boolean hasAlpha,
- long nativePaintOrZero) {
- // create a temp BufferedImage containing the content.
- final BufferedImage image = new BufferedImage(width, height,
- hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
- image.setRGB(0, 0, width, height, colors, offset, stride);
-
- draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/,
- (graphics, paint) -> {
- if (paint != null && paint.isFilterBitmap()) {
- graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
- RenderingHints.VALUE_INTERPOLATION_BILINEAR);
- }
-
- graphics.drawImage(image, (int) x, (int) y, null);
- });
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawColor(long nativeCanvas, final int color, final int mode) {
- // get the delegate from the native int.
- BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return;
- }
-
- final int w = canvasDelegate.mBitmap.getImage().getWidth();
- final int h = canvasDelegate.mBitmap.getImage().getHeight();
- draw(nativeCanvas, (graphics, paint) -> {
- // reset its transform just in case
- graphics.setTransform(new AffineTransform());
-
- // set the color
- graphics.setColor(new java.awt.Color(color, true /*alpha*/));
-
- Composite composite = PorterDuffUtility.getComposite(
- PorterDuffUtility.getPorterDuffMode(mode), 0xFF);
- if (composite != null) {
- graphics.setComposite(composite);
- }
-
- graphics.fillRect(0, 0, w, h);
- });
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawPaint(long nativeCanvas, long paint) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Canvas.drawPaint is not supported.", null, null /*data*/);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawPoint(long nativeCanvas, float x, float y,
- long nativePaint) {
- // TODO: need to support the attribute (e.g. stroke width) of paint
- draw(nativeCanvas, nativePaint, false /*compositeOnly*/, false /*forceSrcMode*/,
- (graphics, paintDelegate) -> graphics.fillRect((int)x, (int)y, 1, 1));
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawPoints(long nativeCanvas, float[] pts, int offset, int count,
- long nativePaint) {
- if (offset < 0 || count < 0 || offset + count > pts.length) {
- throw new IllegalArgumentException("Invalid argument set");
- }
- // ignore the last point if the count is odd (It means it is not paired).
- count = (count >> 1) << 1;
- for (int i = offset; i < offset + count; i += 2) {
- nDrawPoint(nativeCanvas, pts[i], pts[i + 1], nativePaint);
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawLine(long nativeCanvas,
- final float startX, final float startY, final float stopX, final float stopY,
- long paint) {
- draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
- (graphics, paintDelegate) -> graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY));
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawLines(long nativeCanvas,
- final float[] pts, final int offset, final int count,
- long nativePaint) {
- draw(nativeCanvas, nativePaint, false /*compositeOnly*/,
- false /*forceSrcMode*/, (graphics, paintDelegate) -> {
- for (int i = 0; i < count; i += 4) {
- graphics.drawLine((int) pts[i + offset], (int) pts[i + offset + 1],
- (int) pts[i + offset + 2], (int) pts[i + offset + 3]);
- }
- });
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawRect(long nativeCanvas,
- final float left, final float top, final float right, final float bottom, long paint) {
-
- draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
- (graphics, paintDelegate) -> {
- int style = paintDelegate.getStyle();
-
- // draw
- if (style == Paint.Style.FILL.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.fillRect((int)left, (int)top,
- (int)(right-left), (int)(bottom-top));
- }
-
- if (style == Paint.Style.STROKE.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.drawRect((int)left, (int)top,
- (int)(right-left), (int)(bottom-top));
- }
- });
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawOval(long nativeCanvas, final float left,
- final float top, final float right, final float bottom, long paint) {
- if (right > left && bottom > top) {
- draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
- (graphics, paintDelegate) -> {
- int style = paintDelegate.getStyle();
-
- // draw
- if (style == Paint.Style.FILL.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.fillOval((int)left, (int)top,
- (int)(right - left), (int)(bottom - top));
- }
-
- if (style == Paint.Style.STROKE.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.drawOval((int)left, (int)top,
- (int)(right - left), (int)(bottom - top));
- }
- });
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawCircle(long nativeCanvas,
- float cx, float cy, float radius, long paint) {
- nDrawOval(nativeCanvas,
- cx - radius, cy - radius, cx + radius, cy + radius,
- paint);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawArc(long nativeCanvas,
- final float left, final float top, final float right, final float bottom,
- final float startAngle, final float sweep,
- final boolean useCenter, long paint) {
- if (right > left && bottom > top) {
- draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
- (graphics, paintDelegate) -> {
- int style = paintDelegate.getStyle();
-
- Arc2D.Float arc = new Arc2D.Float(
- left, top, right - left, bottom - top,
- -startAngle, -sweep,
- useCenter ? Arc2D.PIE : Arc2D.OPEN);
-
- // draw
- if (style == Paint.Style.FILL.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.fill(arc);
- }
-
- if (style == Paint.Style.STROKE.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.draw(arc);
- }
- });
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawRoundRect(long nativeCanvas,
- final float left, final float top, final float right, final float bottom,
- final float rx, final float ry, long paint) {
- draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
- (graphics, paintDelegate) -> {
- int style = paintDelegate.getStyle();
-
- // draw
- if (style == Paint.Style.FILL.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.fillRoundRect(
- (int)left, (int)top,
- (int)(right - left), (int)(bottom - top),
- 2 * (int)rx, 2 * (int)ry);
- }
-
- if (style == Paint.Style.STROKE.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.drawRoundRect(
- (int)left, (int)top,
- (int)(right - left), (int)(bottom - top),
- 2 * (int)rx, 2 * (int)ry);
- }
- });
- }
-
- @LayoutlibDelegate
- public static void nDrawPath(long nativeCanvas, long path, long paint) {
- final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path);
- if (pathDelegate == null) {
- return;
- }
-
- draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
- (graphics, paintDelegate) -> {
- Shape shape = pathDelegate.getJavaShape();
- Rectangle2D bounds = shape.getBounds2D();
- if (bounds.isEmpty()) {
- // Apple JRE 1.6 doesn't like drawing empty shapes.
- // http://b.android.com/178278
-
- if (pathDelegate.isEmpty()) {
- // This means that the path doesn't have any lines or curves so
- // nothing to draw.
- return;
- }
-
- // The stroke width is not consider for the size of the bounds so,
- // for example, a horizontal line, would be considered as an empty
- // rectangle.
- // If the strokeWidth is not 0, we use it to consider the size of the
- // path as well.
- float strokeWidth = paintDelegate.getStrokeWidth();
- if (strokeWidth <= 0.0f) {
- return;
- }
- bounds.setRect(bounds.getX(), bounds.getY(),
- Math.max(strokeWidth, bounds.getWidth()),
- Math.max(strokeWidth, bounds.getHeight()));
- }
-
- int style = paintDelegate.getStyle();
-
- if (style == Paint.Style.FILL.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.fill(shape);
- }
-
- if (style == Paint.Style.STROKE.nativeInt ||
- style == Paint.Style.FILL_AND_STROKE.nativeInt) {
- graphics.draw(shape);
- }
- });
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawRegion(long nativeCanvas, long nativeRegion,
- long nativePaint) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Some canvas paths may not be drawn", null, null);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawNinePatch(long nativeCanvas, long nativeBitmap, long ninePatch,
- final float dstLeft, final float dstTop, final float dstRight, final float dstBottom,
- long nativePaintOrZero, final int screenDensity, final int bitmapDensity) {
-
- // get the delegate from the native int.
- final Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmap);
- if (bitmapDelegate == null) {
- return;
- }
-
- byte[] c = NinePatch_Delegate.getChunk(ninePatch);
- if (c == null) {
- // not a 9-patch?
- BufferedImage image = bitmapDelegate.getImage();
- drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 0, 0, image.getWidth(),
- image.getHeight(), (int) dstLeft, (int) dstTop, (int) dstRight,
- (int) dstBottom);
- return;
- }
-
- final NinePatchChunk chunkObject = NinePatch_Delegate.getChunk(c);
- assert chunkObject != null;
- if (chunkObject == null) {
- return;
- }
-
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return;
- }
-
- // this one can be null
- Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
-
- canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() {
- @Override
- public void draw(Graphics2D graphics, Paint_Delegate paint) {
- chunkObject.draw(bitmapDelegate.getImage(), graphics, (int) dstLeft, (int) dstTop,
- (int) (dstRight - dstLeft), (int) (dstBottom - dstTop), screenDensity,
- bitmapDensity);
- }
- }, paintDelegate, true, false);
-
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawBitmapMatrix(long nCanvas, Bitmap bitmap,
- long nMatrix, long nPaint) {
- // get the delegate from the native int.
- BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
- if (canvasDelegate == null) {
- return;
- }
-
- // get the delegate from the native int, which can be null
- Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
-
- // get the delegate from the native int.
- Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
- if (bitmapDelegate == null) {
- return;
- }
-
- final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut);
-
- Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
- if (matrixDelegate == null) {
- return;
- }
-
- final AffineTransform mtx = matrixDelegate.getAffineTransform();
-
- canvasDelegate.getSnapshot().draw((graphics, paint) -> {
- if (paint != null && paint.isFilterBitmap()) {
- graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
- RenderingHints.VALUE_INTERPOLATION_BILINEAR);
- }
-
- //FIXME add support for canvas, screen and bitmap densities.
- graphics.drawImage(image, mtx, null);
- }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawBitmapMesh(long nCanvas, Bitmap bitmap,
- int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors,
- int colorOffset, long nPaint) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Canvas.drawBitmapMesh is not supported.", null, null /*data*/);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawVertices(long nCanvas, int mode, int n,
- float[] verts, int vertOffset,
- float[] texs, int texOffset,
- int[] colors, int colorOffset,
- short[] indices, int indexOffset,
- int indexCount, long nPaint) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Canvas.drawVertices is not supported.", null, null /*data*/);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawText(long nativeCanvas, char[] text, int index, int count,
- float startX, float startY, int flags, long paint, long typeface) {
- drawText(nativeCanvas, text, index, count, startX, startY, (flags & 1) != 0,
- paint, typeface);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawText(long nativeCanvas, String text,
- int start, int end, float x, float y, final int flags, long paint,
- long typeface) {
- int count = end - start;
- char[] buffer = TemporaryBuffer.obtain(count);
- TextUtils.getChars(text, start, end, buffer, 0);
-
- nDrawText(nativeCanvas, buffer, 0, count, x, y, flags, paint, typeface);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawTextRun(long nativeCanvas, String text,
- int start, int end, int contextStart, int contextEnd,
- float x, float y, boolean isRtl, long paint, long typeface) {
- int count = end - start;
- char[] buffer = TemporaryBuffer.obtain(count);
- TextUtils.getChars(text, start, end, buffer, 0);
-
- drawText(nativeCanvas, buffer, 0, count, x, y, isRtl, paint, typeface);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawTextRun(long nativeCanvas, char[] text,
- int start, int count, int contextStart, int contextCount,
- float x, float y, boolean isRtl, long paint, long typeface) {
- drawText(nativeCanvas, text, start, count, x, y, isRtl, paint, typeface);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawTextOnPath(long nativeCanvas,
- char[] text, int index,
- int count, long path,
- float hOffset,
- float vOffset, int bidiFlags,
- long paint, long typeface) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nDrawTextOnPath(long nativeCanvas,
- String text, long path,
- float hOffset,
- float vOffset,
- int bidiFlags, long paint,
- long typeface) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
- }
-
- // ---- Private delegate/helper methods ----
-
- /**
- * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint.
- * <p>Note that the drawable may actually be executed several times if there are
- * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
- */
- private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode,
- GcSnapshot.Drawable drawable) {
- // get the delegate from the native int.
- BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
- if (canvasDelegate == null) {
- return;
- }
-
- // get the paint which can be null if nPaint is 0;
- Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
-
- canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode);
- }
-
- /**
- * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided
- * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}.
- * <p>Note that the drawable may actually be executed several times if there are
- * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
- */
- private static void draw(long nCanvas, GcSnapshot.Drawable drawable) {
- // get the delegate from the native int.
- BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
- if (canvasDelegate == null) {
- return;
- }
-
- canvasDelegate.mSnapshot.draw(drawable);
- }
-
- private static void drawText(long nativeCanvas, final char[] text, final int index,
- final int count, final float startX, final float startY, final boolean isRtl,
- long paint, final long typeface) {
-
- draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
- (graphics, paintDelegate) -> {
- // WARNING: the logic in this method is similar to Paint_Delegate.measureText.
- // Any change to this method should be reflected in Paint.measureText
-
- // assert that the typeface passed is actually the one stored in paint.
- assert (typeface == paintDelegate.mNativeTypeface);
-
- // Paint.TextAlign indicates how the text is positioned relative to X.
- // LEFT is the default and there's nothing to do.
- float x = startX;
- int limit = index + count;
- if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
- RectF bounds =
- paintDelegate.measureText(text, index, count, null, 0, isRtl);
- float m = bounds.right - bounds.left;
- if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
- x -= m / 2;
- } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
- x -= m;
- }
- }
-
- new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x,
- startY).renderText(index, limit, isRtl, null, 0, true);
- });
- }
-
- private static void drawBitmap(long nativeCanvas, Bitmap_Delegate bitmap,
- long nativePaintOrZero, final int sleft, final int stop, final int sright,
- final int sbottom, final int dleft, final int dtop, final int dright,
- final int dbottom) {
- // get the delegate from the native int.
- BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return;
- }
-
- // get the paint, which could be null if the int is 0
- Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
-
- final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut);
-
- draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0],
- (graphics, paint) -> {
- if (paint != null && paint.isFilterBitmap()) {
- graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
- RenderingHints.VALUE_INTERPOLATION_BILINEAR);
- }
-
- //FIXME add support for canvas, screen and bitmap densities.
- graphics.drawImage(image, dleft, dtop, dright, dbottom, sleft, stop, sright,
- sbottom, null);
- });
- }
-
- /**
- * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate.
- * The image returns, through a 1-size boolean array, whether the drawing code should
- * use a SRC composite no matter what the paint says.
- *
- * @param bitmap the bitmap
- * @param paint the paint that will be used to draw
- * @param forceSrcMode whether the composite will have to be SRC
- * @return the image to draw
- */
- private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint,
- boolean[] forceSrcMode) {
- BufferedImage image = bitmap.getImage();
- forceSrcMode[0] = false;
-
- // if the bitmap config is alpha_8, then we erase all color value from it
- // before drawing it or apply the texture from the shader if present.
- if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) {
- Shader_Delegate shader = paint.getShader();
- java.awt.Paint javaPaint = null;
- if (shader instanceof BitmapShader_Delegate) {
- javaPaint = shader.getJavaPaint();
- }
-
- fixAlpha8Bitmap(image, javaPaint);
- } else if (!bitmap.hasAlpha()) {
- // hasAlpha is merely a rendering hint. There can in fact be alpha values
- // in the bitmap but it should be ignored at drawing time.
- // There is two ways to do this:
- // - override the composite to be SRC. This can only be used if the composite
- // was going to be SRC or SRC_OVER in the first place
- // - Create a different bitmap to draw in which all the alpha channel values is set
- // to 0xFF.
- if (paint != null) {
- PorterDuff.Mode mode = PorterDuff.intToMode(paint.getPorterDuffMode());
-
- forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER || mode == PorterDuff.Mode.SRC;
- }
-
- // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB
- if (!forceSrcMode[0]) {
- image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF);
- }
- }
-
- return image;
- }
-
- /**
- * This method will apply the correct color to the passed "only alpha" image. Colors on the
- * passed image will be destroyed.
- * If the passed javaPaint is null, the color will be set to 0. If a paint is passed, it will
- * be used to obtain the color that will be applied.
- * <p/>
- * This will destroy the passed image color channel.
- */
- private static void fixAlpha8Bitmap(final BufferedImage image,
- @Nullable java.awt.Paint javaPaint) {
- int w = image.getWidth();
- int h = image.getHeight();
-
- DataBuffer texture = null;
- if (javaPaint != null) {
- PaintContext context = javaPaint.createContext(ColorModel.getRGBdefault(), null, null,
- new AffineTransform(), null);
- texture = context.getRaster(0, 0, w, h).getDataBuffer();
- }
-
- int[] argb = new int[w * h];
- image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
-
- final int length = argb.length;
- for (int i = 0; i < length; i++) {
- argb[i] &= 0xFF000000;
- if (texture != null) {
- argb[i] |= texture.getElem(i) & 0x00FFFFFF;
- }
- }
-
- image.setRGB(0, 0, w, h, argb, 0, w);
- }
-
- protected int save(int saveFlags) {
- // get the current save count
- int count = mSnapshot.size();
-
- mSnapshot = mSnapshot.save(saveFlags);
-
- // return the old save count
- return count;
- }
-
- protected int saveLayerAlpha(RectF rect, int alpha, int saveFlags) {
- Paint_Delegate paint = new Paint_Delegate();
- paint.setAlpha(alpha);
- return saveLayer(rect, paint, saveFlags);
- }
-
- protected int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) {
- // get the current save count
- int count = mSnapshot.size();
-
- mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags);
-
- // return the old save count
- return count;
- }
-
- /**
- * Restores the {@link GcSnapshot} to <var>saveCount</var>
- * @param saveCount the saveCount
- */
- protected void restoreTo(int saveCount) {
- mSnapshot = mSnapshot.restoreTo(saveCount);
- }
-
- /**
- * Restores the top {@link GcSnapshot}
- */
- protected void restore() {
- mSnapshot = mSnapshot.restore();
- }
-
- protected boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
- return mSnapshot.clipRect(left, top, right, bottom, regionOp);
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
deleted file mode 100644
index c6827a3..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- * Copyright (C) 2013 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.graphics;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-
-import android.graphics.Paint_Delegate.FontInfo;
-import android.icu.lang.UScript;
-import android.icu.lang.UScriptRun;
-import android.icu.text.Bidi;
-import android.icu.text.BidiRun;
-
-import java.awt.Font;
-import java.awt.Graphics2D;
-import java.awt.Toolkit;
-import java.awt.font.FontRenderContext;
-import java.awt.font.GlyphVector;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Rectangle2D;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Render the text by breaking it into various scripts and using the right font for each script.
- * Can be used to measure the text without actually drawing it.
- */
-@SuppressWarnings("deprecation")
-public class BidiRenderer {
- private static String JAVA_VENDOR = System.getProperty("java.vendor");
-
- private static class ScriptRun {
- int start;
- int limit;
- boolean isRtl;
- int scriptCode;
- Font font;
-
- public ScriptRun(int start, int limit, boolean isRtl) {
- this.start = start;
- this.limit = limit;
- this.isRtl = isRtl;
- this.scriptCode = UScript.INVALID_CODE;
- }
- }
-
- private final Graphics2D mGraphics;
- private final Paint_Delegate mPaint;
- private char[] mText;
- // This List can contain nulls. A null font implies that the we weren't able to load the font
- // properly. So, if we encounter a situation where we try to use that font, log a warning.
- private List<Font> mFonts;
- // Bounds of the text drawn so far.
- private RectF mBounds;
- private float mBaseline;
-
- /**
- * @param graphics May be null.
- * @param paint The Paint to use to get the fonts. Should not be null.
- * @param text Unidirectional text. Should not be null.
- */
- public BidiRenderer(Graphics2D graphics, Paint_Delegate paint, char[] text) {
- assert (paint != null);
- mGraphics = graphics;
- mPaint = paint;
- mText = text;
- mFonts = new ArrayList<Font>(paint.getFonts().size());
- for (FontInfo fontInfo : paint.getFonts()) {
- if (fontInfo == null) {
- mFonts.add(null);
- continue;
- }
- mFonts.add(fontInfo.mFont);
- }
- mBounds = new RectF();
- }
-
- /**
- *
- * @param x The x-coordinate of the left edge of where the text should be drawn on the given
- * graphics.
- * @param y The y-coordinate at which to draw the text on the given mGraphics.
- *
- */
- public BidiRenderer setRenderLocation(float x, float y) {
- mBounds = new RectF(x, y, x, y);
- mBaseline = y;
- return this;
- }
-
- /**
- * Perform Bidi Analysis on the text and then render it.
- * <p/>
- * To skip the analysis and render unidirectional text, see {@link
- * #renderText(int, int, boolean, float[], int, boolean)}
- */
- public RectF renderText(int start, int limit, int bidiFlags, float[] advances,
- int advancesIndex, boolean draw) {
- Bidi bidi = new Bidi(mText, start, null, 0, limit - start, getIcuFlags(bidiFlags));
- for (int i = 0; i < bidi.countRuns(); i++) {
- BidiRun visualRun = bidi.getVisualRun(i);
- boolean isRtl = visualRun.getDirection() == Bidi.RTL;
- renderText(visualRun.getStart(), visualRun.getLimit(), isRtl, advances,
- advancesIndex, draw);
- }
- return mBounds;
- }
-
- /**
- * Render unidirectional text.
- * <p/>
- * This method can also be used to measure the width of the text without actually drawing it.
- * <p/>
- * @param start index of the first character
- * @param limit index of the first character that should not be rendered.
- * @param isRtl is the text right-to-left
- * @param advances If not null, then advances for each character to be rendered are returned
- * here.
- * @param advancesIndex index into advances from where the advances need to be filled.
- * @param draw If true and {@code graphics} is not null, draw the rendered text on the graphics
- * at the given co-ordinates
- * @return A rectangle specifying the bounds of the text drawn.
- */
- public RectF renderText(int start, int limit, boolean isRtl, float[] advances,
- int advancesIndex, boolean draw) {
- // We break the text into scripts and then select font based on it and then render each of
- // the script runs.
- for (ScriptRun run : getScriptRuns(mText, start, limit, isRtl, mFonts)) {
- int flag = Font.LAYOUT_NO_LIMIT_CONTEXT | Font.LAYOUT_NO_START_CONTEXT;
- flag |= isRtl ? Font.LAYOUT_RIGHT_TO_LEFT : Font.LAYOUT_LEFT_TO_RIGHT;
- renderScript(run.start, run.limit, run.font, flag, advances, advancesIndex, draw);
- advancesIndex += run.limit - run.start;
- }
- return mBounds;
- }
-
- /**
- * Render a script run to the right of the bounds passed. Use the preferred font to render as
- * much as possible. This also implements a fallback mechanism to render characters that cannot
- * be drawn using the preferred font.
- */
- private void renderScript(int start, int limit, Font preferredFont, int flag,
- float[] advances, int advancesIndex, boolean draw) {
- if (mFonts.size() == 0 || preferredFont == null) {
- return;
- }
-
- while (start < limit) {
- boolean foundFont = false;
- int canDisplayUpTo = preferredFont.canDisplayUpTo(mText, start, limit);
- if (canDisplayUpTo == -1) {
- // We can draw all characters in the text.
- render(start, limit, preferredFont, flag, advances, advancesIndex, draw);
- return;
- }
- if (canDisplayUpTo > start) {
- // We can draw something.
- render(start, canDisplayUpTo, preferredFont, flag, advances, advancesIndex, draw);
- advancesIndex += canDisplayUpTo - start;
- start = canDisplayUpTo;
- }
-
- // The current character cannot be drawn with the preferred font. Cycle through all the
- // fonts to check which one can draw it.
- int charCount = Character.isHighSurrogate(mText[start]) ? 2 : 1;
- for (Font font : mFonts) {
- if (font == null) {
- logFontWarning();
- continue;
- }
- canDisplayUpTo = font.canDisplayUpTo(mText, start, start + charCount);
- if (canDisplayUpTo == -1) {
- render(start, start+charCount, font, flag, advances, advancesIndex, draw);
- start += charCount;
- advancesIndex += charCount;
- foundFont = true;
- break;
- }
- }
- if (!foundFont) {
- // No font can display this char. Use the preferred font. The char will most
- // probably appear as a box or a blank space. We could, probably, use some
- // heuristics and break the character into the base character and diacritics and
- // then draw it, but it's probably not worth the effort.
- render(start, start + charCount, preferredFont, flag, advances, advancesIndex,
- draw);
- start += charCount;
- advancesIndex += charCount;
- }
- }
- }
-
- private static void logFontWarning() {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
- "Some fonts could not be loaded. The rendering may not be perfect. " +
- "Try running the IDE with JRE 7.", null, null);
- }
-
- /**
- * Renders the text to the right of the bounds with the given font.
- * @param font The font to render the text with.
- */
- private void render(int start, int limit, Font font, int flag, float[] advances,
- int advancesIndex, boolean draw) {
-
- FontRenderContext frc;
- if (mGraphics != null) {
- frc = mGraphics.getFontRenderContext();
- } else {
- frc = Toolkit.getDefaultToolkit().getFontMetrics(font).getFontRenderContext();
-
- // Metrics obtained this way don't have anti-aliasing set. So,
- // we create a new FontRenderContext with anti-aliasing set.
- AffineTransform transform = font.getTransform();
- if (mPaint.isAntiAliased() &&
- // Workaround for http://b.android.com/211659
- (transform.getScaleX() <= 9.9 ||
- !"JetBrains s.r.o".equals(JAVA_VENDOR))) {
- frc = new FontRenderContext(transform, true, frc.usesFractionalMetrics());
- }
- }
- GlyphVector gv = font.layoutGlyphVector(frc, mText, start, limit, flag);
- int ng = gv.getNumGlyphs();
- int[] ci = gv.getGlyphCharIndices(0, ng, null);
- if (advances != null) {
- for (int i = 0; i < ng; i++) {
- int adv_idx = advancesIndex + ci[i];
- advances[adv_idx] += gv.getGlyphMetrics(i).getAdvanceX();
- }
- }
- if (draw && mGraphics != null) {
- mGraphics.drawGlyphVector(gv, mBounds.right, mBaseline);
- }
-
- // Update the bounds.
- Rectangle2D awtBounds = gv.getLogicalBounds();
- RectF bounds = awtRectToAndroidRect(awtBounds, mBounds.right, mBaseline);
- // If the width of the bounds is zero, no text had been drawn earlier. Hence, use the
- // coordinates from the bounds as an offset.
- if (Math.abs(mBounds.right - mBounds.left) == 0) {
- mBounds = bounds;
- } else {
- mBounds.union(bounds);
- }
- }
-
- // --- Static helper methods ---
-
- private static RectF awtRectToAndroidRect(Rectangle2D awtRec, float offsetX, float offsetY) {
- float left = (float) awtRec.getX();
- float top = (float) awtRec.getY();
- float right = (float) (left + awtRec.getWidth());
- float bottom = (float) (top + awtRec.getHeight());
- RectF androidRect = new RectF(left, top, right, bottom);
- androidRect.offset(offsetX, offsetY);
- return androidRect;
- }
-
- /* package */ static List<ScriptRun> getScriptRuns(char[] text, int start, int limit,
- boolean isRtl, List<Font> fonts) {
- LinkedList<ScriptRun> scriptRuns = new LinkedList<ScriptRun>();
-
- int count = limit - start;
- UScriptRun uScriptRun = new UScriptRun(text, start, count);
- while (uScriptRun.next()) {
- int scriptStart = uScriptRun.getScriptStart();
- int scriptLimit = uScriptRun.getScriptLimit();
- ScriptRun run = new ScriptRun(scriptStart, scriptLimit, isRtl);
- run.scriptCode = uScriptRun.getScriptCode();
- setScriptFont(text, run, fonts);
- scriptRuns.add(run);
- }
-
- return scriptRuns;
- }
-
- // TODO: Replace this method with one which returns the font based on the scriptCode.
- private static void setScriptFont(char[] text, ScriptRun run,
- List<Font> fonts) {
- for (Font font : fonts) {
- if (font == null) {
- logFontWarning();
- continue;
- }
- if (font.canDisplayUpTo(text, run.start, run.limit) == -1) {
- run.font = font;
- return;
- }
- }
- run.font = fonts.get(0);
- }
-
- private static int getIcuFlags(int bidiFlag) {
- switch (bidiFlag) {
- case Paint.BIDI_LTR:
- case Paint.BIDI_FORCE_LTR:
- return Bidi.DIRECTION_LEFT_TO_RIGHT;
- case Paint.BIDI_RTL:
- case Paint.BIDI_FORCE_RTL:
- return Bidi.DIRECTION_RIGHT_TO_LEFT;
- case Paint.BIDI_DEFAULT_LTR:
- return Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
- case Paint.BIDI_DEFAULT_RTL:
- return Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT;
- default:
- assert false;
- return Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
deleted file mode 100644
index 8bd2a7a..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2011 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.graphics;
-
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.ninepatch.NinePatchChunk;
-import com.android.resources.Density;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.annotation.Nullable;
-import com.android.layoutlib.bridge.util.NinePatchInputStream;
-import android.graphics.BitmapFactory.Options;
-import android.graphics.Bitmap_Delegate.BitmapCreateFlags;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.EnumSet;
-import java.util.Set;
-
-/**
- * Delegate implementing the native methods of android.graphics.BitmapFactory
- *
- * Through the layoutlib_create tool, the original native methods of BitmapFactory have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
- * around to map int to instance of the delegate.
- *
- */
-/*package*/ class BitmapFactory_Delegate {
-
- // ------ Native Delegates ------
-
- @LayoutlibDelegate
- /*package*/ static Bitmap nativeDecodeStream(InputStream is, byte[] storage,
- @Nullable Rect padding, @Nullable Options opts) {
- Bitmap bm = null;
-
- Density density = Density.MEDIUM;
- Set<BitmapCreateFlags> bitmapCreateFlags = EnumSet.of(BitmapCreateFlags.MUTABLE);
- if (opts != null) {
- density = Density.getEnum(opts.inDensity);
- if (opts.inPremultiplied) {
- bitmapCreateFlags.add(BitmapCreateFlags.PREMULTIPLIED);
- }
- opts.inScaled = false;
- }
-
- try {
- if (is instanceof NinePatchInputStream) {
- NinePatchInputStream npis = (NinePatchInputStream) is;
- npis.disableFakeMarkSupport();
-
- // load the bitmap as a nine patch
- com.android.ninepatch.NinePatch ninePatch = com.android.ninepatch.NinePatch.load(
- npis, true /*is9Patch*/, false /*convert*/);
-
- // get the bitmap and chunk objects.
- bm = Bitmap_Delegate.createBitmap(ninePatch.getImage(), bitmapCreateFlags,
- density);
- NinePatchChunk chunk = ninePatch.getChunk();
-
- // put the chunk in the bitmap
- bm.setNinePatchChunk(NinePatch_Delegate.serialize(chunk));
-
- if (padding != null) {
- // read the padding
- int[] paddingArray = chunk.getPadding();
- padding.left = paddingArray[0];
- padding.top = paddingArray[1];
- padding.right = paddingArray[2];
- padding.bottom = paddingArray[3];
- }
- } else {
- // load the bitmap directly.
- bm = Bitmap_Delegate.createBitmap(is, bitmapCreateFlags, density);
- }
- } catch (IOException e) {
- Bridge.getLog().error(null, "Failed to load image", e, null);
- }
-
- return bm;
- }
-
- @LayoutlibDelegate
- /*package*/ static Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,
- Rect padding, Options opts) {
- opts.inBitmap = null;
- return null;
- }
-
- @LayoutlibDelegate
- /*package*/ static Bitmap nativeDecodeAsset(long asset, Rect padding, Options opts) {
- opts.inBitmap = null;
- return null;
- }
-
- @LayoutlibDelegate
- /*package*/ static Bitmap nativeDecodeByteArray(byte[] data, int offset,
- int length, Options opts) {
- opts.inBitmap = null;
- return null;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeIsSeekable(FileDescriptor fd) {
- return true;
- }
-
- /**
- * Set the newly decoded bitmap's density based on the Options.
- *
- * Copied from {@link BitmapFactory#setDensityFromOptions(Bitmap, Options)}.
- */
- @LayoutlibDelegate
- /*package*/ static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {
- if (outputBitmap == null || opts == null) return;
-
- final int density = opts.inDensity;
- if (density != 0) {
- outputBitmap.setDensity(density);
- final int targetDensity = opts.inTargetDensity;
- if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
- return;
- }
-
- // --- Change from original implementation begins ---
- // LayoutLib doesn't scale the nine patch when decoding it. Hence, don't change the
- // density of the source bitmap in case of ninepatch.
-
- if (opts.inScaled) {
- // --- Change from original implementation ends. ---
- outputBitmap.setDensity(targetDensity);
- }
- } else if (opts.inBitmap != null) {
- // bitmap was reused, ensure density is reset
- outputBitmap.setDensity(Bitmap.getDefaultDensity());
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java
deleted file mode 100644
index 4914a48..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.graphics.Shader.TileMode;
-
-import java.awt.PaintContext;
-import java.awt.Rectangle;
-import java.awt.RenderingHints;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.NoninvertibleTransformException;
-import java.awt.geom.Rectangle2D;
-import java.awt.image.BufferedImage;
-import java.awt.image.ColorModel;
-import java.awt.image.Raster;
-
-/**
- * Delegate implementing the native methods of android.graphics.BitmapShader
- *
- * Through the layoutlib_create tool, the original native methods of BitmapShader have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original BitmapShader class.
- *
- * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager},
- * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}.
- *
- * @see Shader_Delegate
- *
- */
-public class BitmapShader_Delegate extends Shader_Delegate {
-
- // ---- delegate data ----
- private java.awt.Paint mJavaPaint;
-
- // ---- Public Helper methods ----
-
- @Override
- public java.awt.Paint getJavaPaint() {
- return mJavaPaint;
- }
-
- @Override
- public boolean isSupported() {
- return true;
- }
-
- @Override
- public String getSupportMessage() {
- // no message since isSupported returns true;
- return null;
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreate(long nativeMatrix, Bitmap androidBitmap,
- int shaderTileModeX, int shaderTileModeY) {
- Bitmap_Delegate bitmap = Bitmap_Delegate.getDelegate(androidBitmap);
- if (bitmap == null) {
- return 0;
- }
-
- BitmapShader_Delegate newDelegate = new BitmapShader_Delegate(nativeMatrix,
- bitmap.getImage(),
- Shader_Delegate.getTileMode(shaderTileModeX),
- Shader_Delegate.getTileMode(shaderTileModeY));
- return sManager.addNewDelegate(newDelegate);
- }
-
- // ---- Private delegate/helper methods ----
-
- private BitmapShader_Delegate(long matrix, BufferedImage image,
- TileMode tileModeX, TileMode tileModeY) {
- super(matrix);
- mJavaPaint = new BitmapShaderPaint(image, tileModeX, tileModeY);
- }
-
- private class BitmapShaderPaint implements java.awt.Paint {
- private final BufferedImage mImage;
- private final TileMode mTileModeX;
- private final TileMode mTileModeY;
-
- BitmapShaderPaint(BufferedImage image,
- TileMode tileModeX, TileMode tileModeY) {
- mImage = image;
- mTileModeX = tileModeX;
- mTileModeY = tileModeY;
- }
-
- @Override
- public PaintContext createContext(ColorModel colorModel, Rectangle deviceBounds,
- Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) {
- AffineTransform canvasMatrix;
- try {
- canvasMatrix = xform.createInverse();
- } catch (NoninvertibleTransformException e) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
- "Unable to inverse matrix in BitmapShader", e, null /*data*/);
- canvasMatrix = new AffineTransform();
- }
-
- AffineTransform localMatrix = getLocalMatrix();
- try {
- localMatrix = localMatrix.createInverse();
- } catch (NoninvertibleTransformException e) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
- "Unable to inverse matrix in BitmapShader", e, null /*data*/);
- localMatrix = new AffineTransform();
- }
-
- if (!colorModel.isCompatibleRaster(mImage.getRaster())) {
- // Fallback to the default ARGB color model
- colorModel = ColorModel.getRGBdefault();
- }
-
- return new BitmapShaderContext(canvasMatrix, localMatrix, colorModel);
- }
-
- private class BitmapShaderContext implements PaintContext {
-
- private final AffineTransform mCanvasMatrix;
- private final AffineTransform mLocalMatrix;
- private final ColorModel mColorModel;
-
- public BitmapShaderContext(
- AffineTransform canvasMatrix,
- AffineTransform localMatrix,
- ColorModel colorModel) {
- mCanvasMatrix = canvasMatrix;
- mLocalMatrix = localMatrix;
- mColorModel = colorModel;
- }
-
- @Override
- public void dispose() {
- }
-
- @Override
- public ColorModel getColorModel() {
- return mColorModel;
- }
-
- @Override
- public Raster getRaster(int x, int y, int w, int h) {
- BufferedImage image = new BufferedImage(
- mColorModel, mColorModel.createCompatibleWritableRaster(w, h),
- mColorModel.isAlphaPremultiplied(), null);
-
- int[] data = new int[w*h];
-
- int index = 0;
- float[] pt1 = new float[2];
- float[] pt2 = new float[2];
- for (int iy = 0 ; iy < h ; iy++) {
- for (int ix = 0 ; ix < w ; ix++) {
- // handle the canvas transform
- pt1[0] = x + ix;
- pt1[1] = y + iy;
- mCanvasMatrix.transform(pt1, 0, pt2, 0, 1);
-
- // handle the local matrix.
- pt1[0] = pt2[0];
- pt1[1] = pt2[1];
- mLocalMatrix.transform(pt1, 0, pt2, 0, 1);
-
- data[index++] = getColor(pt2[0], pt2[1]);
- }
- }
-
- image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
-
- return image.getRaster();
- }
- }
-
- /**
- * Returns a color for an arbitrary point.
- */
- private int getColor(float fx, float fy) {
- int x = getCoordinate(Math.round(fx), mImage.getWidth(), mTileModeX);
- int y = getCoordinate(Math.round(fy), mImage.getHeight(), mTileModeY);
-
- return mImage.getRGB(x, y);
- }
-
- private int getCoordinate(int i, int size, TileMode mode) {
- if (i < 0) {
- switch (mode) {
- case CLAMP:
- i = 0;
- break;
- case REPEAT:
- i = size - 1 - (-i % size);
- break;
- case MIRROR:
- // this is the same as the positive side, just make the value positive
- // first.
- i = -i;
- int count = i / size;
- i = i % size;
-
- if ((count % 2) == 1) {
- i = size - 1 - i;
- }
- break;
- }
- } else if (i >= size) {
- switch (mode) {
- case CLAMP:
- i = size - 1;
- break;
- case REPEAT:
- i = i % size;
- break;
- case MIRROR:
- int count = i / size;
- i = i % size;
-
- if ((count % 2) == 1) {
- i = size - 1 - i;
- }
- break;
- }
- }
-
- return i;
- }
-
-
- @Override
- public int getTransparency() {
- return java.awt.Paint.TRANSLUCENT;
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
deleted file mode 100644
index 43c95f4..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
+++ /dev/null
@@ -1,729 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.layoutlib.bridge.impl.RenderAction;
-import com.android.resources.Density;
-import com.android.resources.ResourceType;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.annotation.Nullable;
-import android.graphics.Bitmap.Config;
-import android.os.Parcel;
-
-import java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.Buffer;
-import java.util.Arrays;
-import java.util.EnumSet;
-import java.util.Set;
-
-import javax.imageio.ImageIO;
-import libcore.util.NativeAllocationRegistry_Delegate;
-
-/**
- * Delegate implementing the native methods of android.graphics.Bitmap
- *
- * Through the layoutlib_create tool, the original native methods of Bitmap have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original Bitmap class.
- *
- * @see DelegateManager
- *
- */
-public final class Bitmap_Delegate {
-
-
- public enum BitmapCreateFlags {
- NONE, PREMULTIPLIED, MUTABLE
- }
-
- // ---- delegate manager ----
- private static final DelegateManager<Bitmap_Delegate> sManager =
- new DelegateManager<>(Bitmap_Delegate.class);
- private static long sFinalizer = -1;
-
- // ---- delegate helper data ----
-
- // ---- delegate data ----
- private final Config mConfig;
- private final BufferedImage mImage;
- private boolean mHasAlpha = true;
- private boolean mHasMipMap = false; // TODO: check the default.
- private boolean mIsPremultiplied = true;
- private int mGenerationId = 0;
-
-
- // ---- Public Helper methods ----
-
- /**
- * Returns the native delegate associated to a given an int referencing a {@link Bitmap} object.
- */
- public static Bitmap_Delegate getDelegate(long native_bitmap) {
- return sManager.getDelegate(native_bitmap);
- }
-
- @Nullable
- public static Bitmap_Delegate getDelegate(@Nullable Bitmap bitmap) {
- return bitmap == null ? null : getDelegate(bitmap.getNativeInstance());
- }
-
- /**
- * Creates and returns a {@link Bitmap} initialized with the given file content.
- *
- * @param input the file from which to read the bitmap content
- * @param isMutable whether the bitmap is mutable
- * @param density the density associated with the bitmap
- *
- * @see Bitmap#isMutable()
- * @see Bitmap#getDensity()
- */
- public static Bitmap createBitmap(File input, boolean isMutable, Density density)
- throws IOException {
- return createBitmap(input, getPremultipliedBitmapCreateFlags(isMutable), density);
- }
-
- /**
- * Creates and returns a {@link Bitmap} initialized with the given file content.
- *
- * @param input the file from which to read the bitmap content
- * @param density the density associated with the bitmap
- *
- * @see Bitmap#isPremultiplied()
- * @see Bitmap#isMutable()
- * @see Bitmap#getDensity()
- */
- private static Bitmap createBitmap(File input, Set<BitmapCreateFlags> createFlags,
- Density density) throws IOException {
- // create a delegate with the content of the file.
- BufferedImage image = ImageIO.read(input);
- if (image == null && input.exists()) {
- // There was a problem decoding the image, or the decoder isn't registered. Webp maybe.
- // Replace with a broken image icon.
- BridgeContext currentContext = RenderAction.getCurrentContext();
- if (currentContext != null) {
- RenderResources resources = currentContext.getRenderResources();
- ResourceValue broken = resources.getFrameworkResource(ResourceType.DRAWABLE,
- "ic_menu_report_image");
- File brokenFile = new File(broken.getValue());
- if (brokenFile.exists()) {
- image = ImageIO.read(brokenFile);
- }
- }
- }
- Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
-
- return createBitmap(delegate, createFlags, density.getDpiValue());
- }
-
- /**
- * Creates and returns a {@link Bitmap} initialized with the given stream content.
- *
- * @param input the stream from which to read the bitmap content
- * @param isMutable whether the bitmap is mutable
- * @param density the density associated with the bitmap
- *
- * @see Bitmap#isMutable()
- * @see Bitmap#getDensity()
- */
- public static Bitmap createBitmap(InputStream input, boolean isMutable, Density density)
- throws IOException {
- return createBitmap(input, getPremultipliedBitmapCreateFlags(isMutable), density);
- }
-
- /**
- * Creates and returns a {@link Bitmap} initialized with the given stream content.
- *
- * @param input the stream from which to read the bitmap content
- * @param density the density associated with the bitmap
- *
- * @see Bitmap#isPremultiplied()
- * @see Bitmap#isMutable()
- * @see Bitmap#getDensity()
- */
- public static Bitmap createBitmap(InputStream input, Set<BitmapCreateFlags> createFlags,
- Density density) throws IOException {
- // create a delegate with the content of the stream.
- Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
-
- return createBitmap(delegate, createFlags, density.getDpiValue());
- }
-
- /**
- * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage}
- *
- * @param image the bitmap content
- * @param isMutable whether the bitmap is mutable
- * @param density the density associated with the bitmap
- *
- * @see Bitmap#isMutable()
- * @see Bitmap#getDensity()
- */
- public static Bitmap createBitmap(BufferedImage image, boolean isMutable, Density density) {
- return createBitmap(image, getPremultipliedBitmapCreateFlags(isMutable), density);
- }
-
- /**
- * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage}
- *
- * @param image the bitmap content
- * @param density the density associated with the bitmap
- *
- * @see Bitmap#isPremultiplied()
- * @see Bitmap#isMutable()
- * @see Bitmap#getDensity()
- */
- public static Bitmap createBitmap(BufferedImage image, Set<BitmapCreateFlags> createFlags,
- Density density) {
- // create a delegate with the given image.
- Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
-
- return createBitmap(delegate, createFlags, density.getDpiValue());
- }
-
- private static int getBufferedImageType() {
- return BufferedImage.TYPE_INT_ARGB;
- }
-
- /**
- * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}.
- */
- public BufferedImage getImage() {
- return mImage;
- }
-
- /**
- * Returns the Android bitmap config. Note that this not the config of the underlying
- * Java2D bitmap.
- */
- public Config getConfig() {
- return mConfig;
- }
-
- /**
- * Returns the hasAlpha rendering hint
- * @return true if the bitmap alpha should be used at render time
- */
- public boolean hasAlpha() {
- return mHasAlpha && mConfig != Config.RGB_565;
- }
-
- /**
- * Update the generationId.
- *
- * @see Bitmap#getGenerationId()
- */
- public void change() {
- mGenerationId++;
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width,
- int height, int nativeConfig, boolean isMutable) {
- int imageType = getBufferedImageType();
-
- // create the image
- BufferedImage image = new BufferedImage(width, height, imageType);
-
- if (colors != null) {
- image.setRGB(0, 0, width, height, colors, offset, stride);
- }
-
- // create a delegate with the content of the stream.
- Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig));
-
- return createBitmap(delegate, getPremultipliedBitmapCreateFlags(isMutable),
- Bitmap.getDefaultDensity());
- }
-
- @LayoutlibDelegate
- /*package*/ static Bitmap nativeCopy(long srcBitmap, int nativeConfig, boolean isMutable) {
- Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(srcBitmap);
- if (srcBmpDelegate == null) {
- return null;
- }
-
- BufferedImage srcImage = srcBmpDelegate.getImage();
-
- int width = srcImage.getWidth();
- int height = srcImage.getHeight();
-
- int imageType = getBufferedImageType();
-
- // create the image
- BufferedImage image = new BufferedImage(width, height, imageType);
-
- // copy the source image into the image.
- int[] argb = new int[width * height];
- srcImage.getRGB(0, 0, width, height, argb, 0, width);
- image.setRGB(0, 0, width, height, argb, 0, width);
-
- // create a delegate with the content of the stream.
- Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig));
-
- return createBitmap(delegate, getPremultipliedBitmapCreateFlags(isMutable),
- Bitmap.getDefaultDensity());
- }
-
- @LayoutlibDelegate
- /*package*/ static Bitmap nativeCopyAshmem(long nativeSrcBitmap) {
- // Unused method; no implementation provided.
- assert false;
- return null;
- }
-
- @LayoutlibDelegate
- /*package*/ static Bitmap nativeCopyAshmemConfig(long nativeSrcBitmap, int nativeConfig) {
- // Unused method; no implementation provided.
- assert false;
- return null;
- }
-
- @LayoutlibDelegate
- /*package*/ static long nativeGetNativeFinalizer() {
- synchronized (Bitmap_Delegate.class) {
- if (sFinalizer == -1) {
- sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor);
- }
- return sFinalizer;
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeRecycle(long nativeBitmap) {
- // In our case reycle() is a no-op. We will let the finalizer to dispose the bitmap.
- return true;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nativeReconfigure(long nativeBitmap, int width, int height,
- int config, boolean isPremultiplied) {
- Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
- "Bitmap.reconfigure() is not supported", null /*data*/);
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeCompress(long nativeBitmap, int format, int quality,
- OutputStream stream, byte[] tempStorage) {
- Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
- "Bitmap.compress() is not supported", null /*data*/);
- return true;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nativeErase(long nativeBitmap, int color) {
- // get the delegate from the native int.
- Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
- if (delegate == null) {
- return;
- }
-
- BufferedImage image = delegate.mImage;
-
- Graphics2D g = image.createGraphics();
- try {
- g.setColor(new java.awt.Color(color, true));
-
- g.fillRect(0, 0, image.getWidth(), image.getHeight());
- } finally {
- g.dispose();
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static int nativeRowBytes(long nativeBitmap) {
- // get the delegate from the native int.
- Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
- if (delegate == null) {
- return 0;
- }
-
- return delegate.mImage.getWidth();
- }
-
- @LayoutlibDelegate
- /*package*/ static int nativeConfig(long nativeBitmap) {
- // get the delegate from the native int.
- Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
- if (delegate == null) {
- return 0;
- }
-
- return delegate.mConfig.nativeInt;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeHasAlpha(long nativeBitmap) {
- // get the delegate from the native int.
- Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
- return delegate == null || delegate.mHasAlpha;
-
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeHasMipMap(long nativeBitmap) {
- // get the delegate from the native int.
- Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
- return delegate == null || delegate.mHasMipMap;
-
- }
-
- @LayoutlibDelegate
- /*package*/ static int nativeGetPixel(long nativeBitmap, int x, int y) {
- // get the delegate from the native int.
- Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
- if (delegate == null) {
- return 0;
- }
-
- return delegate.mImage.getRGB(x, y);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nativeGetPixels(long nativeBitmap, int[] pixels, int offset,
- int stride, int x, int y, int width, int height) {
- Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
- if (delegate == null) {
- return;
- }
-
- delegate.getImage().getRGB(x, y, width, height, pixels, offset, stride);
- }
-
-
- @LayoutlibDelegate
- /*package*/ static void nativeSetPixel(long nativeBitmap, int x, int y, int color) {
- Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
- if (delegate == null) {
- return;
- }
-
- delegate.getImage().setRGB(x, y, color);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nativeSetPixels(long nativeBitmap, int[] colors, int offset,
- int stride, int x, int y, int width, int height) {
- Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
- if (delegate == null) {
- return;
- }
-
- delegate.getImage().setRGB(x, y, width, height, colors, offset, stride);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nativeCopyPixelsToBuffer(long nativeBitmap, Buffer dst) {
- // FIXME implement native delegate
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Bitmap.copyPixelsToBuffer is not supported.", null, null /*data*/);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nativeCopyPixelsFromBuffer(long nb, Buffer src) {
- // FIXME implement native delegate
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Bitmap.copyPixelsFromBuffer is not supported.", null, null /*data*/);
- }
-
- @LayoutlibDelegate
- /*package*/ static int nativeGenerationId(long nativeBitmap) {
- Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
- if (delegate == null) {
- return 0;
- }
-
- return delegate.mGenerationId;
- }
-
- @LayoutlibDelegate
- /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) {
- // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only
- // used during aidl call so really this should not be called.
- Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
- "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.",
- null /*data*/);
- return null;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeWriteToParcel(long nativeBitmap, boolean isMutable,
- int density, Parcel p) {
- // This is only called when sending a bitmap through aidl, so really this should not
- // be called.
- Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
- "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.",
- null /*data*/);
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static Bitmap nativeExtractAlpha(long nativeBitmap, long nativePaint,
- int[] offsetXY) {
- Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap);
- if (bitmap == null) {
- return null;
- }
-
- // get the paint which can be null if nativePaint is 0.
- Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint);
-
- if (paint != null && paint.getMaskFilter() != null) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER,
- "MaskFilter not supported in Bitmap.extractAlpha",
- null, null /*data*/);
- }
-
- int alpha = paint != null ? paint.getAlpha() : 0xFF;
- BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha);
-
- // create the delegate. The actual Bitmap config is only an alpha channel
- Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8);
-
- // the density doesn't matter, it's set by the Java method.
- return createBitmap(delegate, EnumSet.of(BitmapCreateFlags.MUTABLE),
- Density.DEFAULT_DENSITY /*density*/);
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeIsPremultiplied(long nativeBitmap) {
- // get the delegate from the native int.
- Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
- return delegate != null && delegate.mIsPremultiplied;
-
- }
-
- @LayoutlibDelegate
- /*package*/ static void nativeSetPremultiplied(long nativeBitmap, boolean isPremul) {
- // get the delegate from the native int.
- Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
- if (delegate == null) {
- return;
- }
-
- delegate.mIsPremultiplied = isPremul;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nativeSetHasAlpha(long nativeBitmap, boolean hasAlpha,
- boolean isPremul) {
- // get the delegate from the native int.
- Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
- if (delegate == null) {
- return;
- }
-
- delegate.mHasAlpha = hasAlpha;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nativeSetHasMipMap(long nativeBitmap, boolean hasMipMap) {
- // get the delegate from the native int.
- Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
- if (delegate == null) {
- return;
- }
-
- delegate.mHasMipMap = hasMipMap;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeSameAs(long nb0, long nb1) {
- Bitmap_Delegate delegate1 = sManager.getDelegate(nb0);
- if (delegate1 == null) {
- return false;
- }
-
- Bitmap_Delegate delegate2 = sManager.getDelegate(nb1);
- if (delegate2 == null) {
- return false;
- }
-
- BufferedImage image1 = delegate1.getImage();
- BufferedImage image2 = delegate2.getImage();
- if (delegate1.mConfig != delegate2.mConfig ||
- image1.getWidth() != image2.getWidth() ||
- image1.getHeight() != image2.getHeight()) {
- return false;
- }
-
- // get the internal data
- int w = image1.getWidth();
- int h = image2.getHeight();
- int[] argb1 = new int[w*h];
- int[] argb2 = new int[w*h];
-
- image1.getRGB(0, 0, w, h, argb1, 0, w);
- image2.getRGB(0, 0, w, h, argb2, 0, w);
-
- // compares
- if (delegate1.mConfig == Config.ALPHA_8) {
- // in this case we have to manually compare the alpha channel as the rest is garbage.
- final int length = w*h;
- for (int i = 0 ; i < length ; i++) {
- if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) {
- return false;
- }
- }
- return true;
- }
-
- return Arrays.equals(argb1, argb2);
- }
-
- @LayoutlibDelegate
- /*package*/ static int nativeGetAllocationByteCount(long nativeBitmap) {
- // get the delegate from the native int.
- Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
- if (delegate == null) {
- return 0;
- }
- return nativeRowBytes(nativeBitmap) * delegate.mImage.getHeight();
-
- }
-
- @LayoutlibDelegate
- /*package*/ static void nativePrepareToDraw(long nativeBitmap) {
- // do nothing as Bitmap_Delegate does not have caches
- }
-
- @LayoutlibDelegate
- /*package*/ static Bitmap nativeCopyPreserveInternalConfig(long nativeBitmap) {
- Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(nativeBitmap);
- if (srcBmpDelegate == null) {
- return null;
- }
-
- BufferedImage srcImage = srcBmpDelegate.getImage();
-
- // create the image
- BufferedImage image = new BufferedImage(srcImage.getColorModel(), srcImage.copyData(null),
- srcImage.isAlphaPremultiplied(), null);
-
- // create a delegate with the content of the stream.
- Bitmap_Delegate delegate = new Bitmap_Delegate(image, srcBmpDelegate.getConfig());
-
- return createBitmap(delegate, EnumSet.of(BitmapCreateFlags.NONE),
- Bitmap.getDefaultDensity());
- }
-
- @LayoutlibDelegate
- /*package*/ static Bitmap nativeCreateHardwareBitmap(GraphicBuffer buffer) {
- Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
- "Bitmap.nativeCreateHardwareBitmap() is not supported", null /*data*/);
- return null;
- }
-
- @LayoutlibDelegate
- /*package*/ static GraphicBuffer nativeCreateGraphicBufferHandle(long nativeBitmap) {
- Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
- "Bitmap.nativeCreateGraphicBufferHandle() is not supported", null /*data*/);
- return null;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeIsSRGB(long nativeBitmap) {
- Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
- "Color spaces are not supported", null /*data*/);
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeGetColorSpace(long nativePtr, float[] xyz, float[] params) {
- Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
- "Color spaces are not supported", null /*data*/);
- return false;
- }
-
- // ---- Private delegate/helper methods ----
-
- private Bitmap_Delegate(BufferedImage image, Config config) {
- mImage = image;
- mConfig = config;
- }
-
- private static Bitmap createBitmap(Bitmap_Delegate delegate,
- Set<BitmapCreateFlags> createFlags, int density) {
- // get its native_int
- long nativeInt = sManager.addNewDelegate(delegate);
-
- int width = delegate.mImage.getWidth();
- int height = delegate.mImage.getHeight();
- boolean isMutable = createFlags.contains(BitmapCreateFlags.MUTABLE);
- boolean isPremultiplied = createFlags.contains(BitmapCreateFlags.PREMULTIPLIED);
-
- // and create/return a new Bitmap with it
- return new Bitmap(nativeInt, width, height, density, isMutable,
- isPremultiplied, null /*ninePatchChunk*/, null /* layoutBounds */);
- }
-
- private static Set<BitmapCreateFlags> getPremultipliedBitmapCreateFlags(boolean isMutable) {
- Set<BitmapCreateFlags> createFlags = EnumSet.of(BitmapCreateFlags.PREMULTIPLIED);
- if (isMutable) {
- createFlags.add(BitmapCreateFlags.MUTABLE);
- }
- return createFlags;
- }
-
- /**
- * Creates and returns a copy of a given BufferedImage.
- * <p/>
- * if alpha is different than 255, then it is applied to the alpha channel of each pixel.
- *
- * @param image the image to copy
- * @param imageType the type of the new image
- * @param alpha an optional alpha modifier
- * @return a new BufferedImage
- */
- /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) {
- int w = image.getWidth();
- int h = image.getHeight();
-
- BufferedImage result = new BufferedImage(w, h, imageType);
-
- int[] argb = new int[w * h];
- image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
-
- if (alpha != 255) {
- final int length = argb.length;
- for (int i = 0 ; i < length; i++) {
- int a = (argb[i] >>> 24 * alpha) / 255;
- argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF);
- }
- }
-
- result.setRGB(0, 0, w, h, argb, 0, w);
-
- return result;
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java b/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java
deleted file mode 100644
index 5cc964a..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright (C) 2014 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.graphics;
-
-import java.awt.Composite;
-import java.awt.CompositeContext;
-import java.awt.RenderingHints;
-import java.awt.image.ColorModel;
-import java.awt.image.DataBuffer;
-import java.awt.image.Raster;
-import java.awt.image.WritableRaster;
-
-/*
- * (non-Javadoc)
- * The class is adapted from a demo tool for Blending Modes written by
- * Romain Guy (romainguy@android.com). The tool is available at
- * http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/
- *
- * This class has been adapted for applying color filters. When applying color filters, the src
- * image should not extend beyond the dest image, but in our implementation of the filters, it does.
- * To compensate for the effect, we recompute the alpha value of the src image before applying
- * the color filter as it should have been applied.
- */
-public final class BlendComposite implements Composite {
- public enum BlendingMode {
- MULTIPLY(),
- SCREEN(),
- DARKEN(),
- LIGHTEN(),
- OVERLAY(),
- ADD();
-
- private final BlendComposite mComposite;
-
- BlendingMode() {
- mComposite = new BlendComposite(this);
- }
-
- BlendComposite getBlendComposite() {
- return mComposite;
- }
- }
-
- private float alpha;
- private BlendingMode mode;
-
- private BlendComposite(BlendingMode mode) {
- this(mode, 1.0f);
- }
-
- private BlendComposite(BlendingMode mode, float alpha) {
- this.mode = mode;
- setAlpha(alpha);
- }
-
- public static BlendComposite getInstance(BlendingMode mode) {
- return mode.getBlendComposite();
- }
-
- public static BlendComposite getInstance(BlendingMode mode, float alpha) {
- if (alpha > 0.9999f) {
- return getInstance(mode);
- }
- return new BlendComposite(mode, alpha);
- }
-
- public float getAlpha() {
- return alpha;
- }
-
- public BlendingMode getMode() {
- return mode;
- }
-
- private void setAlpha(float alpha) {
- if (alpha < 0.0f || alpha > 1.0f) {
- assert false : "alpha must be comprised between 0.0f and 1.0f";
- alpha = Math.min(alpha, 1.0f);
- alpha = Math.max(alpha, 0.0f);
- }
-
- this.alpha = alpha;
- }
-
- @Override
- public int hashCode() {
- return Float.floatToIntBits(alpha) * 31 + mode.ordinal();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof BlendComposite)) {
- return false;
- }
-
- BlendComposite bc = (BlendComposite) obj;
-
- return mode == bc.mode && alpha == bc.alpha;
- }
-
- public CompositeContext createContext(ColorModel srcColorModel,
- ColorModel dstColorModel,
- RenderingHints hints) {
- return new BlendingContext(this);
- }
-
- private static final class BlendingContext implements CompositeContext {
- private final Blender blender;
- private final BlendComposite composite;
-
- private BlendingContext(BlendComposite composite) {
- this.composite = composite;
- this.blender = Blender.getBlenderFor(composite);
- }
-
- public void dispose() {
- }
-
- public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
- if (src.getSampleModel().getDataType() != DataBuffer.TYPE_INT ||
- dstIn.getSampleModel().getDataType() != DataBuffer.TYPE_INT ||
- dstOut.getSampleModel().getDataType() != DataBuffer.TYPE_INT) {
- throw new IllegalStateException(
- "Source and destination must store pixels as INT.");
- }
-
- int width = Math.min(src.getWidth(), dstIn.getWidth());
- int height = Math.min(src.getHeight(), dstIn.getHeight());
-
- float alpha = composite.getAlpha();
-
- int[] srcPixel = new int[4];
- int[] dstPixel = new int[4];
- int[] result = new int[4];
- int[] srcPixels = new int[width];
- int[] dstPixels = new int[width];
-
- for (int y = 0; y < height; y++) {
- dstIn.getDataElements(0, y, width, 1, dstPixels);
- if (alpha != 0) {
- src.getDataElements(0, y, width, 1, srcPixels);
- for (int x = 0; x < width; x++) {
- // pixels are stored as INT_ARGB
- // our arrays are [R, G, B, A]
- int pixel = srcPixels[x];
- srcPixel[0] = (pixel >> 16) & 0xFF;
- srcPixel[1] = (pixel >> 8) & 0xFF;
- srcPixel[2] = (pixel ) & 0xFF;
- srcPixel[3] = (pixel >> 24) & 0xFF;
-
- pixel = dstPixels[x];
- dstPixel[0] = (pixel >> 16) & 0xFF;
- dstPixel[1] = (pixel >> 8) & 0xFF;
- dstPixel[2] = (pixel ) & 0xFF;
- dstPixel[3] = (pixel >> 24) & 0xFF;
-
- // ---- Modified from original ----
- // recompute src pixel for transparency.
- srcPixel[3] *= dstPixel[3] / 0xFF;
- // ---- Modification ends ----
-
- result = blender.blend(srcPixel, dstPixel, result);
-
- // mixes the result with the opacity
- if (alpha == 1) {
- dstPixels[x] = (result[3] & 0xFF) << 24 |
- (result[0] & 0xFF) << 16 |
- (result[1] & 0xFF) << 8 |
- result[2] & 0xFF;
- } else {
- dstPixels[x] =
- ((int) (dstPixel[3] + (result[3] - dstPixel[3]) * alpha) & 0xFF) << 24 |
- ((int) (dstPixel[0] + (result[0] - dstPixel[0]) * alpha) & 0xFF) << 16 |
- ((int) (dstPixel[1] + (result[1] - dstPixel[1]) * alpha) & 0xFF) << 8 |
- (int) (dstPixel[2] + (result[2] - dstPixel[2]) * alpha) & 0xFF;
- }
-
- }
- }
- dstOut.setDataElements(0, y, width, 1, dstPixels);
- }
- }
- }
-
- private static abstract class Blender {
- public abstract int[] blend(int[] src, int[] dst, int[] result);
-
- public static Blender getBlenderFor(BlendComposite composite) {
- switch (composite.getMode()) {
- case ADD:
- return new Blender() {
- @Override
- public int[] blend(int[] src, int[] dst, int[] result) {
- for (int i = 0; i < 4; i++) {
- result[i] = Math.min(255, src[i] + dst[i]);
- }
- return result;
- }
- };
- case DARKEN:
- return new Blender() {
- @Override
- public int[] blend(int[] src, int[] dst, int[] result) {
- for (int i = 0; i < 3; i++) {
- result[i] = Math.min(src[i], dst[i]);
- }
- result[3] = Math.min(255, src[3] + dst[3]);
- return result;
- }
- };
- case LIGHTEN:
- return new Blender() {
- @Override
- public int[] blend(int[] src, int[] dst, int[] result) {
- for (int i = 0; i < 3; i++) {
- result[i] = Math.max(src[i], dst[i]);
- }
- result[3] = Math.min(255, src[3] + dst[3]);
- return result;
- }
- };
- case MULTIPLY:
- return new Blender() {
- @Override
- public int[] blend(int[] src, int[] dst, int[] result) {
- for (int i = 0; i < 3; i++) {
- result[i] = (src[i] * dst[i]) >> 8;
- }
- result[3] = Math.min(255, src[3] + dst[3] - (src[3] * dst[3]) / 255);
- return result;
- }
- };
- case OVERLAY:
- return new Blender() {
- @Override
- public int[] blend(int[] src, int[] dst, int[] result) {
- for (int i = 0; i < 3; i++) {
- result[i] = dst[i] < 128 ? dst[i] * src[i] >> 7 :
- 255 - ((255 - dst[i]) * (255 - src[i]) >> 7);
- }
- result[3] = Math.min(255, src[3] + dst[3]);
- return result;
- }
- };
- case SCREEN:
- return new Blender() {
- @Override
- public int[] blend(int[] src, int[] dst, int[] result) {
- result[0] = 255 - ((255 - src[0]) * (255 - dst[0]) >> 8);
- result[1] = 255 - ((255 - src[1]) * (255 - dst[1]) >> 8);
- result[2] = 255 - ((255 - src[2]) * (255 - dst[2]) >> 8);
- result[3] = Math.min(255, src[3] + dst[3]);
- return result;
- }
- };
- default:
- assert false : "Blender not implement for " + composite.getMode().name();
-
- // Ignore the blend
- return new Blender() {
- @Override
- public int[] blend(int[] src, int[] dst, int[] result) {
- result[0] = dst[0];
- result[1] = dst[1];
- result[2] = dst[2];
- result[3] = dst[3];
- return result;
- }
- };
- }
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/BlurMaskFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BlurMaskFilter_Delegate.java
deleted file mode 100644
index d2569c7..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/BlurMaskFilter_Delegate.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-/**
- * Delegate implementing the native methods of android.graphics.BlurMaskFilter
- *
- * Through the layoutlib_create tool, the original native methods of BlurMaskFilter have
- * been replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original BlurMaskFilter class.
- *
- * Because this extends {@link MaskFilter_Delegate}, there's no need to use a
- * {@link DelegateManager}, as all the Shader classes will be added to the manager
- * owned by {@link MaskFilter_Delegate}.
- *
- * @see MaskFilter_Delegate
- *
- */
-public class BlurMaskFilter_Delegate extends MaskFilter_Delegate {
-
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- @Override
- public boolean isSupported() {
- return false;
- }
-
- @Override
- public String getSupportMessage() {
- return "Blur Mask Filters are not supported.";
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeConstructor(float radius, int style) {
- BlurMaskFilter_Delegate newDelegate = new BlurMaskFilter_Delegate();
- return sManager.addNewDelegate(newDelegate);
- }
-
- // ---- Private delegate/helper methods ----
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
deleted file mode 100644
index 47216ee..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ /dev/null
@@ -1,484 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.layoutlib.bridge.impl.GcSnapshot;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.annotation.Nullable;
-import android.graphics.Bitmap.Config;
-
-import java.awt.Graphics2D;
-import java.awt.Rectangle;
-import java.awt.geom.AffineTransform;
-
-import libcore.util.NativeAllocationRegistry_Delegate;
-
-
-/**
- * Delegate implementing the native methods of android.graphics.Canvas
- *
- * Through the layoutlib_create tool, the original native methods of Canvas have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original Canvas class.
- *
- * @see DelegateManager
- *
- */
-public final class Canvas_Delegate extends BaseCanvas_Delegate {
-
- // ---- delegate manager ----
- private static long sFinalizer = -1;
-
- private DrawFilter_Delegate mDrawFilter = null;
-
- // ---- Public Helper methods ----
-
- /**
- * Returns the native delegate associated to a given {@link Canvas} object.
- */
- public static Canvas_Delegate getDelegate(Canvas canvas) {
- return (Canvas_Delegate) sManager.getDelegate(canvas.getNativeCanvasWrapper());
- }
-
- /**
- * Returns the native delegate associated to a given an int referencing a {@link Canvas} object.
- */
- public static Canvas_Delegate getDelegate(long native_canvas) {
- return (Canvas_Delegate) sManager.getDelegate(native_canvas);
- }
-
- /**
- * Returns the current {@link Graphics2D} used to draw.
- */
- public GcSnapshot getSnapshot() {
- return mSnapshot;
- }
-
- /**
- * Returns the {@link DrawFilter} delegate or null if none have been set.
- *
- * @return the delegate or null.
- */
- public DrawFilter_Delegate getDrawFilter() {
- return mDrawFilter;
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static void nFreeCaches() {
- // nothing to be done here.
- }
-
- @LayoutlibDelegate
- /*package*/ static void nFreeTextLayoutCaches() {
- // nothing to be done here yet.
- }
-
- @LayoutlibDelegate
- /*package*/ static long nInitRaster(@Nullable Bitmap bitmap) {
- long nativeBitmapOrZero = 0;
- if (bitmap != null) {
- nativeBitmapOrZero = bitmap.getNativeInstance();
- }
- if (nativeBitmapOrZero > 0) {
- // get the Bitmap from the int
- Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmapOrZero);
-
- // create a new Canvas_Delegate with the given bitmap and return its new native int.
- Canvas_Delegate newDelegate = new Canvas_Delegate(bitmapDelegate);
-
- return sManager.addNewDelegate(newDelegate);
- }
-
- // create a new Canvas_Delegate and return its new native int.
- Canvas_Delegate newDelegate = new Canvas_Delegate();
-
- return sManager.addNewDelegate(newDelegate);
- }
-
- @LayoutlibDelegate
- public static void nSetBitmap(long canvas, Bitmap bitmap) {
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas);
- Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
- if (canvasDelegate == null || bitmapDelegate==null) {
- return;
- }
- canvasDelegate.mBitmap = bitmapDelegate;
- canvasDelegate.mSnapshot = GcSnapshot.createDefaultSnapshot(bitmapDelegate);
- }
-
- @LayoutlibDelegate
- public static boolean nIsOpaque(long nativeCanvas) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return false;
- }
-
- return canvasDelegate.mBitmap.getConfig() == Config.RGB_565;
- }
-
- @LayoutlibDelegate
- public static void nSetHighContrastText(long nativeCanvas, boolean highContrastText){}
-
- @LayoutlibDelegate
- public static int nGetWidth(long nativeCanvas) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return 0;
- }
-
- return canvasDelegate.mBitmap.getImage().getWidth();
- }
-
- @LayoutlibDelegate
- public static int nGetHeight(long nativeCanvas) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return 0;
- }
-
- return canvasDelegate.mBitmap.getImage().getHeight();
- }
-
- @LayoutlibDelegate
- public static int nSave(long nativeCanvas, int saveFlags) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return 0;
- }
-
- return canvasDelegate.save(saveFlags);
- }
-
- @LayoutlibDelegate
- public static int nSaveLayer(long nativeCanvas, float l,
- float t, float r, float b,
- long paint, int layerFlags) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return 0;
- }
-
- Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
- if (paintDelegate == null) {
- return 0;
- }
-
- return canvasDelegate.saveLayer(new RectF(l, t, r, b),
- paintDelegate, layerFlags);
- }
-
- @LayoutlibDelegate
- public static int nSaveLayerAlpha(long nativeCanvas, float l,
- float t, float r, float b,
- int alpha, int layerFlags) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return 0;
- }
-
- return canvasDelegate.saveLayerAlpha(new RectF(l, t, r, b), alpha, layerFlags);
- }
-
- @LayoutlibDelegate
- public static boolean nRestore(long nativeCanvas) {
- // FIXME: implement throwOnUnderflow.
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return false;
- }
-
- canvasDelegate.restore();
- return true;
- }
-
- @LayoutlibDelegate
- public static void nRestoreToCount(long nativeCanvas, int saveCount) {
- // FIXME: implement throwOnUnderflow.
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return;
- }
-
- canvasDelegate.restoreTo(saveCount);
- }
-
- @LayoutlibDelegate
- public static int nGetSaveCount(long nativeCanvas) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return 0;
- }
-
- return canvasDelegate.getSnapshot().size();
- }
-
- @LayoutlibDelegate
- public static void nTranslate(long nativeCanvas, float dx, float dy) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return;
- }
-
- canvasDelegate.getSnapshot().translate(dx, dy);
- }
-
- @LayoutlibDelegate
- public static void nScale(long nativeCanvas, float sx, float sy) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return;
- }
-
- canvasDelegate.getSnapshot().scale(sx, sy);
- }
-
- @LayoutlibDelegate
- public static void nRotate(long nativeCanvas, float degrees) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return;
- }
-
- canvasDelegate.getSnapshot().rotate(Math.toRadians(degrees));
- }
-
- @LayoutlibDelegate
- public static void nSkew(long nativeCanvas, float kx, float ky) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return;
- }
-
- // get the current top graphics2D object.
- GcSnapshot g = canvasDelegate.getSnapshot();
-
- // get its current matrix
- AffineTransform currentTx = g.getTransform();
- // get the AffineTransform for the given skew.
- float[] mtx = Matrix_Delegate.getSkew(kx, ky);
- AffineTransform matrixTx = Matrix_Delegate.getAffineTransform(mtx);
-
- // combine them so that the given matrix is applied after.
- currentTx.preConcatenate(matrixTx);
-
- // give it to the graphics2D as a new matrix replacing all previous transform
- g.setTransform(currentTx);
- }
-
- @LayoutlibDelegate
- public static void nConcat(long nCanvas, long nMatrix) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas);
- if (canvasDelegate == null) {
- return;
- }
-
- Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
- if (matrixDelegate == null) {
- return;
- }
-
- // get the current top graphics2D object.
- GcSnapshot snapshot = canvasDelegate.getSnapshot();
-
- // get its current matrix
- AffineTransform currentTx = snapshot.getTransform();
- // get the AffineTransform of the given matrix
- AffineTransform matrixTx = matrixDelegate.getAffineTransform();
-
- // combine them so that the given matrix is applied after.
- currentTx.concatenate(matrixTx);
-
- // give it to the graphics2D as a new matrix replacing all previous transform
- snapshot.setTransform(currentTx);
- }
-
- @LayoutlibDelegate
- public static void nSetMatrix(long nCanvas, long nMatrix) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas);
- if (canvasDelegate == null) {
- return;
- }
-
- Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
- if (matrixDelegate == null) {
- return;
- }
-
- // get the current top graphics2D object.
- GcSnapshot snapshot = canvasDelegate.getSnapshot();
-
- // get the AffineTransform of the given matrix
- AffineTransform matrixTx = matrixDelegate.getAffineTransform();
-
- // give it to the graphics2D as a new matrix replacing all previous transform
- snapshot.setTransform(matrixTx);
-
- if (matrixDelegate.hasPerspective()) {
- assert false;
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE,
- "android.graphics.Canvas#setMatrix(android.graphics.Matrix) only " +
- "supports affine transformations.", null, null /*data*/);
- }
- }
-
- @LayoutlibDelegate
- public static boolean nClipRect(long nCanvas,
- float left, float top,
- float right, float bottom,
- int regionOp) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas);
- if (canvasDelegate == null) {
- return false;
- }
-
- return canvasDelegate.clipRect(left, top, right, bottom, regionOp);
- }
-
- @LayoutlibDelegate
- public static boolean nClipPath(long nativeCanvas,
- long nativePath,
- int regionOp) {
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return true;
- }
-
- Path_Delegate pathDelegate = Path_Delegate.getDelegate(nativePath);
- if (pathDelegate == null) {
- return true;
- }
-
- return canvasDelegate.mSnapshot.clip(pathDelegate.getJavaShape(), regionOp);
- }
-
- @LayoutlibDelegate
- public static void nSetDrawFilter(long nativeCanvas, long nativeFilter) {
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return;
- }
-
- canvasDelegate.mDrawFilter = DrawFilter_Delegate.getDelegate(nativeFilter);
-
- if (canvasDelegate.mDrawFilter != null && !canvasDelegate.mDrawFilter.isSupported()) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_DRAWFILTER,
- canvasDelegate.mDrawFilter.getSupportMessage(), null, null /*data*/);
- }
- }
-
- @LayoutlibDelegate
- public static boolean nGetClipBounds(long nativeCanvas,
- Rect bounds) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
- if (canvasDelegate == null) {
- return false;
- }
-
- Rectangle rect = canvasDelegate.getSnapshot().getClip().getBounds();
- if (rect != null && !rect.isEmpty()) {
- bounds.left = rect.x;
- bounds.top = rect.y;
- bounds.right = rect.x + rect.width;
- bounds.bottom = rect.y + rect.height;
- return true;
- }
-
- return false;
- }
-
- @LayoutlibDelegate
- public static void nGetMatrix(long canvas, long matrix) {
- // get the delegate from the native int.
- Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas);
- if (canvasDelegate == null) {
- return;
- }
-
- Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
- if (matrixDelegate == null) {
- return;
- }
-
- AffineTransform transform = canvasDelegate.getSnapshot().getTransform();
- matrixDelegate.set(Matrix_Delegate.makeValues(transform));
- }
-
- @LayoutlibDelegate
- public static boolean nQuickReject(long nativeCanvas, long path) {
- // FIXME properly implement quickReject
- return false;
- }
-
- @LayoutlibDelegate
- public static boolean nQuickReject(long nativeCanvas,
- float left, float top,
- float right, float bottom) {
- // FIXME properly implement quickReject
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static long nGetNativeFinalizer() {
- synchronized (Canvas_Delegate.class) {
- if (sFinalizer == -1) {
- sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(nativePtr -> {
- Canvas_Delegate delegate = Canvas_Delegate.getDelegate(nativePtr);
- if (delegate != null) {
- delegate.dispose();
- }
- sManager.removeJavaReferenceFor(nativePtr);
- });
- }
- }
- return sFinalizer;
- }
-
- private Canvas_Delegate(Bitmap_Delegate bitmap) {
- super(bitmap);
- }
-
- private Canvas_Delegate() {
- super();
- }
-}
-
diff --git a/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java
deleted file mode 100644
index cb013b6..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.awt.Graphics2D;
-
-/**
- * Delegate implementing the native methods of android.graphics.ColorFilter
- *
- * Through the layoutlib_create tool, the original native methods of ColorFilter have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original ColorFilter class.
- *
- * This also serve as a base class for all ColorFilter delegate classes.
- *
- * @see DelegateManager
- *
- */
-public abstract class ColorFilter_Delegate {
-
- // ---- delegate manager ----
- protected static final DelegateManager<ColorFilter_Delegate> sManager =
- new DelegateManager<ColorFilter_Delegate>(ColorFilter_Delegate.class);
-
- // ---- delegate helper data ----
-
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- public static ColorFilter_Delegate getDelegate(long nativeShader) {
- return sManager.getDelegate(nativeShader);
- }
-
- public abstract String getSupportMessage();
-
- public boolean isSupported() {
- return false;
- }
-
- public void applyFilter(Graphics2D g, int width, int height) {
- // This should never be called directly. If supported, the sub class should override this.
- assert false;
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static void nSafeUnref(long native_instance) {
- sManager.removeJavaReferenceFor(native_instance);
- }
-
- // ---- Private delegate/helper methods ----
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java
deleted file mode 100644
index 6739484..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-/**
- * Delegate implementing the native methods of android.graphics.ColorMatrixColorFilter
- *
- * Through the layoutlib_create tool, the original native methods of ColorMatrixColorFilter have
- * been replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original ColorMatrixColorFilter class.
- *
- * Because this extends {@link ColorFilter_Delegate}, there's no need to use a
- * {@link DelegateManager}, as all the Shader classes will be added to the manager
- * owned by {@link ColorFilter_Delegate}.
- *
- * @see ColorFilter_Delegate
- *
- */
-public class ColorMatrixColorFilter_Delegate extends ColorFilter_Delegate {
-
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- @Override
- public String getSupportMessage() {
- return "ColorMatrix Color Filters are not supported.";
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeColorMatrixFilter(float[] array) {
- ColorMatrixColorFilter_Delegate newDelegate = new ColorMatrixColorFilter_Delegate();
- return sManager.addNewDelegate(newDelegate);
- }
-
- // ---- Private delegate/helper methods ----
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java
deleted file mode 100644
index bc3df7d..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.awt.Stroke;
-
-/**
- * Delegate implementing the native methods of android.graphics.ComposePathEffect
- *
- * Through the layoutlib_create tool, the original native methods of ComposePathEffect have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original ComposePathEffect class.
- *
- * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager},
- * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}.
- *
- * @see PathEffect_Delegate
- *
- */
-public class ComposePathEffect_Delegate extends PathEffect_Delegate {
-
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- @Override
- public Stroke getStroke(Paint_Delegate paint) {
- // FIXME
- return null;
- }
-
- @Override
- public boolean isSupported() {
- return false;
- }
-
- @Override
- public String getSupportMessage() {
- return "Compose Path Effects are not supported in Layout Preview mode.";
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreate(long outerpe, long innerpe) {
- ComposePathEffect_Delegate newDelegate = new ComposePathEffect_Delegate();
- return sManager.addNewDelegate(newDelegate);
- }
-
- // ---- Private delegate/helper methods ----
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
deleted file mode 100644
index ab37968..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.awt.Paint;
-
-/**
- * Delegate implementing the native methods of android.graphics.ComposeShader
- *
- * Through the layoutlib_create tool, the original native methods of ComposeShader have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original ComposeShader class.
- *
- * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager},
- * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}.
- *
- * @see Shader_Delegate
- *
- */
-public class ComposeShader_Delegate extends Shader_Delegate {
-
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- @Override
- public Paint getJavaPaint() {
- // FIXME
- return null;
- }
-
- @Override
- public boolean isSupported() {
- return false;
- }
-
- @Override
- public String getSupportMessage() {
- return "Compose Shaders are not supported in Layout Preview mode.";
- }
-
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreate(long nativeMatrix, long native_shaderA,
- long native_shaderB, int native_mode) {
- // FIXME not supported yet.
- ComposeShader_Delegate newDelegate = new ComposeShader_Delegate(nativeMatrix);
- return sManager.addNewDelegate(newDelegate);
- }
-
-
- // ---- Private delegate/helper methods ----
-
- private ComposeShader_Delegate(long nativeMatrix) {
- super(nativeMatrix);
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/CornerPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/CornerPathEffect_Delegate.java
deleted file mode 100644
index 73745c3..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/CornerPathEffect_Delegate.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.awt.Stroke;
-
-/**
- * Delegate implementing the native methods of android.graphics.CornerPathEffect
- *
- * Through the layoutlib_create tool, the original native methods of CornerPathEffect have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original CornerPathEffect class.
- *
- * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager},
- * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}.
- *
- * @see PathEffect_Delegate
- *
- */
-public class CornerPathEffect_Delegate extends PathEffect_Delegate {
-
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- @Override
- public Stroke getStroke(Paint_Delegate paint) {
- // FIXME
- return null;
- }
-
- @Override
- public boolean isSupported() {
- return false;
- }
-
- @Override
- public String getSupportMessage() {
- return "Corner Path Effects are not supported in Layout Preview mode.";
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreate(float radius) {
- CornerPathEffect_Delegate newDelegate = new CornerPathEffect_Delegate();
- return sManager.addNewDelegate(newDelegate);
- }
-
- // ---- Private delegate/helper methods ----
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/DashPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/DashPathEffect_Delegate.java
deleted file mode 100644
index 881afde..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/DashPathEffect_Delegate.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.awt.BasicStroke;
-import java.awt.Stroke;
-
-/**
- * Delegate implementing the native methods of android.graphics.DashPathEffect
- *
- * Through the layoutlib_create tool, the original native methods of DashPathEffect have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original DashPathEffect class.
- *
- * Because this extends {@link PathEffect_Delegate}, there's no need to use a
- * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by
- * {@link PathEffect_Delegate}.
- *
- * @see PathEffect_Delegate
- *
- */
-public final class DashPathEffect_Delegate extends PathEffect_Delegate {
-
- // ---- delegate data ----
-
- private final float[] mIntervals;
- private final float mPhase;
-
- // ---- Public Helper methods ----
-
- @Override
- public Stroke getStroke(Paint_Delegate paint) {
- return new BasicStroke(
- paint.getStrokeWidth(),
- paint.getJavaCap(),
- paint.getJavaJoin(),
- paint.getJavaStrokeMiter(),
- mIntervals,
- mPhase);
- }
-
- @Override
- public boolean isSupported() {
- return true;
- }
-
- @Override
- public String getSupportMessage() {
- // no message since isSupported returns true;
- return null;
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreate(float intervals[], float phase) {
- DashPathEffect_Delegate newDelegate = new DashPathEffect_Delegate(intervals, phase);
- return sManager.addNewDelegate(newDelegate);
- }
-
- // ---- Private delegate/helper methods ----
-
- private DashPathEffect_Delegate(float intervals[], float phase) {
- mIntervals = new float[intervals.length];
- System.arraycopy(intervals, 0, mIntervals, 0, intervals.length);
- mPhase = phase;
- }
-}
-
diff --git a/tools/layoutlib/bridge/src/android/graphics/DiscretePathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/DiscretePathEffect_Delegate.java
deleted file mode 100644
index 46109f3..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/DiscretePathEffect_Delegate.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.awt.Stroke;
-
-/**
- * Delegate implementing the native methods of android.graphics.DiscretePathEffect
- *
- * Through the layoutlib_create tool, the original native methods of DiscretePathEffect have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original DiscretePathEffect class.
- *
- * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager},
- * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}.
- *
- * @see PathEffect_Delegate
- *
- */
-public class DiscretePathEffect_Delegate extends PathEffect_Delegate {
-
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- @Override
- public Stroke getStroke(Paint_Delegate paint) {
- // FIXME
- return null;
- }
-
- @Override
- public boolean isSupported() {
- return false;
- }
-
- @Override
- public String getSupportMessage() {
- return "Discrete Path Effects are not supported in Layout Preview mode.";
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreate(float length, float deviation) {
- DiscretePathEffect_Delegate newDelegate = new DiscretePathEffect_Delegate();
- return sManager.addNewDelegate(newDelegate);
- }
-
- // ---- Private delegate/helper methods ----
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/DrawFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/DrawFilter_Delegate.java
deleted file mode 100644
index 2e10740..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/DrawFilter_Delegate.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-/**
- * Delegate implementing the native methods of android.graphics.DrawFilter
- *
- * Through the layoutlib_create tool, the original native methods of DrawFilter have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original DrawFilter class.
- *
- * This also serve as a base class for all DrawFilter delegate classes.
- *
- * @see DelegateManager
- *
- */
-public abstract class DrawFilter_Delegate {
-
- // ---- delegate manager ----
- protected static final DelegateManager<DrawFilter_Delegate> sManager =
- new DelegateManager<DrawFilter_Delegate>(DrawFilter_Delegate.class);
-
- // ---- delegate helper data ----
-
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- public static DrawFilter_Delegate getDelegate(long nativeDrawFilter) {
- return sManager.getDelegate(nativeDrawFilter);
- }
-
- public abstract boolean isSupported();
- public abstract String getSupportMessage();
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static void nativeDestructor(long nativeDrawFilter) {
- sManager.removeJavaReferenceFor(nativeDrawFilter);
- }
-
- // ---- Private delegate/helper methods ----
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/EmbossMaskFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/EmbossMaskFilter_Delegate.java
deleted file mode 100644
index e5040cc..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/EmbossMaskFilter_Delegate.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-/**
- * Delegate implementing the native methods of android.graphics.EmbossMaskFilter
- *
- * Through the layoutlib_create tool, the original native methods of EmbossMaskFilter have
- * been replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original EmbossMaskFilter class.
- *
- * Because this extends {@link MaskFilter_Delegate}, there's no need to use a
- * {@link DelegateManager}, as all the Shader classes will be added to the manager
- * owned by {@link MaskFilter_Delegate}.
- *
- * @see MaskFilter_Delegate
- *
- */
-public class EmbossMaskFilter_Delegate extends MaskFilter_Delegate {
-
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- @Override
- public boolean isSupported() {
- return false;
- }
-
- @Override
- public String getSupportMessage() {
- return "Emboss Mask Filters are not supported.";
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeConstructor(float[] direction, float ambient,
- float specular, float blurRadius) {
- EmbossMaskFilter_Delegate newDelegate = new EmbossMaskFilter_Delegate();
- return sManager.addNewDelegate(newDelegate);
- }
-
- // ---- Private delegate/helper methods ----
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
deleted file mode 100644
index 78b6f71..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ /dev/null
@@ -1,515 +0,0 @@
-/*
- * Copyright (C) 2014 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.graphics;
-
-import com.android.ide.common.rendering.api.AssetRepository;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.res.AssetManager;
-import android.content.res.BridgeAssetManager;
-import android.graphics.fonts.FontVariationAxis;
-import android.text.FontConfig;
-
-import java.awt.Font;
-import java.awt.FontFormatException;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Scanner;
-import java.util.Set;
-
-import static android.graphics.Typeface.RESOLVE_BY_FONT_TABLE;
-import static android.graphics.Typeface_Delegate.SYSTEM_FONTS;
-
-/**
- * Delegate implementing the native methods of android.graphics.FontFamily
- *
- * Through the layoutlib_create tool, the original native methods of FontFamily have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original FontFamily class.
- *
- * @see DelegateManager
- */
-public class FontFamily_Delegate {
-
- public static final int DEFAULT_FONT_WEIGHT = 400;
- public static final int BOLD_FONT_WEIGHT_DELTA = 300;
- public static final int BOLD_FONT_WEIGHT = 700;
-
- private static final String FONT_SUFFIX_ITALIC = "Italic.ttf";
- private static final String FN_ALL_FONTS_LIST = "fontsInSdk.txt";
- private static final String EXTENSION_OTF = ".otf";
-
- private static final int CACHE_SIZE = 10;
- // The cache has a drawback that if the font file changed after the font object was created,
- // we will not update it.
- private static final Map<String, FontInfo> sCache =
- new LinkedHashMap<String, FontInfo>(CACHE_SIZE) {
- @Override
- protected boolean removeEldestEntry(Map.Entry<String, FontInfo> eldest) {
- return size() > CACHE_SIZE;
- }
-
- @Override
- public FontInfo put(String key, FontInfo value) {
- // renew this entry.
- FontInfo removed = remove(key);
- super.put(key, value);
- return removed;
- }
- };
-
- /**
- * A class associating {@link Font} with its metadata.
- */
- private static final class FontInfo {
- @Nullable
- Font mFont;
- int mWeight;
- boolean mIsItalic;
- }
-
- // ---- delegate manager ----
- private static final DelegateManager<FontFamily_Delegate> sManager =
- new DelegateManager<FontFamily_Delegate>(FontFamily_Delegate.class);
-
- // ---- delegate helper data ----
- private static String sFontLocation;
- private static final List<FontFamily_Delegate> sPostInitDelegate = new
- ArrayList<FontFamily_Delegate>();
- private static Set<String> SDK_FONTS;
-
-
- // ---- delegate data ----
- private List<FontInfo> mFonts = new ArrayList<FontInfo>();
-
- /**
- * The variant of the Font Family - compact or elegant.
- * <p/>
- * 0 is unspecified, 1 is compact and 2 is elegant. This needs to be kept in sync with values in
- * android.graphics.FontFamily
- *
- * @see Paint#setElegantTextHeight(boolean)
- */
- private FontVariant mVariant;
- // List of runnables to process fonts after sFontLoader is initialized.
- private List<Runnable> mPostInitRunnables = new ArrayList<Runnable>();
- /** @see #isValid() */
- private boolean mValid = false;
-
-
- // ---- Public helper class ----
-
- public enum FontVariant {
- // The order needs to be kept in sync with android.graphics.FontFamily.
- NONE, COMPACT, ELEGANT
- }
-
- // ---- Public Helper methods ----
-
- public static FontFamily_Delegate getDelegate(long nativeFontFamily) {
- return sManager.getDelegate(nativeFontFamily);
- }
-
- public static synchronized void setFontLocation(String fontLocation) {
- sFontLocation = fontLocation;
- // init list of bundled fonts.
- File allFonts = new File(fontLocation, FN_ALL_FONTS_LIST);
- // Current number of fonts is 103. Use the next round number to leave scope for more fonts
- // in the future.
- Set<String> allFontsList = new HashSet<String>(128);
- Scanner scanner = null;
- try {
- scanner = new Scanner(allFonts);
- while (scanner.hasNext()) {
- String name = scanner.next();
- // Skip font configuration files.
- if (!name.endsWith(".xml")) {
- allFontsList.add(name);
- }
- }
- } catch (FileNotFoundException e) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Unable to load the list of fonts. Try re-installing the SDK Platform from the SDK Manager.",
- e, null);
- } finally {
- if (scanner != null) {
- scanner.close();
- }
- }
- SDK_FONTS = Collections.unmodifiableSet(allFontsList);
- for (FontFamily_Delegate fontFamily : sPostInitDelegate) {
- fontFamily.init();
- }
- sPostInitDelegate.clear();
- }
-
- @Nullable
- public Font getFont(int desiredWeight, boolean isItalic) {
- FontInfo desiredStyle = new FontInfo();
- desiredStyle.mWeight = desiredWeight;
- desiredStyle.mIsItalic = isItalic;
- FontInfo bestFont = null;
- int bestMatch = Integer.MAX_VALUE;
- //noinspection ForLoopReplaceableByForEach (avoid iterator instantiation)
- for (int i = 0, n = mFonts.size(); i < n; i++) {
- FontInfo font = mFonts.get(i);
- int match = computeMatch(font, desiredStyle);
- if (match < bestMatch) {
- bestMatch = match;
- bestFont = font;
- }
- }
- if (bestFont == null) {
- return null;
- }
- if (bestMatch == 0) {
- return bestFont.mFont;
- }
- // Derive the font as required and add it to the list of Fonts.
- deriveFont(bestFont, desiredStyle);
- addFont(desiredStyle);
- return desiredStyle.mFont;
- }
-
- public FontVariant getVariant() {
- return mVariant;
- }
-
- /**
- * Returns if the FontFamily should contain any fonts. If this returns true and
- * {@link #getFont(int, boolean)} returns an empty list, it means that an error occurred while
- * loading the fonts. However, some fonts are deliberately skipped, for example they are not
- * bundled with the SDK. In such a case, this method returns false.
- */
- public boolean isValid() {
- return mValid;
- }
-
- private static Font loadFont(String path) {
- if (path.startsWith(SYSTEM_FONTS) ) {
- String relativePath = path.substring(SYSTEM_FONTS.length());
- File f = new File(sFontLocation, relativePath);
-
- try {
- return Font.createFont(Font.TRUETYPE_FONT, f);
- } catch (Exception e) {
- if (path.endsWith(EXTENSION_OTF) && e instanceof FontFormatException) {
- // If we aren't able to load an Open Type font, don't log a warning just yet.
- // We wait for a case where font is being used. Only then we try to log the
- // warning.
- return null;
- }
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
- String.format("Unable to load font %1$s", relativePath),
- e, null);
- }
- } else {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Only platform fonts located in " + SYSTEM_FONTS + "can be loaded.",
- null, null);
- }
-
- return null;
- }
-
- @Nullable
- /*package*/ static String getFontLocation() {
- return sFontLocation;
- }
-
- // ---- delegate methods ----
- @LayoutlibDelegate
- /*package*/ static boolean addFont(FontFamily thisFontFamily, String path, int ttcIndex,
- FontVariationAxis[] axes, int weight, int italic) {
- if (thisFontFamily.mBuilderPtr == 0) {
- assert false : "Unable to call addFont after freezing.";
- return false;
- }
- final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mBuilderPtr);
- return delegate != null && delegate.addFont(path, ttcIndex, weight, italic);
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nInitBuilder(String lang, int variant) {
- // TODO: support lang. This is required for japanese locale.
- FontFamily_Delegate delegate = new FontFamily_Delegate();
- // variant can be 0, 1 or 2.
- assert variant < 3;
- delegate.mVariant = FontVariant.values()[variant];
- if (sFontLocation != null) {
- delegate.init();
- } else {
- sPostInitDelegate.add(delegate);
- }
- return sManager.addNewDelegate(delegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static long nCreateFamily(long builderPtr) {
- return builderPtr;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nUnrefFamily(long nativePtr) {
- // Removing the java reference for the object doesn't mean that it's freed for garbage
- // collection. Typeface_Delegate may still hold a reference for it.
- sManager.removeJavaReferenceFor(nativePtr);
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex,
- int weight, int isItalic) {
- assert false : "The only client of this method has been overridden.";
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font,
- int ttcIndex, int weight, int isItalic) {
- assert false : "The only client of this method has been overridden.";
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nAddAxisValue(long builderPtr, int tag, float value) {
- assert false : "The only client of this method has been overridden.";
- }
-
- static boolean addFont(long builderPtr, final String path, final int weight,
- final boolean isItalic) {
- final FontFamily_Delegate delegate = getDelegate(builderPtr);
- int italic = isItalic ? 1 : 0;
- if (delegate != null) {
- if (sFontLocation == null) {
- delegate.mPostInitRunnables.add(() -> delegate.addFont(path, weight, italic));
- return true;
- }
- return delegate.addFont(path, weight, italic);
- }
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nAddFontFromAssetManager(long builderPtr, AssetManager mgr, String path,
- int cookie, boolean isAsset, int ttcIndex, int weight, int isItalic) {
- FontFamily_Delegate ffd = sManager.getDelegate(builderPtr);
- if (ffd == null) {
- return false;
- }
- ffd.mValid = true;
- if (mgr == null) {
- return false;
- }
- if (mgr instanceof BridgeAssetManager) {
- InputStream fontStream = null;
- try {
- AssetRepository assetRepository = ((BridgeAssetManager) mgr).getAssetRepository();
- if (assetRepository == null) {
- Bridge.getLog().error(LayoutLog.TAG_MISSING_ASSET, "Asset not found: " + path,
- null);
- return false;
- }
- if (!assetRepository.isSupported()) {
- // Don't log any warnings on unsupported IDEs.
- return false;
- }
- // Check cache
- FontInfo fontInfo = sCache.get(path);
- if (fontInfo != null) {
- // renew the font's lease.
- sCache.put(path, fontInfo);
- ffd.addFont(fontInfo);
- return true;
- }
- fontStream = isAsset ?
- assetRepository.openAsset(path, AssetManager.ACCESS_STREAMING) :
- assetRepository.openNonAsset(cookie, path, AssetManager.ACCESS_STREAMING);
- if (fontStream == null) {
- Bridge.getLog().error(LayoutLog.TAG_MISSING_ASSET, "Asset not found: " + path,
- path);
- return false;
- }
- Font font = Font.createFont(Font.TRUETYPE_FONT, fontStream);
- fontInfo = new FontInfo();
- fontInfo.mFont = font;
- if (weight == RESOLVE_BY_FONT_TABLE) {
- fontInfo.mWeight = font.isBold() ? BOLD_FONT_WEIGHT : DEFAULT_FONT_WEIGHT;
- } else {
- fontInfo.mWeight = weight;
- }
- fontInfo.mIsItalic = isItalic == RESOLVE_BY_FONT_TABLE ? font.isItalic() :
- isItalic == 1;
- ffd.addFont(fontInfo);
- return true;
- } catch (IOException e) {
- Bridge.getLog().error(LayoutLog.TAG_MISSING_ASSET, "Unable to load font " + path, e,
- path);
- } catch (FontFormatException e) {
- if (path.endsWith(EXTENSION_OTF)) {
- // otf fonts are not supported on the user's config (JRE version + OS)
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "OpenType fonts are not supported yet: " + path, null, path);
- } else {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Unable to load font " + path, e, path);
- }
- } finally {
- if (fontStream != null) {
- try {
- fontStream.close();
- } catch (IOException ignored) {
- }
- }
- }
- return false;
- }
- // This should never happen. AssetManager is a final class (from user's perspective), and
- // we've replaced every creation of AssetManager with our implementation. We create an
- // exception and log it, but continue with rest of the rendering, without loading this font.
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "You have found a bug in the rendering library. Please file a bug at b.android.com.",
- new RuntimeException("Asset Manager is not an instance of BridgeAssetManager"),
- null);
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nAbort(long builderPtr) {
- sManager.removeJavaReferenceFor(builderPtr);
- }
-
- // ---- private helper methods ----
-
- private void init() {
- for (Runnable postInitRunnable : mPostInitRunnables) {
- postInitRunnable.run();
- }
- mPostInitRunnables = null;
- }
-
- private boolean addFont(final String path, int ttcIndex, int weight, int italic) {
- // FIXME: support ttc fonts. Hack JRE??
- if (sFontLocation == null) {
- mPostInitRunnables.add(() -> addFont(path, weight, italic));
- return true;
- }
- return addFont(path, weight, italic);
- }
-
- private boolean addFont(@NonNull String path) {
- return addFont(path, DEFAULT_FONT_WEIGHT, path.endsWith(FONT_SUFFIX_ITALIC) ? 1 : RESOLVE_BY_FONT_TABLE);
- }
-
- private boolean addFont(@NonNull String path, int weight, int italic) {
- if (path.startsWith(SYSTEM_FONTS) &&
- !SDK_FONTS.contains(path.substring(SYSTEM_FONTS.length()))) {
- return mValid = false;
- }
- // Set valid to true, even if the font fails to load.
- mValid = true;
- Font font = loadFont(path);
- if (font == null) {
- return false;
- }
- FontInfo fontInfo = new FontInfo();
- fontInfo.mFont = font;
- fontInfo.mWeight = weight;
- fontInfo.mIsItalic = italic == RESOLVE_BY_FONT_TABLE ? font.isItalic() : italic == 1;
- addFont(fontInfo);
- return true;
- }
-
- private boolean addFont(@NonNull FontInfo fontInfo) {
- int weight = fontInfo.mWeight;
- boolean isItalic = fontInfo.mIsItalic;
- // The list is usually just two fonts big. So iterating over all isn't as bad as it looks.
- // It's biggest for roboto where the size is 12.
- //noinspection ForLoopReplaceableByForEach (avoid iterator instantiation)
- for (int i = 0, n = mFonts.size(); i < n; i++) {
- FontInfo font = mFonts.get(i);
- if (font.mWeight == weight && font.mIsItalic == isItalic) {
- return false;
- }
- }
- mFonts.add(fontInfo);
- return true;
- }
-
- /**
- * Compute matching metric between two styles - 0 is an exact match.
- */
- private static int computeMatch(@NonNull FontInfo font1, @NonNull FontInfo font2) {
- int score = Math.abs(font1.mWeight - font2.mWeight);
- if (font1.mIsItalic != font2.mIsItalic) {
- score += 200;
- }
- return score;
- }
-
- /**
- * Try to derive a font from {@code srcFont} for the style in {@code outFont}.
- * <p/>
- * {@code outFont} is updated to reflect the style of the derived font.
- * @param srcFont the source font
- * @param outFont contains the desired font style. Updated to contain the derived font and
- * its style
- * @return outFont
- */
- @NonNull
- private FontInfo deriveFont(@NonNull FontInfo srcFont, @NonNull FontInfo outFont) {
- int desiredWeight = outFont.mWeight;
- int srcWeight = srcFont.mWeight;
- assert srcFont.mFont != null;
- Font derivedFont = srcFont.mFont;
- // Embolden the font if required.
- if (desiredWeight >= BOLD_FONT_WEIGHT && desiredWeight - srcWeight > BOLD_FONT_WEIGHT_DELTA / 2) {
- derivedFont = derivedFont.deriveFont(Font.BOLD);
- srcWeight += BOLD_FONT_WEIGHT_DELTA;
- }
- // Italicize the font if required.
- if (outFont.mIsItalic && !srcFont.mIsItalic) {
- derivedFont = derivedFont.deriveFont(Font.ITALIC);
- } else if (outFont.mIsItalic != srcFont.mIsItalic) {
- // The desired font is plain, but the src font is italics. We can't convert it back. So
- // we update the value to reflect the true style of the font we're deriving.
- outFont.mIsItalic = srcFont.mIsItalic;
- }
- outFont.mFont = derivedFont;
- outFont.mWeight = srcWeight;
- // No need to update mIsItalics, as it's already been handled above.
- return outFont;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java
deleted file mode 100644
index 64410e4..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/Gradient_Delegate.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import android.graphics.Shader.TileMode;
-
-/**
- * Base class for true Gradient shader delegate.
- */
-public abstract class Gradient_Delegate extends Shader_Delegate {
-
- protected final int[] mColors;
- protected final float[] mPositions;
-
- @Override
- public boolean isSupported() {
- // all gradient shaders are supported.
- return true;
- }
-
- @Override
- public String getSupportMessage() {
- // all gradient shaders are supported, no need for a gradient support
- return null;
- }
-
- /**
- * Creates the base shader and do some basic test on the parameters.
- *
- * @param nativeMatrix reference to the shader's native transformation matrix
- * @param colors The colors to be distributed along the gradient line
- * @param positions May be null. The relative positions [0..1] of each
- * corresponding color in the colors array. If this is null, the
- * the colors are distributed evenly along the gradient line.
- */
- protected Gradient_Delegate(long nativeMatrix, int colors[], float positions[]) {
- super(nativeMatrix);
- assert colors.length >= 2 : "needs >= 2 number of colors";
-
- if (positions == null) {
- float spacing = 1.f / (colors.length - 1);
- positions = new float[colors.length];
- positions[0] = 0.f;
- positions[colors.length - 1] = 1.f;
- for (int i = 1; i < colors.length - 1; i++) {
- positions[i] = spacing * i;
- }
- } else {
- assert colors.length == positions.length :
- "color and position " + "arrays must be of equal length";
- }
-
- mColors = colors;
- mPositions = positions;
- }
-
- /**
- * Base class for (Java) Gradient Paints. This handles computing the gradient colors based
- * on the color and position lists, as well as the {@link TileMode}
- *
- */
- protected abstract static class GradientPaint implements java.awt.Paint {
- private final static int GRADIENT_SIZE = 100;
-
- private final int[] mColors;
- private final float[] mPositions;
- private final TileMode mTileMode;
- private int[] mGradient;
-
- protected GradientPaint(int[] colors, float[] positions, TileMode tileMode) {
- mColors = colors;
- mPositions = positions;
- mTileMode = tileMode;
- }
-
- @Override
- public int getTransparency() {
- return java.awt.Paint.TRANSLUCENT;
- }
-
- /**
- * Pre-computes the colors for the gradient. This must be called once before any call
- * to {@link #getGradientColor(float)}
- */
- protected void precomputeGradientColors() {
- if (mGradient == null) {
- // actually create an array with an extra size, so that we can really go
- // from 0 to SIZE (100%), or currentPos in the loop below will never equal 1.0
- mGradient = new int[GRADIENT_SIZE+1];
-
- int prevPos = 0;
- int nextPos = 1;
- for (int i = 0 ; i <= GRADIENT_SIZE ; i++) {
- // compute current position
- float currentPos = (float)i/GRADIENT_SIZE;
- while (currentPos > mPositions[nextPos]) {
- prevPos = nextPos++;
- }
-
- float percent = (currentPos - mPositions[prevPos]) /
- (mPositions[nextPos] - mPositions[prevPos]);
-
- mGradient[i] = computeColor(mColors[prevPos], mColors[nextPos], percent);
- }
- }
- }
-
- /**
- * Returns the color based on the position in the gradient.
- * <var>pos</var> can be anything, even < 0 or > > 1, as the gradient
- * will use {@link TileMode} value to convert it into a [0,1] value.
- */
- protected int getGradientColor(float pos) {
- if (pos < 0.f) {
- if (mTileMode != null) {
- switch (mTileMode) {
- case CLAMP:
- pos = 0.f;
- break;
- case REPEAT:
- // remove the integer part to stay in the [0,1] range.
- // we also need to invert the value from [-1,0] to [0, 1]
- pos = pos - (float)Math.floor(pos);
- break;
- case MIRROR:
- // this is the same as the positive side, just make the value positive
- // first.
- pos = Math.abs(pos);
-
- // get the integer and the decimal part
- int intPart = (int)Math.floor(pos);
- pos = pos - intPart;
- // 0 -> 1 : normal order
- // 1 -> 2: mirrored
- // etc..
- // this means if the intpart is odd we invert
- if ((intPart % 2) == 1) {
- pos = 1.f - pos;
- }
- break;
- }
- } else {
- pos = 0.0f;
- }
- } else if (pos > 1f) {
- if (mTileMode != null) {
- switch (mTileMode) {
- case CLAMP:
- pos = 1.f;
- break;
- case REPEAT:
- // remove the integer part to stay in the [0,1] range
- pos = pos - (float)Math.floor(pos);
- break;
- case MIRROR:
- // get the integer and the decimal part
- int intPart = (int)Math.floor(pos);
- pos = pos - intPart;
- // 0 -> 1 : normal order
- // 1 -> 2: mirrored
- // etc..
- // this means if the intpart is odd we invert
- if ((intPart % 2) == 1) {
- pos = 1.f - pos;
- }
- break;
- }
- } else {
- pos = 1.0f;
- }
- }
-
- int index = (int)((pos * GRADIENT_SIZE) + .5);
-
- return mGradient[index];
- }
-
- /**
- * Returns the color between c1, and c2, based on the percent of the distance
- * between c1 and c2.
- */
- private int computeColor(int c1, int c2, float percent) {
- int a = computeChannel((c1 >> 24) & 0xFF, (c2 >> 24) & 0xFF, percent);
- int r = computeChannel((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF, percent);
- int g = computeChannel((c1 >> 8) & 0xFF, (c2 >> 8) & 0xFF, percent);
- int b = computeChannel((c1 ) & 0xFF, (c2 ) & 0xFF, percent);
- return a << 24 | r << 16 | g << 8 | b;
- }
-
- /**
- * Returns the channel value between 2 values based on the percent of the distance between
- * the 2 values..
- */
- private int computeChannel(int c1, int c2, float percent) {
- return c1 + (int)((percent * (c2-c1)) + .5);
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java
deleted file mode 100644
index 0dd9703..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-/**
- * Delegate implementing the native methods of android.graphics.LightingColorFilter
- *
- * Through the layoutlib_create tool, the original native methods of LightingColorFilter have
- * been replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original LightingColorFilter class.
- *
- * Because this extends {@link ColorFilter_Delegate}, there's no need to use a
- * {@link DelegateManager}, as all the Shader classes will be added to the manager
- * owned by {@link ColorFilter_Delegate}.
- *
- * @see ColorFilter_Delegate
- *
- */
-public class LightingColorFilter_Delegate extends ColorFilter_Delegate {
-
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- @Override
- public String getSupportMessage() {
- return "Lighting Color Filters are not supported.";
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long native_CreateLightingFilter(int mul, int add) {
- LightingColorFilter_Delegate newDelegate = new LightingColorFilter_Delegate();
- return sManager.addNewDelegate(newDelegate);
- }
-
- // ---- Private delegate/helper methods ----
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java
deleted file mode 100644
index cd4393a..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.graphics.Shader.TileMode;
-
-import java.awt.image.ColorModel;
-
-/**
- * Delegate implementing the native methods of android.graphics.LinearGradient
- *
- * Through the layoutlib_create tool, the original native methods of LinearGradient have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original LinearGradient class.
- *
- * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager},
- * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}.
- *
- * @see Shader_Delegate
- *
- */
-public final class LinearGradient_Delegate extends Gradient_Delegate {
-
- // ---- delegate data ----
- private java.awt.Paint mJavaPaint;
-
- // ---- Public Helper methods ----
-
- @Override
- public java.awt.Paint getJavaPaint() {
- return mJavaPaint;
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreate1(LinearGradient thisGradient, long matrix,
- float x0, float y0, float x1, float y1,
- int colors[], float positions[], int tileMode) {
- LinearGradient_Delegate newDelegate = new LinearGradient_Delegate(matrix, x0, y0,
- x1, y1, colors, positions, Shader_Delegate.getTileMode(tileMode));
- return sManager.addNewDelegate(newDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreate2(LinearGradient thisGradient, long matrix,
- float x0, float y0, float x1, float y1,
- int color0, int color1, int tileMode) {
- return nativeCreate1(thisGradient, matrix, x0, y0, x1, y1, new int[] { color0, color1},
- null /*positions*/, tileMode);
- }
-
- // ---- Private delegate/helper methods ----
-
- /**
- * Create a shader that draws a linear gradient along a line.
- *
- * @param nativeMatrix reference to the shader's native transformation matrix
- * @param x0 The x-coordinate for the start of the gradient line
- * @param y0 The y-coordinate for the start of the gradient line
- * @param x1 The x-coordinate for the end of the gradient line
- * @param y1 The y-coordinate for the end of the gradient line
- * @param colors The colors to be distributed along the gradient line
- * @param positions May be null. The relative positions [0..1] of each
- * corresponding color in the colors array. If this is null, the
- * the colors are distributed evenly along the gradient line.
- * @param tile The Shader tiling mode
- */
- private LinearGradient_Delegate(long nativeMatrix, float x0, float y0, float x1,
- float y1, int colors[], float positions[], TileMode tile) {
- super(nativeMatrix, colors, positions);
- mJavaPaint = new LinearGradientPaint(x0, y0, x1, y1, mColors, mPositions, tile);
- }
-
- // ---- Custom Java Paint ----
- /**
- * Linear Gradient (Java) Paint able to handle more than 2 points, as
- * {@link java.awt.GradientPaint} only supports 2 points and does not support Android's tile
- * modes.
- */
- private class LinearGradientPaint extends GradientPaint {
-
- private final float mX0;
- private final float mY0;
- private final float mDx;
- private final float mDy;
- private final float mDSize2;
-
- public LinearGradientPaint(float x0, float y0, float x1, float y1, int colors[],
- float positions[], TileMode tile) {
- super(colors, positions, tile);
- mX0 = x0;
- mY0 = y0;
- mDx = x1 - x0;
- mDy = y1 - y0;
- mDSize2 = mDx * mDx + mDy * mDy;
- }
-
- @Override
- public java.awt.PaintContext createContext(
- java.awt.image.ColorModel colorModel,
- java.awt.Rectangle deviceBounds,
- java.awt.geom.Rectangle2D userBounds,
- java.awt.geom.AffineTransform xform,
- java.awt.RenderingHints hints) {
- precomputeGradientColors();
-
- java.awt.geom.AffineTransform canvasMatrix;
- try {
- canvasMatrix = xform.createInverse();
- } catch (java.awt.geom.NoninvertibleTransformException e) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
- "Unable to inverse matrix in LinearGradient", e, null /*data*/);
- canvasMatrix = new java.awt.geom.AffineTransform();
- }
-
- java.awt.geom.AffineTransform localMatrix = getLocalMatrix();
- try {
- localMatrix = localMatrix.createInverse();
- } catch (java.awt.geom.NoninvertibleTransformException e) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
- "Unable to inverse matrix in LinearGradient", e, null /*data*/);
- localMatrix = new java.awt.geom.AffineTransform();
- }
-
- return new LinearGradientPaintContext(canvasMatrix, localMatrix, colorModel);
- }
-
- private class LinearGradientPaintContext implements java.awt.PaintContext {
-
- private final java.awt.geom.AffineTransform mCanvasMatrix;
- private final java.awt.geom.AffineTransform mLocalMatrix;
- private final java.awt.image.ColorModel mColorModel;
-
- private LinearGradientPaintContext(
- java.awt.geom.AffineTransform canvasMatrix,
- java.awt.geom.AffineTransform localMatrix,
- java.awt.image.ColorModel colorModel) {
- mCanvasMatrix = canvasMatrix;
- mLocalMatrix = localMatrix;
- mColorModel = colorModel.hasAlpha() ? colorModel : ColorModel.getRGBdefault();
- }
-
- @Override
- public void dispose() {
- }
-
- @Override
- public java.awt.image.ColorModel getColorModel() {
- return mColorModel;
- }
-
- @Override
- public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
- java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(
- mColorModel, mColorModel.createCompatibleWritableRaster(w, h),
- mColorModel.isAlphaPremultiplied(), null);
-
- int[] data = new int[w*h];
-
- int index = 0;
- float[] pt1 = new float[2];
- float[] pt2 = new float[2];
- for (int iy = 0 ; iy < h ; iy++) {
- for (int ix = 0 ; ix < w ; ix++) {
- // handle the canvas transform
- pt1[0] = x + ix;
- pt1[1] = y + iy;
- mCanvasMatrix.transform(pt1, 0, pt2, 0, 1);
-
- // handle the local matrix.
- pt1[0] = pt2[0];
- pt1[1] = pt2[1];
- mLocalMatrix.transform(pt1, 0, pt2, 0, 1);
-
- data[index++] = getColor(pt2[0], pt2[1]);
- }
- }
-
- image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
-
- return image.getRaster();
- }
- }
-
- /**
- * Returns a color for an arbitrary point.
- */
- private int getColor(float x, float y) {
- float pos;
- if (mDx == 0) {
- pos = (y - mY0) / mDy;
- } else if (mDy == 0) {
- pos = (x - mX0) / mDx;
- } else {
- // find the x position on the gradient vector.
- float _x = (mDx*mDy*(y-mY0) + mDy*mDy*mX0 + mDx*mDx*x) / mDSize2;
- // from it get the position relative to the vector
- pos = (_x - mX0) / mDx;
- }
-
- return getGradientColor(pos);
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/MaskFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/MaskFilter_Delegate.java
deleted file mode 100644
index e726c59..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/MaskFilter_Delegate.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-/**
- * Delegate implementing the native methods of android.graphics.MaskFilter
- *
- * Through the layoutlib_create tool, the original native methods of MaskFilter have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original MaskFilter class.
- *
- * This also serve as a base class for all MaskFilter delegate classes.
- *
- * @see DelegateManager
- *
- */
-public abstract class MaskFilter_Delegate {
-
- // ---- delegate manager ----
- protected static final DelegateManager<MaskFilter_Delegate> sManager =
- new DelegateManager<MaskFilter_Delegate>(MaskFilter_Delegate.class);
-
- // ---- delegate helper data ----
-
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- public static MaskFilter_Delegate getDelegate(long nativeShader) {
- return sManager.getDelegate(nativeShader);
- }
-
- public abstract boolean isSupported();
- public abstract String getSupportMessage();
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static void nativeDestructor(long native_filter) {
- sManager.removeJavaReferenceFor(native_filter);
- }
-
- // ---- Private delegate/helper methods ----
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
deleted file mode 100644
index 354f919..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
+++ /dev/null
@@ -1,1077 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.graphics.Matrix.ScaleToFit;
-
-import java.awt.geom.AffineTransform;
-import java.awt.geom.NoninvertibleTransformException;
-
-import libcore.util.NativeAllocationRegistry_Delegate;
-
-/**
- * Delegate implementing the native methods of android.graphics.Matrix
- *
- * Through the layoutlib_create tool, the original native methods of Matrix have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original Matrix class.
- *
- * @see DelegateManager
- *
- */
-public final class Matrix_Delegate {
-
- private final static int MATRIX_SIZE = 9;
-
- // ---- delegate manager ----
- private static final DelegateManager<Matrix_Delegate> sManager =
- new DelegateManager<Matrix_Delegate>(Matrix_Delegate.class);
- private static long sFinalizer = -1;
-
- // ---- delegate data ----
- private float mValues[] = new float[MATRIX_SIZE];
-
- // ---- Public Helper methods ----
-
- public static Matrix_Delegate getDelegate(long native_instance) {
- return sManager.getDelegate(native_instance);
- }
-
- /**
- * Returns an {@link AffineTransform} matching the given Matrix.
- */
- public static AffineTransform getAffineTransform(Matrix m) {
- Matrix_Delegate delegate = sManager.getDelegate(m.native_instance);
- if (delegate == null) {
- return null;
- }
-
- return delegate.getAffineTransform();
- }
-
- public static boolean hasPerspective(Matrix m) {
- Matrix_Delegate delegate = sManager.getDelegate(m.native_instance);
- if (delegate == null) {
- return false;
- }
-
- return delegate.hasPerspective();
- }
-
- /**
- * Sets the content of the matrix with the content of another matrix.
- */
- public void set(Matrix_Delegate matrix) {
- System.arraycopy(matrix.mValues, 0, mValues, 0, MATRIX_SIZE);
- }
-
- /**
- * Sets the content of the matrix with the content of another matrix represented as an array
- * of values.
- */
- public void set(float[] values) {
- System.arraycopy(values, 0, mValues, 0, MATRIX_SIZE);
- }
-
- /**
- * Resets the matrix to be the identity matrix.
- */
- public void reset() {
- reset(mValues);
- }
-
- /**
- * Returns whether or not the matrix is identity.
- */
- public boolean isIdentity() {
- for (int i = 0, k = 0; i < 3; i++) {
- for (int j = 0; j < 3; j++, k++) {
- if (mValues[k] != ((i==j) ? 1 : 0)) {
- return false;
- }
- }
- }
-
- return true;
- }
-
- public static float[] makeValues(AffineTransform matrix) {
- float[] values = new float[MATRIX_SIZE];
- values[0] = (float) matrix.getScaleX();
- values[1] = (float) matrix.getShearX();
- values[2] = (float) matrix.getTranslateX();
- values[3] = (float) matrix.getShearY();
- values[4] = (float) matrix.getScaleY();
- values[5] = (float) matrix.getTranslateY();
- values[6] = 0.f;
- values[7] = 0.f;
- values[8] = 1.f;
-
- return values;
- }
-
- public static Matrix_Delegate make(AffineTransform matrix) {
- return new Matrix_Delegate(makeValues(matrix));
- }
-
- public boolean mapRect(RectF dst, RectF src) {
- // array with 4 corners
- float[] corners = new float[] {
- src.left, src.top,
- src.right, src.top,
- src.right, src.bottom,
- src.left, src.bottom,
- };
-
- // apply the transform to them.
- mapPoints(corners);
-
- // now put the result in the rect. We take the min/max of Xs and min/max of Ys
- dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
- dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
-
- dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
- dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
-
-
- return (computeTypeMask() & kRectStaysRect_Mask) != 0;
- }
-
-
- /**
- * Returns an {@link AffineTransform} matching the matrix.
- */
- public AffineTransform getAffineTransform() {
- return getAffineTransform(mValues);
- }
-
- public boolean hasPerspective() {
- return (mValues[6] != 0 || mValues[7] != 0 || mValues[8] != 1);
- }
-
-
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nCreate(long native_src_or_zero) {
- // create the delegate
- Matrix_Delegate newDelegate = new Matrix_Delegate();
-
- // copy from values if needed.
- if (native_src_or_zero > 0) {
- Matrix_Delegate oldDelegate = sManager.getDelegate(native_src_or_zero);
- if (oldDelegate != null) {
- System.arraycopy(
- oldDelegate.mValues, 0,
- newDelegate.mValues, 0,
- MATRIX_SIZE);
- }
- }
-
- return sManager.addNewDelegate(newDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nIsIdentity(long native_object) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
- }
-
- return d.isIdentity();
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nIsAffine(long native_object) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return true;
- }
-
- return (d.computeTypeMask() & kPerspective_Mask) == 0;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nRectStaysRect(long native_object) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return true;
- }
-
- return (d.computeTypeMask() & kRectStaysRect_Mask) != 0;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nReset(long native_object) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return;
- }
-
- reset(d.mValues);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSet(long native_object, long other) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return;
- }
-
- Matrix_Delegate src = sManager.getDelegate(other);
- if (src == null) {
- return;
- }
-
- System.arraycopy(src.mValues, 0, d.mValues, 0, MATRIX_SIZE);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetTranslate(long native_object, float dx, float dy) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return;
- }
-
- setTranslate(d.mValues, dx, dy);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetScale(long native_object, float sx, float sy,
- float px, float py) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return;
- }
-
- d.mValues = getScale(sx, sy, px, py);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetScale(long native_object, float sx, float sy) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return;
- }
-
- d.mValues[0] = sx;
- d.mValues[1] = 0;
- d.mValues[2] = 0;
- d.mValues[3] = 0;
- d.mValues[4] = sy;
- d.mValues[5] = 0;
- d.mValues[6] = 0;
- d.mValues[7] = 0;
- d.mValues[8] = 1;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetRotate(long native_object, float degrees, float px, float py) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return;
- }
-
- d.mValues = getRotate(degrees, px, py);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetRotate(long native_object, float degrees) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return;
- }
-
- setRotate(d.mValues, degrees);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetSinCos(long native_object, float sinValue, float cosValue,
- float px, float py) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return;
- }
-
- // TODO: do it in one pass
-
- // translate so that the pivot is in 0,0
- setTranslate(d.mValues, -px, -py);
-
- // scale
- d.postTransform(getRotate(sinValue, cosValue));
- // translate back the pivot
- d.postTransform(getTranslate(px, py));
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetSinCos(long native_object, float sinValue, float cosValue) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return;
- }
-
- setRotate(d.mValues, sinValue, cosValue);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetSkew(long native_object, float kx, float ky,
- float px, float py) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return;
- }
-
- d.mValues = getSkew(kx, ky, px, py);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetSkew(long native_object, float kx, float ky) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return;
- }
-
- d.mValues[0] = 1;
- d.mValues[1] = kx;
- d.mValues[2] = -0;
- d.mValues[3] = ky;
- d.mValues[4] = 1;
- d.mValues[5] = 0;
- d.mValues[6] = 0;
- d.mValues[7] = 0;
- d.mValues[8] = 1;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetConcat(long native_object, long a, long b) {
- if (a == native_object) {
- nPreConcat(native_object, b);
- return;
- } else if (b == native_object) {
- nPostConcat(native_object, a);
- return;
- }
-
- Matrix_Delegate d = sManager.getDelegate(native_object);
- Matrix_Delegate a_mtx = sManager.getDelegate(a);
- Matrix_Delegate b_mtx = sManager.getDelegate(b);
- if (d != null && a_mtx != null && b_mtx != null) {
- multiply(d.mValues, a_mtx.mValues, b_mtx.mValues);
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nPreTranslate(long native_object, float dx, float dy) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d != null) {
- d.preTransform(getTranslate(dx, dy));
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nPreScale(long native_object, float sx, float sy,
- float px, float py) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d != null) {
- d.preTransform(getScale(sx, sy, px, py));
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nPreScale(long native_object, float sx, float sy) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d != null) {
- d.preTransform(getScale(sx, sy));
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nPreRotate(long native_object, float degrees,
- float px, float py) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d != null) {
- d.preTransform(getRotate(degrees, px, py));
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nPreRotate(long native_object, float degrees) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d != null) {
-
- double rad = Math.toRadians(degrees);
- float sin = (float) Math.sin(rad);
- float cos = (float) Math.cos(rad);
-
- d.preTransform(getRotate(sin, cos));
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nPreSkew(long native_object, float kx, float ky,
- float px, float py) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d != null) {
- d.preTransform(getSkew(kx, ky, px, py));
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nPreSkew(long native_object, float kx, float ky) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d != null) {
- d.preTransform(getSkew(kx, ky));
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nPreConcat(long native_object, long other_matrix) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- Matrix_Delegate other = sManager.getDelegate(other_matrix);
- if (d != null && other != null) {
- d.preTransform(other.mValues);
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nPostTranslate(long native_object, float dx, float dy) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d != null) {
- d.postTransform(getTranslate(dx, dy));
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nPostScale(long native_object, float sx, float sy,
- float px, float py) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d != null) {
- d.postTransform(getScale(sx, sy, px, py));
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nPostScale(long native_object, float sx, float sy) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d != null) {
- d.postTransform(getScale(sx, sy));
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nPostRotate(long native_object, float degrees,
- float px, float py) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d != null) {
- d.postTransform(getRotate(degrees, px, py));
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nPostRotate(long native_object, float degrees) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d != null) {
- d.postTransform(getRotate(degrees));
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nPostSkew(long native_object, float kx, float ky,
- float px, float py) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d != null) {
- d.postTransform(getSkew(kx, ky, px, py));
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nPostSkew(long native_object, float kx, float ky) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d != null) {
- d.postTransform(getSkew(kx, ky));
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nPostConcat(long native_object, long other_matrix) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- Matrix_Delegate other = sManager.getDelegate(other_matrix);
- if (d != null && other != null) {
- d.postTransform(other.mValues);
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nSetRectToRect(long native_object, RectF src,
- RectF dst, int stf) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
- }
-
- if (src.isEmpty()) {
- reset(d.mValues);
- return false;
- }
-
- if (dst.isEmpty()) {
- d.mValues[0] = d.mValues[1] = d.mValues[2] = d.mValues[3] = d.mValues[4] = d.mValues[5]
- = d.mValues[6] = d.mValues[7] = 0;
- d.mValues[8] = 1;
- } else {
- float tx, sx = dst.width() / src.width();
- float ty, sy = dst.height() / src.height();
- boolean xLarger = false;
-
- if (stf != ScaleToFit.FILL.nativeInt) {
- if (sx > sy) {
- xLarger = true;
- sx = sy;
- } else {
- sy = sx;
- }
- }
-
- tx = dst.left - src.left * sx;
- ty = dst.top - src.top * sy;
- if (stf == ScaleToFit.CENTER.nativeInt || stf == ScaleToFit.END.nativeInt) {
- float diff;
-
- if (xLarger) {
- diff = dst.width() - src.width() * sy;
- } else {
- diff = dst.height() - src.height() * sy;
- }
-
- if (stf == ScaleToFit.CENTER.nativeInt) {
- diff = diff / 2;
- }
-
- if (xLarger) {
- tx += diff;
- } else {
- ty += diff;
- }
- }
-
- d.mValues[0] = sx;
- d.mValues[4] = sy;
- d.mValues[2] = tx;
- d.mValues[5] = ty;
- d.mValues[1] = d.mValues[3] = d.mValues[6] = d.mValues[7] = 0;
-
- }
- // shared cleanup
- d.mValues[8] = 1;
- return true;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nSetPolyToPoly(long native_object, float[] src, int srcIndex,
- float[] dst, int dstIndex, int pointCount) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Matrix.setPolyToPoly is not supported.",
- null, null /*data*/);
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nInvert(long native_object, long inverse) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
- }
-
- Matrix_Delegate inv_mtx = sManager.getDelegate(inverse);
- if (inv_mtx == null) {
- return false;
- }
-
- try {
- AffineTransform affineTransform = d.getAffineTransform();
- AffineTransform inverseTransform = affineTransform.createInverse();
- inv_mtx.mValues[0] = (float)inverseTransform.getScaleX();
- inv_mtx.mValues[1] = (float)inverseTransform.getShearX();
- inv_mtx.mValues[2] = (float)inverseTransform.getTranslateX();
- inv_mtx.mValues[3] = (float)inverseTransform.getScaleX();
- inv_mtx.mValues[4] = (float)inverseTransform.getShearY();
- inv_mtx.mValues[5] = (float)inverseTransform.getTranslateY();
-
- return true;
- } catch (NoninvertibleTransformException e) {
- return false;
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nMapPoints(long native_object, float[] dst, int dstIndex,
- float[] src, int srcIndex, int ptCount, boolean isPts) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return;
- }
-
- if (isPts) {
- d.mapPoints(dst, dstIndex, src, srcIndex, ptCount);
- } else {
- d.mapVectors(dst, dstIndex, src, srcIndex, ptCount);
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nMapRect(long native_object, RectF dst, RectF src) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return false;
- }
-
- return d.mapRect(dst, src);
- }
-
- @LayoutlibDelegate
- /*package*/ static float nMapRadius(long native_object, float radius) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return 0.f;
- }
-
- float[] src = new float[] { radius, 0.f, 0.f, radius };
- d.mapVectors(src, 0, src, 0, 2);
-
- float l1 = (float) Math.hypot(src[0], src[1]);
- float l2 = (float) Math.hypot(src[2], src[3]);
- return (float) Math.sqrt(l1 * l2);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nGetValues(long native_object, float[] values) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return;
- }
-
- System.arraycopy(d.mValues, 0, values, 0, MATRIX_SIZE);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetValues(long native_object, float[] values) {
- Matrix_Delegate d = sManager.getDelegate(native_object);
- if (d == null) {
- return;
- }
-
- System.arraycopy(values, 0, d.mValues, 0, MATRIX_SIZE);
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nEquals(long native_a, long native_b) {
- Matrix_Delegate a = sManager.getDelegate(native_a);
- if (a == null) {
- return false;
- }
-
- Matrix_Delegate b = sManager.getDelegate(native_b);
- if (b == null) {
- return false;
- }
-
- for (int i = 0 ; i < MATRIX_SIZE ; i++) {
- if (a.mValues[i] != b.mValues[i]) {
- return false;
- }
- }
-
- return true;
- }
-
- @LayoutlibDelegate
- /*package*/ static long nGetNativeFinalizer() {
- synchronized (Matrix_Delegate.class) {
- if (sFinalizer == -1) {
- sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor);
- }
- }
- return sFinalizer;
- }
-
- // ---- Private helper methods ----
-
- /*package*/ static AffineTransform getAffineTransform(float[] matrix) {
- // the AffineTransform constructor takes the value in a different order
- // for a matrix [ 0 1 2 ]
- // [ 3 4 5 ]
- // the order is 0, 3, 1, 4, 2, 5...
- return new AffineTransform(
- matrix[0], matrix[3], matrix[1],
- matrix[4], matrix[2], matrix[5]);
- }
-
- /**
- * Reset a matrix to the identity
- */
- private static void reset(float[] mtx) {
- for (int i = 0, k = 0; i < 3; i++) {
- for (int j = 0; j < 3; j++, k++) {
- mtx[k] = ((i==j) ? 1 : 0);
- }
- }
- }
-
- @SuppressWarnings("unused")
- private final static int kIdentity_Mask = 0;
- private final static int kTranslate_Mask = 0x01; //!< set if the matrix has translation
- private final static int kScale_Mask = 0x02; //!< set if the matrix has X or Y scale
- private final static int kAffine_Mask = 0x04; //!< set if the matrix skews or rotates
- private final static int kPerspective_Mask = 0x08; //!< set if the matrix is in perspective
- private final static int kRectStaysRect_Mask = 0x10;
- @SuppressWarnings("unused")
- private final static int kUnknown_Mask = 0x80;
-
- @SuppressWarnings("unused")
- private final static int kAllMasks = kTranslate_Mask |
- kScale_Mask |
- kAffine_Mask |
- kPerspective_Mask |
- kRectStaysRect_Mask;
-
- // these guys align with the masks, so we can compute a mask from a variable 0/1
- @SuppressWarnings("unused")
- private final static int kTranslate_Shift = 0;
- @SuppressWarnings("unused")
- private final static int kScale_Shift = 1;
- @SuppressWarnings("unused")
- private final static int kAffine_Shift = 2;
- @SuppressWarnings("unused")
- private final static int kPerspective_Shift = 3;
- private final static int kRectStaysRect_Shift = 4;
-
- private int computeTypeMask() {
- int mask = 0;
-
- if (mValues[6] != 0. || mValues[7] != 0. || mValues[8] != 1.) {
- mask |= kPerspective_Mask;
- }
-
- if (mValues[2] != 0. || mValues[5] != 0.) {
- mask |= kTranslate_Mask;
- }
-
- float m00 = mValues[0];
- float m01 = mValues[1];
- float m10 = mValues[3];
- float m11 = mValues[4];
-
- if (m01 != 0. || m10 != 0.) {
- mask |= kAffine_Mask;
- }
-
- if (m00 != 1. || m11 != 1.) {
- mask |= kScale_Mask;
- }
-
- if ((mask & kPerspective_Mask) == 0) {
- // map non-zero to 1
- int im00 = m00 != 0 ? 1 : 0;
- int im01 = m01 != 0 ? 1 : 0;
- int im10 = m10 != 0 ? 1 : 0;
- int im11 = m11 != 0 ? 1 : 0;
-
- // record if the (p)rimary and (s)econdary diagonals are all 0 or
- // all non-zero (answer is 0 or 1)
- int dp0 = (im00 | im11) ^ 1; // true if both are 0
- int dp1 = im00 & im11; // true if both are 1
- int ds0 = (im01 | im10) ^ 1; // true if both are 0
- int ds1 = im01 & im10; // true if both are 1
-
- // return 1 if primary is 1 and secondary is 0 or
- // primary is 0 and secondary is 1
- mask |= ((dp0 & ds1) | (dp1 & ds0)) << kRectStaysRect_Shift;
- }
-
- return mask;
- }
-
- private Matrix_Delegate() {
- reset();
- }
-
- private Matrix_Delegate(float[] values) {
- System.arraycopy(values, 0, mValues, 0, MATRIX_SIZE);
- }
-
- /**
- * Adds the given transformation to the current Matrix
- * <p/>This in effect does this = this*matrix
- * @param matrix
- */
- private void postTransform(float[] matrix) {
- float[] tmp = new float[9];
- multiply(tmp, mValues, matrix);
- mValues = tmp;
- }
-
- /**
- * Adds the given transformation to the current Matrix
- * <p/>This in effect does this = matrix*this
- * @param matrix
- */
- private void preTransform(float[] matrix) {
- float[] tmp = new float[9];
- multiply(tmp, matrix, mValues);
- mValues = tmp;
- }
-
- /**
- * Apply this matrix to the array of 2D points specified by src, and write
- * the transformed points into the array of points specified by dst. The
- * two arrays represent their "points" as pairs of floats [x, y].
- *
- * @param dst The array of dst points (x,y pairs)
- * @param dstIndex The index of the first [x,y] pair of dst floats
- * @param src The array of src points (x,y pairs)
- * @param srcIndex The index of the first [x,y] pair of src floats
- * @param pointCount The number of points (x,y pairs) to transform
- */
-
- private void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex,
- int pointCount) {
- final int count = pointCount * 2;
-
- float[] tmpDest = dst;
- boolean inPlace = dst == src;
- if (inPlace) {
- tmpDest = new float[dstIndex + count];
- }
-
- for (int i = 0 ; i < count ; i += 2) {
- // just in case we are doing in place, we better put this in temp vars
- float x = mValues[0] * src[i + srcIndex] +
- mValues[1] * src[i + srcIndex + 1] +
- mValues[2];
- float y = mValues[3] * src[i + srcIndex] +
- mValues[4] * src[i + srcIndex + 1] +
- mValues[5];
-
- tmpDest[i + dstIndex] = x;
- tmpDest[i + dstIndex + 1] = y;
- }
-
- if (inPlace) {
- System.arraycopy(tmpDest, dstIndex, dst, dstIndex, count);
- }
- }
-
- /**
- * Apply this matrix to the array of 2D points, and write the transformed
- * points back into the array
- *
- * @param pts The array [x0, y0, x1, y1, ...] of points to transform.
- */
-
- private void mapPoints(float[] pts) {
- mapPoints(pts, 0, pts, 0, pts.length >> 1);
- }
-
- private void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex, int ptCount) {
- if (hasPerspective()) {
- // transform the (0,0) point
- float[] origin = new float[] { 0.f, 0.f};
- mapPoints(origin);
-
- // translate the vector data as points
- mapPoints(dst, dstIndex, src, srcIndex, ptCount);
-
- // then substract the transformed origin.
- final int count = ptCount * 2;
- for (int i = 0 ; i < count ; i += 2) {
- dst[dstIndex + i] = dst[dstIndex + i] - origin[0];
- dst[dstIndex + i + 1] = dst[dstIndex + i + 1] - origin[1];
- }
- } else {
- // make a copy of the matrix
- Matrix_Delegate copy = new Matrix_Delegate(mValues);
-
- // remove the translation
- setTranslate(copy.mValues, 0, 0);
-
- // map the content as points.
- copy.mapPoints(dst, dstIndex, src, srcIndex, ptCount);
- }
- }
-
- /**
- * multiply two matrices and store them in a 3rd.
- * <p/>This in effect does dest = a*b
- * dest cannot be the same as a or b.
- */
- /*package*/ static void multiply(float dest[], float[] a, float[] b) {
- // first row
- dest[0] = b[0] * a[0] + b[1] * a[3] + b[2] * a[6];
- dest[1] = b[0] * a[1] + b[1] * a[4] + b[2] * a[7];
- dest[2] = b[0] * a[2] + b[1] * a[5] + b[2] * a[8];
-
- // 2nd row
- dest[3] = b[3] * a[0] + b[4] * a[3] + b[5] * a[6];
- dest[4] = b[3] * a[1] + b[4] * a[4] + b[5] * a[7];
- dest[5] = b[3] * a[2] + b[4] * a[5] + b[5] * a[8];
-
- // 3rd row
- dest[6] = b[6] * a[0] + b[7] * a[3] + b[8] * a[6];
- dest[7] = b[6] * a[1] + b[7] * a[4] + b[8] * a[7];
- dest[8] = b[6] * a[2] + b[7] * a[5] + b[8] * a[8];
- }
-
- /**
- * Returns a matrix that represents a given translate
- * @param dx
- * @param dy
- * @return
- */
- /*package*/ static float[] getTranslate(float dx, float dy) {
- return setTranslate(new float[9], dx, dy);
- }
-
- /*package*/ static float[] setTranslate(float[] dest, float dx, float dy) {
- dest[0] = 1;
- dest[1] = 0;
- dest[2] = dx;
- dest[3] = 0;
- dest[4] = 1;
- dest[5] = dy;
- dest[6] = 0;
- dest[7] = 0;
- dest[8] = 1;
- return dest;
- }
-
- /*package*/ static float[] getScale(float sx, float sy) {
- return new float[] { sx, 0, 0, 0, sy, 0, 0, 0, 1 };
- }
-
- /**
- * Returns a matrix that represents the given scale info.
- * @param sx
- * @param sy
- * @param px
- * @param py
- */
- /*package*/ static float[] getScale(float sx, float sy, float px, float py) {
- float[] tmp = new float[9];
- float[] tmp2 = new float[9];
-
- // TODO: do it in one pass
-
- // translate tmp so that the pivot is in 0,0
- setTranslate(tmp, -px, -py);
-
- // scale into tmp2
- multiply(tmp2, tmp, getScale(sx, sy));
-
- // translate back the pivot back into tmp
- multiply(tmp, tmp2, getTranslate(px, py));
-
- return tmp;
- }
-
-
- /*package*/ static float[] getRotate(float degrees) {
- double rad = Math.toRadians(degrees);
- float sin = (float)Math.sin(rad);
- float cos = (float)Math.cos(rad);
-
- return getRotate(sin, cos);
- }
-
- /*package*/ static float[] getRotate(float sin, float cos) {
- return setRotate(new float[9], sin, cos);
- }
-
- /*package*/ static float[] setRotate(float[] dest, float degrees) {
- double rad = Math.toRadians(degrees);
- float sin = (float)Math.sin(rad);
- float cos = (float)Math.cos(rad);
-
- return setRotate(dest, sin, cos);
- }
-
- /*package*/ static float[] setRotate(float[] dest, float sin, float cos) {
- dest[0] = cos;
- dest[1] = -sin;
- dest[2] = 0;
- dest[3] = sin;
- dest[4] = cos;
- dest[5] = 0;
- dest[6] = 0;
- dest[7] = 0;
- dest[8] = 1;
- return dest;
- }
-
- /*package*/ static float[] getRotate(float degrees, float px, float py) {
- float[] tmp = new float[9];
- float[] tmp2 = new float[9];
-
- // TODO: do it in one pass
-
- // translate so that the pivot is in 0,0
- setTranslate(tmp, -px, -py);
-
- // rotate into tmp2
- double rad = Math.toRadians(degrees);
- float cos = (float)Math.cos(rad);
- float sin = (float)Math.sin(rad);
- multiply(tmp2, tmp, getRotate(sin, cos));
-
- // translate back the pivot back into tmp
- multiply(tmp, tmp2, getTranslate(px, py));
-
- return tmp;
- }
-
- /*package*/ static float[] getSkew(float kx, float ky) {
- return new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 };
- }
-
- /*package*/ static float[] getSkew(float kx, float ky, float px, float py) {
- float[] tmp = new float[9];
- float[] tmp2 = new float[9];
-
- // TODO: do it in one pass
-
- // translate so that the pivot is in 0,0
- setTranslate(tmp, -px, -py);
-
- // skew into tmp2
- multiply(tmp2, tmp, new float[] { 1, kx, 0, ky, 1, 0, 0, 0, 1 });
- // translate back the pivot back into tmp
- multiply(tmp, tmp2, getTranslate(px, py));
-
- return tmp;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
deleted file mode 100644
index 1f0eb3b..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.layoutlib.bridge.impl.GcSnapshot;
-import com.android.ninepatch.NinePatchChunk;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.graphics.drawable.NinePatchDrawable;
-
-import java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.lang.ref.SoftReference;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Delegate implementing the native methods of android.graphics.NinePatch
- *
- * Through the layoutlib_create tool, the original native methods of NinePatch have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
- * around to map int to instance of the delegate.
- *
- */
-public final class NinePatch_Delegate {
-
- // ---- delegate manager ----
- private static final DelegateManager<NinePatch_Delegate> sManager =
- new DelegateManager<NinePatch_Delegate>(NinePatch_Delegate.class);
-
- // ---- delegate helper data ----
- /**
- * Cache map for {@link NinePatchChunk}.
- * When the chunks are created they are serialized into a byte[], and both are put
- * in the cache, using a {@link SoftReference} for the chunk. The default Java classes
- * for {@link NinePatch} and {@link NinePatchDrawable} only reference to the byte[] data, and
- * provide this for drawing.
- * Using the cache map allows us to not have to deserialize the byte[] back into a
- * {@link NinePatchChunk} every time a rendering is done.
- */
- private final static Map<byte[], SoftReference<NinePatchChunk>> sChunkCache =
- new HashMap<byte[], SoftReference<NinePatchChunk>>();
-
- // ---- delegate data ----
- private byte[] chunk;
-
-
- // ---- Public Helper methods ----
-
- /**
- * Serializes the given chunk.
- *
- * @return the serialized data for the chunk.
- */
- public static byte[] serialize(NinePatchChunk chunk) {
- // serialize the chunk to get a byte[]
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- ObjectOutputStream oos = null;
- try {
- oos = new ObjectOutputStream(baos);
- oos.writeObject(chunk);
- } catch (IOException e) {
- Bridge.getLog().error(null, "Failed to serialize NinePatchChunk.", e, null /*data*/);
- return null;
- } finally {
- if (oos != null) {
- try {
- oos.close();
- } catch (IOException ignored) {
- }
- }
- }
-
- // get the array and add it to the cache
- byte[] array = baos.toByteArray();
- sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk));
- return array;
- }
-
- /**
- * Returns a {@link NinePatchChunk} object for the given serialized representation.
- *
- * If the chunk is present in the cache then the object from the cache is returned, otherwise
- * the array is deserialized into a {@link NinePatchChunk} object.
- *
- * @param array the serialized representation of the chunk.
- * @return the NinePatchChunk or null if deserialization failed.
- */
- public static NinePatchChunk getChunk(byte[] array) {
- SoftReference<NinePatchChunk> chunkRef = sChunkCache.get(array);
- NinePatchChunk chunk = chunkRef.get();
- if (chunk == null) {
- ByteArrayInputStream bais = new ByteArrayInputStream(array);
- ObjectInputStream ois = null;
- try {
- ois = new ObjectInputStream(bais);
- chunk = (NinePatchChunk) ois.readObject();
-
- // put back the chunk in the cache
- if (chunk != null) {
- sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk));
- }
- } catch (IOException e) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Failed to deserialize NinePatchChunk content.", e, null /*data*/);
- return null;
- } catch (ClassNotFoundException e) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Failed to deserialize NinePatchChunk class.", e, null /*data*/);
- return null;
- } finally {
- if (ois != null) {
- try {
- ois.close();
- } catch (IOException ignored) {
- }
- }
- }
- }
-
- return chunk;
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static boolean isNinePatchChunk(byte[] chunk) {
- NinePatchChunk chunkObject = getChunk(chunk);
- return chunkObject != null;
-
- }
-
- @LayoutlibDelegate
- /*package*/ static long validateNinePatchChunk(byte[] chunk) {
- // the default JNI implementation only checks that the byte[] has the same
- // size as the C struct it represent. Since we cannot do the same check (serialization
- // will return different size depending on content), we do nothing.
- NinePatch_Delegate newDelegate = new NinePatch_Delegate();
- newDelegate.chunk = chunk;
- return sManager.addNewDelegate(newDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nativeFinalize(long chunk) {
- sManager.removeJavaReferenceFor(chunk);
- }
-
-
- @LayoutlibDelegate
- /*package*/ static long nativeGetTransparentRegion(Bitmap bitmap, long chunk, Rect location) {
- return 0;
- }
-
- static byte[] getChunk(long nativeNinePatch) {
- NinePatch_Delegate delegate = sManager.getDelegate(nativeNinePatch);
- if (delegate != null) {
- return delegate.chunk;
- }
- return null;
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/PaintFlagsDrawFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PaintFlagsDrawFilter_Delegate.java
deleted file mode 100644
index fa20746..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/PaintFlagsDrawFilter_Delegate.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-/**
- * Delegate implementing the native methods of android.graphics.PaintFlagsDrawFilter
- *
- * Through the layoutlib_create tool, the original native methods of PaintFlagsDrawFilter have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original PaintFlagsDrawFilter class.
- *
- * Because this extends {@link DrawFilter_Delegate}, there's no need to use a
- * {@link DelegateManager}, as all the DrawFilter classes will be added to the manager owned by
- * {@link DrawFilter_Delegate}.
- *
- * @see DrawFilter_Delegate
- *
- */
-public class PaintFlagsDrawFilter_Delegate extends DrawFilter_Delegate {
-
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- @Override
- public boolean isSupported() {
- return false;
- }
-
- @Override
- public String getSupportMessage() {
- return "Paint Flags Draw Filters are not supported.";
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeConstructor(int clearBits, int setBits) {
- PaintFlagsDrawFilter_Delegate newDelegate = new PaintFlagsDrawFilter_Delegate();
- return sManager.addNewDelegate(newDelegate);
- }
-
- // ---- Private delegate/helper methods ----
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
deleted file mode 100644
index 1bb56e3..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
+++ /dev/null
@@ -1,1329 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.FontFamily_Delegate.FontVariant;
-import android.graphics.Paint.FontMetrics;
-import android.graphics.Paint.FontMetricsInt;
-import android.text.TextUtils;
-
-import java.awt.BasicStroke;
-import java.awt.Font;
-import java.awt.Shape;
-import java.awt.Stroke;
-import java.awt.Toolkit;
-import java.awt.geom.AffineTransform;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
-import libcore.util.NativeAllocationRegistry_Delegate;
-
-/**
- * Delegate implementing the native methods of android.graphics.Paint
- *
- * Through the layoutlib_create tool, the original native methods of Paint have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original Paint class.
- *
- * @see DelegateManager
- *
- */
-public class Paint_Delegate {
-
- /**
- * Class associating a {@link Font} and its {@link java.awt.FontMetrics}.
- */
- /*package*/ static final class FontInfo {
- Font mFont;
- java.awt.FontMetrics mMetrics;
- }
-
- // ---- delegate manager ----
- private static final DelegateManager<Paint_Delegate> sManager =
- new DelegateManager<Paint_Delegate>(Paint_Delegate.class);
- private static long sFinalizer = -1;
-
- // ---- delegate helper data ----
-
- // This list can contain null elements.
- private List<FontInfo> mFonts;
-
- // ---- delegate data ----
- private int mFlags;
- private int mColor;
- private int mStyle;
- private int mCap;
- private int mJoin;
- private int mTextAlign;
- private Typeface_Delegate mTypeface;
- private float mStrokeWidth;
- private float mStrokeMiter;
- private float mTextSize;
- private float mTextScaleX;
- private float mTextSkewX;
- private int mHintingMode = Paint.HINTING_ON;
- private int mHyphenEdit;
- private float mLetterSpacing; // not used in actual text rendering.
- private float mWordSpacing; // not used in actual text rendering.
- // Variant of the font. A paint's variant can only be compact or elegant.
- private FontVariant mFontVariant = FontVariant.COMPACT;
-
- private int mPorterDuffMode = Xfermode.DEFAULT;
- private ColorFilter_Delegate mColorFilter;
- private Shader_Delegate mShader;
- private PathEffect_Delegate mPathEffect;
- private MaskFilter_Delegate mMaskFilter;
-
- private Locale mLocale = Locale.getDefault();
-
- // Used only to assert invariants.
- public long mNativeTypeface;
-
- // ---- Public Helper methods ----
-
- @Nullable
- public static Paint_Delegate getDelegate(long native_paint) {
- return sManager.getDelegate(native_paint);
- }
-
- /**
- * Returns the list of {@link Font} objects.
- */
- public List<FontInfo> getFonts() {
- return mFonts;
- }
-
- public boolean isAntiAliased() {
- return (mFlags & Paint.ANTI_ALIAS_FLAG) != 0;
- }
-
- public boolean isFilterBitmap() {
- return (mFlags & Paint.FILTER_BITMAP_FLAG) != 0;
- }
-
- public int getStyle() {
- return mStyle;
- }
-
- public int getColor() {
- return mColor;
- }
-
- public int getAlpha() {
- return mColor >>> 24;
- }
-
- public void setAlpha(int alpha) {
- mColor = (alpha << 24) | (mColor & 0x00FFFFFF);
- }
-
- public int getTextAlign() {
- return mTextAlign;
- }
-
- public float getStrokeWidth() {
- return mStrokeWidth;
- }
-
- /**
- * returns the value of stroke miter needed by the java api.
- */
- public float getJavaStrokeMiter() {
- return mStrokeMiter;
- }
-
- public int getJavaCap() {
- switch (Paint.sCapArray[mCap]) {
- case BUTT:
- return BasicStroke.CAP_BUTT;
- case ROUND:
- return BasicStroke.CAP_ROUND;
- default:
- case SQUARE:
- return BasicStroke.CAP_SQUARE;
- }
- }
-
- public int getJavaJoin() {
- switch (Paint.sJoinArray[mJoin]) {
- default:
- case MITER:
- return BasicStroke.JOIN_MITER;
- case ROUND:
- return BasicStroke.JOIN_ROUND;
- case BEVEL:
- return BasicStroke.JOIN_BEVEL;
- }
- }
-
- public Stroke getJavaStroke() {
- if (mPathEffect != null) {
- if (mPathEffect.isSupported()) {
- Stroke stroke = mPathEffect.getStroke(this);
- assert stroke != null;
- if (stroke != null) {
- return stroke;
- }
- } else {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_PATHEFFECT,
- mPathEffect.getSupportMessage(),
- null, null /*data*/);
- }
- }
-
- // if no custom stroke as been set, set the default one.
- return new BasicStroke(
- getStrokeWidth(),
- getJavaCap(),
- getJavaJoin(),
- getJavaStrokeMiter());
- }
-
- /**
- * Returns the {@link PorterDuff.Mode} as an int
- */
- public int getPorterDuffMode() {
- return mPorterDuffMode;
- }
-
- /**
- * Returns the {@link ColorFilter} delegate or null if none have been set
- *
- * @return the delegate or null.
- */
- public ColorFilter_Delegate getColorFilter() {
- return mColorFilter;
- }
-
- public void setColorFilter(long colorFilterPtr) {
- mColorFilter = ColorFilter_Delegate.getDelegate(colorFilterPtr);
- }
-
- public void setShader(long shaderPtr) {
- mShader = Shader_Delegate.getDelegate(shaderPtr);
- }
-
- /**
- * Returns the {@link Shader} delegate or null if none have been set
- *
- * @return the delegate or null.
- */
- public Shader_Delegate getShader() {
- return mShader;
- }
-
- /**
- * Returns the {@link MaskFilter} delegate or null if none have been set
- *
- * @return the delegate or null.
- */
- public MaskFilter_Delegate getMaskFilter() {
- return mMaskFilter;
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static int nGetFlags(long nativePaint) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return 0;
- }
-
- return delegate.mFlags;
- }
-
-
-
- @LayoutlibDelegate
- /*package*/ static void nSetFlags(long nativePaint, int flags) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return;
- }
-
- delegate.mFlags = flags;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetFilterBitmap(long nativePaint, boolean filter) {
- setFlag(nativePaint, Paint.FILTER_BITMAP_FLAG, filter);
- }
-
- @LayoutlibDelegate
- /*package*/ static int nGetHinting(long nativePaint) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return Paint.HINTING_ON;
- }
-
- return delegate.mHintingMode;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetHinting(long nativePaint, int mode) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return;
- }
-
- delegate.mHintingMode = mode;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetAntiAlias(long nativePaint, boolean aa) {
- setFlag(nativePaint, Paint.ANTI_ALIAS_FLAG, aa);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetSubpixelText(long nativePaint,
- boolean subpixelText) {
- setFlag(nativePaint, Paint.SUBPIXEL_TEXT_FLAG, subpixelText);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetUnderlineText(long nativePaint,
- boolean underlineText) {
- setFlag(nativePaint, Paint.UNDERLINE_TEXT_FLAG, underlineText);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetStrikeThruText(long nativePaint,
- boolean strikeThruText) {
- setFlag(nativePaint, Paint.STRIKE_THRU_TEXT_FLAG, strikeThruText);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetFakeBoldText(long nativePaint,
- boolean fakeBoldText) {
- setFlag(nativePaint, Paint.FAKE_BOLD_TEXT_FLAG, fakeBoldText);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetDither(long nativePaint, boolean dither) {
- setFlag(nativePaint, Paint.DITHER_FLAG, dither);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetLinearText(long nativePaint, boolean linearText) {
- setFlag(nativePaint, Paint.LINEAR_TEXT_FLAG, linearText);
- }
-
- @LayoutlibDelegate
- /*package*/ static int nGetColor(long nativePaint) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return 0;
- }
-
- return delegate.mColor;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetColor(long nativePaint, int color) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return;
- }
-
- delegate.mColor = color;
- }
-
- @LayoutlibDelegate
- /*package*/ static int nGetAlpha(long nativePaint) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return 0;
- }
-
- return delegate.getAlpha();
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetAlpha(long nativePaint, int a) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return;
- }
-
- delegate.setAlpha(a);
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetStrokeWidth(long nativePaint) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return 1.f;
- }
-
- return delegate.mStrokeWidth;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetStrokeWidth(long nativePaint, float width) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return;
- }
-
- delegate.mStrokeWidth = width;
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetStrokeMiter(long nativePaint) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return 1.f;
- }
-
- return delegate.mStrokeMiter;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetStrokeMiter(long nativePaint, float miter) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return;
- }
-
- delegate.mStrokeMiter = miter;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetShadowLayer(long paint, float radius, float dx, float dy,
- int color) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Paint.setShadowLayer is not supported.", null, null /*data*/);
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nHasShadowLayer(long paint) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Paint.hasShadowLayer is not supported.", null, null /*data*/);
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nIsElegantTextHeight(long nativePaint) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- return delegate != null && delegate.mFontVariant == FontVariant.ELEGANT;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetElegantTextHeight(long nativePaint,
- boolean elegant) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return;
- }
-
- delegate.mFontVariant = elegant ? FontVariant.ELEGANT : FontVariant.COMPACT;
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetTextSize(long nativePaint) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return 1.f;
- }
-
- return delegate.mTextSize;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetTextSize(long nativePaint, float textSize) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return;
- }
-
- if (delegate.mTextSize != textSize) {
- delegate.mTextSize = textSize;
- delegate.updateFontObject();
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetTextScaleX(long nativePaint) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return 1.f;
- }
-
- return delegate.mTextScaleX;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetTextScaleX(long nativePaint, float scaleX) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return;
- }
-
- if (delegate.mTextScaleX != scaleX) {
- delegate.mTextScaleX = scaleX;
- delegate.updateFontObject();
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetTextSkewX(long nativePaint) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return 1.f;
- }
-
- return delegate.mTextSkewX;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetTextSkewX(long nativePaint, float skewX) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return;
- }
-
- if (delegate.mTextSkewX != skewX) {
- delegate.mTextSkewX = skewX;
- delegate.updateFontObject();
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static float nAscent(long nativePaint, long nativeTypeface) {
- // get the delegate
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return 0;
- }
-
- if (delegate.mFonts.size() > 0) {
- java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics;
- // Android expects negative ascent so we invert the value from Java.
- return - javaMetrics.getAscent();
- }
-
- return 0;
- }
-
- @LayoutlibDelegate
- /*package*/ static float nDescent(long nativePaint, long nativeTypeface) {
- // get the delegate
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return 0;
- }
-
- if (delegate.mFonts.size() > 0) {
- java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics;
- return javaMetrics.getDescent();
- }
-
- return 0;
-
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetFontMetrics(long nativePaint, long nativeTypeface,
- FontMetrics metrics) {
- // get the delegate
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return 0;
- }
-
- return delegate.getFontMetrics(metrics);
- }
-
- @LayoutlibDelegate
- /*package*/ static int nGetFontMetricsInt(long nativePaint,
- long nativeTypeface, FontMetricsInt fmi) {
- // get the delegate
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return 0;
- }
-
- if (delegate.mFonts.size() > 0) {
- java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics;
- if (fmi != null) {
- // Android expects negative ascent so we invert the value from Java.
- fmi.top = - javaMetrics.getMaxAscent();
- fmi.ascent = - javaMetrics.getAscent();
- fmi.descent = javaMetrics.getDescent();
- fmi.bottom = javaMetrics.getMaxDescent();
- fmi.leading = javaMetrics.getLeading();
- }
-
- return javaMetrics.getHeight();
- }
-
- return 0;
- }
-
- @LayoutlibDelegate
- /*package*/ static int nBreakText(long nativePaint, long nativeTypeface, char[] text,
- int index, int count, float maxWidth, int bidiFlags, float[] measuredWidth) {
-
- // get the delegate
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return 0;
- }
-
- int inc = count > 0 ? 1 : -1;
-
- int measureIndex = 0;
- for (int i = index; i != index + count; i += inc, measureIndex++) {
- int start, end;
- if (i < index) {
- start = i;
- end = index;
- } else {
- start = index;
- end = i;
- }
-
- // measure from start to end
- RectF bounds = delegate.measureText(text, start, end - start + 1, null, 0, bidiFlags);
- float res = bounds.right - bounds.left;
-
- if (measuredWidth != null) {
- measuredWidth[measureIndex] = res;
- }
-
- if (res > maxWidth) {
- // we should not return this char index, but since it's 0-based
- // and we need to return a count, we simply return measureIndex;
- return measureIndex;
- }
-
- }
-
- return measureIndex;
- }
-
- @LayoutlibDelegate
- /*package*/ static int nBreakText(long nativePaint, long nativeTypeface, String text,
- boolean measureForwards,
- float maxWidth, int bidiFlags, float[] measuredWidth) {
- return nBreakText(nativePaint, nativeTypeface, text.toCharArray(), 0, text.length(),
- maxWidth, bidiFlags, measuredWidth);
- }
-
- @LayoutlibDelegate
- /*package*/ static long nInit() {
- Paint_Delegate newDelegate = new Paint_Delegate();
- return sManager.addNewDelegate(newDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static long nInitWithPaint(long paint) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(paint);
- if (delegate == null) {
- return 0;
- }
-
- Paint_Delegate newDelegate = new Paint_Delegate(delegate);
- return sManager.addNewDelegate(newDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nReset(long native_object) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return;
- }
-
- delegate.reset();
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSet(long native_dst, long native_src) {
- // get the delegate from the native int.
- Paint_Delegate delegate_dst = sManager.getDelegate(native_dst);
- if (delegate_dst == null) {
- return;
- }
-
- // get the delegate from the native int.
- Paint_Delegate delegate_src = sManager.getDelegate(native_src);
- if (delegate_src == null) {
- return;
- }
-
- delegate_dst.set(delegate_src);
- }
-
- @LayoutlibDelegate
- /*package*/ static int nGetStyle(long native_object) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return 0;
- }
-
- return delegate.mStyle;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetStyle(long native_object, int style) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return;
- }
-
- delegate.mStyle = style;
- }
-
- @LayoutlibDelegate
- /*package*/ static int nGetStrokeCap(long native_object) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return 0;
- }
-
- return delegate.mCap;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetStrokeCap(long native_object, int cap) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return;
- }
-
- delegate.mCap = cap;
- }
-
- @LayoutlibDelegate
- /*package*/ static int nGetStrokeJoin(long native_object) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return 0;
- }
-
- return delegate.mJoin;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetStrokeJoin(long native_object, int join) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return;
- }
-
- delegate.mJoin = join;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nGetFillPath(long native_object, long src, long dst) {
- Paint_Delegate paint = sManager.getDelegate(native_object);
- if (paint == null) {
- return false;
- }
-
- Path_Delegate srcPath = Path_Delegate.getDelegate(src);
- if (srcPath == null) {
- return true;
- }
-
- Path_Delegate dstPath = Path_Delegate.getDelegate(dst);
- if (dstPath == null) {
- return true;
- }
-
- Stroke stroke = paint.getJavaStroke();
- Shape strokeShape = stroke.createStrokedShape(srcPath.getJavaShape());
-
- dstPath.setJavaShape(strokeShape);
-
- // FIXME figure out the return value?
- return true;
- }
-
- @LayoutlibDelegate
- /*package*/ static long nSetShader(long native_object, long shader) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return shader;
- }
-
- delegate.mShader = Shader_Delegate.getDelegate(shader);
-
- return shader;
- }
-
- @LayoutlibDelegate
- /*package*/ static long nSetColorFilter(long native_object, long filter) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return filter;
- }
-
- delegate.mColorFilter = ColorFilter_Delegate.getDelegate(filter);
-
- // Log warning if it's not supported.
- if (delegate.mColorFilter != null && !delegate.mColorFilter.isSupported()) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_COLORFILTER,
- delegate.mColorFilter.getSupportMessage(), null, null /*data*/);
- }
-
- return filter;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetXfermode(long native_object, int xfermode) {
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return;
- }
- delegate.mPorterDuffMode = xfermode;
- }
-
- @LayoutlibDelegate
- /*package*/ static long nSetPathEffect(long native_object, long effect) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return effect;
- }
-
- delegate.mPathEffect = PathEffect_Delegate.getDelegate(effect);
-
- return effect;
- }
-
- @LayoutlibDelegate
- /*package*/ static long nSetMaskFilter(long native_object, long maskfilter) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return maskfilter;
- }
-
- delegate.mMaskFilter = MaskFilter_Delegate.getDelegate(maskfilter);
-
- // since none of those are supported, display a fidelity warning right away
- if (delegate.mMaskFilter != null && !delegate.mMaskFilter.isSupported()) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER,
- delegate.mMaskFilter.getSupportMessage(), null, null /*data*/);
- }
-
- return maskfilter;
- }
-
- @LayoutlibDelegate
- /*package*/ static long nSetTypeface(long native_object, long typeface) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return 0;
- }
-
- Typeface_Delegate typefaceDelegate = Typeface_Delegate.getDelegate(typeface);
- if (delegate.mTypeface != typefaceDelegate || delegate.mNativeTypeface != typeface) {
- delegate.mTypeface = Typeface_Delegate.getDelegate(typeface);
- delegate.mNativeTypeface = typeface;
- delegate.updateFontObject();
- }
- return typeface;
- }
-
- @LayoutlibDelegate
- /*package*/ static int nGetTextAlign(long native_object) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return 0;
- }
-
- return delegate.mTextAlign;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetTextAlign(long native_object, int align) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return;
- }
-
- delegate.mTextAlign = align;
- }
-
- @LayoutlibDelegate
- /*package*/ static int nSetTextLocales(long native_object, String locale) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return 0;
- }
-
- delegate.setTextLocale(locale);
- return 0;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetTextLocalesByMinikinLangListId(long paintPtr,
- int mMinikinLangListId) {
- // FIXME
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetTextAdvances(long native_object, long native_typeface,
- char[] text, int index, int count, int contextIndex, int contextCount,
- int bidiFlags, float[] advances, int advancesIndex) {
-
- if (advances != null)
- for (int i = advancesIndex; i< advancesIndex+count; i++)
- advances[i]=0;
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(native_object);
- if (delegate == null) {
- return 0.f;
- }
-
- // native_typeface is passed here since Framework's old implementation did not have the
- // typeface object associated with the Paint. Since, we follow the new framework way,
- // we store the typeface with the paint and use it directly.
- assert (native_typeface == delegate.mNativeTypeface);
-
- RectF bounds = delegate.measureText(text, index, count, advances, advancesIndex, bidiFlags);
- return bounds.right - bounds.left;
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetTextAdvances(long native_object, long native_typeface,
- String text, int start, int end, int contextStart, int contextEnd,
- int bidiFlags, float[] advances, int advancesIndex) {
- // FIXME: support contextStart and contextEnd
- int count = end - start;
- char[] buffer = TemporaryBuffer.obtain(count);
- TextUtils.getChars(text, start, end, buffer, 0);
-
- return nGetTextAdvances(native_object, native_typeface, buffer, 0, count,
- contextStart, contextEnd - contextStart, bidiFlags, advances, advancesIndex);
- }
-
- @LayoutlibDelegate
- /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, long typefacePtr,
- char[] text, int contextStart, int contextLength, int flags, int offset,
- int cursorOpt) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Paint.getTextRunCursor is not supported.", null, null /*data*/);
- return 0;
- }
-
- @LayoutlibDelegate
- /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, long typefacePtr,
- String text, int contextStart, int contextEnd, int flags, int offset, int cursorOpt) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Paint.getTextRunCursor is not supported.", null, null /*data*/);
- return 0;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nGetTextPath(long native_object, long native_typeface,
- int bidiFlags, char[] text, int index, int count, float x, float y, long path) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Paint.getTextPath is not supported.", null, null /*data*/);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nGetTextPath(long native_object, long native_typeface,
- int bidiFlags, String text, int start, int end, float x, float y, long path) {
- // FIXME
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Paint.getTextPath is not supported.", null, null /*data*/);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nGetStringBounds(long nativePaint, long native_typeface,
- String text, int start, int end, int bidiFlags, Rect bounds) {
- nGetCharArrayBounds(nativePaint, native_typeface, text.toCharArray(), start,
- end - start, bidiFlags, bounds);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nGetCharArrayBounds(long nativePaint, long native_typeface,
- char[] text, int index, int count, int bidiFlags, Rect bounds) {
-
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return;
- }
-
- // assert that the typeface passed is actually the one that we had stored.
- assert (native_typeface == delegate.mNativeTypeface);
-
- delegate.measureText(text, index, count, null, 0, bidiFlags).roundOut(bounds);
- }
-
- @LayoutlibDelegate
- /*package*/ static long nGetNativeFinalizer() {
- synchronized (Paint_Delegate.class) {
- if (sFinalizer == -1) {
- sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
- sManager::removeJavaReferenceFor);
- }
- }
- return sFinalizer;
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetLetterSpacing(long nativePaint) {
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return 0;
- }
- return delegate.mLetterSpacing;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetLetterSpacing(long nativePaint, float letterSpacing) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
- "Paint.setLetterSpacing() not supported.", null, null);
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return;
- }
- delegate.mLetterSpacing = letterSpacing;
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetWordSpacing(long nativePaint) {
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return 0;
- }
- return delegate.mWordSpacing;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetWordSpacing(long nativePaint, float wordSpacing) {
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return;
- }
- delegate.mWordSpacing = wordSpacing;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetFontFeatureSettings(long nativePaint, String settings) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
- "Paint.setFontFeatureSettings() not supported.", null, null);
- }
-
- @LayoutlibDelegate
- /*package*/ static int nGetHyphenEdit(long nativePaint) {
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return 0;
- }
- return delegate.mHyphenEdit;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetHyphenEdit(long nativePaint, int hyphen) {
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return;
- }
- delegate.mHyphenEdit = hyphen;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nHasGlyph(long nativePaint, long nativeTypeface, int bidiFlags,
- String string) {
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return false;
- }
- if (string.length() == 0) {
- return false;
- }
- if (string.length() > 1) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
- "Paint.hasGlyph() is not supported for ligatures.", null, null);
- return false;
- }
- assert nativeTypeface == delegate.mNativeTypeface;
- Typeface_Delegate typeface_delegate = Typeface_Delegate.getDelegate(nativeTypeface);
-
- char c = string.charAt(0);
- for (Font font : typeface_delegate.getFonts(delegate.mFontVariant)) {
- if (font.canDisplay(c)) {
- return true;
- }
- }
- return false;
- }
-
-
- @LayoutlibDelegate
- /*package*/ static float nGetRunAdvance(long nativePaint, long nativeTypeface,
- @NonNull char[] text, int start, int end, int contextStart, int contextEnd,
- boolean isRtl, int offset) {
- int count = end - start;
- float[] advances = new float[count];
- int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
- nGetTextAdvances(nativePaint, nativeTypeface, text, start, count,
- contextStart, contextEnd - contextStart, bidiFlags, advances, 0);
- int startOffset = offset - start; // offset from start.
- float sum = 0;
- for (int i = 0; i < startOffset; i++) {
- sum += advances[i];
- }
- return sum;
- }
-
- @LayoutlibDelegate
- /*package*/ static int nGetOffsetForAdvance(long nativePaint, long nativeTypeface,
- char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl,
- float advance) {
- int count = end - start;
- float[] advances = new float[count];
- int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
- nGetTextAdvances(nativePaint, nativeTypeface, text, start, count,
- contextStart, contextEnd - contextStart, bidiFlags, advances, 0);
- float sum = 0;
- int i;
- for (i = 0; i < count && sum < advance; i++) {
- sum += advances[i];
- }
- float distanceToI = sum - advance;
- float distanceToIMinus1 = advance - (sum - advances[i]);
- return distanceToI > distanceToIMinus1 ? i : i - 1;
- }
-
- // ---- Private delegate/helper methods ----
-
- /*package*/ Paint_Delegate() {
- reset();
- }
-
- private Paint_Delegate(Paint_Delegate paint) {
- set(paint);
- }
-
- private void set(Paint_Delegate paint) {
- mFlags = paint.mFlags;
- mColor = paint.mColor;
- mStyle = paint.mStyle;
- mCap = paint.mCap;
- mJoin = paint.mJoin;
- mTextAlign = paint.mTextAlign;
-
- boolean needsFontUpdate = false;
- if (mTypeface != paint.mTypeface || mNativeTypeface != paint.mNativeTypeface) {
- mTypeface = paint.mTypeface;
- mNativeTypeface = paint.mNativeTypeface;
- needsFontUpdate = true;
- }
-
- if (mTextSize != paint.mTextSize) {
- mTextSize = paint.mTextSize;
- needsFontUpdate = true;
- }
-
- if (mTextScaleX != paint.mTextScaleX) {
- mTextScaleX = paint.mTextScaleX;
- needsFontUpdate = true;
- }
-
- if (mTextSkewX != paint.mTextSkewX) {
- mTextSkewX = paint.mTextSkewX;
- needsFontUpdate = true;
- }
-
- mStrokeWidth = paint.mStrokeWidth;
- mStrokeMiter = paint.mStrokeMiter;
- mPorterDuffMode = paint.mPorterDuffMode;
- mColorFilter = paint.mColorFilter;
- mShader = paint.mShader;
- mPathEffect = paint.mPathEffect;
- mMaskFilter = paint.mMaskFilter;
- mHintingMode = paint.mHintingMode;
-
- if (needsFontUpdate) {
- updateFontObject();
- }
- }
-
- private void reset() {
- mFlags = Paint.HIDDEN_DEFAULT_PAINT_FLAGS;
- mColor = 0xFF000000;
- mStyle = Paint.Style.FILL.nativeInt;
- mCap = Paint.Cap.BUTT.nativeInt;
- mJoin = Paint.Join.MITER.nativeInt;
- mTextAlign = 0;
- mTypeface = Typeface_Delegate.getDelegate(Typeface.sDefaults[0].native_instance);
- mNativeTypeface = 0;
- mStrokeWidth = 1.f;
- mStrokeMiter = 4.f;
- mTextSize = 20.f;
- mTextScaleX = 1.f;
- mTextSkewX = 0.f;
- mPorterDuffMode = Xfermode.DEFAULT;
- mColorFilter = null;
- mShader = null;
- mPathEffect = null;
- mMaskFilter = null;
- updateFontObject();
- mHintingMode = Paint.HINTING_ON;
- }
-
- /**
- * Update the {@link Font} object from the typeface, text size and scaling
- */
- @SuppressWarnings("deprecation")
- private void updateFontObject() {
- if (mTypeface != null) {
- // Get the fonts from the TypeFace object.
- List<Font> fonts = mTypeface.getFonts(mFontVariant);
-
- if (fonts.isEmpty()) {
- mFonts = Collections.emptyList();
- return;
- }
-
- // create new font objects as well as FontMetrics, based on the current text size
- // and skew info.
- int nFonts = fonts.size();
- ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(nFonts);
- //noinspection ForLoopReplaceableByForEach (avoid iterator instantiation)
- for (int i = 0; i < nFonts; i++) {
- Font font = fonts.get(i);
- if (font == null) {
- // If the font is null, add null to infoList. When rendering the text, if this
- // null is reached, a warning will be logged.
- infoList.add(null);
- continue;
- }
- FontInfo info = new FontInfo();
- info.mFont = font.deriveFont(mTextSize);
- if (mTextScaleX != 1.0 || mTextSkewX != 0) {
- // TODO: support skew
- info.mFont = info.mFont.deriveFont(new AffineTransform(
- mTextScaleX, mTextSkewX, 0, 1, 0, 0));
- }
- // The metrics here don't have anti-aliasing set.
- info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont);
-
- infoList.add(info);
- }
-
- mFonts = Collections.unmodifiableList(infoList);
- }
- }
-
- /*package*/ RectF measureText(char[] text, int index, int count, float[] advances,
- int advancesIndex, int bidiFlags) {
- return new BidiRenderer(null, this, text)
- .renderText(index, index + count, bidiFlags, advances, advancesIndex, false);
- }
-
- /*package*/ RectF measureText(char[] text, int index, int count, float[] advances,
- int advancesIndex, boolean isRtl) {
- return new BidiRenderer(null, this, text)
- .renderText(index, index + count, isRtl, advances, advancesIndex, false);
- }
-
- private float getFontMetrics(FontMetrics metrics) {
- if (mFonts.size() > 0) {
- java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
- if (metrics != null) {
- // Android expects negative ascent so we invert the value from Java.
- metrics.top = - javaMetrics.getMaxAscent();
- metrics.ascent = - javaMetrics.getAscent();
- metrics.descent = javaMetrics.getDescent();
- metrics.bottom = javaMetrics.getMaxDescent();
- metrics.leading = javaMetrics.getLeading();
- }
-
- return javaMetrics.getHeight();
- }
-
- return 0;
- }
-
- private void setTextLocale(String locale) {
- mLocale = new Locale(locale);
- }
-
- private static void setFlag(long nativePaint, int flagMask, boolean flagValue) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return;
- }
-
- if (flagValue) {
- delegate.mFlags |= flagMask;
- } else {
- delegate.mFlags &= ~flagMask;
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/PathDashPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathDashPathEffect_Delegate.java
deleted file mode 100644
index fd9ba62..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/PathDashPathEffect_Delegate.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.awt.Stroke;
-
-/**
- * Delegate implementing the native methods of android.graphics.PathDashPathEffect
- *
- * Through the layoutlib_create tool, the original native methods of PathDashPathEffect have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original PathDashPathEffect class.
- *
- * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager},
- * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}.
- *
- * @see PathEffect_Delegate
- *
- */
-public class PathDashPathEffect_Delegate extends PathEffect_Delegate {
-
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- @Override
- public Stroke getStroke(Paint_Delegate paint) {
- // FIXME
- return null;
- }
-
- @Override
- public boolean isSupported() {
- return false;
- }
-
- @Override
- public String getSupportMessage() {
- return "Path Dash Path Effects are not supported in Layout Preview mode.";
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreate(long native_path, float advance, float phase,
- int native_style) {
- PathDashPathEffect_Delegate newDelegate = new PathDashPathEffect_Delegate();
- return sManager.addNewDelegate(newDelegate);
- }
-
- // ---- Private delegate/helper methods ----
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/PathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathEffect_Delegate.java
deleted file mode 100644
index 000481e..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/PathEffect_Delegate.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.awt.Stroke;
-
-/**
- * Delegate implementing the native methods of android.graphics.PathEffect
- *
- * Through the layoutlib_create tool, the original native methods of PathEffect have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original PathEffect class.
- *
- * This also serve as a base class for all PathEffect delegate classes.
- *
- * @see DelegateManager
- *
- */
-public abstract class PathEffect_Delegate {
-
- // ---- delegate manager ----
- protected static final DelegateManager<PathEffect_Delegate> sManager =
- new DelegateManager<PathEffect_Delegate>(PathEffect_Delegate.class);
-
- // ---- delegate helper data ----
-
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- public static PathEffect_Delegate getDelegate(long nativeShader) {
- return sManager.getDelegate(nativeShader);
- }
-
- public abstract Stroke getStroke(Paint_Delegate paint);
- public abstract boolean isSupported();
- public abstract String getSupportMessage();
-
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static void nativeDestructor(long native_patheffect) {
- sManager.removeJavaReferenceFor(native_patheffect);
- }
-
- // ---- Private delegate/helper methods ----
-
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
deleted file mode 100644
index 7f707c9..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2015 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.graphics;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.layoutlib.bridge.util.CachedPathIteratorFactory;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import com.android.layoutlib.bridge.util.CachedPathIteratorFactory.CachedPathIterator;
-
-import java.awt.geom.PathIterator;
-
-/**
- * Delegate implementing the native methods of {@link android.graphics.PathMeasure}
- * <p/>
- * Through the layoutlib_create tool, the original native methods of PathMeasure have been
- * replaced by
- * calls to methods of the same name in this delegate class.
- * <p/>
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between it
- * and the original PathMeasure class.
- *
- * @see DelegateManager
- */
-public final class PathMeasure_Delegate {
-
- // ---- delegate manager ----
- private static final DelegateManager<PathMeasure_Delegate> sManager =
- new DelegateManager<PathMeasure_Delegate>(PathMeasure_Delegate.class);
-
- // ---- delegate data ----
- private CachedPathIteratorFactory mOriginalPathIterator;
-
- private long mNativePath;
-
-
- private PathMeasure_Delegate(long native_path, boolean forceClosed) {
- mNativePath = native_path;
- if (native_path != 0) {
- if (forceClosed) {
- // Copy the path and call close
- native_path = Path_Delegate.nInit(native_path);
- Path_Delegate.nClose(native_path);
- }
-
- Path_Delegate pathDelegate = Path_Delegate.getDelegate(native_path);
- mOriginalPathIterator = new CachedPathIteratorFactory(pathDelegate.getJavaShape()
- .getPathIterator(null));
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static long native_create(long native_path, boolean forceClosed) {
- return sManager.addNewDelegate(new PathMeasure_Delegate(native_path, forceClosed));
- }
-
- @LayoutlibDelegate
- /*package*/ static void native_destroy(long native_instance) {
- sManager.removeJavaReferenceFor(native_instance);
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean native_getPosTan(long native_instance, float distance, float pos[],
- float tan[]) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "PathMeasure.getPostTan is not supported.", null, null);
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean native_getMatrix(long native_instance, float distance, long
- native_matrix, int flags) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "PathMeasure.getMatrix is not supported.", null, null);
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean native_nextContour(long native_instance) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "PathMeasure.nextContour is not supported.", null, null);
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static void native_setPath(long native_instance, long native_path, boolean
- forceClosed) {
- PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
- assert pathMeasure != null;
-
- if (native_path != 0) {
- if (forceClosed) {
- // Copy the path and call close
- native_path = Path_Delegate.nInit(native_path);
- Path_Delegate.nClose(native_path);
- }
-
- Path_Delegate pathDelegate = Path_Delegate.getDelegate(native_path);
- pathMeasure.mOriginalPathIterator = new CachedPathIteratorFactory(pathDelegate.getJavaShape()
- .getPathIterator(null));
- }
-
- pathMeasure.mNativePath = native_path;
- }
-
- @LayoutlibDelegate
- /*package*/ static float native_getLength(long native_instance) {
- PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
- assert pathMeasure != null;
-
- if (pathMeasure.mOriginalPathIterator == null) {
- return 0;
- }
-
- return pathMeasure.mOriginalPathIterator.iterator().getTotalLength();
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean native_isClosed(long native_instance) {
- PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
- assert pathMeasure != null;
-
- Path_Delegate path = Path_Delegate.getDelegate(pathMeasure.mNativePath);
- if (path == null) {
- return false;
- }
-
- int type = 0;
- float segment[] = new float[6];
- for (PathIterator pi = path.getJavaShape().getPathIterator(null); !pi.isDone(); pi.next()) {
- type = pi.currentSegment(segment);
- }
-
- // A path is a closed path if the last element is SEG_CLOSE
- return type == PathIterator.SEG_CLOSE;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean native_getSegment(long native_instance, float startD, float stopD,
- long native_dst_path, boolean startWithMoveTo) {
- if (startD < 0) {
- startD = 0;
- }
-
- if (startD >= stopD) {
- return false;
- }
-
- PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
- assert pathMeasure != null;
-
- CachedPathIterator iterator = pathMeasure.mOriginalPathIterator.iterator();
- float accLength = startD;
- boolean isZeroLength = true; // Whether the output has zero length or not
- float[] points = new float[6];
-
- iterator.jumpToSegment(accLength);
- while (!iterator.isDone() && (stopD - accLength > 0.1f)) {
- int type = iterator.currentSegment(points, stopD - accLength);
-
- if (accLength - iterator.getCurrentSegmentLength() <= stopD) {
- if (startWithMoveTo) {
- startWithMoveTo = false;
-
- // If this segment is a MOVETO, then we just use that one. If not, then we issue
- // a first moveto
- if (type != PathIterator.SEG_MOVETO) {
- float[] lastPoint = new float[2];
- iterator.getCurrentSegmentEnd(lastPoint);
- Path_Delegate.nMoveTo(native_dst_path, lastPoint[0], lastPoint[1]);
- }
- }
-
- isZeroLength = isZeroLength && iterator.getCurrentSegmentLength() > 0;
- switch (type) {
- case PathIterator.SEG_MOVETO:
- Path_Delegate.nMoveTo(native_dst_path, points[0], points[1]);
- break;
- case PathIterator.SEG_LINETO:
- Path_Delegate.nLineTo(native_dst_path, points[0], points[1]);
- break;
- case PathIterator.SEG_CLOSE:
- Path_Delegate.nClose(native_dst_path);
- break;
- case PathIterator.SEG_CUBICTO:
- Path_Delegate.nCubicTo(native_dst_path, points[0], points[1],
- points[2], points[3],
- points[4], points[5]);
- break;
- case PathIterator.SEG_QUADTO:
- Path_Delegate.nQuadTo(native_dst_path, points[0], points[1],
- points[2],
- points[3]);
- break;
- default:
- assert false;
- }
- }
-
- accLength += iterator.getCurrentSegmentLength();
- iterator.next();
- }
-
- return !isZeroLength;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
deleted file mode 100644
index 50b9165..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
+++ /dev/null
@@ -1,896 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.annotation.NonNull;
-import android.graphics.Path.Direction;
-import android.graphics.Path.FillType;
-
-import java.awt.Shape;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Arc2D;
-import java.awt.geom.Area;
-import java.awt.geom.Ellipse2D;
-import java.awt.geom.GeneralPath;
-import java.awt.geom.Path2D;
-import java.awt.geom.PathIterator;
-import java.awt.geom.Point2D;
-import java.awt.geom.Rectangle2D;
-import java.awt.geom.RoundRectangle2D;
-import java.util.ArrayList;
-
-/**
- * Delegate implementing the native methods of android.graphics.Path
- *
- * Through the layoutlib_create tool, the original native methods of Path have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original Path class.
- *
- * @see DelegateManager
- *
- */
-public final class Path_Delegate {
-
- // ---- delegate manager ----
- private static final DelegateManager<Path_Delegate> sManager =
- new DelegateManager<Path_Delegate>(Path_Delegate.class);
-
- private static final float EPSILON = 1e-4f;
-
- // ---- delegate data ----
- private FillType mFillType = FillType.WINDING;
- private Path2D mPath = new Path2D.Double();
-
- private float mLastX = 0;
- private float mLastY = 0;
-
- // true if the path contains does not contain a curve or line.
- private boolean mCachedIsEmpty = true;
-
- // ---- Public Helper methods ----
-
- public static Path_Delegate getDelegate(long nPath) {
- return sManager.getDelegate(nPath);
- }
-
- public Path2D getJavaShape() {
- return mPath;
- }
-
- public void setJavaShape(Shape shape) {
- reset();
- mPath.append(shape, false /*connect*/);
- }
-
- public void reset() {
- mPath.reset();
- mLastX = 0;
- mLastY = 0;
- }
-
- public void setPathIterator(PathIterator iterator) {
- reset();
- mPath.append(iterator, false /*connect*/);
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nInit() {
- // create the delegate
- Path_Delegate newDelegate = new Path_Delegate();
-
- return sManager.addNewDelegate(newDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static long nInit(long nPath) {
- // create the delegate
- Path_Delegate newDelegate = new Path_Delegate();
-
- // get the delegate to copy, which could be null if nPath is 0
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate != null) {
- newDelegate.set(pathDelegate);
- }
-
- return sManager.addNewDelegate(newDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nReset(long nPath) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.reset();
- }
-
- @LayoutlibDelegate
- /*package*/ static void nRewind(long nPath) {
- // call out to reset since there's nothing to optimize in
- // terms of data structs.
- nReset(nPath);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSet(long native_dst, long nSrc) {
- Path_Delegate pathDstDelegate = sManager.getDelegate(native_dst);
- if (pathDstDelegate == null) {
- return;
- }
-
- Path_Delegate pathSrcDelegate = sManager.getDelegate(nSrc);
- if (pathSrcDelegate == null) {
- return;
- }
-
- pathDstDelegate.set(pathSrcDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nIsConvex(long nPath) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Path.isConvex is not supported.", null, null);
- return true;
- }
-
- @LayoutlibDelegate
- /*package*/ static int nGetFillType(long nPath) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return 0;
- }
-
- return pathDelegate.mFillType.nativeInt;
- }
-
- @LayoutlibDelegate
- public static void nSetFillType(long nPath, int ft) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.setFillType(Path.sFillTypeArray[ft]);
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nIsEmpty(long nPath) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- return pathDelegate == null || pathDelegate.isEmpty();
-
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nIsRect(long nPath, RectF rect) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return false;
- }
-
- // create an Area that can test if the path is a rect
- Area area = new Area(pathDelegate.mPath);
- if (area.isRectangular()) {
- if (rect != null) {
- pathDelegate.fillBounds(rect);
- }
-
- return true;
- }
-
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nComputeBounds(long nPath, RectF bounds) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.fillBounds(bounds);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nIncReserve(long nPath, int extraPtCount) {
- // since we use a java2D path, there's no way to pre-allocate new points,
- // so we do nothing.
- }
-
- @LayoutlibDelegate
- /*package*/ static void nMoveTo(long nPath, float x, float y) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.moveTo(x, y);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nRMoveTo(long nPath, float dx, float dy) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.rMoveTo(dx, dy);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nLineTo(long nPath, float x, float y) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.lineTo(x, y);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nRLineTo(long nPath, float dx, float dy) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.rLineTo(dx, dy);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nQuadTo(long nPath, float x1, float y1, float x2, float y2) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.quadTo(x1, y1, x2, y2);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nRQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.rQuadTo(dx1, dy1, dx2, dy2);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nCubicTo(long nPath, float x1, float y1,
- float x2, float y2, float x3, float y3) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.cubicTo(x1, y1, x2, y2, x3, y3);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nRCubicTo(long nPath, float x1, float y1,
- float x2, float y2, float x3, float y3) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.rCubicTo(x1, y1, x2, y2, x3, y3);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nArcTo(long nPath, float left, float top, float right,
- float bottom,
- float startAngle, float sweepAngle, boolean forceMoveTo) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.arcTo(left, top, right, bottom, startAngle, sweepAngle, forceMoveTo);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nClose(long nPath) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.close();
- }
-
- @LayoutlibDelegate
- /*package*/ static void nAddRect(long nPath,
- float left, float top, float right, float bottom, int dir) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.addRect(left, top, right, bottom, dir);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nAddOval(long nPath, float left, float top, float right,
- float bottom, int dir) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.mPath.append(new Ellipse2D.Float(
- left, top, right - left, bottom - top), false);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nAddCircle(long nPath, float x, float y, float radius, int dir) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- // because x/y is the center of the circle, need to offset this by the radius
- pathDelegate.mPath.append(new Ellipse2D.Float(
- x - radius, y - radius, radius * 2, radius * 2), false);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nAddArc(long nPath, float left, float top, float right,
- float bottom, float startAngle, float sweepAngle) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- // because x/y is the center of the circle, need to offset this by the radius
- pathDelegate.mPath.append(new Arc2D.Float(
- left, top, right - left, bottom - top,
- -startAngle, -sweepAngle, Arc2D.OPEN), false);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nAddRoundRect(long nPath, float left, float top, float right,
- float bottom, float rx, float ry, int dir) {
-
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.mPath.append(new RoundRectangle2D.Float(
- left, top, right - left, bottom - top, rx * 2, ry * 2), false);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nAddRoundRect(long nPath, float left, float top, float right,
- float bottom, float[] radii, int dir) {
-
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- float[] cornerDimensions = new float[radii.length];
- for (int i = 0; i < radii.length; i++) {
- cornerDimensions[i] = 2 * radii[i];
- }
- pathDelegate.mPath.append(new RoundRectangle(left, top, right - left, bottom - top,
- cornerDimensions), false);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nAddPath(long nPath, long src, float dx, float dy) {
- addPath(nPath, src, AffineTransform.getTranslateInstance(dx, dy));
- }
-
- @LayoutlibDelegate
- /*package*/ static void nAddPath(long nPath, long src) {
- addPath(nPath, src, null /*transform*/);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nAddPath(long nPath, long src, long matrix) {
- Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
- if (matrixDelegate == null) {
- return;
- }
-
- addPath(nPath, src, matrixDelegate.getAffineTransform());
- }
-
- @LayoutlibDelegate
- /*package*/ static void nOffset(long nPath, float dx, float dy) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.offset(dx, dy);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetLastPoint(long nPath, float dx, float dy) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- pathDelegate.mLastX = dx;
- pathDelegate.mLastY = dy;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nTransform(long nPath, long matrix,
- long dst_path) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return;
- }
-
- Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
- if (matrixDelegate == null) {
- return;
- }
-
- // this can be null if dst_path is 0
- Path_Delegate dstDelegate = sManager.getDelegate(dst_path);
-
- pathDelegate.transform(matrixDelegate, dstDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nTransform(long nPath, long matrix) {
- nTransform(nPath, matrix, 0);
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nOp(long nPath1, long nPath2, int op, long result) {
- Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Path.op() not supported", null);
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nFinalize(long nPath) {
- sManager.removeJavaReferenceFor(nPath);
- }
-
- @LayoutlibDelegate
- /*package*/ static float[] nApproximate(long nPath, float error) {
- Path_Delegate pathDelegate = sManager.getDelegate(nPath);
- if (pathDelegate == null) {
- return null;
- }
- // Get a FlatteningIterator
- PathIterator iterator = pathDelegate.getJavaShape().getPathIterator(null, error);
-
- float segment[] = new float[6];
- float totalLength = 0;
- ArrayList<Point2D.Float> points = new ArrayList<Point2D.Float>();
- Point2D.Float previousPoint = null;
- while (!iterator.isDone()) {
- int type = iterator.currentSegment(segment);
- Point2D.Float currentPoint = new Point2D.Float(segment[0], segment[1]);
- // MoveTo shouldn't affect the length
- if (previousPoint != null && type != PathIterator.SEG_MOVETO) {
- totalLength += currentPoint.distance(previousPoint);
- }
- previousPoint = currentPoint;
- points.add(currentPoint);
- iterator.next();
- }
-
- int nPoints = points.size();
- float[] result = new float[nPoints * 3];
- previousPoint = null;
- for (int i = 0; i < nPoints; i++) {
- Point2D.Float point = points.get(i);
- float distance = previousPoint != null ? (float) previousPoint.distance(point) : .0f;
- result[i * 3] = distance / totalLength;
- result[i * 3 + 1] = point.x;
- result[i * 3 + 2] = point.y;
-
- totalLength += distance;
- previousPoint = point;
- }
-
- return result;
- }
-
- // ---- Private helper methods ----
-
- private void set(Path_Delegate delegate) {
- mPath.reset();
- setFillType(delegate.mFillType);
- mPath.append(delegate.mPath, false /*connect*/);
- }
-
- private void setFillType(FillType fillType) {
- mFillType = fillType;
- mPath.setWindingRule(getWindingRule(fillType));
- }
-
- /**
- * Returns the Java2D winding rules matching a given Android {@link FillType}.
- * @param type the android fill type
- * @return the matching java2d winding rule.
- */
- private static int getWindingRule(FillType type) {
- switch (type) {
- case WINDING:
- case INVERSE_WINDING:
- return GeneralPath.WIND_NON_ZERO;
- case EVEN_ODD:
- case INVERSE_EVEN_ODD:
- return GeneralPath.WIND_EVEN_ODD;
-
- default:
- assert false;
- return GeneralPath.WIND_NON_ZERO;
- }
- }
-
- @NonNull
- private static Direction getDirection(int direction) {
- for (Direction d : Direction.values()) {
- if (direction == d.nativeInt) {
- return d;
- }
- }
-
- assert false;
- return null;
- }
-
- public static void addPath(long destPath, long srcPath, AffineTransform transform) {
- Path_Delegate destPathDelegate = sManager.getDelegate(destPath);
- if (destPathDelegate == null) {
- return;
- }
-
- Path_Delegate srcPathDelegate = sManager.getDelegate(srcPath);
- if (srcPathDelegate == null) {
- return;
- }
-
- if (transform != null) {
- destPathDelegate.mPath.append(
- srcPathDelegate.mPath.getPathIterator(transform), false);
- } else {
- destPathDelegate.mPath.append(srcPathDelegate.mPath, false);
- }
- }
-
-
- /**
- * Returns whether the path already contains any points.
- * Note that this is different to
- * {@link #isEmpty} because if all elements are {@link PathIterator#SEG_MOVETO},
- * {@link #isEmpty} will return true while hasPoints will return false.
- */
- public boolean hasPoints() {
- return !mPath.getPathIterator(null).isDone();
- }
-
- /**
- * Returns whether the path is empty (contains no lines or curves).
- * @see Path#isEmpty
- */
- public boolean isEmpty() {
- if (!mCachedIsEmpty) {
- return false;
- }
-
- float[] coords = new float[6];
- mCachedIsEmpty = Boolean.TRUE;
- for (PathIterator it = mPath.getPathIterator(null); !it.isDone(); it.next()) {
- int type = it.currentSegment(coords);
- if (type != PathIterator.SEG_MOVETO) {
- // Once we know that the path is not empty, we do not need to check again unless
- // Path#reset is called.
- mCachedIsEmpty = false;
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Fills the given {@link RectF} with the path bounds.
- * @param bounds the RectF to be filled.
- */
- public void fillBounds(RectF bounds) {
- Rectangle2D rect = mPath.getBounds2D();
- bounds.left = (float)rect.getMinX();
- bounds.right = (float)rect.getMaxX();
- bounds.top = (float)rect.getMinY();
- bounds.bottom = (float)rect.getMaxY();
- }
-
- /**
- * Set the beginning of the next contour to the point (x,y).
- *
- * @param x The x-coordinate of the start of a new contour
- * @param y The y-coordinate of the start of a new contour
- */
- public void moveTo(float x, float y) {
- mPath.moveTo(mLastX = x, mLastY = y);
- }
-
- /**
- * Set the beginning of the next contour relative to the last point on the
- * previous contour. If there is no previous contour, this is treated the
- * same as moveTo().
- *
- * @param dx The amount to add to the x-coordinate of the end of the
- * previous contour, to specify the start of a new contour
- * @param dy The amount to add to the y-coordinate of the end of the
- * previous contour, to specify the start of a new contour
- */
- public void rMoveTo(float dx, float dy) {
- dx += mLastX;
- dy += mLastY;
- mPath.moveTo(mLastX = dx, mLastY = dy);
- }
-
- /**
- * Add a line from the last point to the specified point (x,y).
- * If no moveTo() call has been made for this contour, the first point is
- * automatically set to (0,0).
- *
- * @param x The x-coordinate of the end of a line
- * @param y The y-coordinate of the end of a line
- */
- public void lineTo(float x, float y) {
- if (!hasPoints()) {
- mPath.moveTo(mLastX = 0, mLastY = 0);
- }
- mPath.lineTo(mLastX = x, mLastY = y);
- }
-
- /**
- * Same as lineTo, but the coordinates are considered relative to the last
- * point on this contour. If there is no previous point, then a moveTo(0,0)
- * is inserted automatically.
- *
- * @param dx The amount to add to the x-coordinate of the previous point on
- * this contour, to specify a line
- * @param dy The amount to add to the y-coordinate of the previous point on
- * this contour, to specify a line
- */
- public void rLineTo(float dx, float dy) {
- if (!hasPoints()) {
- mPath.moveTo(mLastX = 0, mLastY = 0);
- }
-
- if (Math.abs(dx) < EPSILON && Math.abs(dy) < EPSILON) {
- // The delta is so small that this shouldn't generate a line
- return;
- }
-
- dx += mLastX;
- dy += mLastY;
- mPath.lineTo(mLastX = dx, mLastY = dy);
- }
-
- /**
- * Add a quadratic bezier from the last point, approaching control point
- * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
- * this contour, the first point is automatically set to (0,0).
- *
- * @param x1 The x-coordinate of the control point on a quadratic curve
- * @param y1 The y-coordinate of the control point on a quadratic curve
- * @param x2 The x-coordinate of the end point on a quadratic curve
- * @param y2 The y-coordinate of the end point on a quadratic curve
- */
- public void quadTo(float x1, float y1, float x2, float y2) {
- mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2);
- }
-
- /**
- * Same as quadTo, but the coordinates are considered relative to the last
- * point on this contour. If there is no previous point, then a moveTo(0,0)
- * is inserted automatically.
- *
- * @param dx1 The amount to add to the x-coordinate of the last point on
- * this contour, for the control point of a quadratic curve
- * @param dy1 The amount to add to the y-coordinate of the last point on
- * this contour, for the control point of a quadratic curve
- * @param dx2 The amount to add to the x-coordinate of the last point on
- * this contour, for the end point of a quadratic curve
- * @param dy2 The amount to add to the y-coordinate of the last point on
- * this contour, for the end point of a quadratic curve
- */
- public void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
- if (!hasPoints()) {
- mPath.moveTo(mLastX = 0, mLastY = 0);
- }
- dx1 += mLastX;
- dy1 += mLastY;
- dx2 += mLastX;
- dy2 += mLastY;
- mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2);
- }
-
- /**
- * Add a cubic bezier from the last point, approaching control points
- * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
- * made for this contour, the first point is automatically set to (0,0).
- *
- * @param x1 The x-coordinate of the 1st control point on a cubic curve
- * @param y1 The y-coordinate of the 1st control point on a cubic curve
- * @param x2 The x-coordinate of the 2nd control point on a cubic curve
- * @param y2 The y-coordinate of the 2nd control point on a cubic curve
- * @param x3 The x-coordinate of the end point on a cubic curve
- * @param y3 The y-coordinate of the end point on a cubic curve
- */
- public void cubicTo(float x1, float y1, float x2, float y2,
- float x3, float y3) {
- if (!hasPoints()) {
- mPath.moveTo(0, 0);
- }
- mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
- }
-
- /**
- * Same as cubicTo, but the coordinates are considered relative to the
- * current point on this contour. If there is no previous point, then a
- * moveTo(0,0) is inserted automatically.
- */
- public void rCubicTo(float dx1, float dy1, float dx2, float dy2,
- float dx3, float dy3) {
- if (!hasPoints()) {
- mPath.moveTo(mLastX = 0, mLastY = 0);
- }
- dx1 += mLastX;
- dy1 += mLastY;
- dx2 += mLastX;
- dy2 += mLastY;
- dx3 += mLastX;
- dy3 += mLastY;
- mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3);
- }
-
- /**
- * Append the specified arc to the path as a new contour. If the start of
- * the path is different from the path's current last point, then an
- * automatic lineTo() is added to connect the current contour to the
- * start of the arc. However, if the path is empty, then we call moveTo()
- * with the first point of the arc. The sweep angle is tread mod 360.
- *
- * @param left The left of oval defining shape and size of the arc
- * @param top The top of oval defining shape and size of the arc
- * @param right The right of oval defining shape and size of the arc
- * @param bottom The bottom of oval defining shape and size of the arc
- * @param startAngle Starting angle (in degrees) where the arc begins
- * @param sweepAngle Sweep angle (in degrees) measured clockwise, treated
- * mod 360.
- * @param forceMoveTo If true, always begin a new contour with the arc
- */
- public void arcTo(float left, float top, float right, float bottom, float startAngle,
- float sweepAngle,
- boolean forceMoveTo) {
- Arc2D arc = new Arc2D.Float(left, top, right - left, bottom - top, -startAngle,
- -sweepAngle, Arc2D.OPEN);
- mPath.append(arc, true /*connect*/);
-
- resetLastPointFromPath();
- }
-
- /**
- * Close the current contour. If the current point is not equal to the
- * first point of the contour, a line segment is automatically added.
- */
- public void close() {
- mPath.closePath();
- }
-
- private void resetLastPointFromPath() {
- Point2D last = mPath.getCurrentPoint();
- mLastX = (float) last.getX();
- mLastY = (float) last.getY();
- }
-
- /**
- * Add a closed rectangle contour to the path
- *
- * @param left The left side of a rectangle to add to the path
- * @param top The top of a rectangle to add to the path
- * @param right The right side of a rectangle to add to the path
- * @param bottom The bottom of a rectangle to add to the path
- * @param dir The direction to wind the rectangle's contour
- */
- public void addRect(float left, float top, float right, float bottom,
- int dir) {
- moveTo(left, top);
-
- Direction direction = getDirection(dir);
-
- switch (direction) {
- case CW:
- lineTo(right, top);
- lineTo(right, bottom);
- lineTo(left, bottom);
- break;
- case CCW:
- lineTo(left, bottom);
- lineTo(right, bottom);
- lineTo(right, top);
- break;
- }
-
- close();
-
- resetLastPointFromPath();
- }
-
- /**
- * Offset the path by (dx,dy), returning true on success
- *
- * @param dx The amount in the X direction to offset the entire path
- * @param dy The amount in the Y direction to offset the entire path
- */
- public void offset(float dx, float dy) {
- GeneralPath newPath = new GeneralPath();
-
- PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
-
- newPath.append(iterator, false /*connect*/);
- mPath = newPath;
- }
-
- /**
- * Transform the points in this path by matrix, and write the answer
- * into dst. If dst is null, then the the original path is modified.
- *
- * @param matrix The matrix to apply to the path
- * @param dst The transformed path is written here. If dst is null,
- * then the the original path is modified
- */
- public void transform(Matrix_Delegate matrix, Path_Delegate dst) {
- if (matrix.hasPerspective()) {
- assert false;
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE,
- "android.graphics.Path#transform() only " +
- "supports affine transformations.", null, null /*data*/);
- }
-
- GeneralPath newPath = new GeneralPath();
-
- PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform());
-
- newPath.append(iterator, false /*connect*/);
-
- if (dst != null) {
- dst.mPath = newPath;
- } else {
- mPath = newPath;
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
deleted file mode 100644
index ff3f19f..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.graphics.PorterDuff.Mode;
-
-import java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
-
-import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getComposite;
-import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode;
-
-/**
- * Delegate implementing the native methods of android.graphics.PorterDuffColorFilter
- *
- * Through the layoutlib_create tool, the original native methods of PorterDuffColorFilter have
- * been replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original PorterDuffColorFilter class.
- *
- * Because this extends {@link ColorFilter_Delegate}, there's no need to use a
- * {@link DelegateManager}, as all the Shader classes will be added to the manager
- * owned by {@link ColorFilter_Delegate}.
- *
- * @see ColorFilter_Delegate
- *
- */
-public class PorterDuffColorFilter_Delegate extends ColorFilter_Delegate {
-
- // ---- delegate data ----
-
- private final java.awt.Color mSrcColor;
- private final Mode mMode;
-
-
- // ---- Public Helper methods ----
-
- @Override
- public boolean isSupported() {
- return true;
- }
-
- @Override
- public String getSupportMessage() {
- return "PorterDuff Color Filter is not supported for mode: " + mMode.name() + ".";
- }
-
- @Override
- public void applyFilter(Graphics2D g, int width, int height) {
- g.setComposite(getComposite(mMode, 0xFF));
- g.setColor(mSrcColor);
- g.fillRect(0, 0, width, height);
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long native_CreatePorterDuffFilter(int srcColor, int porterDuffMode) {
- PorterDuffColorFilter_Delegate newDelegate =
- new PorterDuffColorFilter_Delegate(srcColor, porterDuffMode);
- return sManager.addNewDelegate(newDelegate);
- }
-
-
- // ---- Private delegate/helper methods ----
-
- private PorterDuffColorFilter_Delegate(int srcColor, int mode) {
- mSrcColor = new java.awt.Color(srcColor, true /* hasAlpha */);
- mMode = getCompatibleMode(getPorterDuffMode(mode));
- }
-
- // For filtering the colors, the src image should contain the "color" only for pixel values
- // which are not transparent in the target image. But, we are using a simple rectangular image
- // completely filled with color. Hence some Composite rules do not apply as intended. However,
- // in such cases, they can usually be mapped to some other mode, which produces an approximately
- // equivalent result.
- private Mode getCompatibleMode(Mode mode) {
- Mode m = mode;
- // Modes that are directly supported:
- // CLEAR, DST, SRC_IN, DST_IN, DST_OUT, SRC_ATOP, DARKEN, LIGHTEN, MULTIPLY, SCREEN,
- // ADD, OVERLAY
- switch (mode) {
- // Modes that can be mapped to one of the supported modes.
- case SRC:
- m = Mode.SRC_IN;
- break;
- case SRC_OVER:
- m = Mode.SRC_ATOP;
- break;
- case DST_OVER:
- m = Mode.DST;
- break;
- case SRC_OUT:
- m = Mode.CLEAR;
- break;
- case DST_ATOP:
- m = Mode.DST_IN;
- break;
- case XOR:
- m = Mode.DST_OUT;
- break;
- }
- return m;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java
deleted file mode 100644
index b5ba468..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.graphics.Shader.TileMode;
-
-import java.awt.image.ColorModel;
-
-/**
- * Delegate implementing the native methods of android.graphics.RadialGradient
- *
- * Through the layoutlib_create tool, the original native methods of RadialGradient have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original RadialGradient class.
- *
- * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager},
- * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}.
- *
- * @see Shader_Delegate
- *
- */
-public class RadialGradient_Delegate extends Gradient_Delegate {
-
- // ---- delegate data ----
- private java.awt.Paint mJavaPaint;
-
- // ---- Public Helper methods ----
-
- @Override
- public java.awt.Paint getJavaPaint() {
- return mJavaPaint;
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreate1(long matrix, float x, float y, float radius,
- int colors[], float positions[], int tileMode) {
- RadialGradient_Delegate newDelegate = new RadialGradient_Delegate(matrix, x, y, radius,
- colors, positions, Shader_Delegate.getTileMode(tileMode));
- return sManager.addNewDelegate(newDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreate2(long matrix, float x, float y, float radius,
- int color0, int color1, int tileMode) {
- return nativeCreate1(matrix, x, y, radius, new int[] { color0, color1 },
- null /*positions*/, tileMode);
- }
-
- // ---- Private delegate/helper methods ----
-
- /**
- * Create a shader that draws a radial gradient given the center and radius.
- *
- * @param nativeMatrix reference to the shader's native transformation matrix
- * @param x The x-coordinate of the center of the radius
- * @param y The y-coordinate of the center of the radius
- * @param radius Must be positive. The radius of the circle for this
- * gradient
- * @param colors The colors to be distributed between the center and edge of
- * the circle
- * @param positions May be NULL. The relative position of each corresponding
- * color in the colors array. If this is NULL, the the colors are
- * distributed evenly between the center and edge of the circle.
- * @param tile The Shader tiling mode
- */
- private RadialGradient_Delegate(long nativeMatrix, float x, float y, float radius,
- int colors[], float positions[], TileMode tile) {
- super(nativeMatrix, colors, positions);
- mJavaPaint = new RadialGradientPaint(x, y, radius, mColors, mPositions, tile);
- }
-
- private class RadialGradientPaint extends GradientPaint {
-
- private final float mX;
- private final float mY;
- private final float mRadius;
-
- public RadialGradientPaint(float x, float y, float radius,
- int[] colors, float[] positions, TileMode mode) {
- super(colors, positions, mode);
- mX = x;
- mY = y;
- mRadius = radius;
- }
-
- @Override
- public java.awt.PaintContext createContext(
- java.awt.image.ColorModel colorModel,
- java.awt.Rectangle deviceBounds,
- java.awt.geom.Rectangle2D userBounds,
- java.awt.geom.AffineTransform xform,
- java.awt.RenderingHints hints) {
- precomputeGradientColors();
-
- java.awt.geom.AffineTransform canvasMatrix;
- try {
- canvasMatrix = xform.createInverse();
- } catch (java.awt.geom.NoninvertibleTransformException e) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
- "Unable to inverse matrix in RadialGradient", e, null /*data*/);
- canvasMatrix = new java.awt.geom.AffineTransform();
- }
-
- java.awt.geom.AffineTransform localMatrix = getLocalMatrix();
- try {
- localMatrix = localMatrix.createInverse();
- } catch (java.awt.geom.NoninvertibleTransformException e) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
- "Unable to inverse matrix in RadialGradient", e, null /*data*/);
- localMatrix = new java.awt.geom.AffineTransform();
- }
-
- return new RadialGradientPaintContext(canvasMatrix, localMatrix, colorModel);
- }
-
- private class RadialGradientPaintContext implements java.awt.PaintContext {
-
- private final java.awt.geom.AffineTransform mCanvasMatrix;
- private final java.awt.geom.AffineTransform mLocalMatrix;
- private final java.awt.image.ColorModel mColorModel;
-
- public RadialGradientPaintContext(
- java.awt.geom.AffineTransform canvasMatrix,
- java.awt.geom.AffineTransform localMatrix,
- java.awt.image.ColorModel colorModel) {
- mCanvasMatrix = canvasMatrix;
- mLocalMatrix = localMatrix;
- mColorModel = colorModel.hasAlpha() ? colorModel : ColorModel.getRGBdefault();
- }
-
- @Override
- public void dispose() {
- }
-
- @Override
- public java.awt.image.ColorModel getColorModel() {
- return mColorModel;
- }
-
- @Override
- public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
- java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(
- mColorModel, mColorModel.createCompatibleWritableRaster(w, h),
- mColorModel.isAlphaPremultiplied(), null);
-
- int[] data = new int[w*h];
-
- // compute distance from each point to the center, and figure out the distance from
- // it.
- int index = 0;
- float[] pt1 = new float[2];
- float[] pt2 = new float[2];
- for (int iy = 0 ; iy < h ; iy++) {
- for (int ix = 0 ; ix < w ; ix++) {
- // handle the canvas transform
- pt1[0] = x + ix;
- pt1[1] = y + iy;
- mCanvasMatrix.transform(pt1, 0, pt2, 0, 1);
-
- // handle the local matrix
- pt1[0] = pt2[0] - mX;
- pt1[1] = pt2[1] - mY;
- mLocalMatrix.transform(pt1, 0, pt2, 0, 1);
-
- float _x = pt2[0];
- float _y = pt2[1];
- float distance = (float) Math.hypot(_x, _y);
-
- data[index++] = getGradientColor(distance / mRadius);
- }
- }
-
- image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
-
- return image.getRaster();
- }
-
- }
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java
deleted file mode 100644
index edb7025..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java
+++ /dev/null
@@ -1,483 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.os.Parcel;
-
-import java.awt.Rectangle;
-import java.awt.Shape;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Area;
-import java.awt.geom.Rectangle2D;
-
-/**
- * Delegate implementing the native methods of android.graphics.Region
- *
- * Through the layoutlib_create tool, the original native methods of Region have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original Region class.
- *
- * This also serve as a base class for all Region delegate classes.
- *
- * @see DelegateManager
- *
- */
-public class Region_Delegate {
-
- // ---- delegate manager ----
- protected static final DelegateManager<Region_Delegate> sManager =
- new DelegateManager<Region_Delegate>(Region_Delegate.class);
-
- // ---- delegate helper data ----
-
- // ---- delegate data ----
- private Area mArea = new Area();
-
- // ---- Public Helper methods ----
-
- public static Region_Delegate getDelegate(long nativeShader) {
- return sManager.getDelegate(nativeShader);
- }
-
- public Area getJavaArea() {
- return mArea;
- }
-
- /**
- * Combines two {@link Shape} into another one (actually an {@link Area}), according
- * to the given {@link Region.Op}.
- *
- * If the Op is not one that combines two shapes, then this return null
- *
- * @param shape1 the firt shape to combine which can be null if there's no original clip.
- * @param shape2 the 2nd shape to combine
- * @param regionOp the operande for the combine
- * @return a new area or null.
- */
- public static Area combineShapes(Shape shape1, Shape shape2, int regionOp) {
- if (regionOp == Region.Op.DIFFERENCE.nativeInt) {
- // if shape1 is null (empty), then the result is null.
- if (shape1 == null) {
- return null;
- }
-
- // result is always a new area.
- Area result = new Area(shape1);
- result.subtract(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
- return result;
-
- } else if (regionOp == Region.Op.INTERSECT.nativeInt) {
- // if shape1 is null, then the result is simply shape2.
- if (shape1 == null) {
- return new Area(shape2);
- }
-
- // result is always a new area.
- Area result = new Area(shape1);
- result.intersect(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
- return result;
-
- } else if (regionOp == Region.Op.UNION.nativeInt) {
- // if shape1 is null, then the result is simply shape2.
- if (shape1 == null) {
- return new Area(shape2);
- }
-
- // result is always a new area.
- Area result = new Area(shape1);
- result.add(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
- return result;
-
- } else if (regionOp == Region.Op.XOR.nativeInt) {
- // if shape1 is null, then the result is simply shape2
- if (shape1 == null) {
- return new Area(shape2);
- }
-
- // result is always a new area.
- Area result = new Area(shape1);
- result.exclusiveOr(shape2 instanceof Area ? (Area) shape2 : new Area(shape2));
- return result;
-
- } else if (regionOp == Region.Op.REVERSE_DIFFERENCE.nativeInt) {
- // result is always a new area.
- Area result = new Area(shape2);
-
- if (shape1 != null) {
- result.subtract(shape1 instanceof Area ? (Area) shape1 : new Area(shape1));
- }
-
- return result;
- }
-
- return null;
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static boolean isEmpty(Region thisRegion) {
- Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
- if (regionDelegate == null) {
- return true;
- }
-
- return regionDelegate.mArea.isEmpty();
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean isRect(Region thisRegion) {
- Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
- if (regionDelegate == null) {
- return true;
- }
-
- return regionDelegate.mArea.isRectangular();
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean isComplex(Region thisRegion) {
- Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
- if (regionDelegate == null) {
- return true;
- }
-
- return regionDelegate.mArea.isSingular() == false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean contains(Region thisRegion, int x, int y) {
- Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
- if (regionDelegate == null) {
- return false;
- }
-
- return regionDelegate.mArea.contains(x, y);
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean quickContains(Region thisRegion,
- int left, int top, int right, int bottom) {
- Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
- if (regionDelegate == null) {
- return false;
- }
-
- return regionDelegate.mArea.isRectangular() &&
- regionDelegate.mArea.contains(left, top, right - left, bottom - top);
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean quickReject(Region thisRegion,
- int left, int top, int right, int bottom) {
- Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
- if (regionDelegate == null) {
- return false;
- }
-
- return regionDelegate.mArea.isEmpty() ||
- regionDelegate.mArea.intersects(left, top, right - left, bottom - top) == false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean quickReject(Region thisRegion, Region rgn) {
- Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
- if (regionDelegate == null) {
- return false;
- }
-
- Region_Delegate targetRegionDelegate = sManager.getDelegate(rgn.mNativeRegion);
- if (targetRegionDelegate == null) {
- return false;
- }
-
- return regionDelegate.mArea.isEmpty() ||
- regionDelegate.mArea.getBounds().intersects(
- targetRegionDelegate.mArea.getBounds()) == false;
-
- }
-
- @LayoutlibDelegate
- /*package*/ static void translate(Region thisRegion, int dx, int dy, Region dst) {
- Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
- if (regionDelegate == null) {
- return;
- }
-
- Region_Delegate targetRegionDelegate = sManager.getDelegate(dst.mNativeRegion);
- if (targetRegionDelegate == null) {
- return;
- }
-
- if (regionDelegate.mArea.isEmpty()) {
- targetRegionDelegate.mArea = new Area();
- } else {
- targetRegionDelegate.mArea = new Area(regionDelegate.mArea);
- AffineTransform mtx = new AffineTransform();
- mtx.translate(dx, dy);
- targetRegionDelegate.mArea.transform(mtx);
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void scale(Region thisRegion, float scale, Region dst) {
- Region_Delegate regionDelegate = sManager.getDelegate(thisRegion.mNativeRegion);
- if (regionDelegate == null) {
- return;
- }
-
- Region_Delegate targetRegionDelegate = sManager.getDelegate(dst.mNativeRegion);
- if (targetRegionDelegate == null) {
- return;
- }
-
- if (regionDelegate.mArea.isEmpty()) {
- targetRegionDelegate.mArea = new Area();
- } else {
- targetRegionDelegate.mArea = new Area(regionDelegate.mArea);
- AffineTransform mtx = new AffineTransform();
- mtx.scale(scale, scale);
- targetRegionDelegate.mArea.transform(mtx);
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static long nativeConstructor() {
- Region_Delegate newDelegate = new Region_Delegate();
- return sManager.addNewDelegate(newDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nativeDestructor(long native_region) {
- sManager.removeJavaReferenceFor(native_region);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nativeSetRegion(long native_dst, long native_src) {
- Region_Delegate dstRegion = sManager.getDelegate(native_dst);
- if (dstRegion == null) {
- return;
- }
-
- Region_Delegate srcRegion = sManager.getDelegate(native_src);
- if (srcRegion == null) {
- return;
- }
-
- dstRegion.mArea.reset();
- dstRegion.mArea.add(srcRegion.mArea);
-
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeSetRect(long native_dst,
- int left, int top, int right, int bottom) {
- Region_Delegate dstRegion = sManager.getDelegate(native_dst);
- if (dstRegion == null) {
- return true;
- }
-
- dstRegion.mArea = new Area(new Rectangle2D.Float(left, top, right - left, bottom - top));
- return dstRegion.mArea.getBounds().isEmpty() == false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeSetPath(long native_dst, long native_path, long native_clip) {
- Region_Delegate dstRegion = sManager.getDelegate(native_dst);
- if (dstRegion == null) {
- return true;
- }
-
- Path_Delegate path = Path_Delegate.getDelegate(native_path);
- if (path == null) {
- return true;
- }
-
- dstRegion.mArea = new Area(path.getJavaShape());
-
- Region_Delegate clip = sManager.getDelegate(native_clip);
- if (clip != null) {
- dstRegion.mArea.subtract(clip.getJavaArea());
- }
-
- return dstRegion.mArea.getBounds().isEmpty() == false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeGetBounds(long native_region, Rect rect) {
- Region_Delegate region = sManager.getDelegate(native_region);
- if (region == null) {
- return true;
- }
-
- Rectangle bounds = region.mArea.getBounds();
- if (bounds.isEmpty()) {
- rect.left = rect.top = rect.right = rect.bottom = 0;
- return false;
- }
-
- rect.left = bounds.x;
- rect.top = bounds.y;
- rect.right = bounds.x + bounds.width;
- rect.bottom = bounds.y + bounds.height;
- return true;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeGetBoundaryPath(long native_region, long native_path) {
- Region_Delegate region = sManager.getDelegate(native_region);
- if (region == null) {
- return false;
- }
-
- Path_Delegate path = Path_Delegate.getDelegate(native_path);
- if (path == null) {
- return false;
- }
-
- if (region.mArea.isEmpty()) {
- path.reset();
- return false;
- }
-
- path.setPathIterator(region.mArea.getPathIterator(new AffineTransform()));
- return true;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeOp(long native_dst,
- int left, int top, int right, int bottom, int op) {
- Region_Delegate region = sManager.getDelegate(native_dst);
- if (region == null) {
- return false;
- }
-
- region.mArea = combineShapes(region.mArea,
- new Rectangle2D.Float(left, top, right - left, bottom - top), op);
-
- assert region.mArea != null;
- if (region.mArea != null) {
- region.mArea = new Area();
- }
-
- return region.mArea.getBounds().isEmpty() == false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeOp(long native_dst, Rect rect, long native_region, int op) {
- Region_Delegate region = sManager.getDelegate(native_dst);
- if (region == null) {
- return false;
- }
-
- region.mArea = combineShapes(region.mArea,
- new Rectangle2D.Float(rect.left, rect.top, rect.width(), rect.height()), op);
-
- assert region.mArea != null;
- if (region.mArea != null) {
- region.mArea = new Area();
- }
-
- return region.mArea.getBounds().isEmpty() == false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeOp(long native_dst,
- long native_region1, long native_region2, int op) {
- Region_Delegate dstRegion = sManager.getDelegate(native_dst);
- if (dstRegion == null) {
- return true;
- }
-
- Region_Delegate region1 = sManager.getDelegate(native_region1);
- if (region1 == null) {
- return false;
- }
-
- Region_Delegate region2 = sManager.getDelegate(native_region2);
- if (region2 == null) {
- return false;
- }
-
- dstRegion.mArea = combineShapes(region1.mArea, region2.mArea, op);
-
- assert dstRegion.mArea != null;
- if (dstRegion.mArea != null) {
- dstRegion.mArea = new Area();
- }
-
- return dstRegion.mArea.getBounds().isEmpty() == false;
-
- }
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreateFromParcel(Parcel p) {
- // This is only called by Region.CREATOR (Parcelable.Creator<Region>), which is only
- // used during aidl call so really this should not be called.
- Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
- "AIDL is not suppored, and therefore Regions cannot be created from parcels.",
- null /*data*/);
- return 0;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeWriteToParcel(long native_region,
- Parcel p) {
- // This is only called when sending a region through aidl, so really this should not
- // be called.
- Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
- "AIDL is not suppored, and therefore Regions cannot be written to parcels.",
- null /*data*/);
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nativeEquals(long native_r1, long native_r2) {
- Region_Delegate region1 = sManager.getDelegate(native_r1);
- if (region1 == null) {
- return false;
- }
-
- Region_Delegate region2 = sManager.getDelegate(native_r2);
- if (region2 == null) {
- return false;
- }
-
- return region1.mArea.equals(region2.mArea);
- }
-
- @LayoutlibDelegate
- /*package*/ static String nativeToString(long native_region) {
- Region_Delegate region = sManager.getDelegate(native_region);
- if (region == null) {
- return "not found";
- }
-
- return region.mArea.toString();
- }
-
- // ---- Private delegate/helper methods ----
-
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/RoundRectangle.java b/tools/layoutlib/bridge/src/android/graphics/RoundRectangle.java
deleted file mode 100644
index 736f03e..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/RoundRectangle.java
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.graphics;
-
-import java.awt.geom.AffineTransform;
-import java.awt.geom.PathIterator;
-import java.awt.geom.Rectangle2D;
-import java.awt.geom.RectangularShape;
-import java.awt.geom.RoundRectangle2D;
-import java.util.EnumSet;
-import java.util.NoSuchElementException;
-
-/**
- * Defines a rectangle with rounded corners, where the sizes of the corners
- * are potentially different.
- */
-public class RoundRectangle extends RectangularShape {
- public double x;
- public double y;
- public double width;
- public double height;
- public double ulWidth;
- public double ulHeight;
- public double urWidth;
- public double urHeight;
- public double lrWidth;
- public double lrHeight;
- public double llWidth;
- public double llHeight;
-
- private enum Zone {
- CLOSE_OUTSIDE,
- CLOSE_INSIDE,
- MIDDLE,
- FAR_INSIDE,
- FAR_OUTSIDE
- }
-
- private final EnumSet<Zone> close = EnumSet.of(Zone.CLOSE_OUTSIDE, Zone.CLOSE_INSIDE);
- private final EnumSet<Zone> far = EnumSet.of(Zone.FAR_OUTSIDE, Zone.FAR_INSIDE);
-
- /**
- * @param cornerDimensions array of 8 floating-point number corresponding to the width and
- * the height of each corner in the following order: upper-left, upper-right, lower-right,
- * lower-left. It assumes for the size the same convention as {@link RoundRectangle2D}, that
- * is that the width and height of a corner correspond to the total width and height of the
- * ellipse that corner is a quarter of.
- */
- public RoundRectangle(float x, float y, float width, float height, float[] cornerDimensions) {
- assert cornerDimensions.length == 8 : "The array of corner dimensions must have eight " +
- "elements";
-
- this.x = x;
- this.y = y;
- this.width = width;
- this.height = height;
-
- float[] dimensions = cornerDimensions.clone();
- // If a value is negative, the corresponding corner is squared
- for (int i = 0; i < dimensions.length; i += 2) {
- if (dimensions[i] < 0 || dimensions[i + 1] < 0) {
- dimensions[i] = 0;
- dimensions[i + 1] = 0;
- }
- }
-
- double topCornerWidth = (dimensions[0] + dimensions[2]) / 2d;
- double bottomCornerWidth = (dimensions[4] + dimensions[6]) / 2d;
- double leftCornerHeight = (dimensions[1] + dimensions[7]) / 2d;
- double rightCornerHeight = (dimensions[3] + dimensions[5]) / 2d;
-
- // Rescale the corner dimensions if they are bigger than the rectangle
- double scale = Math.min(1.0, width / topCornerWidth);
- scale = Math.min(scale, width / bottomCornerWidth);
- scale = Math.min(scale, height / leftCornerHeight);
- scale = Math.min(scale, height / rightCornerHeight);
-
- this.ulWidth = dimensions[0] * scale;
- this.ulHeight = dimensions[1] * scale;
- this.urWidth = dimensions[2] * scale;
- this.urHeight = dimensions[3] * scale;
- this.lrWidth = dimensions[4] * scale;
- this.lrHeight = dimensions[5] * scale;
- this.llWidth = dimensions[6] * scale;
- this.llHeight = dimensions[7] * scale;
- }
-
- @Override
- public double getX() {
- return x;
- }
-
- @Override
- public double getY() {
- return y;
- }
-
- @Override
- public double getWidth() {
- return width;
- }
-
- @Override
- public double getHeight() {
- return height;
- }
-
- @Override
- public boolean isEmpty() {
- return (width <= 0d) || (height <= 0d);
- }
-
- @Override
- public void setFrame(double x, double y, double w, double h) {
- this.x = x;
- this.y = y;
- this.width = w;
- this.height = h;
- }
-
- @Override
- public Rectangle2D getBounds2D() {
- return new Rectangle2D.Double(x, y, width, height);
- }
-
- @Override
- public boolean contains(double x, double y) {
- if (isEmpty()) {
- return false;
- }
-
- double x0 = getX();
- double y0 = getY();
- double x1 = x0 + getWidth();
- double y1 = y0 + getHeight();
- // Check for trivial rejection - point is outside bounding rectangle
- if (x < x0 || y < y0 || x >= x1 || y >= y1) {
- return false;
- }
-
- double insideTopX0 = x0 + ulWidth / 2d;
- double insideLeftY0 = y0 + ulHeight / 2d;
- if (x < insideTopX0 && y < insideLeftY0) {
- // In the upper-left corner
- return isInsideCorner(x - insideTopX0, y - insideLeftY0, ulWidth / 2d, ulHeight / 2d);
- }
-
- double insideTopX1 = x1 - urWidth / 2d;
- double insideRightY0 = y0 + urHeight / 2d;
- if (x > insideTopX1 && y < insideRightY0) {
- // In the upper-right corner
- return isInsideCorner(x - insideTopX1, y - insideRightY0, urWidth / 2d, urHeight / 2d);
- }
-
- double insideBottomX1 = x1 - lrWidth / 2d;
- double insideRightY1 = y1 - lrHeight / 2d;
- if (x > insideBottomX1 && y > insideRightY1) {
- // In the lower-right corner
- return isInsideCorner(x - insideBottomX1, y - insideRightY1, lrWidth / 2d,
- lrHeight / 2d);
- }
-
- double insideBottomX0 = x0 + llWidth / 2d;
- double insideLeftY1 = y1 - llHeight / 2d;
- if (x < insideBottomX0 && y > insideLeftY1) {
- // In the lower-left corner
- return isInsideCorner(x - insideBottomX0, y - insideLeftY1, llWidth / 2d,
- llHeight / 2d);
- }
-
- // In the central part of the rectangle
- return true;
- }
-
- private boolean isInsideCorner(double x, double y, double width, double height) {
- double squareDist = height * height * x * x + width * width * y * y;
- return squareDist <= width * width * height * height;
- }
-
- private Zone classify(double coord, double side1, double arcSize1, double side2,
- double arcSize2) {
- if (coord < side1) {
- return Zone.CLOSE_OUTSIDE;
- } else if (coord < side1 + arcSize1) {
- return Zone.CLOSE_INSIDE;
- } else if (coord < side2 - arcSize2) {
- return Zone.MIDDLE;
- } else if (coord < side2) {
- return Zone.FAR_INSIDE;
- } else {
- return Zone.FAR_OUTSIDE;
- }
- }
-
- public boolean intersects(double x, double y, double w, double h) {
- if (isEmpty() || w <= 0 || h <= 0) {
- return false;
- }
- double x0 = getX();
- double y0 = getY();
- double x1 = x0 + getWidth();
- double y1 = y0 + getHeight();
- // Check for trivial rejection - bounding rectangles do not intersect
- if (x + w <= x0 || x >= x1 || y + h <= y0 || y >= y1) {
- return false;
- }
-
- double maxLeftCornerWidth = Math.max(ulWidth, llWidth) / 2d;
- double maxRightCornerWidth = Math.max(urWidth, lrWidth) / 2d;
- double maxUpperCornerHeight = Math.max(ulHeight, urHeight) / 2d;
- double maxLowerCornerHeight = Math.max(llHeight, lrHeight) / 2d;
- Zone x0class = classify(x, x0, maxLeftCornerWidth, x1, maxRightCornerWidth);
- Zone x1class = classify(x + w, x0, maxLeftCornerWidth, x1, maxRightCornerWidth);
- Zone y0class = classify(y, y0, maxUpperCornerHeight, y1, maxLowerCornerHeight);
- Zone y1class = classify(y + h, y0, maxUpperCornerHeight, y1, maxLowerCornerHeight);
-
- // Trivially accept if any point is inside inner rectangle
- if (x0class == Zone.MIDDLE || x1class == Zone.MIDDLE || y0class == Zone.MIDDLE || y1class == Zone.MIDDLE) {
- return true;
- }
- // Trivially accept if either edge spans inner rectangle
- if ((close.contains(x0class) && far.contains(x1class)) || (close.contains(y0class) &&
- far.contains(y1class))) {
- return true;
- }
-
- // Since neither edge spans the center, then one of the corners
- // must be in one of the rounded edges. We detect this case if
- // a [xy]0class is 3 or a [xy]1class is 1. One of those two cases
- // must be true for each direction.
- // We now find a "nearest point" to test for being inside a rounded
- // corner.
- if (x1class == Zone.CLOSE_INSIDE && y1class == Zone.CLOSE_INSIDE) {
- // Potentially in upper-left corner
- x = x + w - x0 - ulWidth / 2d;
- y = y + h - y0 - ulHeight / 2d;
- return x > 0 || y > 0 || isInsideCorner(x, y, ulWidth / 2d, ulHeight / 2d);
- }
- if (x1class == Zone.CLOSE_INSIDE) {
- // Potentially in lower-left corner
- x = x + w - x0 - llWidth / 2d;
- y = y - y1 + llHeight / 2d;
- return x > 0 || y < 0 || isInsideCorner(x, y, llWidth / 2d, llHeight / 2d);
- }
- if (y1class == Zone.CLOSE_INSIDE) {
- //Potentially in the upper-right corner
- x = x - x1 + urWidth / 2d;
- y = y + h - y0 - urHeight / 2d;
- return x < 0 || y > 0 || isInsideCorner(x, y, urWidth / 2d, urHeight / 2d);
- }
- // Potentially in the lower-right corner
- x = x - x1 + lrWidth / 2d;
- y = y - y1 + lrHeight / 2d;
- return x < 0 || y < 0 || isInsideCorner(x, y, lrWidth / 2d, lrHeight / 2d);
- }
-
- @Override
- public boolean contains(double x, double y, double w, double h) {
- if (isEmpty() || w <= 0 || h <= 0) {
- return false;
- }
- return (contains(x, y) &&
- contains(x + w, y) &&
- contains(x, y + h) &&
- contains(x + w, y + h));
- }
-
- @Override
- public PathIterator getPathIterator(final AffineTransform at) {
- return new PathIterator() {
- int index;
-
- // ArcIterator.btan(Math.PI/2)
- public static final double CtrlVal = 0.5522847498307933;
- private final double ncv = 1.0 - CtrlVal;
-
- // Coordinates of control points for Bezier curves approximating the straight lines
- // and corners of the rounded rectangle.
- private final double[][] ctrlpts = {
- {0.0, 0.0, 0.0, ulHeight},
- {0.0, 0.0, 1.0, -llHeight},
- {0.0, 0.0, 1.0, -llHeight * ncv, 0.0, ncv * llWidth, 1.0, 0.0, 0.0, llWidth,
- 1.0, 0.0},
- {1.0, -lrWidth, 1.0, 0.0},
- {1.0, -lrWidth * ncv, 1.0, 0.0, 1.0, 0.0, 1.0, -lrHeight * ncv, 1.0, 0.0, 1.0,
- -lrHeight},
- {1.0, 0.0, 0.0, urHeight},
- {1.0, 0.0, 0.0, ncv * urHeight, 1.0, -urWidth * ncv, 0.0, 0.0, 1.0, -urWidth,
- 0.0, 0.0},
- {0.0, ulWidth, 0.0, 0.0},
- {0.0, ncv * ulWidth, 0.0, 0.0, 0.0, 0.0, 0.0, ncv * ulHeight, 0.0, 0.0, 0.0,
- ulHeight},
- {}
- };
- private final int[] types = {
- SEG_MOVETO,
- SEG_LINETO, SEG_CUBICTO,
- SEG_LINETO, SEG_CUBICTO,
- SEG_LINETO, SEG_CUBICTO,
- SEG_LINETO, SEG_CUBICTO,
- SEG_CLOSE,
- };
-
- @Override
- public int getWindingRule() {
- return WIND_NON_ZERO;
- }
-
- @Override
- public boolean isDone() {
- return index >= ctrlpts.length;
- }
-
- @Override
- public void next() {
- index++;
- }
-
- @Override
- public int currentSegment(float[] coords) {
- if (isDone()) {
- throw new NoSuchElementException("roundrect iterator out of bounds");
- }
- int nc = 0;
- double ctrls[] = ctrlpts[index];
- for (int i = 0; i < ctrls.length; i += 4) {
- coords[nc++] = (float) (x + ctrls[i] * width + ctrls[i + 1] / 2d);
- coords[nc++] = (float) (y + ctrls[i + 2] * height + ctrls[i + 3] / 2d);
- }
- if (at != null) {
- at.transform(coords, 0, coords, 0, nc / 2);
- }
- return types[index];
- }
-
- @Override
- public int currentSegment(double[] coords) {
- if (isDone()) {
- throw new NoSuchElementException("roundrect iterator out of bounds");
- }
- int nc = 0;
- double ctrls[] = ctrlpts[index];
- for (int i = 0; i < ctrls.length; i += 4) {
- coords[nc++] = x + ctrls[i] * width + ctrls[i + 1] / 2d;
- coords[nc++] = y + ctrls[i + 2] * height + ctrls[i + 3] / 2d;
- }
- if (at != null) {
- at.transform(coords, 0, coords, 0, nc / 2);
- }
- return types[index];
- }
- };
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java
deleted file mode 100644
index 5b75089..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.graphics.Shader.TileMode;
-
-/**
- * Delegate implementing the native methods of android.graphics.Shader
- *
- * Through the layoutlib_create tool, the original native methods of Shader have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original Shader class.
- *
- * This also serve as a base class for all Shader delegate classes.
- *
- * @see DelegateManager
- *
- */
-public abstract class Shader_Delegate {
-
- // ---- delegate manager ----
- protected static final DelegateManager<Shader_Delegate> sManager =
- new DelegateManager<Shader_Delegate>(Shader_Delegate.class);
-
- // ---- delegate helper data ----
-
- // ---- delegate data ----
- private Matrix_Delegate mLocalMatrix = null;
-
- // ---- Public Helper methods ----
-
- public static Shader_Delegate getDelegate(long nativeShader) {
- return sManager.getDelegate(nativeShader);
- }
-
- /**
- * Returns the {@link TileMode} matching the given int.
- * @param tileMode the tile mode int value
- * @return the TileMode enum.
- */
- public static TileMode getTileMode(int tileMode) {
- for (TileMode tm : TileMode.values()) {
- if (tm.nativeInt == tileMode) {
- return tm;
- }
- }
-
- assert false;
- return TileMode.CLAMP;
- }
-
- public abstract java.awt.Paint getJavaPaint();
- public abstract boolean isSupported();
- public abstract String getSupportMessage();
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static void nativeSafeUnref(long nativeInstance) {
- sManager.removeJavaReferenceFor(nativeInstance);
- }
-
- // ---- Private delegate/helper methods ----
-
- protected Shader_Delegate(long nativeMatrix) {
- mLocalMatrix = Matrix_Delegate.getDelegate(nativeMatrix);
- }
-
- protected java.awt.geom.AffineTransform getLocalMatrix() {
- if (mLocalMatrix != null) {
- return mLocalMatrix.getAffineTransform();
- }
-
- return new java.awt.geom.AffineTransform();
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/SumPathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/SumPathEffect_Delegate.java
deleted file mode 100644
index 6d2e9b4..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/SumPathEffect_Delegate.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.awt.Stroke;
-
-/**
- * Delegate implementing the native methods of android.graphics.SumPathEffect
- *
- * Through the layoutlib_create tool, the original native methods of SumPathEffect have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original SumPathEffect class.
- *
- * Because this extends {@link PathEffect_Delegate}, there's no need to use a {@link DelegateManager},
- * as all the Shader classes will be added to the manager owned by {@link PathEffect_Delegate}.
- *
- * @see PathEffect_Delegate
- *
- */
-public class SumPathEffect_Delegate extends PathEffect_Delegate {
-
- // ---- delegate data ----
-
- // ---- Public Helper methods ----
-
- @Override
- public Stroke getStroke(Paint_Delegate paint) {
- // FIXME
- return null;
- }
-
- @Override
- public boolean isSupported() {
- return false;
- }
-
- @Override
- public String getSupportMessage() {
- return "Sum Path Effects are not supported in Layout Preview mode.";
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreate(long first, long second) {
- SumPathEffect_Delegate newDelegate = new SumPathEffect_Delegate();
- return sManager.addNewDelegate(newDelegate);
- }
-
- // ---- Private delegate/helper methods ----
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java
deleted file mode 100644
index 30152bc..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-/**
- * Delegate implementing the native methods of android.graphics.SweepGradient
- *
- * Through the layoutlib_create tool, the original native methods of SweepGradient have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original SweepGradient class.
- *
- * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager},
- * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}.
- *
- * @see Shader_Delegate
- *
- */
-public class SweepGradient_Delegate extends Gradient_Delegate {
-
- // ---- delegate data ----
- private java.awt.Paint mJavaPaint;
-
- // ---- Public Helper methods ----
-
- @Override
- public java.awt.Paint getJavaPaint() {
- return mJavaPaint;
- }
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreate1(long matrix, float x, float y, int colors[], float
- positions[]) {
- SweepGradient_Delegate newDelegate = new SweepGradient_Delegate(matrix, x, y, colors,
- positions);
- return sManager.addNewDelegate(newDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreate2(long matrix, float x, float y, int color0, int color1) {
- return nativeCreate1(matrix, x, y, new int[] { color0, color1 },
- null /*positions*/);
- }
-
- // ---- Private delegate/helper methods ----
-
- /**
- * A subclass of Shader that draws a sweep gradient around a center point.
- *
- * @param nativeMatrix reference to the shader's native transformation matrix
- * @param cx The x-coordinate of the center
- * @param cy The y-coordinate of the center
- * @param colors The colors to be distributed between around the center.
- * There must be at least 2 colors in the array.
- * @param positions May be NULL. The relative position of
- * each corresponding color in the colors array, beginning
- * with 0 and ending with 1.0. If the values are not
- * monotonic, the drawing may produce unexpected results.
- * If positions is NULL, then the colors are automatically
- * spaced evenly.
- */
- private SweepGradient_Delegate(long nativeMatrix, float cx, float cy,
- int colors[], float positions[]) {
- super(nativeMatrix, colors, positions);
- mJavaPaint = new SweepGradientPaint(cx, cy, mColors, mPositions);
- }
-
- private class SweepGradientPaint extends GradientPaint {
-
- private final float mCx;
- private final float mCy;
-
- public SweepGradientPaint(float cx, float cy, int[] colors,
- float[] positions) {
- super(colors, positions, null /*tileMode*/);
- mCx = cx;
- mCy = cy;
- }
-
- @Override
- public java.awt.PaintContext createContext(
- java.awt.image.ColorModel colorModel,
- java.awt.Rectangle deviceBounds,
- java.awt.geom.Rectangle2D userBounds,
- java.awt.geom.AffineTransform xform,
- java.awt.RenderingHints hints) {
- precomputeGradientColors();
-
- java.awt.geom.AffineTransform canvasMatrix;
- try {
- canvasMatrix = xform.createInverse();
- } catch (java.awt.geom.NoninvertibleTransformException e) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
- "Unable to inverse matrix in SweepGradient", e, null /*data*/);
- canvasMatrix = new java.awt.geom.AffineTransform();
- }
-
- java.awt.geom.AffineTransform localMatrix = getLocalMatrix();
- try {
- localMatrix = localMatrix.createInverse();
- } catch (java.awt.geom.NoninvertibleTransformException e) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE,
- "Unable to inverse matrix in SweepGradient", e, null /*data*/);
- localMatrix = new java.awt.geom.AffineTransform();
- }
-
- return new SweepGradientPaintContext(canvasMatrix, localMatrix, colorModel);
- }
-
- private class SweepGradientPaintContext implements java.awt.PaintContext {
-
- private final java.awt.geom.AffineTransform mCanvasMatrix;
- private final java.awt.geom.AffineTransform mLocalMatrix;
- private final java.awt.image.ColorModel mColorModel;
-
- public SweepGradientPaintContext(
- java.awt.geom.AffineTransform canvasMatrix,
- java.awt.geom.AffineTransform localMatrix,
- java.awt.image.ColorModel colorModel) {
- mCanvasMatrix = canvasMatrix;
- mLocalMatrix = localMatrix;
- mColorModel = colorModel;
- }
-
- @Override
- public void dispose() {
- }
-
- @Override
- public java.awt.image.ColorModel getColorModel() {
- return mColorModel;
- }
-
- @Override
- public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
- java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(
- mColorModel, mColorModel.createCompatibleWritableRaster(w, h),
- mColorModel.isAlphaPremultiplied(), null);
-
- int[] data = new int[w*h];
-
- // compute angle from each point to the center, and figure out the distance from
- // it.
- int index = 0;
- float[] pt1 = new float[2];
- float[] pt2 = new float[2];
- for (int iy = 0 ; iy < h ; iy++) {
- for (int ix = 0 ; ix < w ; ix++) {
- // handle the canvas transform
- pt1[0] = x + ix;
- pt1[1] = y + iy;
- mCanvasMatrix.transform(pt1, 0, pt2, 0, 1);
-
- // handle the local matrix
- pt1[0] = pt2[0] - mCx;
- pt1[1] = pt2[1] - mCy;
- mLocalMatrix.transform(pt1, 0, pt2, 0, 1);
-
- float dx = pt2[0];
- float dy = pt2[1];
-
- float angle;
- if (dx == 0) {
- angle = (float) (dy < 0 ? 3 * Math.PI / 2 : Math.PI / 2);
- } else if (dy == 0) {
- angle = (float) (dx < 0 ? Math.PI : 0);
- } else {
- angle = (float) Math.atan(dy / dx);
- if (dx > 0) {
- if (dy < 0) {
- angle += Math.PI * 2;
- }
- } else {
- angle += Math.PI;
- }
- }
-
- // convert to 0-1. value and get color
- data[index++] = getGradientColor((float) (angle / (2 * Math.PI)));
- }
- }
-
- image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
-
- return image.getRaster();
- }
-
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java
deleted file mode 100644
index ce669cb..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Accessor.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.graphics;
-
-import android.annotation.NonNull;
-
-/**
- * Class allowing access to package-protected methods/fields.
- */
-public class Typeface_Accessor {
- public static boolean isSystemFont(@NonNull String fontName) {
- return Typeface.sSystemFontMap.containsKey(fontName);
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
deleted file mode 100644
index c7818a3..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2010 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.graphics;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.annotation.NonNull;
-import android.graphics.FontFamily_Delegate.FontVariant;
-import android.graphics.fonts.FontVariationAxis;
-import android.text.FontConfig;
-
-import java.awt.Font;
-import java.io.File;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import static android.graphics.FontFamily_Delegate.getFontLocation;
-
-/**
- * Delegate implementing the native methods of android.graphics.Typeface
- *
- * Through the layoutlib_create tool, the original native methods of Typeface have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original Typeface class.
- *
- * @see DelegateManager
- *
- */
-public final class Typeface_Delegate {
-
- public static final String SYSTEM_FONTS = "/system/fonts/";
-
- // ---- delegate manager ----
- private static final DelegateManager<Typeface_Delegate> sManager =
- new DelegateManager<Typeface_Delegate>(Typeface_Delegate.class);
-
-
- // ---- delegate data ----
-
- @NonNull
- private final FontFamily_Delegate[] mFontFamilies; // the reference to FontFamily_Delegate.
- /** @see Font#getStyle() */
- private final int mStyle;
- private final int mWeight;
-
- private static long sDefaultTypeface;
-
-
- // ---- Public Helper methods ----
-
- public static Typeface_Delegate getDelegate(long nativeTypeface) {
- return sManager.getDelegate(nativeTypeface);
- }
-
- /**
- * Return a list of fonts that match the style and variant. The list is ordered according to
- * preference of fonts.
- *
- * The list may contain null when the font failed to load. If null is reached when trying to
- * render with this list of fonts, then a warning should be logged letting the user know that
- * some font failed to load.
- *
- * @param variant The variant preferred. Can only be {@link FontVariant#COMPACT} or
- * {@link FontVariant#ELEGANT}
- */
- @NonNull
- public List<Font> getFonts(FontVariant variant) {
- assert variant != FontVariant.NONE;
-
- // Calculate the required weight based on style and weight of this typeface.
- int weight = mWeight + ((mStyle & Font.BOLD) == 0 ? 0 : FontFamily_Delegate.BOLD_FONT_WEIGHT_DELTA);
- if (weight > 900) {
- weight = 900;
- }
- final boolean isItalic = (mStyle & Font.ITALIC) != 0;
- List<Font> fonts = new ArrayList<Font>(mFontFamilies.length);
- for (int i = 0; i < mFontFamilies.length; i++) {
- FontFamily_Delegate ffd = mFontFamilies[i];
- if (ffd != null && ffd.isValid()) {
- Font font = ffd.getFont(weight, isItalic);
- if (font != null) {
- FontVariant ffdVariant = ffd.getVariant();
- if (ffdVariant == FontVariant.NONE) {
- fonts.add(font);
- continue;
- }
- // We cannot open each font and get locales supported, etc to match the fonts.
- // As a workaround, we hardcode certain assumptions like Elegant and Compact
- // always appear in pairs.
- assert i < mFontFamilies.length - 1;
- FontFamily_Delegate ffd2 = mFontFamilies[++i];
- assert ffd2 != null;
- FontVariant ffd2Variant = ffd2.getVariant();
- Font font2 = ffd2.getFont(weight, isItalic);
- assert ffd2Variant != FontVariant.NONE && ffd2Variant != ffdVariant
- && font2 != null;
- // Add the font with the matching variant to the list.
- if (variant == ffd.getVariant()) {
- fonts.add(font);
- } else {
- fonts.add(font2);
- }
- } else {
- // The FontFamily is valid but doesn't contain any matching font. This means
- // that the font failed to load. We add null to the list of fonts. Don't throw
- // the warning just yet. If this is a non-english font, we don't want to warn
- // users who are trying to render only english text.
- fonts.add(null);
- }
- }
- }
- return fonts;
- }
-
- /**
- * Clear the default typefaces when disposing bridge.
- */
- public static void resetDefaults() {
- // Sometimes this is called before the Bridge is initialized. In that case, we don't want to
- // initialize Typeface because the SDK fonts location hasn't been set.
- if (FontFamily_Delegate.getFontLocation() != null) {
- Typeface.sDefaults = null;
- }
- }
-
-
- // ---- native methods ----
-
- @LayoutlibDelegate
- /*package*/ static synchronized long nativeCreateFromTypeface(long native_instance, int style) {
- Typeface_Delegate delegate = sManager.getDelegate(native_instance);
- if (delegate == null) {
- delegate = sManager.getDelegate(sDefaultTypeface);
- }
- if (delegate == null) {
- return 0;
- }
-
- return sManager.addNewDelegate(new Typeface_Delegate(delegate.mFontFamilies, style,
- delegate.mWeight));
- }
-
- @LayoutlibDelegate
- /*package*/ static synchronized long nativeCreateFromTypefaceWithVariation(long native_instance,
- List<FontVariationAxis> axes) {
- long newInstance = nativeCreateFromTypeface(native_instance, 0);
-
- if (newInstance != 0) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "nativeCreateFromTypefaceWithVariation is not supported", null, null);
- }
- return newInstance;
- }
-
- @LayoutlibDelegate
- /*package*/ static synchronized int[] nativeGetSupportedAxes(long native_instance) {
- // nativeCreateFromTypefaceWithVariation is not supported so we do not keep the axes
- return null;
- }
-
- @LayoutlibDelegate
- /*package*/ static long nativeCreateWeightAlias(long native_instance, int weight) {
- Typeface_Delegate delegate = sManager.getDelegate(native_instance);
- if (delegate == null) {
- delegate = sManager.getDelegate(sDefaultTypeface);
- }
- if (delegate == null) {
- return 0;
- }
- Typeface_Delegate weightAlias =
- new Typeface_Delegate(delegate.mFontFamilies, delegate.mStyle, weight);
- return sManager.addNewDelegate(weightAlias);
- }
-
- @LayoutlibDelegate
- /*package*/ static synchronized long nativeCreateFromArray(long[] familyArray) {
- FontFamily_Delegate[] fontFamilies = new FontFamily_Delegate[familyArray.length];
- for (int i = 0; i < familyArray.length; i++) {
- fontFamilies[i] = FontFamily_Delegate.getDelegate(familyArray[i]);
- }
- Typeface_Delegate delegate = new Typeface_Delegate(fontFamilies, Typeface.NORMAL);
- return sManager.addNewDelegate(delegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nativeUnref(long native_instance) {
- sManager.removeJavaReferenceFor(native_instance);
- }
-
- @LayoutlibDelegate
- /*package*/ static int nativeGetStyle(long native_instance) {
- Typeface_Delegate delegate = sManager.getDelegate(native_instance);
- if (delegate == null) {
- return 0;
- }
-
- return delegate.mStyle;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nativeSetDefault(long native_instance) {
- sDefaultTypeface = native_instance;
- }
-
- @LayoutlibDelegate
- /*package*/ static File getSystemFontConfigLocation() {
- return new File(getFontLocation());
- }
-
- @LayoutlibDelegate
- /*package*/ static FontFamily makeFamilyFromParsed(FontConfig.Family family,
- Map<String, ByteBuffer> bufferForPath) {
- FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
- for (FontConfig.Font font : family.getFonts()) {
- String fullPathName = "/system/fonts/" + font.getFontName();
- FontFamily_Delegate.addFont(fontFamily.mBuilderPtr, fullPathName,
- font.getWeight(), font.isItalic());
- }
- fontFamily.freeze();
- return fontFamily;
- }
-
- // ---- Private delegate/helper methods ----
-
- private Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style) {
- this(fontFamilies, style, FontFamily_Delegate.DEFAULT_FONT_WEIGHT);
- }
-
- public Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style, int weight) {
- mFontFamilies = fontFamilies;
- mStyle = style;
- mWeight = weight;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java
deleted file mode 100644
index ad2c564..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License") {}
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.graphics.drawable;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.internal.view.animation.NativeInterpolatorFactoryHelper_Delegate;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.drawable.AnimatedVectorDrawable.VectorDrawableAnimatorRT;
-import android.graphics.drawable.VectorDrawable_Delegate.VFullPath_Delegate;
-import android.graphics.drawable.VectorDrawable_Delegate.VGroup_Delegate;
-import android.graphics.drawable.VectorDrawable_Delegate.VNativeObject;
-import android.graphics.drawable.VectorDrawable_Delegate.VPathRenderer_Delegate;
-
-import java.util.ArrayList;
-import java.util.function.Consumer;
-
-/**
- * Delegate used to provide new implementation of a select few methods of {@link
- * AnimatedVectorDrawable}
- * <p>
- * Through the layoutlib_create tool, the original methods of AnimatedVectorDrawable have been
- * replaced by calls to methods of the same name in this delegate class.
- */
-@SuppressWarnings("unused")
-public class AnimatedVectorDrawable_Delegate {
- private static DelegateManager<AnimatorSetHolder> sAnimatorSets = new
- DelegateManager<>(AnimatorSetHolder.class);
- private static DelegateManager<PropertySetter> sHolders = new
- DelegateManager<>(PropertySetter.class);
-
-
- @LayoutlibDelegate
- /*package*/ static long nCreateAnimatorSet() {
- return sAnimatorSets.addNewDelegate(new AnimatorSetHolder());
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr) {
- // TODO: implement
- }
- @LayoutlibDelegate
- /*package*/ static void nAddAnimator(long setPtr, long propertyValuesHolder,
- long nativeInterpolator, long startDelay, long duration, int repeatCount,
- int repeatMode) {
- PropertySetter holder = sHolders.getDelegate(propertyValuesHolder);
- if (holder == null || holder.getValues() == null) {
- return;
- }
-
- ObjectAnimator animator = new ObjectAnimator();
- animator.setValues(holder.getValues());
- animator.setInterpolator(
- NativeInterpolatorFactoryHelper_Delegate.getDelegate(nativeInterpolator));
- animator.setStartDelay(startDelay);
- animator.setDuration(duration);
- animator.setRepeatCount(repeatCount);
- animator.setRepeatMode(repeatMode);
- animator.setTarget(holder);
- animator.setPropertyName(holder.getValues().getPropertyName());
-
- AnimatorSetHolder set = sAnimatorSets.getDelegate(setPtr);
- assert set != null;
- set.addAnimator(animator);
- }
-
- @LayoutlibDelegate
- /*package*/ static long nCreateGroupPropertyHolder(long nativePtr, int propertyId,
- float startValue, float endValue) {
- VGroup_Delegate group = VNativeObject.getDelegate(nativePtr);
- Consumer<Float> setter = group.getPropertySetter(propertyId);
-
- return sHolders.addNewDelegate(FloatPropertySetter.of(setter, startValue,
- endValue));
- }
-
- @LayoutlibDelegate
- /*package*/ static long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr,
- long endValuePtr) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "AnimatedVectorDrawable path " +
- "animations are not supported.", null, null);
- return 0;
- }
-
- @LayoutlibDelegate
- /*package*/ static long nCreatePathColorPropertyHolder(long nativePtr, int propertyId,
- int startValue, int endValue) {
- VFullPath_Delegate path = VNativeObject.getDelegate(nativePtr);
- Consumer<Integer> setter = path.getIntPropertySetter(propertyId);
-
- return sHolders.addNewDelegate(IntPropertySetter.of(setter, startValue,
- endValue));
- }
-
- @LayoutlibDelegate
- /*package*/ static long nCreatePathPropertyHolder(long nativePtr, int propertyId,
- float startValue, float endValue) {
- VFullPath_Delegate path = VNativeObject.getDelegate(nativePtr);
- Consumer<Float> setter = path.getFloatPropertySetter(propertyId);
-
- return sHolders.addNewDelegate(FloatPropertySetter.of(setter, startValue,
- endValue));
- }
-
- @LayoutlibDelegate
- /*package*/ static long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
- float endValue) {
- VPathRenderer_Delegate renderer = VNativeObject.getDelegate(nativePtr);
-
- return sHolders.addNewDelegate(FloatPropertySetter.of(renderer::setRootAlpha,
- startValue,
- endValue));
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetPropertyHolderData(long nativePtr, float[] data, int length) {
- PropertySetter setter = sHolders.getDelegate(nativePtr);
- assert setter != null;
-
- setter.setValues(data);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetPropertyHolderData(long nativePtr, int[] data, int length) {
- PropertySetter setter = sHolders.getDelegate(nativePtr);
- assert setter != null;
-
- setter.setValues(data);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) {
- AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr);
- assert animatorSet != null;
-
- animatorSet.start();
- }
-
- @LayoutlibDelegate
- /*package*/ static void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) {
- AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr);
- assert animatorSet != null;
-
- animatorSet.reverse();
- }
-
- @LayoutlibDelegate
- /*package*/ static void nEnd(long animatorSetPtr) {
- AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr);
- assert animatorSet != null;
-
- animatorSet.end();
- }
-
- @LayoutlibDelegate
- /*package*/ static void nReset(long animatorSetPtr) {
- AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr);
- assert animatorSet != null;
-
- animatorSet.end();
- animatorSet.start();
- }
-
- private static class AnimatorSetHolder {
- private ArrayList<Animator> mAnimators = new ArrayList<>();
- private AnimatorSet mAnimatorSet = null;
-
- private void addAnimator(@NonNull Animator animator) {
- mAnimators.add(animator);
- }
-
- private void ensureAnimatorSet() {
- if (mAnimatorSet == null) {
- mAnimatorSet = new AnimatorSet();
- mAnimatorSet.playTogether(mAnimators);
- }
- }
-
- private void start() {
- ensureAnimatorSet();
-
- mAnimatorSet.start();
- }
-
- private void end() {
- mAnimatorSet.end();
- }
-
- private void reset() {
- end();
- start();
- }
-
- private void reverse() {
- mAnimatorSet.reverse();
- }
- }
-
- /**
- * Class that allows setting a value and holds the range of values for the given property.
- *
- * @param <T> the type of the property
- */
- private static class PropertySetter<T> {
- final Consumer<T> mValueSetter;
- private PropertyValuesHolder mValues;
-
- private PropertySetter(@NonNull Consumer<T> valueSetter) {
- mValueSetter = valueSetter;
- }
-
- /**
- * Method to set an {@link Integer} value for this property. The default implementation of
- * this method doesn't do anything. This method is accessed via reflection by the
- * PropertyValuesHolder.
- */
- public void setIntValue(Integer value) {
- }
-
- /**
- * Method to set an {@link Integer} value for this property. The default implementation of
- * this method doesn't do anything. This method is accessed via reflection by the
- * PropertyValuesHolder.
- */
- public void setFloatValue(Float value) {
- }
-
- void setValues(float... values) {
- mValues = PropertyValuesHolder.ofFloat("floatValue", values);
- }
-
- @Nullable
- PropertyValuesHolder getValues() {
- return mValues;
- }
-
- void setValues(int... values) {
- mValues = PropertyValuesHolder.ofInt("intValue", values);
- }
- }
-
- private static class IntPropertySetter extends PropertySetter<Integer> {
- private IntPropertySetter(Consumer<Integer> valueSetter) {
- super(valueSetter);
- }
-
- private static PropertySetter of(Consumer<Integer> valueSetter, int... values) {
- PropertySetter setter = new IntPropertySetter(valueSetter);
- setter.setValues(values);
-
- return setter;
- }
-
- public void setIntValue(Integer value) {
- mValueSetter.accept(value);
- }
- }
-
- private static class FloatPropertySetter extends PropertySetter<Float> {
- private FloatPropertySetter(Consumer<Float> valueSetter) {
- super(valueSetter);
- }
-
- private static PropertySetter of(Consumer<Float> valueSetter, float... values) {
- PropertySetter setter = new FloatPropertySetter(valueSetter);
- setter.setValues(values);
-
- return setter;
- }
-
- public void setFloatValue(Float value) {
- mValueSetter.accept(value);
- }
-
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java
deleted file mode 100644
index fc848d9..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.graphics.drawable;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.graphics.Canvas;
-import android.graphics.drawable.AnimatedVectorDrawable.VectorDrawableAnimatorRT;
-
-public class AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate {
- @LayoutlibDelegate
- /*package*/ static boolean useLastSeenTarget(VectorDrawableAnimatorRT thisDrawableAnimator) {
- return true;
- }
-
- @LayoutlibDelegate
- /*package*/ static void onDraw(VectorDrawableAnimatorRT thisDrawableAnimator, Canvas canvas) {
- // Do not attempt to record as we are not using a DisplayListCanvas
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/GradientDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/GradientDrawable_Delegate.java
deleted file mode 100644
index a3ad2aac..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/drawable/GradientDrawable_Delegate.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2015 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.graphics.drawable;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.graphics.Path;
-import android.graphics.drawable.GradientDrawable.GradientState;
-
-import java.lang.reflect.Field;
-
-/**
- * Delegate implementing the native methods of {@link GradientDrawable}
- *
- * Through the layoutlib_create tool, the original native methods of GradientDrawable have been
- * replaced by calls to methods of the same name in this delegate class.
- */
-public class GradientDrawable_Delegate {
-
- /**
- * The ring can be built either by drawing full circles, or by drawing arcs in case the
- * circle isn't complete. LayoutLib cannot handle drawing full circles (requires path
- * subtraction). So, if we need to draw full circles, we switch to drawing 99% circle.
- */
- @LayoutlibDelegate
- /*package*/ static Path buildRing(GradientDrawable thisDrawable, GradientState st) {
- boolean useLevel = st.mUseLevelForShape;
- int level = thisDrawable.getLevel();
- // 10000 is the max level. See android.graphics.drawable.Drawable#getLevel()
- float sweep = useLevel ? (360.0f * level / 10000.0f) : 360f;
- Field mLevel = null;
- if (sweep >= 360 || sweep <= -360) {
- st.mUseLevelForShape = true;
- // Use reflection to set the value of the field to prevent setting the drawable to
- // dirty again.
- try {
- mLevel = Drawable.class.getDeclaredField("mLevel");
- mLevel.setAccessible(true);
- mLevel.setInt(thisDrawable, 9999); // set to one less than max.
- } catch (NoSuchFieldException e) {
- // The field has been removed in a recent framework change. Fall back to old
- // buggy behaviour.
- } catch (IllegalAccessException e) {
- // We've already set the field to be accessible.
- assert false;
- }
- }
- Path path = thisDrawable.buildRing_Original(st);
- st.mUseLevelForShape = useLevel;
- if (mLevel != null) {
- try {
- mLevel.setInt(thisDrawable, level);
- } catch (IllegalAccessException e) {
- assert false;
- }
- }
- return path;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
deleted file mode 100644
index 616784c..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
+++ /dev/null
@@ -1,1262 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.graphics.drawable;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.annotation.NonNull;
-import android.content.res.Resources;
-import android.content.res.Resources.Theme;
-import android.content.res.TypedArray;
-import android.graphics.BaseCanvas_Delegate;
-import android.graphics.Canvas_Delegate;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Paint.Cap;
-import android.graphics.Paint.Join;
-import android.graphics.Paint_Delegate;
-import android.graphics.Path;
-import android.graphics.PathMeasure;
-import android.graphics.Path_Delegate;
-import android.graphics.Rect;
-import android.graphics.Region.Op;
-import android.util.ArrayMap;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.MathUtils;
-import android.util.PathParser_Delegate;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-import java.util.ArrayList;
-import java.util.function.Consumer;
-
-import static android.graphics.Canvas.CLIP_SAVE_FLAG;
-import static android.graphics.Canvas.MATRIX_SAVE_FLAG;
-import static android.graphics.Paint.Cap.BUTT;
-import static android.graphics.Paint.Cap.ROUND;
-import static android.graphics.Paint.Cap.SQUARE;
-import static android.graphics.Paint.Join.BEVEL;
-import static android.graphics.Paint.Join.MITER;
-import static android.graphics.Paint.Style;
-
-/**
- * Delegate used to provide new implementation of a select few methods of {@link VectorDrawable}
- * <p>
- * Through the layoutlib_create tool, the original methods of VectorDrawable have been replaced by
- * calls to methods of the same name in this delegate class.
- */
-@SuppressWarnings("unused")
-public class VectorDrawable_Delegate {
- private static final String LOGTAG = VectorDrawable_Delegate.class.getSimpleName();
- private static final boolean DBG_VECTOR_DRAWABLE = false;
-
- private static final DelegateManager<VNativeObject> sPathManager =
- new DelegateManager<>(VNativeObject.class);
-
- private static long addNativeObject(VNativeObject object) {
- long ptr = sPathManager.addNewDelegate(object);
- object.setNativePtr(ptr);
-
- return ptr;
- }
-
- /**
- * Obtains styled attributes from the theme, if available, or unstyled resources if the theme is
- * null.
- */
- private static TypedArray obtainAttributes(
- Resources res, Theme theme, AttributeSet set, int[] attrs) {
- if (theme == null) {
- return res.obtainAttributes(set, attrs);
- }
- return theme.obtainStyledAttributes(set, attrs, 0, 0);
- }
-
- private static int applyAlpha(int color, float alpha) {
- int alphaBytes = Color.alpha(color);
- color &= 0x00FFFFFF;
- color |= ((int) (alphaBytes * alpha)) << 24;
- return color;
- }
-
- @LayoutlibDelegate
- static long nCreateTree(long rootGroupPtr) {
- return addNativeObject(new VPathRenderer_Delegate(rootGroupPtr));
- }
-
- @LayoutlibDelegate
- static long nCreateTreeFromCopy(long rendererToCopyPtr, long rootGroupPtr) {
- VPathRenderer_Delegate rendererToCopy = VNativeObject.getDelegate(rendererToCopyPtr);
- return addNativeObject(new VPathRenderer_Delegate(rendererToCopy,
- rootGroupPtr));
- }
-
- @LayoutlibDelegate
- static void nSetRendererViewportSize(long rendererPtr, float viewportWidth,
- float viewportHeight) {
- VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
- nativePathRenderer.mViewportWidth = viewportWidth;
- nativePathRenderer.mViewportHeight = viewportHeight;
- }
-
- @LayoutlibDelegate
- static boolean nSetRootAlpha(long rendererPtr, float alpha) {
- VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
- nativePathRenderer.setRootAlpha(alpha);
-
- return true;
- }
-
- @LayoutlibDelegate
- static float nGetRootAlpha(long rendererPtr) {
- VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
-
- return nativePathRenderer.getRootAlpha();
- }
-
- @LayoutlibDelegate
- static void nSetAllowCaching(long rendererPtr, boolean allowCaching) {
- // ignored
- }
-
- @LayoutlibDelegate
- static int nDraw(long rendererPtr, long canvasWrapperPtr,
- long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache) {
- VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
-
- Canvas_Delegate.nSave(canvasWrapperPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
- Canvas_Delegate.nTranslate(canvasWrapperPtr, bounds.left, bounds.top);
-
- if (needsMirroring) {
- Canvas_Delegate.nTranslate(canvasWrapperPtr, bounds.width(), 0);
- Canvas_Delegate.nScale(canvasWrapperPtr, -1.0f, 1.0f);
- }
-
- // At this point, canvas has been translated to the right position.
- // And we use this bound for the destination rect for the drawBitmap, so
- // we offset to (0, 0);
- bounds.offsetTo(0, 0);
- nativePathRenderer.draw(canvasWrapperPtr, colorFilterPtr, bounds.width(), bounds.height());
-
- Canvas_Delegate.nRestore(canvasWrapperPtr);
-
- return bounds.width() * bounds.height();
- }
-
- @LayoutlibDelegate
- static long nCreateFullPath() {
- return addNativeObject(new VFullPath_Delegate());
- }
-
- @LayoutlibDelegate
- static long nCreateFullPath(long nativeFullPathPtr) {
- VFullPath_Delegate original = VNativeObject.getDelegate(nativeFullPathPtr);
- return addNativeObject(new VFullPath_Delegate(original));
- }
-
- @LayoutlibDelegate
- static boolean nGetFullPathProperties(long pathPtr, byte[] propertiesData,
- int length) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
-
- ByteBuffer properties = ByteBuffer.wrap(propertiesData);
- properties.order(ByteOrder.nativeOrder());
-
- properties.putFloat(VFullPath_Delegate.STROKE_WIDTH_INDEX * 4, path.getStrokeWidth());
- properties.putInt(VFullPath_Delegate.STROKE_COLOR_INDEX * 4, path.getStrokeColor());
- properties.putFloat(VFullPath_Delegate.STROKE_ALPHA_INDEX * 4, path.getStrokeAlpha());
- properties.putInt(VFullPath_Delegate.FILL_COLOR_INDEX * 4, path.getFillColor());
- properties.putFloat(VFullPath_Delegate.FILL_ALPHA_INDEX * 4, path.getStrokeAlpha());
- properties.putFloat(VFullPath_Delegate.TRIM_PATH_START_INDEX * 4, path.getTrimPathStart());
- properties.putFloat(VFullPath_Delegate.TRIM_PATH_END_INDEX * 4, path.getTrimPathEnd());
- properties.putFloat(VFullPath_Delegate.TRIM_PATH_OFFSET_INDEX * 4,
- path.getTrimPathOffset());
- properties.putInt(VFullPath_Delegate.STROKE_LINE_CAP_INDEX * 4, path.getStrokeLineCap());
- properties.putInt(VFullPath_Delegate.STROKE_LINE_JOIN_INDEX * 4, path.getStrokeLineJoin());
- properties.putFloat(VFullPath_Delegate.STROKE_MITER_LIMIT_INDEX * 4,
- path.getStrokeMiterlimit());
- properties.putInt(VFullPath_Delegate.FILL_TYPE_INDEX * 4, path.getFillType());
-
- return true;
- }
-
- @LayoutlibDelegate
- static void nUpdateFullPathProperties(long pathPtr, float strokeWidth,
- int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart,
- float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap,
- int strokeLineJoin, int fillType) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
-
- path.setStrokeWidth(strokeWidth);
- path.setStrokeColor(strokeColor);
- path.setStrokeAlpha(strokeAlpha);
- path.setFillColor(fillColor);
- path.setFillAlpha(fillAlpha);
- path.setTrimPathStart(trimPathStart);
- path.setTrimPathEnd(trimPathEnd);
- path.setTrimPathOffset(trimPathOffset);
- path.setStrokeMiterlimit(strokeMiterLimit);
- path.setStrokeLineCap(strokeLineCap);
- path.setStrokeLineJoin(strokeLineJoin);
- path.setFillType(fillType);
- }
-
- @LayoutlibDelegate
- static void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
-
- path.setFillGradient(fillGradientPtr);
- }
-
- @LayoutlibDelegate
- static void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
-
- path.setStrokeGradient(strokeGradientPtr);
- }
-
- @LayoutlibDelegate
- static long nCreateClipPath() {
- return addNativeObject(new VClipPath_Delegate());
- }
-
- @LayoutlibDelegate
- static long nCreateClipPath(long clipPathPtr) {
- VClipPath_Delegate original = VNativeObject.getDelegate(clipPathPtr);
- return addNativeObject(new VClipPath_Delegate(original));
- }
-
- @LayoutlibDelegate
- static long nCreateGroup() {
- return addNativeObject(new VGroup_Delegate());
- }
-
- @LayoutlibDelegate
- static long nCreateGroup(long groupPtr) {
- VGroup_Delegate original = VNativeObject.getDelegate(groupPtr);
- return addNativeObject(new VGroup_Delegate(original, new ArrayMap<>()));
- }
-
- @LayoutlibDelegate
- static void nSetName(long nodePtr, String name) {
- VNativeObject group = VNativeObject.getDelegate(nodePtr);
- group.setName(name);
- }
-
- @LayoutlibDelegate
- static boolean nGetGroupProperties(long groupPtr, float[] propertiesData,
- int length) {
- VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
-
- FloatBuffer properties = FloatBuffer.wrap(propertiesData);
-
- properties.put(VGroup_Delegate.ROTATE_INDEX, group.getRotation());
- properties.put(VGroup_Delegate.PIVOT_X_INDEX, group.getPivotX());
- properties.put(VGroup_Delegate.PIVOT_Y_INDEX, group.getPivotY());
- properties.put(VGroup_Delegate.SCALE_X_INDEX, group.getScaleX());
- properties.put(VGroup_Delegate.SCALE_Y_INDEX, group.getScaleY());
- properties.put(VGroup_Delegate.TRANSLATE_X_INDEX, group.getTranslateX());
- properties.put(VGroup_Delegate.TRANSLATE_Y_INDEX, group.getTranslateY());
-
- return true;
- }
- @LayoutlibDelegate
- static void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX,
- float pivotY, float scaleX, float scaleY, float translateX, float translateY) {
- VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
-
- group.setRotation(rotate);
- group.setPivotX(pivotX);
- group.setPivotY(pivotY);
- group.setScaleX(scaleX);
- group.setScaleY(scaleY);
- group.setTranslateX(translateX);
- group.setTranslateY(translateY);
- }
-
- @LayoutlibDelegate
- static void nAddChild(long groupPtr, long nodePtr) {
- VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
- group.mChildren.add(VNativeObject.getDelegate(nodePtr));
- }
-
- @LayoutlibDelegate
- static void nSetPathString(long pathPtr, String pathString, int length) {
- VPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- path.setPathData(PathParser_Delegate.createNodesFromPathData(pathString));
- }
-
- /**
- * The setters and getters below for paths and groups are here temporarily, and will be removed
- * once the animation in AVD is replaced with RenderNodeAnimator, in which case the animation
- * will modify these properties in native. By then no JNI hopping would be necessary for VD
- * during animation, and these setters and getters will be obsolete.
- */
- // Setters and getters during animation.
- @LayoutlibDelegate
- static float nGetRotation(long groupPtr) {
- VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
- return group.getRotation();
- }
-
- @LayoutlibDelegate
- static void nSetRotation(long groupPtr, float rotation) {
- VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
- group.setRotation(rotation);
- }
-
- @LayoutlibDelegate
- static float nGetPivotX(long groupPtr) {
- VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
- return group.getPivotX();
- }
-
- @LayoutlibDelegate
- static void nSetPivotX(long groupPtr, float pivotX) {
- VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
- group.setPivotX(pivotX);
- }
-
- @LayoutlibDelegate
- static float nGetPivotY(long groupPtr) {
- VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
- return group.getPivotY();
- }
-
- @LayoutlibDelegate
- static void nSetPivotY(long groupPtr, float pivotY) {
- VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
- group.setPivotY(pivotY);
- }
-
- @LayoutlibDelegate
- static float nGetScaleX(long groupPtr) {
- VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
- return group.getScaleX();
- }
-
- @LayoutlibDelegate
- static void nSetScaleX(long groupPtr, float scaleX) {
- VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
- group.setScaleX(scaleX);
- }
-
- @LayoutlibDelegate
- static float nGetScaleY(long groupPtr) {
- VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
- return group.getScaleY();
- }
-
- @LayoutlibDelegate
- static void nSetScaleY(long groupPtr, float scaleY) {
- VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
- group.setScaleY(scaleY);
- }
-
- @LayoutlibDelegate
- static float nGetTranslateX(long groupPtr) {
- VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
- return group.getTranslateX();
- }
-
- @LayoutlibDelegate
- static void nSetTranslateX(long groupPtr, float translateX) {
- VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
- group.setTranslateX(translateX);
- }
-
- @LayoutlibDelegate
- static float nGetTranslateY(long groupPtr) {
- VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
- return group.getTranslateY();
- }
-
- @LayoutlibDelegate
- static void nSetTranslateY(long groupPtr, float translateY) {
- VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
- group.setTranslateY(translateY);
- }
-
- @LayoutlibDelegate
- static void nSetPathData(long pathPtr, long pathDataPtr) {
- VPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- path.setPathData(PathParser_Delegate.getDelegate(pathDataPtr).getPathDataNodes());
- }
-
- @LayoutlibDelegate
- static float nGetStrokeWidth(long pathPtr) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- return path.getStrokeWidth();
- }
-
- @LayoutlibDelegate
- static void nSetStrokeWidth(long pathPtr, float width) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- path.setStrokeWidth(width);
- }
-
- @LayoutlibDelegate
- static int nGetStrokeColor(long pathPtr) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- return path.getStrokeColor();
- }
-
- @LayoutlibDelegate
- static void nSetStrokeColor(long pathPtr, int strokeColor) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- path.setStrokeColor(strokeColor);
- }
-
- @LayoutlibDelegate
- static float nGetStrokeAlpha(long pathPtr) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- return path.getStrokeAlpha();
- }
-
- @LayoutlibDelegate
- static void nSetStrokeAlpha(long pathPtr, float alpha) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- path.setStrokeAlpha(alpha);
- }
-
- @LayoutlibDelegate
- static int nGetFillColor(long pathPtr) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- return path.getFillColor();
- }
-
- @LayoutlibDelegate
- static void nSetFillColor(long pathPtr, int fillColor) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- path.setFillColor(fillColor);
- }
-
- @LayoutlibDelegate
- static float nGetFillAlpha(long pathPtr) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- return path.getFillAlpha();
- }
-
- @LayoutlibDelegate
- static void nSetFillAlpha(long pathPtr, float fillAlpha) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- path.setFillAlpha(fillAlpha);
- }
-
- @LayoutlibDelegate
- static float nGetTrimPathStart(long pathPtr) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- return path.getTrimPathStart();
- }
-
- @LayoutlibDelegate
- static void nSetTrimPathStart(long pathPtr, float trimPathStart) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- path.setTrimPathStart(trimPathStart);
- }
-
- @LayoutlibDelegate
- static float nGetTrimPathEnd(long pathPtr) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- return path.getTrimPathEnd();
- }
-
- @LayoutlibDelegate
- static void nSetTrimPathEnd(long pathPtr, float trimPathEnd) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- path.setTrimPathEnd(trimPathEnd);
- }
-
- @LayoutlibDelegate
- static float nGetTrimPathOffset(long pathPtr) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- return path.getTrimPathOffset();
- }
-
- @LayoutlibDelegate
- static void nSetTrimPathOffset(long pathPtr, float trimPathOffset) {
- VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
- path.setTrimPathOffset(trimPathOffset);
- }
-
- /**
- * Base class for all the internal Delegates that does two functions:
- * <ol>
- * <li>Serves as base class to store all the delegates in one {@link DelegateManager}
- * <li>Provides setName for all the classes. {@link VPathRenderer_Delegate} does actually
- * not need it
- * </ol>
- */
- abstract static class VNativeObject {
- long mNativePtr = 0;
-
- @NonNull
- static <T> T getDelegate(long nativePtr) {
- //noinspection unchecked
- T vNativeObject = (T) sPathManager.getDelegate(nativePtr);
-
- assert vNativeObject != null;
- return vNativeObject;
- }
-
- abstract void setName(String name);
-
- void setNativePtr(long nativePtr) {
- mNativePtr = nativePtr;
- }
-
- /**
- * Method to explicitly dispose native objects
- */
- void dispose() {
- }
- }
-
- private static class VClipPath_Delegate extends VPath_Delegate {
- private VClipPath_Delegate() {
- // Empty constructor.
- }
-
- private VClipPath_Delegate(VClipPath_Delegate copy) {
- super(copy);
- }
-
- @Override
- public boolean isClipPath() {
- return true;
- }
- }
-
- static class VFullPath_Delegate extends VPath_Delegate {
- // These constants need to be kept in sync with their values in VectorDrawable.VFullPath
- private static final int STROKE_WIDTH_INDEX = 0;
- private static final int STROKE_COLOR_INDEX = 1;
- private static final int STROKE_ALPHA_INDEX = 2;
- private static final int FILL_COLOR_INDEX = 3;
- private static final int FILL_ALPHA_INDEX = 4;
- private static final int TRIM_PATH_START_INDEX = 5;
- private static final int TRIM_PATH_END_INDEX = 6;
- private static final int TRIM_PATH_OFFSET_INDEX = 7;
- private static final int STROKE_LINE_CAP_INDEX = 8;
- private static final int STROKE_LINE_JOIN_INDEX = 9;
- private static final int STROKE_MITER_LIMIT_INDEX = 10;
- private static final int FILL_TYPE_INDEX = 11;
-
- private static final int LINECAP_BUTT = 0;
- private static final int LINECAP_ROUND = 1;
- private static final int LINECAP_SQUARE = 2;
-
- private static final int LINEJOIN_MITER = 0;
- private static final int LINEJOIN_ROUND = 1;
- private static final int LINEJOIN_BEVEL = 2;
-
- @NonNull
- public Consumer<Float> getFloatPropertySetter(int propertyIdx) {
- switch (propertyIdx) {
- case STROKE_WIDTH_INDEX:
- return this::setStrokeWidth;
- case STROKE_ALPHA_INDEX:
- return this::setStrokeAlpha;
- case FILL_ALPHA_INDEX:
- return this::setFillAlpha;
- case TRIM_PATH_START_INDEX:
- return this::setTrimPathStart;
- case TRIM_PATH_END_INDEX:
- return this::setTrimPathEnd;
- case TRIM_PATH_OFFSET_INDEX:
- return this::setTrimPathOffset;
- }
-
- assert false : ("Invalid VFullPath_Delegate property index " + propertyIdx);
- return t -> {};
- }
-
- @NonNull
- public Consumer<Integer> getIntPropertySetter(int propertyIdx) {
- switch (propertyIdx) {
- case STROKE_COLOR_INDEX:
- return this::setStrokeColor;
- case FILL_COLOR_INDEX:
- return this::setFillColor;
- }
-
- assert false : ("Invalid VFullPath_Delegate property index " + propertyIdx);
- return t -> {};
- }
-
- /////////////////////////////////////////////////////
- // Variables below need to be copied (deep copy if applicable) for mutation.
-
- int mStrokeColor = Color.TRANSPARENT;
- float mStrokeWidth = 0;
-
- int mFillColor = Color.TRANSPARENT;
- long mStrokeGradient = 0;
- long mFillGradient = 0;
- float mStrokeAlpha = 1.0f;
- float mFillAlpha = 1.0f;
- float mTrimPathStart = 0;
- float mTrimPathEnd = 1;
- float mTrimPathOffset = 0;
-
- Cap mStrokeLineCap = BUTT;
- Join mStrokeLineJoin = MITER;
- float mStrokeMiterlimit = 4;
-
- int mFillType = 0; // WINDING(0) is the default value. See Path.FillType
-
- private VFullPath_Delegate() {
- // Empty constructor.
- }
-
- private VFullPath_Delegate(VFullPath_Delegate copy) {
- super(copy);
-
- mStrokeColor = copy.mStrokeColor;
- mStrokeWidth = copy.mStrokeWidth;
- mStrokeAlpha = copy.mStrokeAlpha;
- mFillColor = copy.mFillColor;
- mFillAlpha = copy.mFillAlpha;
- mTrimPathStart = copy.mTrimPathStart;
- mTrimPathEnd = copy.mTrimPathEnd;
- mTrimPathOffset = copy.mTrimPathOffset;
-
- mStrokeLineCap = copy.mStrokeLineCap;
- mStrokeLineJoin = copy.mStrokeLineJoin;
- mStrokeMiterlimit = copy.mStrokeMiterlimit;
-
- mStrokeGradient = copy.mStrokeGradient;
- mFillGradient = copy.mFillGradient;
- mFillType = copy.mFillType;
- }
-
- private int getStrokeLineCap() {
- switch (mStrokeLineCap) {
- case BUTT:
- return LINECAP_BUTT;
- case ROUND:
- return LINECAP_ROUND;
- case SQUARE:
- return LINECAP_SQUARE;
- default:
- assert false;
- }
-
- return -1;
- }
-
- private void setStrokeLineCap(int cap) {
- switch (cap) {
- case LINECAP_BUTT:
- mStrokeLineCap = BUTT;
- break;
- case LINECAP_ROUND:
- mStrokeLineCap = ROUND;
- break;
- case LINECAP_SQUARE:
- mStrokeLineCap = SQUARE;
- break;
- default:
- assert false;
- }
- }
-
- private int getStrokeLineJoin() {
- switch (mStrokeLineJoin) {
- case MITER:
- return LINEJOIN_MITER;
- case ROUND:
- return LINEJOIN_ROUND;
- case BEVEL:
- return LINEJOIN_BEVEL;
- default:
- assert false;
- }
-
- return -1;
- }
-
- private void setStrokeLineJoin(int join) {
- switch (join) {
- case LINEJOIN_BEVEL:
- mStrokeLineJoin = BEVEL;
- break;
- case LINEJOIN_MITER:
- mStrokeLineJoin = MITER;
- break;
- case LINEJOIN_ROUND:
- mStrokeLineJoin = Join.ROUND;
- break;
- default:
- assert false;
- }
- }
-
- private int getStrokeColor() {
- return mStrokeColor;
- }
-
- private void setStrokeColor(int strokeColor) {
- mStrokeColor = strokeColor;
- }
-
- private float getStrokeWidth() {
- return mStrokeWidth;
- }
-
- private void setStrokeWidth(float strokeWidth) {
- mStrokeWidth = strokeWidth;
- }
-
- private float getStrokeAlpha() {
- return mStrokeAlpha;
- }
-
- private void setStrokeAlpha(float strokeAlpha) {
- mStrokeAlpha = strokeAlpha;
- }
-
- private int getFillColor() {
- return mFillColor;
- }
-
- private void setFillColor(int fillColor) {
- mFillColor = fillColor;
- }
-
- private float getFillAlpha() {
- return mFillAlpha;
- }
-
- private void setFillAlpha(float fillAlpha) {
- mFillAlpha = fillAlpha;
- }
-
- private float getTrimPathStart() {
- return mTrimPathStart;
- }
-
- private void setTrimPathStart(float trimPathStart) {
- mTrimPathStart = trimPathStart;
- }
-
- private float getTrimPathEnd() {
- return mTrimPathEnd;
- }
-
- private void setTrimPathEnd(float trimPathEnd) {
- mTrimPathEnd = trimPathEnd;
- }
-
- private float getTrimPathOffset() {
- return mTrimPathOffset;
- }
-
- private void setTrimPathOffset(float trimPathOffset) {
- mTrimPathOffset = trimPathOffset;
- }
-
- private void setStrokeMiterlimit(float limit) {
- mStrokeMiterlimit = limit;
- }
-
- private float getStrokeMiterlimit() {
- return mStrokeMiterlimit;
- }
-
- private void setStrokeGradient(long gradientPtr) {
- mStrokeGradient = gradientPtr;
- }
-
- private void setFillGradient(long gradientPtr) {
- mFillGradient = gradientPtr;
- }
-
- private void setFillType(int fillType) {
- mFillType = fillType;
- }
-
- private int getFillType() {
- return mFillType;
- }
- }
-
- static class VGroup_Delegate extends VNativeObject {
- // This constants need to be kept in sync with their definitions in VectorDrawable.Group
- private static final int ROTATE_INDEX = 0;
- private static final int PIVOT_X_INDEX = 1;
- private static final int PIVOT_Y_INDEX = 2;
- private static final int SCALE_X_INDEX = 3;
- private static final int SCALE_Y_INDEX = 4;
- private static final int TRANSLATE_X_INDEX = 5;
- private static final int TRANSLATE_Y_INDEX = 6;
-
- public Consumer<Float> getPropertySetter(int propertyIdx) {
- switch (propertyIdx) {
- case ROTATE_INDEX:
- return this::setRotation;
- case PIVOT_X_INDEX:
- return this::setPivotX;
- case PIVOT_Y_INDEX:
- return this::setPivotY;
- case SCALE_X_INDEX:
- return this::setScaleX;
- case SCALE_Y_INDEX:
- return this::setScaleY;
- case TRANSLATE_X_INDEX:
- return this::setTranslateX;
- case TRANSLATE_Y_INDEX:
- return this::setTranslateY;
- }
-
- assert false : ("Invalid VGroup_Delegate property index " + propertyIdx);
- return t -> {};
- }
-
- /////////////////////////////////////////////////////
- // Variables below need to be copied (deep copy if applicable) for mutation.
- final ArrayList<Object> mChildren = new ArrayList<>();
- // mStackedMatrix is only used temporarily when drawing, it combines all
- // the parents' local matrices with the current one.
- private final Matrix mStackedMatrix = new Matrix();
- // mLocalMatrix is updated based on the update of transformation information,
- // either parsed from the XML or by animation.
- private final Matrix mLocalMatrix = new Matrix();
- private float mRotate = 0;
- private float mPivotX = 0;
- private float mPivotY = 0;
- private float mScaleX = 1;
- private float mScaleY = 1;
- private float mTranslateX = 0;
- private float mTranslateY = 0;
- private int mChangingConfigurations;
- private String mGroupName = null;
-
- private VGroup_Delegate(VGroup_Delegate copy, ArrayMap<String, Object> targetsMap) {
- mRotate = copy.mRotate;
- mPivotX = copy.mPivotX;
- mPivotY = copy.mPivotY;
- mScaleX = copy.mScaleX;
- mScaleY = copy.mScaleY;
- mTranslateX = copy.mTranslateX;
- mTranslateY = copy.mTranslateY;
- mGroupName = copy.mGroupName;
- mChangingConfigurations = copy.mChangingConfigurations;
- if (mGroupName != null) {
- targetsMap.put(mGroupName, this);
- }
-
- mLocalMatrix.set(copy.mLocalMatrix);
- }
-
- private VGroup_Delegate() {
- }
-
- private void updateLocalMatrix() {
- // The order we apply is the same as the
- // RenderNode.cpp::applyViewPropertyTransforms().
- mLocalMatrix.reset();
- mLocalMatrix.postTranslate(-mPivotX, -mPivotY);
- mLocalMatrix.postScale(mScaleX, mScaleY);
- mLocalMatrix.postRotate(mRotate, 0, 0);
- mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
- }
-
- /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
- private float getRotation() {
- return mRotate;
- }
-
- private void setRotation(float rotation) {
- if (rotation != mRotate) {
- mRotate = rotation;
- updateLocalMatrix();
- }
- }
-
- private float getPivotX() {
- return mPivotX;
- }
-
- private void setPivotX(float pivotX) {
- if (pivotX != mPivotX) {
- mPivotX = pivotX;
- updateLocalMatrix();
- }
- }
-
- private float getPivotY() {
- return mPivotY;
- }
-
- private void setPivotY(float pivotY) {
- if (pivotY != mPivotY) {
- mPivotY = pivotY;
- updateLocalMatrix();
- }
- }
-
- private float getScaleX() {
- return mScaleX;
- }
-
- private void setScaleX(float scaleX) {
- if (scaleX != mScaleX) {
- mScaleX = scaleX;
- updateLocalMatrix();
- }
- }
-
- private float getScaleY() {
- return mScaleY;
- }
-
- private void setScaleY(float scaleY) {
- if (scaleY != mScaleY) {
- mScaleY = scaleY;
- updateLocalMatrix();
- }
- }
-
- private float getTranslateX() {
- return mTranslateX;
- }
-
- private void setTranslateX(float translateX) {
- if (translateX != mTranslateX) {
- mTranslateX = translateX;
- updateLocalMatrix();
- }
- }
-
- private float getTranslateY() {
- return mTranslateY;
- }
-
- private void setTranslateY(float translateY) {
- if (translateY != mTranslateY) {
- mTranslateY = translateY;
- updateLocalMatrix();
- }
- }
-
- @Override
- public void setName(String name) {
- mGroupName = name;
- }
-
- @Override
- protected void dispose() {
- mChildren.stream().filter(child -> child instanceof VNativeObject).forEach(child
- -> {
- VNativeObject nativeObject = (VNativeObject) child;
- if (nativeObject.mNativePtr != 0) {
- sPathManager.removeJavaReferenceFor(nativeObject.mNativePtr);
- nativeObject.mNativePtr = 0;
- }
- nativeObject.dispose();
- });
- mChildren.clear();
- }
-
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
- }
- }
-
- public static class VPath_Delegate extends VNativeObject {
- protected PathParser_Delegate.PathDataNode[] mNodes = null;
- String mPathName;
- int mChangingConfigurations;
-
- public VPath_Delegate() {
- // Empty constructor.
- }
-
- public VPath_Delegate(VPath_Delegate copy) {
- mPathName = copy.mPathName;
- mChangingConfigurations = copy.mChangingConfigurations;
- mNodes = copy.mNodes != null ? PathParser_Delegate.deepCopyNodes(copy.mNodes) : null;
- }
-
- public void toPath(Path path) {
- path.reset();
- if (mNodes != null) {
- PathParser_Delegate.PathDataNode.nodesToPath(mNodes,
- Path_Delegate.getDelegate(path.mNativePath));
- }
- }
-
- @Override
- public void setName(String name) {
- mPathName = name;
- }
-
- public boolean isClipPath() {
- return false;
- }
-
- private void setPathData(PathParser_Delegate.PathDataNode[] nodes) {
- if (!PathParser_Delegate.canMorph(mNodes, nodes)) {
- // This should not happen in the middle of animation.
- mNodes = PathParser_Delegate.deepCopyNodes(nodes);
- } else {
- PathParser_Delegate.updateNodes(mNodes, nodes);
- }
- }
-
- @Override
- void dispose() {
- mNodes = null;
- }
- }
-
- static class VPathRenderer_Delegate extends VNativeObject {
- /* Right now the internal data structure is organized as a tree.
- * Each node can be a group node, or a path.
- * A group node can have groups or paths as children, but a path node has
- * no children.
- * One example can be:
- * Root Group
- * / | \
- * Group Path Group
- * / \ |
- * Path Path Path
- *
- */
- // Variables that only used temporarily inside the draw() call, so there
- // is no need for deep copying.
- private final Path mPath;
- private final Path mRenderPath;
- private final Matrix mFinalPathMatrix = new Matrix();
- private final long mRootGroupPtr;
- private float mViewportWidth = 0;
- private float mViewportHeight = 0;
- private float mRootAlpha = 1.0f;
- private Paint mStrokePaint;
- private Paint mFillPaint;
- private PathMeasure mPathMeasure;
-
- private VPathRenderer_Delegate(long rootGroupPtr) {
- mRootGroupPtr = rootGroupPtr;
- mPath = new Path();
- mRenderPath = new Path();
- }
-
- private VPathRenderer_Delegate(VPathRenderer_Delegate rendererToCopy,
- long rootGroupPtr) {
- this(rootGroupPtr);
- mViewportWidth = rendererToCopy.mViewportWidth;
- mViewportHeight = rendererToCopy.mViewportHeight;
- mRootAlpha = rendererToCopy.mRootAlpha;
- }
-
- private float getRootAlpha() {
- return mRootAlpha;
- }
-
- void setRootAlpha(float alpha) {
- mRootAlpha = alpha;
- }
-
- private void drawGroupTree(VGroup_Delegate currentGroup, Matrix currentMatrix,
- long canvasPtr, int w, int h, long filterPtr) {
- // Calculate current group's matrix by preConcat the parent's and
- // and the current one on the top of the stack.
- // Basically the Mfinal = Mviewport * M0 * M1 * M2;
- // Mi the local matrix at level i of the group tree.
- currentGroup.mStackedMatrix.set(currentMatrix);
- currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
-
- // Save the current clip information, which is local to this group.
- Canvas_Delegate.nSave(canvasPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
- // Draw the group tree in the same order as the XML file.
- for (int i = 0; i < currentGroup.mChildren.size(); i++) {
- Object child = currentGroup.mChildren.get(i);
- if (child instanceof VGroup_Delegate) {
- VGroup_Delegate childGroup = (VGroup_Delegate) child;
- drawGroupTree(childGroup, currentGroup.mStackedMatrix,
- canvasPtr, w, h, filterPtr);
- } else if (child instanceof VPath_Delegate) {
- VPath_Delegate childPath = (VPath_Delegate) child;
- drawPath(currentGroup, childPath, canvasPtr, w, h, filterPtr);
- }
- }
- Canvas_Delegate.nRestore(canvasPtr);
- }
-
- public void draw(long canvasPtr, long filterPtr, int w, int h) {
- // Traverse the tree in pre-order to draw.
- drawGroupTree(VNativeObject.getDelegate(mRootGroupPtr), Matrix.IDENTITY_MATRIX, canvasPtr, w, h, filterPtr);
- }
-
- private void drawPath(VGroup_Delegate VGroup, VPath_Delegate VPath, long canvasPtr,
- int w,
- int h,
- long filterPtr) {
- final float scaleX = w / mViewportWidth;
- final float scaleY = h / mViewportHeight;
- final float minScale = Math.min(scaleX, scaleY);
- final Matrix groupStackedMatrix = VGroup.mStackedMatrix;
-
- mFinalPathMatrix.set(groupStackedMatrix);
- mFinalPathMatrix.postScale(scaleX, scaleY);
-
- final float matrixScale = getMatrixScale(groupStackedMatrix);
- if (matrixScale == 0) {
- // When either x or y is scaled to 0, we don't need to draw anything.
- return;
- }
- VPath.toPath(mPath);
- final Path path = mPath;
-
- mRenderPath.reset();
-
- if (VPath.isClipPath()) {
- mRenderPath.addPath(path, mFinalPathMatrix);
- Canvas_Delegate.nClipPath(canvasPtr, mRenderPath.mNativePath, Op
- .INTERSECT.nativeInt);
- } else {
- VFullPath_Delegate fullPath = (VFullPath_Delegate) VPath;
- if (fullPath.mTrimPathStart != 0.0f || fullPath.mTrimPathEnd != 1.0f) {
- float start = (fullPath.mTrimPathStart + fullPath.mTrimPathOffset) % 1.0f;
- float end = (fullPath.mTrimPathEnd + fullPath.mTrimPathOffset) % 1.0f;
-
- if (mPathMeasure == null) {
- mPathMeasure = new PathMeasure();
- }
- mPathMeasure.setPath(mPath, false);
-
- float len = mPathMeasure.getLength();
- start = start * len;
- end = end * len;
- path.reset();
- if (start > end) {
- mPathMeasure.getSegment(start, len, path, true);
- mPathMeasure.getSegment(0f, end, path, true);
- } else {
- mPathMeasure.getSegment(start, end, path, true);
- }
- path.rLineTo(0, 0); // fix bug in measure
- }
- mRenderPath.addPath(path, mFinalPathMatrix);
-
- if (fullPath.mFillColor != Color.TRANSPARENT) {
- if (mFillPaint == null) {
- mFillPaint = new Paint();
- mFillPaint.setStyle(Style.FILL);
- mFillPaint.setAntiAlias(true);
- }
-
- final Paint fillPaint = mFillPaint;
- fillPaint.setColor(applyAlpha(applyAlpha(fullPath.mFillColor, fullPath
- .mFillAlpha), getRootAlpha()));
- Paint_Delegate fillPaintDelegate = Paint_Delegate.getDelegate(fillPaint
- .getNativeInstance());
- // mFillPaint can not be null at this point so we will have a delegate
- assert fillPaintDelegate != null;
- fillPaintDelegate.setColorFilter(filterPtr);
- fillPaintDelegate.setShader(fullPath.mFillGradient);
- Path_Delegate.nSetFillType(mRenderPath.mNativePath, fullPath.mFillType);
- BaseCanvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, fillPaint
- .getNativeInstance());
- }
-
- if (fullPath.mStrokeColor != Color.TRANSPARENT) {
- if (mStrokePaint == null) {
- mStrokePaint = new Paint();
- mStrokePaint.setStyle(Style.STROKE);
- mStrokePaint.setAntiAlias(true);
- }
-
- final Paint strokePaint = mStrokePaint;
- if (fullPath.mStrokeLineJoin != null) {
- strokePaint.setStrokeJoin(fullPath.mStrokeLineJoin);
- }
-
- if (fullPath.mStrokeLineCap != null) {
- strokePaint.setStrokeCap(fullPath.mStrokeLineCap);
- }
-
- strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit);
- strokePaint.setColor(applyAlpha(applyAlpha(fullPath.mStrokeColor, fullPath
- .mStrokeAlpha), getRootAlpha()));
- Paint_Delegate strokePaintDelegate = Paint_Delegate.getDelegate(strokePaint
- .getNativeInstance());
- // mStrokePaint can not be null at this point so we will have a delegate
- assert strokePaintDelegate != null;
- strokePaintDelegate.setColorFilter(filterPtr);
- final float finalStrokeScale = minScale * matrixScale;
- strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale);
- strokePaintDelegate.setShader(fullPath.mStrokeGradient);
- BaseCanvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, strokePaint
- .getNativeInstance());
- }
- }
- }
-
- private float getMatrixScale(Matrix groupStackedMatrix) {
- // Given unit vectors A = (0, 1) and B = (1, 0).
- // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
- // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
- // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
- // If max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
- //
- // For non-skew case, which is most of the cases, matrix scale is computing exactly the
- // scale on x and y axis, and take the minimal of these two.
- // For skew case, an unit square will mapped to a parallelogram. And this function will
- // return the minimal height of the 2 bases.
- float[] unitVectors = new float[]{0, 1, 1, 0};
- groupStackedMatrix.mapVectors(unitVectors);
- float scaleX = MathUtils.mag(unitVectors[0], unitVectors[1]);
- float scaleY = MathUtils.mag(unitVectors[2], unitVectors[3]);
- float crossProduct = MathUtils.cross(unitVectors[0], unitVectors[1],
- unitVectors[2], unitVectors[3]);
- float maxScale = MathUtils.max(scaleX, scaleY);
-
- float matrixScale = 0;
- if (maxScale > 0) {
- matrixScale = MathUtils.abs(crossProduct) / maxScale;
- }
- if (DBG_VECTOR_DRAWABLE) {
- Log.d(LOGTAG, "Scale x " + scaleX + " y " + scaleY + " final " + matrixScale);
- }
- return matrixScale;
- }
-
- @Override
- public void setName(String name) {
- }
-
- @Override
- protected void finalize() throws Throwable {
- // The mRootGroupPtr is not explicitly freed by anything in the VectorDrawable so we
- // need to free it here.
- VNativeObject nativeObject = sPathManager.getDelegate(mRootGroupPtr);
- sPathManager.removeJavaReferenceFor(mRootGroupPtr);
- assert nativeObject != null;
- nativeObject.dispose();
-
- super.finalize();
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java b/tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java
deleted file mode 100644
index afbe97c0..0000000
--- a/tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2011 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 com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.impl.RenderAction;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Delegate overriding selected methods of android.os.HandlerThread
- *
- * Through the layoutlib_create tool, selected methods of Handler have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- *
- */
-public class HandlerThread_Delegate {
-
- private static Map<BridgeContext, List<HandlerThread>> sThreads =
- new HashMap<BridgeContext, List<HandlerThread>>();
-
- public static void cleanUp(BridgeContext context) {
- List<HandlerThread> list = sThreads.get(context);
- if (list != null) {
- for (HandlerThread thread : list) {
- thread.quit();
- }
-
- list.clear();
- sThreads.remove(context);
- }
- }
-
- // -------- Delegate methods
-
- @LayoutlibDelegate
- /*package*/ static void run(HandlerThread theThread) {
- // record the thread so that it can be quit() on clean up.
- BridgeContext context = RenderAction.getCurrentContext();
- List<HandlerThread> list = sThreads.get(context);
- if (list == null) {
- list = new ArrayList<HandlerThread>();
- sThreads.put(context, list);
- }
-
- list.add(theThread);
-
- // ---- START DEFAULT IMPLEMENTATION.
-
- theThread.mTid = Process.myTid();
- Looper.prepare();
- synchronized (theThread) {
- theThread.mLooper = Looper.myLooper();
- theThread.notifyAll();
- }
- Process.setThreadPriority(theThread.mPriority);
- theThread.onLooperPrepared();
- Looper.loop();
- theThread.mTid = -1;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java b/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java
deleted file mode 100644
index 2152c8a..0000000
--- a/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2010 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 com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-
-/**
- * Delegate overriding selected methods of android.os.Handler
- *
- * Through the layoutlib_create tool, selected methods of Handler have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- *
- */
-public class Handler_Delegate {
-
- // -------- Delegate methods
-
- @LayoutlibDelegate
- /*package*/ static boolean sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) {
- // get the callback
- IHandlerCallback callback = sCallbacks.get();
- if (callback != null) {
- callback.sendMessageAtTime(handler, msg, uptimeMillis);
- }
- return true;
- }
-
- // -------- Delegate implementation
-
- public interface IHandlerCallback {
- void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis);
- }
-
- private final static ThreadLocal<IHandlerCallback> sCallbacks =
- new ThreadLocal<IHandlerCallback>();
-
- public static void setCallback(IHandlerCallback callback) {
- sCallbacks.set(callback);
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/android/os/Looper_Accessor.java b/tools/layoutlib/bridge/src/android/os/Looper_Accessor.java
deleted file mode 100644
index 09f3e47..0000000
--- a/tools/layoutlib/bridge/src/android/os/Looper_Accessor.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2011 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 java.lang.reflect.Field;
-
-/**
- * Class allowing access to package-protected methods/fields.
- */
-public class Looper_Accessor {
-
- public static void cleanupThread() {
- // clean up the looper
- Looper.sThreadLocal.remove();
- try {
- Field sMainLooper = Looper.class.getDeclaredField("sMainLooper");
- sMainLooper.setAccessible(true);
- sMainLooper.set(null, null);
- } catch (SecurityException e) {
- catchReflectionException();
- } catch (IllegalArgumentException e) {
- catchReflectionException();
- } catch (NoSuchFieldException e) {
- catchReflectionException();
- } catch (IllegalAccessException e) {
- catchReflectionException();
- }
-
- }
-
- private static void catchReflectionException() {
- assert(false);
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/os/ServiceManager.java b/tools/layoutlib/bridge/src/android/os/ServiceManager.java
deleted file mode 100644
index 34c7845..0000000
--- a/tools/layoutlib/bridge/src/android/os/ServiceManager.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2009 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 java.util.Map;
-
-public final class ServiceManager {
-
- /**
- * Returns a reference to a service with the given name.
- *
- * @param name the name of the service to get
- * @return a reference to the service, or <code>null</code> if the service doesn't exist
- */
- public static IBinder getService(String name) {
- return null;
- }
-
- /**
- * Is not supposed to return null, but that is fine for layoutlib.
- */
- public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
- throw new ServiceNotFoundException(name);
- }
-
- /**
- * Place a new @a service called @a name into the service
- * manager.
- *
- * @param name the name of the new service
- * @param service the service object
- */
- public static void addService(String name, IBinder service) {
- // pass
- }
-
- /**
- * Retrieve an existing service called @a name from the
- * service manager. Non-blocking.
- */
- public static IBinder checkService(String name) {
- return null;
- }
-
- /**
- * Return a list of all currently running services.
- * @return an array of all currently running services, or <code>null</code> in
- * case of an exception
- */
- public static String[] listServices() {
- // actual implementation returns null sometimes, so it's ok
- // to return null instead of an empty list.
- return null;
- }
-
- /**
- * This is only intended to be called when the process is first being brought
- * up and bound by the activity manager. There is only one thread in the process
- * at that time, so no locking is done.
- *
- * @param cache the cache of service references
- * @hide
- */
- public static void initServiceCache(Map<String, IBinder> cache) {
- // pass
- }
-
- /**
- * Exception thrown when no service published for given name. This might be
- * thrown early during boot before certain services have published
- * themselves.
- *
- * @hide
- */
- public static class ServiceNotFoundException extends Exception {
- // identical to the original implementation
- public ServiceNotFoundException(String name) {
- super("No service published for: " + name);
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
deleted file mode 100644
index 9677aaf..0000000
--- a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2010 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 com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-import com.android.tools.layoutlib.java.System_Delegate;
-
-/**
- * Delegate implementing the native methods of android.os.SystemClock
- *
- * Through the layoutlib_create tool, the original native methods of SystemClock have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
- * around to map int to instance of the delegate.
- *
- */
-public class SystemClock_Delegate {
- /**
- * Returns milliseconds since boot, not counting time spent in deep sleep.
- * <b>Note:</b> This value may get reset occasionally (before it would
- * otherwise wrap around).
- *
- * @return milliseconds of non-sleep uptime since boot.
- */
- @LayoutlibDelegate
- /*package*/ static long uptimeMillis() {
- return System_Delegate.currentTimeMillis() - System_Delegate.bootTimeMillis();
- }
-
- /**
- * Returns milliseconds since boot, including time spent in sleep.
- *
- * @return elapsed milliseconds since boot.
- */
- @LayoutlibDelegate
- /*package*/ static long elapsedRealtime() {
- return System_Delegate.currentTimeMillis() - System_Delegate.bootTimeMillis();
- }
-
- /**
- * Returns nanoseconds since boot, including time spent in sleep.
- *
- * @return elapsed nanoseconds since boot.
- */
- @LayoutlibDelegate
- /*package*/ static long elapsedRealtimeNanos() {
- return System_Delegate.nanoTime() - System_Delegate.bootTime();
- }
-
- /**
- * Returns milliseconds running in the current thread.
- *
- * @return elapsed milliseconds in the thread
- */
- @LayoutlibDelegate
- /*package*/ static long currentThreadTimeMillis() {
- return System_Delegate.currentTimeMillis();
- }
-
- /**
- * Returns microseconds running in the current thread.
- *
- * @return elapsed microseconds in the thread
- *
- * @hide
- */
- @LayoutlibDelegate
- /*package*/ static long currentThreadTimeMicro() {
- return System_Delegate.currentTimeMillis() * 1000;
- }
-
- /**
- * Returns current wall time in microseconds.
- *
- * @return elapsed microseconds in wall time
- *
- * @hide
- */
- @LayoutlibDelegate
- /*package*/ static long currentTimeMicro() {
- return elapsedRealtime() * 1000;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java
deleted file mode 100644
index d299add..0000000
--- a/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2014 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 com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.util.Map;
-
-/**
- * Delegate implementing the native methods of android.os.SystemProperties
- *
- * Through the layoutlib_create tool, the original native methods of SystemProperties have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
- * around to map int to instance of the delegate.
- */
-public class SystemProperties_Delegate {
-
- @LayoutlibDelegate
- /*package*/ static String native_get(String key) {
- return native_get(key, "");
- }
-
- @LayoutlibDelegate
- /*package*/ static String native_get(String key, String def) {
- Map<String, String> properties = Bridge.getPlatformProperties();
- String value = properties.get(key);
- if (value != null) {
- return value;
- }
-
- return def;
- }
- @LayoutlibDelegate
- /*package*/ static int native_get_int(String key, int def) {
- Map<String, String> properties = Bridge.getPlatformProperties();
- String value = properties.get(key);
- if (value != null) {
- return Integer.decode(value);
- }
-
- return def;
- }
-
- @LayoutlibDelegate
- /*package*/ static long native_get_long(String key, long def) {
- Map<String, String> properties = Bridge.getPlatformProperties();
- String value = properties.get(key);
- if (value != null) {
- return Long.decode(value);
- }
-
- return def;
- }
-
- /**
- * Values 'n', 'no', '0', 'false' or 'off' are considered false.
- * Values 'y', 'yes', '1', 'true' or 'on' are considered true.
- */
- @LayoutlibDelegate
- /*package*/ static boolean native_get_boolean(String key, boolean def) {
- Map<String, String> properties = Bridge.getPlatformProperties();
- String value = properties.get(key);
-
- if ("n".equals(value) || "no".equals(value) || "0".equals(value) || "false".equals(value)
- || "off".equals(value)) {
- return false;
- }
- //noinspection SimplifiableIfStatement
- if ("y".equals(value) || "yes".equals(value) || "1".equals(value) || "true".equals(value)
- || "on".equals(value)) {
- return true;
- }
-
- return def;
- }
-
- @LayoutlibDelegate
- /*package*/ static void native_set(String key, String def) {
- Map<String, String> properties = Bridge.getPlatformProperties();
- properties.put(key, def);
- }
-
- @LayoutlibDelegate
- /*package*/ static void native_add_change_callback() {
- // pass.
- }
-
- @LayoutlibDelegate
- /*package*/ static void native_report_sysprop_change() {
- // pass.
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java b/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java
deleted file mode 100644
index aa393a9..0000000
--- a/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2014 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.preference;
-
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.InflateException;
-
-public class BridgePreferenceInflater extends PreferenceInflater {
-
- public BridgePreferenceInflater(Context context, PreferenceManager preferenceManager) {
- super(context, preferenceManager);
- }
-
- @Override
- protected Preference onCreateItem(String name, AttributeSet attrs)
- throws ClassNotFoundException {
- Object viewKey = null;
- BridgeContext bc = null;
-
- Context context = getContext();
- if (context instanceof BridgeContext) {
- bc = (BridgeContext) context;
- }
- if (attrs instanceof BridgeXmlBlockParser) {
- viewKey = ((BridgeXmlBlockParser) attrs).getViewCookie();
- }
-
- Preference preference = null;
- try {
- preference = super.onCreateItem(name, attrs);
- } catch (ClassNotFoundException | InflateException exception) {
- // name is probably not a valid preference type
- if ("SwitchPreferenceCompat".equals(name)) {
- preference = super.onCreateItem("SwitchPreference", attrs);
- }
- }
-
- if (viewKey != null && bc != null) {
- bc.addCookie(preference, viewKey);
- }
- return preference;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java b/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java
deleted file mode 100644
index 2e44a77..0000000
--- a/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2014 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.preference;
-
-import com.android.internal.R;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import org.xmlpull.v1.XmlPullParser;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ListView;
-
-/**
- * Delegate that provides implementation for native methods in {@link Preference}
- * <p/>
- * Through the layoutlib_create tool, selected methods of Preference have been replaced by calls to
- * methods of the same name in this delegate class.
- */
-public class Preference_Delegate {
-
- @LayoutlibDelegate
- /*package*/ static View getView(Preference pref, View convertView, ViewGroup parent) {
- Context context = pref.getContext();
- BridgeContext bc = context instanceof BridgeContext ? ((BridgeContext) context) : null;
- convertView = pref.getView_Original(convertView, parent);
- if (bc != null) {
- Object cookie = bc.getCookie(pref);
- if (cookie != null) {
- bc.addViewKey(convertView, cookie);
- }
- }
- return convertView;
- }
-
- /**
- * Inflates the parser and returns the ListView containing the Preferences.
- */
- public static View inflatePreference(Context context, XmlPullParser parser, ViewGroup root) {
- PreferenceManager pm = new PreferenceManager(context);
- PreferenceInflater inflater = new BridgePreferenceInflater(context, pm);
- PreferenceScreen ps = (PreferenceScreen) inflater.inflate(parser, null, true);
- pm.setPreferences(ps);
- ListView preferenceView = createContainerView(context, root);
- ps.bind(preferenceView);
- return preferenceView;
- }
-
- private static ListView createContainerView(Context context, ViewGroup root) {
- TypedArray a = context.obtainStyledAttributes(null, R.styleable.PreferenceFragment,
- R.attr.preferenceFragmentStyle, 0);
- int mLayoutResId = a.getResourceId(R.styleable.PreferenceFragment_layout,
- R.layout.preference_list_fragment);
- a.recycle();
-
- LayoutInflater inflater =
- (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(mLayoutResId, root, true);
-
- return (ListView) root.findViewById(android.R.id.list);
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java b/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java
deleted file mode 100644
index 38171dc..0000000
--- a/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2011 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.text;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.icu.text.Bidi;
-
-/**
- * Delegate used to provide new implementation for the native methods of {@link AndroidBidi}
- *
- * Through the layoutlib_create tool, the original methods of AndroidBidi have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class AndroidBidi_Delegate {
-
- @LayoutlibDelegate
- /*package*/ static int runBidi(int dir, char[] chars, byte[] charInfo, int count,
- boolean haveInfo) {
-
- switch (dir) {
- case 0: // Layout.DIR_REQUEST_LTR
- dir = Bidi.LTR;
- break;
- case 1: // Layout.DIR_REQUEST_RTL
- dir = Bidi.RTL;
- break;
- case -1: // Layout.DIR_REQUEST_DEFAULT_RTL
- dir = Bidi.LEVEL_DEFAULT_RTL;
- break;
- case -2: // Layout.DIR_REQUEST_DEFAULT_LTR
- dir = Bidi.LEVEL_DEFAULT_LTR;
- break;
- default:
- // Invalid code. Log error, assume LEVEL_DEFAULT_LTR and continue.
- Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Invalid direction flag", null);
- dir = Bidi.LEVEL_DEFAULT_LTR;
- }
- Bidi bidi = new Bidi(chars, 0, null, 0, count, dir);
- if (charInfo != null) {
- for (int i = 0; i < count; ++i)
- charInfo[i] = bidi.getLevelAt(i);
- }
- return bidi.getParaLevel();
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/text/GreedyLineBreaker.java b/tools/layoutlib/bridge/src/android/text/GreedyLineBreaker.java
deleted file mode 100644
index 50289e9..0000000
--- a/tools/layoutlib/bridge/src/android/text/GreedyLineBreaker.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2014 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.text;
-
-import android.annotation.NonNull;
-import android.text.Primitive.PrimitiveType;
-import android.text.StaticLayout.LineBreaks;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static android.text.Primitive.PrimitiveType.PENALTY_INFINITY;
-
-// Based on the native implementation of GreedyLineBreaker in
-// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260
-public class GreedyLineBreaker extends LineBreaker {
-
- public GreedyLineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth,
- @NonNull TabStops tabStops) {
- super(primitives, lineWidth, tabStops);
- }
-
- @Override
- public void computeBreaks(@NonNull LineBreaks lineBreaks) {
- BreakInfo breakInfo = new BreakInfo();
- int lineNum = 0;
- float width = 0, printedWidth = 0;
- boolean breakFound = false, goodBreakFound = false;
- int breakIndex = 0, goodBreakIndex = 0;
- float breakWidth = 0, goodBreakWidth = 0;
- int firstTabIndex = Integer.MAX_VALUE;
-
- float maxWidth = mLineWidth.getLineWidth(lineNum);
-
- int numPrimitives = mPrimitives.size();
- // greedily fit as many characters as possible on each line
- // loop over all primitives, and choose the best break point
- // (if possible, a break point without splitting a word)
- // after going over the maximum length
- for (int i = 0; i < numPrimitives; i++) {
- Primitive p = mPrimitives.get(i);
-
- // update the current line width
- if (p.type == PrimitiveType.BOX || p.type == PrimitiveType.GLUE) {
- width += p.width;
- if (p.type == PrimitiveType.BOX) {
- printedWidth = width;
- }
- } else if (p.type == PrimitiveType.VARIABLE) {
- width = mTabStops.width(width);
- // keep track of first tab character in the region we are examining
- // so we can determine whether or not a line contains a tab
- firstTabIndex = Math.min(firstTabIndex, i);
- }
-
- // find the best break point for the characters examined so far
- if (printedWidth > maxWidth) {
- //noinspection StatementWithEmptyBody
- if (breakFound || goodBreakFound) {
- if (goodBreakFound) {
- // a true line break opportunity existed in the characters examined so far,
- // so there is no need to split a word
- i = goodBreakIndex; // no +1 because of i++
- lineNum++;
- maxWidth = mLineWidth.getLineWidth(lineNum);
- breakInfo.mBreaksList.add(mPrimitives.get(goodBreakIndex).location);
- breakInfo.mWidthsList.add(goodBreakWidth);
- breakInfo.mFlagsList.add(firstTabIndex < goodBreakIndex);
- firstTabIndex = Integer.MAX_VALUE;
- } else {
- // must split a word because there is no other option
- i = breakIndex; // no +1 because of i++
- lineNum++;
- maxWidth = mLineWidth.getLineWidth(lineNum);
- breakInfo.mBreaksList.add(mPrimitives.get(breakIndex).location);
- breakInfo.mWidthsList.add(breakWidth);
- breakInfo.mFlagsList.add(firstTabIndex < breakIndex);
- firstTabIndex = Integer.MAX_VALUE;
- }
- printedWidth = width = 0;
- goodBreakFound = breakFound = false;
- goodBreakWidth = breakWidth = 0;
- continue;
- } else {
- // no choice, keep going... must make progress by putting at least one
- // character on a line, even if part of that character is cut off --
- // there is no other option
- }
- }
-
- // update possible break points
- if (p.type == PrimitiveType.PENALTY &&
- p.penalty < PENALTY_INFINITY) {
- // this does not handle penalties with width
-
- // handle forced line break
- if (p.penalty == -PENALTY_INFINITY) {
- lineNum++;
- maxWidth = mLineWidth.getLineWidth(lineNum);
- breakInfo.mBreaksList.add(p.location);
- breakInfo.mWidthsList.add(printedWidth);
- breakInfo.mFlagsList.add(firstTabIndex < i);
- firstTabIndex = Integer.MAX_VALUE;
- printedWidth = width = 0;
- goodBreakFound = breakFound = false;
- goodBreakWidth = breakWidth = 0;
- continue;
- }
- if (i > breakIndex && (printedWidth <= maxWidth || !breakFound)) {
- breakFound = true;
- breakIndex = i;
- breakWidth = printedWidth;
- }
- if (i > goodBreakIndex && printedWidth <= maxWidth) {
- goodBreakFound = true;
- goodBreakIndex = i;
- goodBreakWidth = printedWidth;
- }
- } else if (p.type == PrimitiveType.WORD_BREAK) {
- // only do this if necessary -- we don't want to break words
- // when possible, but sometimes it is unavoidable
- if (i > breakIndex && (printedWidth <= maxWidth || !breakFound)) {
- breakFound = true;
- breakIndex = i;
- breakWidth = printedWidth;
- }
- }
- }
-
- if (breakFound || goodBreakFound) {
- // output last break if there are more characters to output
- if (goodBreakFound) {
- breakInfo.mBreaksList.add(mPrimitives.get(goodBreakIndex).location);
- breakInfo.mWidthsList.add(goodBreakWidth);
- breakInfo.mFlagsList.add(firstTabIndex < goodBreakIndex);
- } else {
- breakInfo.mBreaksList.add(mPrimitives.get(breakIndex).location);
- breakInfo.mWidthsList.add(breakWidth);
- breakInfo.mFlagsList.add(firstTabIndex < breakIndex);
- }
- }
- breakInfo.copyTo(lineBreaks);
- }
-
- private static class BreakInfo {
- List<Integer> mBreaksList = new ArrayList<Integer>();
- List<Float> mWidthsList = new ArrayList<Float>();
- List<Boolean> mFlagsList = new ArrayList<Boolean>();
-
- public void copyTo(LineBreaks lineBreaks) {
- if (lineBreaks.breaks.length != mBreaksList.size()) {
- lineBreaks.breaks = new int[mBreaksList.size()];
- lineBreaks.widths = new float[mWidthsList.size()];
- lineBreaks.flags = new int[mFlagsList.size()];
- }
-
- int i = 0;
- for (int b : mBreaksList) {
- lineBreaks.breaks[i] = b;
- i++;
- }
- i = 0;
- for (float b : mWidthsList) {
- lineBreaks.widths[i] = b;
- i++;
- }
- i = 0;
- for (boolean b : mFlagsList) {
- lineBreaks.flags[i] = b ? TAB_MASK : 0;
- i++;
- }
-
- mBreaksList = null;
- mWidthsList = null;
- mFlagsList = null;
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java b/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java
deleted file mode 100644
index 499e58a..0000000
--- a/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2015 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.text;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.io.File;
-import java.nio.ByteBuffer;
-
-/**
- * Delegate that overrides implementation for certain methods in {@link android.text.Hyphenator}
- * <p/>
- * Through the layoutlib_create tool, selected methods of Hyphenator have been replaced
- * by calls to methods of the same name in this delegate class.
- */
-public class Hyphenator_Delegate {
-
- private static final DelegateManager<Hyphenator_Delegate> sDelegateManager = new
- DelegateManager<Hyphenator_Delegate>(Hyphenator_Delegate.class);
-
- @LayoutlibDelegate
- /*package*/ static File getSystemHyphenatorLocation() {
- // FIXME
- return null;
- }
-
- /*package*/ @SuppressWarnings("UnusedParameters") // TODO implement this.
- static long loadHyphenator(ByteBuffer buffer, int offset, int minPrefix, int minSuffix) {
- return sDelegateManager.addNewDelegate(new Hyphenator_Delegate());
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/text/LineBreaker.java b/tools/layoutlib/bridge/src/android/text/LineBreaker.java
deleted file mode 100644
index 06e9c84..0000000
--- a/tools/layoutlib/bridge/src/android/text/LineBreaker.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2014 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.text;
-
-import android.annotation.NonNull;
-import android.text.StaticLayout.LineBreaks;
-
-import java.util.Collections;
-import java.util.List;
-
-// Based on the native implementation of LineBreaker in
-// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260
-public abstract class LineBreaker {
-
- protected static final int TAB_MASK = 0x20000000; // keep in sync with StaticLayout
-
- protected final @NonNull List<Primitive> mPrimitives;
- protected final @NonNull LineWidth mLineWidth;
- protected final @NonNull TabStops mTabStops;
-
- public LineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth,
- @NonNull TabStops tabStops) {
- mPrimitives = Collections.unmodifiableList(primitives);
- mLineWidth = lineWidth;
- mTabStops = tabStops;
- }
-
- public abstract void computeBreaks(@NonNull LineBreaks breakInfo);
-}
diff --git a/tools/layoutlib/bridge/src/android/text/LineWidth.java b/tools/layoutlib/bridge/src/android/text/LineWidth.java
deleted file mode 100644
index 2ea886d..0000000
--- a/tools/layoutlib/bridge/src/android/text/LineWidth.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2014 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.text;
-
-// Based on the native implementation of LineWidth in
-// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260
-public class LineWidth {
- private final float mFirstWidth;
- private final int mFirstWidthLineCount;
- private float mRestWidth;
-
- public LineWidth(float firstWidth, int firstWidthLineCount, float restWidth) {
- mFirstWidth = firstWidth;
- mFirstWidthLineCount = firstWidthLineCount;
- mRestWidth = restWidth;
- }
-
- public float getLineWidth(int line) {
- return (line < mFirstWidthLineCount) ? mFirstWidth : mRestWidth;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/text/OptimizingLineBreaker.java b/tools/layoutlib/bridge/src/android/text/OptimizingLineBreaker.java
deleted file mode 100644
index ed8e33a..0000000
--- a/tools/layoutlib/bridge/src/android/text/OptimizingLineBreaker.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright (C) 2014 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.text;
-
-import android.annotation.NonNull;
-import android.text.Primitive.PrimitiveType;
-import android.text.StaticLayout.LineBreaks;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.ListIterator;
-
-import static android.text.Primitive.PrimitiveType.PENALTY_INFINITY;
-
-
-// Based on the native implementation of OptimizingLineBreaker in
-// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260
-/**
- * A more complex version of line breaking where we try to prevent the right edge from being too
- * jagged.
- */
-public class OptimizingLineBreaker extends LineBreaker {
-
- public OptimizingLineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth,
- @NonNull TabStops tabStops) {
- super(primitives, lineWidth, tabStops);
- }
-
- @Override
- public void computeBreaks(@NonNull LineBreaks breakInfo) {
- int numBreaks = mPrimitives.size();
- assert numBreaks > 0;
- if (numBreaks == 1) {
- // This can be true only if it's an empty paragraph.
- Primitive p = mPrimitives.get(0);
- assert p.type == PrimitiveType.PENALTY;
- breakInfo.breaks = new int[]{0};
- breakInfo.widths = new float[]{p.width};
- breakInfo.flags = new int[]{0};
- return;
- }
- Node[] opt = new Node[numBreaks];
- opt[0] = new Node(-1, 0, 0, 0, false);
- opt[numBreaks - 1] = new Node(-1, 0, 0, 0, false);
-
- ArrayList<Integer> active = new ArrayList<Integer>();
- active.add(0);
- int lastBreak = 0;
- for (int i = 0; i < numBreaks; i++) {
- Primitive p = mPrimitives.get(i);
- if (p.type == PrimitiveType.PENALTY) {
- boolean finalBreak = (i + 1 == numBreaks);
- Node bestBreak = null;
-
- for (ListIterator<Integer> it = active.listIterator(); it.hasNext();
- /* incrementing done in loop */) {
- int pos = it.next();
- int lines = opt[pos].mPrevCount;
- float maxWidth = mLineWidth.getLineWidth(lines);
- // we have to compute metrics every time --
- // we can't really pre-compute this stuff and just deal with breaks
- // because of the way tab characters work, this makes it computationally
- // harder, but this way, we can still optimize while treating tab characters
- // correctly
- LineMetrics lineMetrics = computeMetrics(pos, i);
- if (lineMetrics.mPrintedWidth <= maxWidth) {
- float demerits = computeDemerits(maxWidth, lineMetrics.mPrintedWidth,
- finalBreak, p.penalty) + opt[pos].mDemerits;
- if (bestBreak == null || demerits < bestBreak.mDemerits) {
- if (bestBreak == null) {
- bestBreak = new Node(pos, opt[pos].mPrevCount + 1, demerits,
- lineMetrics.mPrintedWidth, lineMetrics.mHasTabs);
- } else {
- bestBreak.mPrev = pos;
- bestBreak.mPrevCount = opt[pos].mPrevCount + 1;
- bestBreak.mDemerits = demerits;
- bestBreak.mWidth = lineMetrics.mPrintedWidth;
- bestBreak.mHasTabs = lineMetrics.mHasTabs;
- }
- }
- } else {
- it.remove();
- }
- }
- if (p.penalty == -PENALTY_INFINITY) {
- active.clear();
- }
- if (bestBreak != null) {
- opt[i] = bestBreak;
- active.add(i);
- lastBreak = i;
- }
- if (active.isEmpty()) {
- // we can't give up!
- LineMetrics lineMetrics = new LineMetrics();
- int lines = opt[lastBreak].mPrevCount;
- float maxWidth = mLineWidth.getLineWidth(lines);
- int breakIndex = desperateBreak(lastBreak, numBreaks, maxWidth, lineMetrics);
- opt[breakIndex] = new Node(lastBreak, lines + 1, 0 /*doesn't matter*/,
- lineMetrics.mWidth, lineMetrics.mHasTabs);
- active.add(breakIndex);
- lastBreak = breakIndex;
- i = breakIndex; // incremented by i++
- }
- }
- }
-
- int idx = numBreaks - 1;
- int count = opt[idx].mPrevCount;
- resize(breakInfo, count);
- while (opt[idx].mPrev != -1) {
- count--;
- assert count >=0;
-
- breakInfo.breaks[count] = mPrimitives.get(idx).location;
- breakInfo.widths[count] = opt[idx].mWidth;
- breakInfo.flags [count] = opt[idx].mHasTabs ? TAB_MASK : 0;
- idx = opt[idx].mPrev;
- }
- }
-
- private static void resize(LineBreaks lineBreaks, int size) {
- if (lineBreaks.breaks.length == size) {
- return;
- }
- int[] breaks = new int[size];
- float[] widths = new float[size];
- int[] flags = new int[size];
-
- int toCopy = Math.min(size, lineBreaks.breaks.length);
- System.arraycopy(lineBreaks.breaks, 0, breaks, 0, toCopy);
- System.arraycopy(lineBreaks.widths, 0, widths, 0, toCopy);
- System.arraycopy(lineBreaks.flags, 0, flags, 0, toCopy);
-
- lineBreaks.breaks = breaks;
- lineBreaks.widths = widths;
- lineBreaks.flags = flags;
- }
-
- @NonNull
- private LineMetrics computeMetrics(int start, int end) {
- boolean f = false;
- float w = 0, pw = 0;
- for (int i = start; i < end; i++) {
- Primitive p = mPrimitives.get(i);
- if (p.type == PrimitiveType.BOX || p.type == PrimitiveType.GLUE) {
- w += p.width;
- if (p.type == PrimitiveType.BOX) {
- pw = w;
- }
- } else if (p.type == PrimitiveType.VARIABLE) {
- w = mTabStops.width(w);
- f = true;
- }
- }
- return new LineMetrics(w, pw, f);
- }
-
- private static float computeDemerits(float maxWidth, float width, boolean finalBreak,
- float penalty) {
- float deviation = finalBreak ? 0 : maxWidth - width;
- return (deviation * deviation) + penalty;
- }
-
- /**
- * @return the last break position or -1 if failed.
- */
- @SuppressWarnings("ConstantConditions") // method too complex to be analyzed.
- private int desperateBreak(int start, int limit, float maxWidth,
- @NonNull LineMetrics lineMetrics) {
- float w = 0, pw = 0;
- boolean breakFound = false;
- int breakIndex = 0, firstTabIndex = Integer.MAX_VALUE;
- for (int i = start; i < limit; i++) {
- Primitive p = mPrimitives.get(i);
-
- if (p.type == PrimitiveType.BOX || p.type == PrimitiveType.GLUE) {
- w += p.width;
- if (p.type == PrimitiveType.BOX) {
- pw = w;
- }
- } else if (p.type == PrimitiveType.VARIABLE) {
- w = mTabStops.width(w);
- firstTabIndex = Math.min(firstTabIndex, i);
- }
-
- if (pw > maxWidth && breakFound) {
- break;
- }
-
- // must make progress
- if (i > start &&
- (p.type == PrimitiveType.PENALTY || p.type == PrimitiveType.WORD_BREAK)) {
- breakFound = true;
- breakIndex = i;
- }
- }
-
- if (breakFound) {
- lineMetrics.mWidth = w;
- lineMetrics.mPrintedWidth = pw;
- lineMetrics.mHasTabs = (start <= firstTabIndex && firstTabIndex < breakIndex);
- return breakIndex;
- } else {
- return -1;
- }
- }
-
- private static class LineMetrics {
- /** Actual width of the line. */
- float mWidth;
- /** Width of the line minus trailing whitespace. */
- float mPrintedWidth;
- boolean mHasTabs;
-
- public LineMetrics() {
- }
-
- public LineMetrics(float width, float printedWidth, boolean hasTabs) {
- mWidth = width;
- mPrintedWidth = printedWidth;
- mHasTabs = hasTabs;
- }
- }
-
- /**
- * A struct to store the info about a break.
- */
- @SuppressWarnings("SpellCheckingInspection") // For the word struct.
- private static class Node {
- // -1 for the first node.
- int mPrev;
- // number of breaks so far.
- int mPrevCount;
- float mDemerits;
- float mWidth;
- boolean mHasTabs;
-
- public Node(int prev, int prevCount, float demerits, float width, boolean hasTabs) {
- mPrev = prev;
- mPrevCount = prevCount;
- mDemerits = demerits;
- mWidth = width;
- mHasTabs = hasTabs;
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/text/Primitive.java b/tools/layoutlib/bridge/src/android/text/Primitive.java
deleted file mode 100644
index 37ed072..0000000
--- a/tools/layoutlib/bridge/src/android/text/Primitive.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2014 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.text;
-
-import android.annotation.NonNull;
-
-// Based on the native implementation of Primitive in
-// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260
-public class Primitive {
- public final @NonNull PrimitiveType type;
- public final int location;
- // The following fields don't make sense for all types.
- // Box and Glue have width only.
- // Penalty has both width and penalty.
- // Word_break has penalty only.
- public final float width;
- public final float penalty;
-
- /**
- * Use {@code PrimitiveType#getNewPrimitive()}
- */
- private Primitive(@NonNull PrimitiveType type, int location, float width, float penalty) {
- this.type = type;
- this.location = location;
- this.width = width;
- this.penalty = penalty;
- }
-
- public static enum PrimitiveType {
- /**
- * Something with a constant width that is to be typeset - like a character.
- */
- BOX,
- /**
- * Blank space with fixed width.
- */
- GLUE,
- /**
- * Aesthetic cost indicating how desirable breaking at this point will be. A penalty of
- * {@link #PENALTY_INFINITY} means a forced non-break, whereas a penalty of negative
- * {@code #PENALTY_INFINITY} means a forced break.
- * <p/>
- * Currently, it only stores penalty with values 0 or -infinity.
- */
- PENALTY,
- /**
- * For tabs - variable width space.
- */
- VARIABLE,
- /**
- * Possible breakpoints within a word. Think of this as a high cost {@link #PENALTY}.
- */
- WORD_BREAK;
-
- public Primitive getNewPrimitive(int location) {
- assert this == VARIABLE;
- return new Primitive(this, location, 0f, 0f);
- }
-
- public Primitive getNewPrimitive(int location, float value) {
- assert this == BOX || this == GLUE || this == WORD_BREAK;
- if (this == BOX || this == GLUE) {
- return new Primitive(this, location, value, 0f);
- } else {
- return new Primitive(this, location, 0f, value);
- }
- }
-
- public Primitive getNewPrimitive(int location, float width, float penalty) {
- assert this == PENALTY;
- return new Primitive(this, location, width, penalty);
- }
-
- // forced non-break, negative infinity is forced break.
- public static final float PENALTY_INFINITY = 1e7f;
- }
-}
-
diff --git a/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java
deleted file mode 100644
index cc03143..0000000
--- a/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java
+++ /dev/null
@@ -1,238 +0,0 @@
-package android.text;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.annotation.NonNull;
-import android.graphics.BidiRenderer;
-import android.graphics.Paint;
-import android.graphics.Paint_Delegate;
-import android.graphics.RectF;
-import android.icu.text.BreakIterator;
-import android.icu.util.ULocale;
-import android.text.Primitive.PrimitiveType;
-import android.text.StaticLayout.LineBreaks;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import javax.swing.text.Segment;
-
-/**
- * Delegate that provides implementation for native methods in {@link android.text.StaticLayout}
- * <p/>
- * Through the layoutlib_create tool, selected methods of StaticLayout have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class StaticLayout_Delegate {
-
- private static final char CHAR_SPACE = 0x20;
- private static final char CHAR_TAB = 0x09;
- private static final char CHAR_NEWLINE = 0x0A;
- private static final char CHAR_ZWSP = 0x200B; // Zero width space.
-
- // ---- Builder delegate manager ----
- private static final DelegateManager<Builder> sBuilderManager =
- new DelegateManager<Builder>(Builder.class);
-
- @LayoutlibDelegate
- /*package*/ static long nNewBuilder() {
- return sBuilderManager.addNewDelegate(new Builder());
- }
-
- @LayoutlibDelegate
- /*package*/ static void nFreeBuilder(long nativeBuilder) {
- sBuilderManager.removeJavaReferenceFor(nativeBuilder);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nFinishBuilder(long nativeBuilder) {
- }
-
- @LayoutlibDelegate
- /*package*/ static long nLoadHyphenator(ByteBuffer buf, int offset, int minPrefix,
- int minSuffix) {
- return Hyphenator_Delegate.loadHyphenator(buf, offset, minPrefix, minSuffix);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetLocale(long nativeBuilder, String locale, long nativeHyphenator) {
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
- if (builder != null) {
- builder.mLocale = locale;
- builder.mNativeHyphenator = nativeHyphenator;
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetIndents(long nativeBuilder, int[] indents) {
- // TODO.
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetupParagraph(long nativeBuilder, char[] text, int length,
- float firstWidth, int firstWidthLineCount, float restWidth,
- int[] variableTabStops, int defaultTabStop, int breakStrategy,
- int hyphenationFrequency, boolean isJustified) {
- // TODO: implement justified alignment
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
- if (builder == null) {
- return;
- }
-
- builder.mText = text;
- builder.mWidths = new float[length];
- builder.mLineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth);
- builder.mTabStopCalculator = new TabStops(variableTabStops, defaultTabStop);
- }
-
- @LayoutlibDelegate
- /*package*/ static float nAddStyleRun(long nativeBuilder, long nativePaint, long nativeTypeface,
- int start, int end, boolean isRtl) {
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
-
- int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
- return builder == null ? 0 :
- measureText(nativePaint, builder.mText, start, end - start, builder.mWidths,
- bidiFlags);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nAddMeasuredRun(long nativeBuilder, int start, int end, float[] widths) {
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
- if (builder != null) {
- System.arraycopy(widths, start, builder.mWidths, start, end - start);
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nAddReplacementRun(long nativeBuilder, int start, int end, float width) {
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
- if (builder == null) {
- return;
- }
- builder.mWidths[start] = width;
- Arrays.fill(builder.mWidths, start + 1, end, 0.0f);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nGetWidths(long nativeBuilder, float[] floatsArray) {
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
- if (builder != null) {
- System.arraycopy(builder.mWidths, 0, floatsArray, 0, builder.mWidths.length);
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static int nComputeLineBreaks(long nativeBuilder,
- LineBreaks recycle, int[] recycleBreaks, float[] recycleWidths,
- int[] recycleFlags, int recycleLength) {
-
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
- if (builder == null) {
- return 0;
- }
-
- // compute all possible breakpoints.
- int length = builder.mWidths.length;
- BreakIterator it = BreakIterator.getLineInstance(new ULocale(builder.mLocale));
- it.setText(new Segment(builder.mText, 0, length));
-
- // average word length in english is 5. So, initialize the possible breaks with a guess.
- List<Integer> breaks = new ArrayList<Integer>((int) Math.ceil(length / 5d));
- int loc;
- it.first();
- while ((loc = it.next()) != BreakIterator.DONE) {
- breaks.add(loc);
- }
-
- List<Primitive> primitives =
- computePrimitives(builder.mText, builder.mWidths, length, breaks);
- switch (builder.mBreakStrategy) {
- case Layout.BREAK_STRATEGY_SIMPLE:
- builder.mLineBreaker = new GreedyLineBreaker(primitives, builder.mLineWidth,
- builder.mTabStopCalculator);
- break;
- case Layout.BREAK_STRATEGY_HIGH_QUALITY:
- // TODO
-// break;
- case Layout.BREAK_STRATEGY_BALANCED:
- builder.mLineBreaker = new OptimizingLineBreaker(primitives, builder.mLineWidth,
- builder.mTabStopCalculator);
- break;
- default:
- assert false : "Unknown break strategy: " + builder.mBreakStrategy;
- builder.mLineBreaker = new GreedyLineBreaker(primitives, builder.mLineWidth,
- builder.mTabStopCalculator);
- }
- builder.mLineBreaker.computeBreaks(recycle);
- return recycle.breaks.length;
- }
-
- /**
- * Compute metadata each character - things which help in deciding if it's possible to break
- * at a point or not.
- */
- @NonNull
- private static List<Primitive> computePrimitives(@NonNull char[] text, @NonNull float[] widths,
- int length, @NonNull List<Integer> breaks) {
- // Initialize the list with a guess of the number of primitives:
- // 2 Primitives per non-whitespace char and approx 5 chars per word (i.e. 83% chars)
- List<Primitive> primitives = new ArrayList<Primitive>(((int) Math.ceil(length * 1.833)));
- int breaksSize = breaks.size();
- int breakIndex = 0;
- for (int i = 0; i < length; i++) {
- char c = text[i];
- if (c == CHAR_SPACE || c == CHAR_ZWSP) {
- primitives.add(PrimitiveType.GLUE.getNewPrimitive(i, widths[i]));
- } else if (c == CHAR_TAB) {
- primitives.add(PrimitiveType.VARIABLE.getNewPrimitive(i));
- } else if (c != CHAR_NEWLINE) {
- while (breakIndex < breaksSize && breaks.get(breakIndex) < i) {
- breakIndex++;
- }
- Primitive p;
- if (widths[i] != 0) {
- if (breakIndex < breaksSize && breaks.get(breakIndex) == i) {
- p = PrimitiveType.PENALTY.getNewPrimitive(i, 0, 0);
- } else {
- p = PrimitiveType.WORD_BREAK.getNewPrimitive(i, 0);
- }
- primitives.add(p);
- }
-
- primitives.add(PrimitiveType.BOX.getNewPrimitive(i, widths[i]));
- }
- }
- // final break at end of everything
- primitives.add(
- PrimitiveType.PENALTY.getNewPrimitive(length, 0, -PrimitiveType.PENALTY_INFINITY));
- return primitives;
- }
-
- private static float measureText(long nativePaint, char []text, int index, int count,
- float[] widths, int bidiFlags) {
- Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint);
- RectF bounds = new BidiRenderer(null, paint, text)
- .renderText(index, index + count, bidiFlags, widths, 0, false);
- return bounds.right - bounds.left;
- }
-
- // TODO: Rename to LineBreakerRef and move everything other than LineBreaker to LineBreaker.
- /**
- * Java representation of the native Builder class.
- */
- private static class Builder {
- String mLocale;
- char[] mText;
- float[] mWidths;
- LineBreaker mLineBreaker;
- long mNativeHyphenator;
- int mBreakStrategy;
- LineWidth mLineWidth;
- TabStops mTabStopCalculator;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/text/TabStops.java b/tools/layoutlib/bridge/src/android/text/TabStops.java
deleted file mode 100644
index 6c2f1e1..0000000
--- a/tools/layoutlib/bridge/src/android/text/TabStops.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2014 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.text;
-
-import android.annotation.Nullable;
-
-// Based on the native implementation of TabStops in
-// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260
-public class TabStops {
- @Nullable
- private int[] mStops;
- private final int mTabWidth;
-
- public TabStops(@Nullable int[] stops, int defaultTabWidth) {
- mTabWidth = defaultTabWidth;
- mStops = stops;
- }
-
- public float width(float widthSoFar) {
- if (mStops != null) {
- for (int i : mStops) {
- if (i > widthSoFar) {
- return i;
- }
- }
- }
- // find the next tabStop after widthSoFar.
- return (int) ((widthSoFar + mTabWidth) / mTabWidth) * mTabWidth;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java b/tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java
deleted file mode 100644
index 1e4f213..0000000
--- a/tools/layoutlib/bridge/src/android/text/format/DateFormat_Delegate.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2013 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.text.format;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.content.Context;
-
-
-/**
- * Delegate used to provide new implementation for the native methods of {@link DateFormat}
- *
- * Through the layoutlib_create tool, the original methods of DateFormat have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class DateFormat_Delegate {
-
- @LayoutlibDelegate
- /*package*/ static boolean is24HourFormat(Context context) {
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean is24HourFormat(Context context, int userHandle) {
- return false;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java b/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java
deleted file mode 100644
index 9fd1e15..0000000
--- a/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Copyright (C) 2008 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.util;
-
-import com.android.ide.common.rendering.api.AttrResourceValue;
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.internal.util.XmlUtils;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.BridgeConstants;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.impl.ResourceHelper;
-import com.android.resources.ResourceType;
-
-import org.xmlpull.v1.XmlPullParser;
-
-import android.annotation.NonNull;
-
-import java.util.Map;
-import java.util.function.Function;
-
-/**
- * A correct implementation of the {@link AttributeSet} interface on top of a XmlPullParser
- */
-public class BridgeXmlPullAttributes extends XmlPullAttributes {
-
- private final BridgeContext mContext;
- private final boolean mPlatformFile;
- private final Function<String, Map<String, Integer>> mFrameworkEnumValueSupplier;
- private final Function<String, Map<String, Integer>> mProjectEnumValueSupplier;
-
- // VisibleForTesting
- BridgeXmlPullAttributes(@NonNull XmlPullParser parser, @NonNull BridgeContext context,
- boolean platformFile,
- @NonNull Function<String, Map<String, Integer>> frameworkEnumValueSupplier,
- @NonNull Function<String, Map<String, Integer>> projectEnumValueSupplier) {
- super(parser);
- mContext = context;
- mPlatformFile = platformFile;
- mFrameworkEnumValueSupplier = frameworkEnumValueSupplier;
- mProjectEnumValueSupplier = projectEnumValueSupplier;
- }
-
- public BridgeXmlPullAttributes(@NonNull XmlPullParser parser, @NonNull BridgeContext context,
- boolean platformFile) {
- this(parser, context, platformFile, Bridge::getEnumValues, attrName -> {
- // get the styleable matching the resolved name
- RenderResources res = context.getRenderResources();
- ResourceValue attr = res.getProjectResource(ResourceType.ATTR, attrName);
- return attr instanceof AttrResourceValue ?
- ((AttrResourceValue) attr).getAttributeValues() : null;
- });
- }
-
- /*
- * (non-Javadoc)
- * @see android.util.XmlPullAttributes#getAttributeNameResource(int)
- *
- * This methods must return com.android.internal.R.attr.<name> matching
- * the name of the attribute.
- * It returns 0 if it doesn't find anything.
- */
- @Override
- public int getAttributeNameResource(int index) {
- // get the attribute name.
- String name = getAttributeName(index);
-
- // get the attribute namespace
- String ns = mParser.getAttributeNamespace(index);
-
- if (BridgeConstants.NS_RESOURCES.equals(ns)) {
- return Bridge.getResourceId(ResourceType.ATTR, name);
-
- }
-
- // this is not an attribute in the android namespace, we query the customviewloader, if
- // the namespaces match.
- if (mContext.getLayoutlibCallback().getNamespace().equals(ns)) {
- Integer v = mContext.getLayoutlibCallback().getResourceId(ResourceType.ATTR, name);
- if (v != null) {
- return v;
- }
- }
-
- return 0;
- }
-
- @Override
- public int getAttributeListValue(String namespace, String attribute,
- String[] options, int defaultValue) {
- String value = getAttributeValue(namespace, attribute);
- if (value != null) {
- ResourceValue r = getResourceValue(value);
-
- if (r != null) {
- value = r.getValue();
- }
-
- return XmlUtils.convertValueToList(value, options, defaultValue);
- }
-
- return defaultValue;
- }
-
- @Override
- public boolean getAttributeBooleanValue(String namespace, String attribute,
- boolean defaultValue) {
- String value = getAttributeValue(namespace, attribute);
- if (value != null) {
- ResourceValue r = getResourceValue(value);
-
- if (r != null) {
- value = r.getValue();
- }
-
- return XmlUtils.convertValueToBoolean(value, defaultValue);
- }
-
- return defaultValue;
- }
-
- @Override
- public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
- String value = getAttributeValue(namespace, attribute);
-
- return resolveResourceValue(value, defaultValue);
- }
-
- @Override
- public int getAttributeIntValue(String namespace, String attribute, int defaultValue) {
- String value = getAttributeValue(namespace, attribute);
- if (value == null) {
- return defaultValue;
- }
-
- ResourceValue r = getResourceValue(value);
-
- if (r != null) {
- value = r.getValue();
- }
-
- if (value.charAt(0) == '#') {
- return ResourceHelper.getColor(value);
- }
-
- try {
- return XmlUtils.convertValueToInt(value, defaultValue);
- } catch (NumberFormatException e) {
- // This is probably an enum
- Map<String, Integer> enumValues = BridgeConstants.NS_RESOURCES.equals(namespace) ?
- mFrameworkEnumValueSupplier.apply(attribute) :
- mProjectEnumValueSupplier.apply(attribute);
-
- Integer enumValue = enumValues != null ? enumValues.get(value) : null;
- if (enumValue != null) {
- return enumValue;
- }
-
- // We weren't able to find the enum int value
- throw e;
- }
- }
-
- @Override
- public int getAttributeUnsignedIntValue(String namespace, String attribute,
- int defaultValue) {
- String value = getAttributeValue(namespace, attribute);
- if (value != null) {
- ResourceValue r = getResourceValue(value);
-
- if (r != null) {
- value = r.getValue();
- }
-
- return XmlUtils.convertValueToUnsignedInt(value, defaultValue);
- }
-
- return defaultValue;
- }
-
- @Override
- public float getAttributeFloatValue(String namespace, String attribute,
- float defaultValue) {
- String s = getAttributeValue(namespace, attribute);
- if (s != null) {
- ResourceValue r = getResourceValue(s);
-
- if (r != null) {
- s = r.getValue();
- }
-
- return Float.parseFloat(s);
- }
-
- return defaultValue;
- }
-
- @Override
- public int getAttributeListValue(int index,
- String[] options, int defaultValue) {
- return XmlUtils.convertValueToList(
- getAttributeValue(index), options, defaultValue);
- }
-
- @Override
- public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
- String value = getAttributeValue(index);
- if (value != null) {
- ResourceValue r = getResourceValue(value);
-
- if (r != null) {
- value = r.getValue();
- }
-
- return XmlUtils.convertValueToBoolean(value, defaultValue);
- }
-
- return defaultValue;
- }
-
- @Override
- public int getAttributeResourceValue(int index, int defaultValue) {
- String value = getAttributeValue(index);
-
- return resolveResourceValue(value, defaultValue);
- }
-
- @Override
- public int getAttributeIntValue(int index, int defaultValue) {
- return getAttributeIntValue(mParser.getAttributeNamespace(index),
- getAttributeName(index)
- , defaultValue);
- }
-
- @Override
- public int getAttributeUnsignedIntValue(int index, int defaultValue) {
- String value = getAttributeValue(index);
- if (value != null) {
- ResourceValue r = getResourceValue(value);
-
- if (r != null) {
- value = r.getValue();
- }
-
- return XmlUtils.convertValueToUnsignedInt(value, defaultValue);
- }
-
- return defaultValue;
- }
-
- @Override
- public float getAttributeFloatValue(int index, float defaultValue) {
- String s = getAttributeValue(index);
- if (s != null) {
- ResourceValue r = getResourceValue(s);
-
- if (r != null) {
- s = r.getValue();
- }
-
- return Float.parseFloat(s);
- }
-
- return defaultValue;
- }
-
- // -- private helper methods
-
- /**
- * Returns a resolved {@link ResourceValue} from a given value.
- */
- private ResourceValue getResourceValue(String value) {
- // now look for this particular value
- RenderResources resources = mContext.getRenderResources();
- return resources.resolveResValue(resources.findResValue(value, mPlatformFile));
- }
-
- /**
- * Resolves and return a value to its associated integer.
- */
- private int resolveResourceValue(String value, int defaultValue) {
- ResourceValue resource = getResourceValue(value);
- if (resource != null) {
- Integer id = null;
- if (mPlatformFile || resource.isFramework()) {
- id = Bridge.getResourceId(resource.getResourceType(), resource.getName());
- } else {
- id = mContext.getLayoutlibCallback().getResourceId(
- resource.getResourceType(), resource.getName());
- }
-
- if (id != null) {
- return id;
- }
- }
-
- return defaultValue;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/util/Log_Delegate.java b/tools/layoutlib/bridge/src/android/util/Log_Delegate.java
deleted file mode 100644
index 7f432ab..0000000
--- a/tools/layoutlib/bridge/src/android/util/Log_Delegate.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2011 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.util;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-class Log_Delegate {
- // to replicate prefix visible when using 'adb logcat'
- private static char priorityChar(int priority) {
- switch (priority) {
- case Log.VERBOSE:
- return 'V';
- case Log.DEBUG:
- return 'D';
- case Log.INFO:
- return 'I';
- case Log.WARN:
- return 'W';
- case Log.ERROR:
- return 'E';
- case Log.ASSERT:
- return 'A';
- default:
- return '?';
- }
- }
-
- @LayoutlibDelegate
- static int println_native(int bufID, int priority, String tag, String msgs) {
- String prefix = priorityChar(priority) + "/" + tag + ": ";
- for (String msg: msgs.split("\n")) {
- System.out.println(prefix + msg);
- }
- return 0;
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/android/util/LruCache.java b/tools/layoutlib/bridge/src/android/util/LruCache.java
deleted file mode 100644
index 5208606..0000000
--- a/tools/layoutlib/bridge/src/android/util/LruCache.java
+++ /dev/null
@@ -1,391 +0,0 @@
-/*
- * Copyright (C) 2011 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.util;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- * BEGIN LAYOUTLIB CHANGE
- * This is a custom version that doesn't use the non standard LinkedHashMap#eldest.
- * END LAYOUTLIB CHANGE
- *
- * A cache that holds strong references to a limited number of values. Each time
- * a value is accessed, it is moved to the head of a queue. When a value is
- * added to a full cache, the value at the end of that queue is evicted and may
- * become eligible for garbage collection.
- *
- * <p>If your cached values hold resources that need to be explicitly released,
- * override {@link #entryRemoved}.
- *
- * <p>If a cache miss should be computed on demand for the corresponding keys,
- * override {@link #create}. This simplifies the calling code, allowing it to
- * assume a value will always be returned, even when there's a cache miss.
- *
- * <p>By default, the cache size is measured in the number of entries. Override
- * {@link #sizeOf} to size the cache in different units. For example, this cache
- * is limited to 4MiB of bitmaps:
- * <pre> {@code
- * int cacheSize = 4 * 1024 * 1024; // 4MiB
- * LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
- * protected int sizeOf(String key, Bitmap value) {
- * return value.getByteCount();
- * }
- * }}</pre>
- *
- * <p>This class is thread-safe. Perform multiple cache operations atomically by
- * synchronizing on the cache: <pre> {@code
- * synchronized (cache) {
- * if (cache.get(key) == null) {
- * cache.put(key, value);
- * }
- * }}</pre>
- *
- * <p>This class does not allow null to be used as a key or value. A return
- * value of null from {@link #get}, {@link #put} or {@link #remove} is
- * unambiguous: the key was not in the cache.
- *
- * <p>This class appeared in Android 3.1 (Honeycomb MR1); it's available as part
- * of <a href="http://developer.android.com/sdk/compatibility-library.html">Android's
- * Support Package</a> for earlier releases.
- */
-public class LruCache<K, V> {
- private final LinkedHashMap<K, V> map;
-
- /** Size of this cache in units. Not necessarily the number of elements. */
- private int size;
- private int maxSize;
-
- private int putCount;
- private int createCount;
- private int evictionCount;
- private int hitCount;
- private int missCount;
-
- /**
- * @param maxSize for caches that do not override {@link #sizeOf}, this is
- * the maximum number of entries in the cache. For all other caches,
- * this is the maximum sum of the sizes of the entries in this cache.
- */
- public LruCache(int maxSize) {
- if (maxSize <= 0) {
- throw new IllegalArgumentException("maxSize <= 0");
- }
- this.maxSize = maxSize;
- this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
- }
-
- /**
- * Sets the size of the cache.
- * @param maxSize The new maximum size.
- *
- * @hide
- */
- public void resize(int maxSize) {
- if (maxSize <= 0) {
- throw new IllegalArgumentException("maxSize <= 0");
- }
-
- synchronized (this) {
- this.maxSize = maxSize;
- }
- trimToSize(maxSize);
- }
-
- /**
- * Returns the value for {@code key} if it exists in the cache or can be
- * created by {@code #create}. If a value was returned, it is moved to the
- * head of the queue. This returns null if a value is not cached and cannot
- * be created.
- */
- public final V get(K key) {
- if (key == null) {
- throw new NullPointerException("key == null");
- }
-
- V mapValue;
- synchronized (this) {
- mapValue = map.get(key);
- if (mapValue != null) {
- hitCount++;
- return mapValue;
- }
- missCount++;
- }
-
- /*
- * Attempt to create a value. This may take a long time, and the map
- * may be different when create() returns. If a conflicting value was
- * added to the map while create() was working, we leave that value in
- * the map and release the created value.
- */
-
- V createdValue = create(key);
- if (createdValue == null) {
- return null;
- }
-
- synchronized (this) {
- createCount++;
- mapValue = map.put(key, createdValue);
-
- if (mapValue != null) {
- // There was a conflict so undo that last put
- map.put(key, mapValue);
- } else {
- size += safeSizeOf(key, createdValue);
- }
- }
-
- if (mapValue != null) {
- entryRemoved(false, key, createdValue, mapValue);
- return mapValue;
- } else {
- trimToSize(maxSize);
- return createdValue;
- }
- }
-
- /**
- * Caches {@code value} for {@code key}. The value is moved to the head of
- * the queue.
- *
- * @return the previous value mapped by {@code key}.
- */
- public final V put(K key, V value) {
- if (key == null || value == null) {
- throw new NullPointerException("key == null || value == null");
- }
-
- V previous;
- synchronized (this) {
- putCount++;
- size += safeSizeOf(key, value);
- previous = map.put(key, value);
- if (previous != null) {
- size -= safeSizeOf(key, previous);
- }
- }
-
- if (previous != null) {
- entryRemoved(false, key, previous, value);
- }
-
- trimToSize(maxSize);
- return previous;
- }
-
- /**
- * @param maxSize the maximum size of the cache before returning. May be -1
- * to evict even 0-sized elements.
- */
- private void trimToSize(int maxSize) {
- while (true) {
- K key;
- V value;
- synchronized (this) {
- if (size < 0 || (map.isEmpty() && size != 0)) {
- throw new IllegalStateException(getClass().getName()
- + ".sizeOf() is reporting inconsistent results!");
- }
-
- if (size <= maxSize) {
- break;
- }
-
- // BEGIN LAYOUTLIB CHANGE
- // get the last item in the linked list.
- // This is not efficient, the goal here is to minimize the changes
- // compared to the platform version.
- Map.Entry<K, V> toEvict = null;
- for (Map.Entry<K, V> entry : map.entrySet()) {
- toEvict = entry;
- }
- // END LAYOUTLIB CHANGE
-
- if (toEvict == null) {
- break;
- }
-
- key = toEvict.getKey();
- value = toEvict.getValue();
- map.remove(key);
- size -= safeSizeOf(key, value);
- evictionCount++;
- }
-
- entryRemoved(true, key, value, null);
- }
- }
-
- /**
- * Removes the entry for {@code key} if it exists.
- *
- * @return the previous value mapped by {@code key}.
- */
- public final V remove(K key) {
- if (key == null) {
- throw new NullPointerException("key == null");
- }
-
- V previous;
- synchronized (this) {
- previous = map.remove(key);
- if (previous != null) {
- size -= safeSizeOf(key, previous);
- }
- }
-
- if (previous != null) {
- entryRemoved(false, key, previous, null);
- }
-
- return previous;
- }
-
- /**
- * Called for entries that have been evicted or removed. This method is
- * invoked when a value is evicted to make space, removed by a call to
- * {@link #remove}, or replaced by a call to {@link #put}. The default
- * implementation does nothing.
- *
- * <p>The method is called without synchronization: other threads may
- * access the cache while this method is executing.
- *
- * @param evicted true if the entry is being removed to make space, false
- * if the removal was caused by a {@link #put} or {@link #remove}.
- * @param newValue the new value for {@code key}, if it exists. If non-null,
- * this removal was caused by a {@link #put}. Otherwise it was caused by
- * an eviction or a {@link #remove}.
- */
- protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
-
- /**
- * Called after a cache miss to compute a value for the corresponding key.
- * Returns the computed value or null if no value can be computed. The
- * default implementation returns null.
- *
- * <p>The method is called without synchronization: other threads may
- * access the cache while this method is executing.
- *
- * <p>If a value for {@code key} exists in the cache when this method
- * returns, the created value will be released with {@link #entryRemoved}
- * and discarded. This can occur when multiple threads request the same key
- * at the same time (causing multiple values to be created), or when one
- * thread calls {@link #put} while another is creating a value for the same
- * key.
- */
- protected V create(K key) {
- return null;
- }
-
- private int safeSizeOf(K key, V value) {
- int result = sizeOf(key, value);
- if (result < 0) {
- throw new IllegalStateException("Negative size: " + key + "=" + value);
- }
- return result;
- }
-
- /**
- * Returns the size of the entry for {@code key} and {@code value} in
- * user-defined units. The default implementation returns 1 so that size
- * is the number of entries and max size is the maximum number of entries.
- *
- * <p>An entry's size must not change while it is in the cache.
- */
- protected int sizeOf(K key, V value) {
- return 1;
- }
-
- /**
- * Clear the cache, calling {@link #entryRemoved} on each removed entry.
- */
- public final void evictAll() {
- trimToSize(-1); // -1 will evict 0-sized elements
- }
-
- /**
- * For caches that do not override {@link #sizeOf}, this returns the number
- * of entries in the cache. For all other caches, this returns the sum of
- * the sizes of the entries in this cache.
- */
- public synchronized final int size() {
- return size;
- }
-
- /**
- * For caches that do not override {@link #sizeOf}, this returns the maximum
- * number of entries in the cache. For all other caches, this returns the
- * maximum sum of the sizes of the entries in this cache.
- */
- public synchronized final int maxSize() {
- return maxSize;
- }
-
- /**
- * Returns the number of times {@link #get} returned a value that was
- * already present in the cache.
- */
- public synchronized final int hitCount() {
- return hitCount;
- }
-
- /**
- * Returns the number of times {@link #get} returned null or required a new
- * value to be created.
- */
- public synchronized final int missCount() {
- return missCount;
- }
-
- /**
- * Returns the number of times {@link #create(Object)} returned a value.
- */
- public synchronized final int createCount() {
- return createCount;
- }
-
- /**
- * Returns the number of times {@link #put} was called.
- */
- public synchronized final int putCount() {
- return putCount;
- }
-
- /**
- * Returns the number of values that have been evicted.
- */
- public synchronized final int evictionCount() {
- return evictionCount;
- }
-
- /**
- * Returns a copy of the current contents of the cache, ordered from least
- * recently accessed to most recently accessed.
- */
- public synchronized final Map<K, V> snapshot() {
- return new LinkedHashMap<K, V>(map);
- }
-
- @Override public synchronized final String toString() {
- int accesses = hitCount + missCount;
- int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
- return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
- maxSize, hitCount, missCount, hitPercent);
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java b/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java
deleted file mode 100644
index 7b69388..0000000
--- a/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java
+++ /dev/null
@@ -1,844 +0,0 @@
-/*
- * Copyright (C) 2015 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.util;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.annotation.NonNull;
-import android.graphics.Path_Delegate;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Delegate that provides implementation for native methods in {@link android.util.PathParser}
- * <p/>
- * Through the layoutlib_create tool, selected methods of PathParser have been replaced by calls to
- * methods of the same name in this delegate class.
- *
- * Most of the code has been taken from the implementation in
- * {@code tools/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/PathParser.java}
- * revision be6fe89a3b686db5a75e7e692a148699973957f3
- */
-public class PathParser_Delegate {
-
- private static final Logger LOGGER = Logger.getLogger("PathParser");
-
- // ---- Builder delegate manager ----
- private static final DelegateManager<PathParser_Delegate> sManager =
- new DelegateManager<PathParser_Delegate>(PathParser_Delegate.class);
-
- // ---- delegate data ----
- @NonNull
- private PathDataNode[] mPathDataNodes;
-
- public static PathParser_Delegate getDelegate(long nativePtr) {
- return sManager.getDelegate(nativePtr);
- }
-
- private PathParser_Delegate(@NonNull PathDataNode[] nodes) {
- mPathDataNodes = nodes;
- }
-
- public PathDataNode[] getPathDataNodes() {
- return mPathDataNodes;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nParseStringForPath(long pathPtr, @NonNull String pathString, int
- stringLength) {
- Path_Delegate path_delegate = Path_Delegate.getDelegate(pathPtr);
- if (path_delegate == null) {
- return;
- }
- assert pathString.length() == stringLength;
- PathDataNode.nodesToPath(createNodesFromPathData(pathString), path_delegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nCreatePathFromPathData(long outPathPtr, long pathData) {
- Path_Delegate path_delegate = Path_Delegate.getDelegate(outPathPtr);
- PathParser_Delegate source = sManager.getDelegate(outPathPtr);
- if (source == null || path_delegate == null) {
- return;
- }
- PathDataNode.nodesToPath(source.mPathDataNodes, path_delegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static long nCreateEmptyPathData() {
- PathParser_Delegate newDelegate = new PathParser_Delegate(new PathDataNode[0]);
- return sManager.addNewDelegate(newDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static long nCreatePathData(long nativePtr) {
- PathParser_Delegate source = sManager.getDelegate(nativePtr);
- if (source == null) {
- return 0;
- }
- PathParser_Delegate dest = new PathParser_Delegate(deepCopyNodes(source.mPathDataNodes));
- return sManager.addNewDelegate(dest);
- }
-
- @LayoutlibDelegate
- /*package*/ static long nCreatePathDataFromString(@NonNull String pathString,
- int stringLength) {
- assert pathString.length() == stringLength : "Inconsistent path string length.";
- PathDataNode[] nodes = createNodesFromPathData(pathString);
- PathParser_Delegate delegate = new PathParser_Delegate(nodes);
- return sManager.addNewDelegate(delegate);
-
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nInterpolatePathData(long outDataPtr, long fromDataPtr,
- long toDataPtr, float fraction) {
- PathParser_Delegate out = sManager.getDelegate(outDataPtr);
- PathParser_Delegate from = sManager.getDelegate(fromDataPtr);
- PathParser_Delegate to = sManager.getDelegate(toDataPtr);
- if (out == null || from == null || to == null) {
- return false;
- }
- int length = from.mPathDataNodes.length;
- if (length != to.mPathDataNodes.length) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Cannot interpolate path data with different lengths (from " + length + " to " +
- to.mPathDataNodes.length + ").", null);
- return false;
- }
- if (out.mPathDataNodes.length != length) {
- out.mPathDataNodes = new PathDataNode[length];
- }
- for (int i = 0; i < length; i++) {
- if (out.mPathDataNodes[i] == null) {
- out.mPathDataNodes[i] = new PathDataNode(from.mPathDataNodes[i]);
- }
- out.mPathDataNodes[i].interpolatePathDataNode(from.mPathDataNodes[i],
- to.mPathDataNodes[i], fraction);
- }
- return true;
- }
-
- @LayoutlibDelegate
- /*package*/ static void nFinalize(long nativePtr) {
- sManager.removeJavaReferenceFor(nativePtr);
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nCanMorph(long fromDataPtr, long toDataPtr) {
- PathParser_Delegate fromPath = PathParser_Delegate.getDelegate(fromDataPtr);
- PathParser_Delegate toPath = PathParser_Delegate.getDelegate(toDataPtr);
- if (fromPath == null || toPath == null || fromPath.getPathDataNodes() == null || toPath
- .getPathDataNodes() == null) {
- return true;
- }
- return PathParser_Delegate.canMorph(fromPath.getPathDataNodes(), toPath.getPathDataNodes());
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetPathData(long outDataPtr, long fromDataPtr) {
- PathParser_Delegate out = sManager.getDelegate(outDataPtr);
- PathParser_Delegate from = sManager.getDelegate(fromDataPtr);
- if (from == null || out == null) {
- return;
- }
- out.mPathDataNodes = deepCopyNodes(from.mPathDataNodes);
- }
-
- /**
- * @param pathData The string representing a path, the same as "d" string in svg file.
- *
- * @return an array of the PathDataNode.
- */
- @NonNull
- public static PathDataNode[] createNodesFromPathData(@NonNull String pathData) {
- int start = 0;
- int end = 1;
-
- ArrayList<PathDataNode> list = new ArrayList<PathDataNode>();
- while (end < pathData.length()) {
- end = nextStart(pathData, end);
- String s = pathData.substring(start, end).trim();
- if (s.length() > 0) {
- float[] val = getFloats(s);
- addNode(list, s.charAt(0), val);
- }
-
- start = end;
- end++;
- }
- if ((end - start) == 1 && start < pathData.length()) {
- addNode(list, pathData.charAt(start), new float[0]);
- }
- return list.toArray(new PathDataNode[list.size()]);
- }
-
- /**
- * @param source The array of PathDataNode to be duplicated.
- *
- * @return a deep copy of the <code>source</code>.
- */
- @NonNull
- public static PathDataNode[] deepCopyNodes(@NonNull PathDataNode[] source) {
- PathDataNode[] copy = new PathDataNode[source.length];
- for (int i = 0; i < source.length; i++) {
- copy[i] = new PathDataNode(source[i]);
- }
- return copy;
- }
-
- /**
- * @param nodesFrom The source path represented in an array of PathDataNode
- * @param nodesTo The target path represented in an array of PathDataNode
- * @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code>
- */
- public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) {
- if (nodesFrom == null || nodesTo == null) {
- return false;
- }
-
- if (nodesFrom.length != nodesTo.length) {
- return false;
- }
-
- for (int i = 0; i < nodesFrom.length; i ++) {
- if (nodesFrom[i].mType != nodesTo[i].mType
- || nodesFrom[i].mParams.length != nodesTo[i].mParams.length) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Update the target's data to match the source.
- * Before calling this, make sure canMorph(target, source) is true.
- *
- * @param target The target path represented in an array of PathDataNode
- * @param source The source path represented in an array of PathDataNode
- */
- public static void updateNodes(PathDataNode[] target, PathDataNode[] source) {
- for (int i = 0; i < source.length; i ++) {
- target[i].mType = source[i].mType;
- for (int j = 0; j < source[i].mParams.length; j ++) {
- target[i].mParams[j] = source[i].mParams[j];
- }
- }
- }
-
- private static int nextStart(@NonNull String s, int end) {
- char c;
-
- while (end < s.length()) {
- c = s.charAt(end);
- // Note that 'e' or 'E' are not valid path commands, but could be
- // used for floating point numbers' scientific notation.
- // Therefore, when searching for next command, we should ignore 'e'
- // and 'E'.
- if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
- && c != 'e' && c != 'E') {
- return end;
- }
- end++;
- }
- return end;
- }
-
- /**
- * Calculate the position of the next comma or space or negative sign
- *
- * @param s the string to search
- * @param start the position to start searching
- * @param result the result of the extraction, including the position of the the starting
- * position of next number, whether it is ending with a '-'.
- */
- private static void extract(@NonNull String s, int start, @NonNull ExtractFloatResult result) {
- // Now looking for ' ', ',', '.' or '-' from the start.
- int currentIndex = start;
- boolean foundSeparator = false;
- result.mEndWithNegOrDot = false;
- boolean secondDot = false;
- boolean isExponential = false;
- for (; currentIndex < s.length(); currentIndex++) {
- boolean isPrevExponential = isExponential;
- isExponential = false;
- char currentChar = s.charAt(currentIndex);
- switch (currentChar) {
- case ' ':
- case ',':
- foundSeparator = true;
- break;
- case '-':
- // The negative sign following a 'e' or 'E' is not a separator.
- if (currentIndex != start && !isPrevExponential) {
- foundSeparator = true;
- result.mEndWithNegOrDot = true;
- }
- break;
- case '.':
- if (!secondDot) {
- secondDot = true;
- } else {
- // This is the second dot, and it is considered as a separator.
- foundSeparator = true;
- result.mEndWithNegOrDot = true;
- }
- break;
- case 'e':
- case 'E':
- isExponential = true;
- break;
- }
- if (foundSeparator) {
- break;
- }
- }
- // When there is nothing found, then we put the end position to the end
- // of the string.
- result.mEndPosition = currentIndex;
- }
-
- /**
- * Parse the floats in the string. This is an optimized version of
- * parseFloat(s.split(",|\\s"));
- *
- * @param s the string containing a command and list of floats
- *
- * @return array of floats
- */
- @NonNull
- private static float[] getFloats(@NonNull String s) {
- if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') {
- return new float[0];
- }
- try {
- float[] results = new float[s.length()];
- int count = 0;
- int startPosition = 1;
- int endPosition;
-
- ExtractFloatResult result = new ExtractFloatResult();
- int totalLength = s.length();
-
- // The startPosition should always be the first character of the
- // current number, and endPosition is the character after the current
- // number.
- while (startPosition < totalLength) {
- extract(s, startPosition, result);
- endPosition = result.mEndPosition;
-
- if (startPosition < endPosition) {
- results[count++] = Float.parseFloat(
- s.substring(startPosition, endPosition));
- }
-
- if (result.mEndWithNegOrDot) {
- // Keep the '-' or '.' sign with next number.
- startPosition = endPosition;
- } else {
- startPosition = endPosition + 1;
- }
- }
- return Arrays.copyOf(results, count);
- } catch (NumberFormatException e) {
- assert false : "error in parsing \"" + s + "\"" + e;
- return new float[0];
- }
- }
-
-
- private static void addNode(@NonNull ArrayList<PathDataNode> list, char cmd,
- @NonNull float[] val) {
- list.add(new PathDataNode(cmd, val));
- }
-
- private static class ExtractFloatResult {
- // We need to return the position of the next separator and whether the
- // next float starts with a '-' or a '.'.
- private int mEndPosition;
- private boolean mEndWithNegOrDot;
- }
-
- /**
- * Each PathDataNode represents one command in the "d" attribute of the svg file. An array of
- * PathDataNode can represent the whole "d" attribute.
- */
- public static class PathDataNode {
- private char mType;
- @NonNull
- private float[] mParams;
-
- private PathDataNode(char type, @NonNull float[] params) {
- mType = type;
- mParams = params;
- }
-
- public char getType() {
- return mType;
- }
-
- @NonNull
- public float[] getParams() {
- return mParams;
- }
-
- private PathDataNode(@NonNull PathDataNode n) {
- mType = n.mType;
- mParams = Arrays.copyOf(n.mParams, n.mParams.length);
- }
-
- /**
- * Convert an array of PathDataNode to Path. Reset the passed path as needed before
- * calling this method.
- *
- * @param node The source array of PathDataNode.
- * @param path The target Path object.
- */
- public static void nodesToPath(@NonNull PathDataNode[] node, @NonNull Path_Delegate path) {
- float[] current = new float[6];
- char previousCommand = 'm';
- //noinspection ForLoopReplaceableByForEach
- for (int i = 0; i < node.length; i++) {
- addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
- previousCommand = node[i].mType;
- }
- }
-
- /**
- * The current PathDataNode will be interpolated between the <code>nodeFrom</code> and
- * <code>nodeTo</code> according to the <code>fraction</code>.
- *
- * @param nodeFrom The start value as a PathDataNode.
- * @param nodeTo The end value as a PathDataNode
- * @param fraction The fraction to interpolate.
- */
- private void interpolatePathDataNode(@NonNull PathDataNode nodeFrom,
- @NonNull PathDataNode nodeTo, float fraction) {
- for (int i = 0; i < nodeFrom.mParams.length; i++) {
- mParams[i] = nodeFrom.mParams[i] * (1 - fraction)
- + nodeTo.mParams[i] * fraction;
- }
- }
-
- @SuppressWarnings("PointlessArithmeticExpression")
- private static void addCommand(@NonNull Path_Delegate path, float[] current,
- char previousCmd, char cmd, @NonNull float[] val) {
-
- int incr = 2;
- float currentX = current[0];
- float currentY = current[1];
- float ctrlPointX = current[2];
- float ctrlPointY = current[3];
- float currentSegmentStartX = current[4];
- float currentSegmentStartY = current[5];
- float reflectiveCtrlPointX;
- float reflectiveCtrlPointY;
-
- switch (cmd) {
- case 'z':
- case 'Z':
- path.close();
- // Path is closed here, but we need to move the pen to the
- // closed position. So we cache the segment's starting position,
- // and restore it here.
- currentX = currentSegmentStartX;
- currentY = currentSegmentStartY;
- ctrlPointX = currentSegmentStartX;
- ctrlPointY = currentSegmentStartY;
- path.moveTo(currentX, currentY);
- break;
- case 'm':
- case 'M':
- case 'l':
- case 'L':
- case 't':
- case 'T':
- incr = 2;
- break;
- case 'h':
- case 'H':
- case 'v':
- case 'V':
- incr = 1;
- break;
- case 'c':
- case 'C':
- incr = 6;
- break;
- case 's':
- case 'S':
- case 'q':
- case 'Q':
- incr = 4;
- break;
- case 'a':
- case 'A':
- incr = 7;
- break;
- }
-
- for (int k = 0; k < val.length; k += incr) {
- switch (cmd) {
- case 'm': // moveto - Start a new sub-path (relative)
- currentX += val[k + 0];
- currentY += val[k + 1];
-
- if (k > 0) {
- // According to the spec, if a moveto is followed by multiple
- // pairs of coordinates, the subsequent pairs are treated as
- // implicit lineto commands.
- path.rLineTo(val[k + 0], val[k + 1]);
- } else {
- path.rMoveTo(val[k + 0], val[k + 1]);
- currentSegmentStartX = currentX;
- currentSegmentStartY = currentY;
- }
- break;
- case 'M': // moveto - Start a new sub-path
- currentX = val[k + 0];
- currentY = val[k + 1];
-
- if (k > 0) {
- // According to the spec, if a moveto is followed by multiple
- // pairs of coordinates, the subsequent pairs are treated as
- // implicit lineto commands.
- path.lineTo(val[k + 0], val[k + 1]);
- } else {
- path.moveTo(val[k + 0], val[k + 1]);
- currentSegmentStartX = currentX;
- currentSegmentStartY = currentY;
- }
- break;
- case 'l': // lineto - Draw a line from the current point (relative)
- path.rLineTo(val[k + 0], val[k + 1]);
- currentX += val[k + 0];
- currentY += val[k + 1];
- break;
- case 'L': // lineto - Draw a line from the current point
- path.lineTo(val[k + 0], val[k + 1]);
- currentX = val[k + 0];
- currentY = val[k + 1];
- break;
- case 'h': // horizontal lineto - Draws a horizontal line (relative)
- path.rLineTo(val[k + 0], 0);
- currentX += val[k + 0];
- break;
- case 'H': // horizontal lineto - Draws a horizontal line
- path.lineTo(val[k + 0], currentY);
- currentX = val[k + 0];
- break;
- case 'v': // vertical lineto - Draws a vertical line from the current point (r)
- path.rLineTo(0, val[k + 0]);
- currentY += val[k + 0];
- break;
- case 'V': // vertical lineto - Draws a vertical line from the current point
- path.lineTo(currentX, val[k + 0]);
- currentY = val[k + 0];
- break;
- case 'c': // curveto - Draws a cubic Bézier curve (relative)
- path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
- val[k + 4], val[k + 5]);
-
- ctrlPointX = currentX + val[k + 2];
- ctrlPointY = currentY + val[k + 3];
- currentX += val[k + 4];
- currentY += val[k + 5];
-
- break;
- case 'C': // curveto - Draws a cubic Bézier curve
- path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
- val[k + 4], val[k + 5]);
- currentX = val[k + 4];
- currentY = val[k + 5];
- ctrlPointX = val[k + 2];
- ctrlPointY = val[k + 3];
- break;
- case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
- reflectiveCtrlPointX = 0;
- reflectiveCtrlPointY = 0;
- if (previousCmd == 'c' || previousCmd == 's'
- || previousCmd == 'C' || previousCmd == 'S') {
- reflectiveCtrlPointX = currentX - ctrlPointX;
- reflectiveCtrlPointY = currentY - ctrlPointY;
- }
- path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
- val[k + 0], val[k + 1],
- val[k + 2], val[k + 3]);
-
- ctrlPointX = currentX + val[k + 0];
- ctrlPointY = currentY + val[k + 1];
- currentX += val[k + 2];
- currentY += val[k + 3];
- break;
- case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
- reflectiveCtrlPointX = currentX;
- reflectiveCtrlPointY = currentY;
- if (previousCmd == 'c' || previousCmd == 's'
- || previousCmd == 'C' || previousCmd == 'S') {
- reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
- reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
- }
- path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
- val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
- ctrlPointX = val[k + 0];
- ctrlPointY = val[k + 1];
- currentX = val[k + 2];
- currentY = val[k + 3];
- break;
- case 'q': // Draws a quadratic Bézier (relative)
- path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
- ctrlPointX = currentX + val[k + 0];
- ctrlPointY = currentY + val[k + 1];
- currentX += val[k + 2];
- currentY += val[k + 3];
- break;
- case 'Q': // Draws a quadratic Bézier
- path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
- ctrlPointX = val[k + 0];
- ctrlPointY = val[k + 1];
- currentX = val[k + 2];
- currentY = val[k + 3];
- break;
- case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
- reflectiveCtrlPointX = 0;
- reflectiveCtrlPointY = 0;
- if (previousCmd == 'q' || previousCmd == 't'
- || previousCmd == 'Q' || previousCmd == 'T') {
- reflectiveCtrlPointX = currentX - ctrlPointX;
- reflectiveCtrlPointY = currentY - ctrlPointY;
- }
- path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
- val[k + 0], val[k + 1]);
- ctrlPointX = currentX + reflectiveCtrlPointX;
- ctrlPointY = currentY + reflectiveCtrlPointY;
- currentX += val[k + 0];
- currentY += val[k + 1];
- break;
- case 'T': // Draws a quadratic Bézier curve (reflective control point)
- reflectiveCtrlPointX = currentX;
- reflectiveCtrlPointY = currentY;
- if (previousCmd == 'q' || previousCmd == 't'
- || previousCmd == 'Q' || previousCmd == 'T') {
- reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
- reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
- }
- path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
- val[k + 0], val[k + 1]);
- ctrlPointX = reflectiveCtrlPointX;
- ctrlPointY = reflectiveCtrlPointY;
- currentX = val[k + 0];
- currentY = val[k + 1];
- break;
- case 'a': // Draws an elliptical arc
- // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
- drawArc(path,
- currentX,
- currentY,
- val[k + 5] + currentX,
- val[k + 6] + currentY,
- val[k + 0],
- val[k + 1],
- val[k + 2],
- val[k + 3] != 0,
- val[k + 4] != 0);
- currentX += val[k + 5];
- currentY += val[k + 6];
- ctrlPointX = currentX;
- ctrlPointY = currentY;
- break;
- case 'A': // Draws an elliptical arc
- drawArc(path,
- currentX,
- currentY,
- val[k + 5],
- val[k + 6],
- val[k + 0],
- val[k + 1],
- val[k + 2],
- val[k + 3] != 0,
- val[k + 4] != 0);
- currentX = val[k + 5];
- currentY = val[k + 6];
- ctrlPointX = currentX;
- ctrlPointY = currentY;
- break;
- }
- previousCmd = cmd;
- }
- current[0] = currentX;
- current[1] = currentY;
- current[2] = ctrlPointX;
- current[3] = ctrlPointY;
- current[4] = currentSegmentStartX;
- current[5] = currentSegmentStartY;
- }
-
- private static void drawArc(@NonNull Path_Delegate p, float x0, float y0, float x1,
- float y1, float a, float b, float theta, boolean isMoreThanHalf,
- boolean isPositiveArc) {
-
- LOGGER.log(Level.FINE, "(" + x0 + "," + y0 + ")-(" + x1 + "," + y1
- + ") {" + a + " " + b + "}");
- /* Convert rotation angle from degrees to radians */
- double thetaD = theta * Math.PI / 180.0f;
- /* Pre-compute rotation matrix entries */
- double cosTheta = Math.cos(thetaD);
- double sinTheta = Math.sin(thetaD);
- /* Transform (x0, y0) and (x1, y1) into unit space */
- /* using (inverse) rotation, followed by (inverse) scale */
- double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
- double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
- double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
- double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
- LOGGER.log(Level.FINE, "unit space (" + x0p + "," + y0p + ")-(" + x1p
- + "," + y1p + ")");
- /* Compute differences and averages */
- double dx = x0p - x1p;
- double dy = y0p - y1p;
- double xm = (x0p + x1p) / 2;
- double ym = (y0p + y1p) / 2;
- /* Solve for intersecting unit circles */
- double dsq = dx * dx + dy * dy;
- if (dsq == 0.0) {
- LOGGER.log(Level.FINE, " Points are coincident");
- return; /* Points are coincident */
- }
- double disc = 1.0 / dsq - 1.0 / 4.0;
- if (disc < 0.0) {
- LOGGER.log(Level.FINE, "Points are too far apart " + dsq);
- float adjust = (float) (Math.sqrt(dsq) / 1.99999);
- drawArc(p, x0, y0, x1, y1, a * adjust, b * adjust, theta,
- isMoreThanHalf, isPositiveArc);
- return; /* Points are too far apart */
- }
- double s = Math.sqrt(disc);
- double sdx = s * dx;
- double sdy = s * dy;
- double cx;
- double cy;
- if (isMoreThanHalf == isPositiveArc) {
- cx = xm - sdy;
- cy = ym + sdx;
- } else {
- cx = xm + sdy;
- cy = ym - sdx;
- }
-
- double eta0 = Math.atan2((y0p - cy), (x0p - cx));
- LOGGER.log(Level.FINE, "eta0 = Math.atan2( " + (y0p - cy) + " , "
- + (x0p - cx) + ") = " + Math.toDegrees(eta0));
-
- double eta1 = Math.atan2((y1p - cy), (x1p - cx));
- LOGGER.log(Level.FINE, "eta1 = Math.atan2( " + (y1p - cy) + " , "
- + (x1p - cx) + ") = " + Math.toDegrees(eta1));
- double sweep = (eta1 - eta0);
- if (isPositiveArc != (sweep >= 0)) {
- if (sweep > 0) {
- sweep -= 2 * Math.PI;
- } else {
- sweep += 2 * Math.PI;
- }
- }
-
- cx *= a;
- cy *= b;
- double tcx = cx;
- cx = cx * cosTheta - cy * sinTheta;
- cy = tcx * sinTheta + cy * cosTheta;
- LOGGER.log(
- Level.FINE,
- "cx, cy, a, b, x0, y0, thetaD, eta0, sweep = " + cx + " , "
- + cy + " , " + a + " , " + b + " , " + x0 + " , " + y0
- + " , " + Math.toDegrees(thetaD) + " , "
- + Math.toDegrees(eta0) + " , " + Math.toDegrees(sweep));
-
- arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
- }
-
- /**
- * Converts an arc to cubic Bezier segments and records them in p.
- *
- * @param p The target for the cubic Bezier segments
- * @param cx The x coordinate center of the ellipse
- * @param cy The y coordinate center of the ellipse
- * @param a The radius of the ellipse in the horizontal direction
- * @param b The radius of the ellipse in the vertical direction
- * @param e1x E(eta1) x coordinate of the starting point of the arc
- * @param e1y E(eta2) y coordinate of the starting point of the arc
- * @param theta The angle that the ellipse bounding rectangle makes with the horizontal
- * plane
- * @param start The start angle of the arc on the ellipse
- * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
- */
- private static void arcToBezier(@NonNull Path_Delegate p, double cx, double cy, double a,
- double b, double e1x, double e1y, double theta, double start,
- double sweep) {
- // Taken from equations at:
- // http://spaceroots.org/documents/ellipse/node8.html
- // and http://www.spaceroots.org/documents/ellipse/node22.html
- // Maximum of 45 degrees per cubic Bezier segment
- int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
-
-
- double eta1 = start;
- double cosTheta = Math.cos(theta);
- double sinTheta = Math.sin(theta);
- double cosEta1 = Math.cos(eta1);
- double sinEta1 = Math.sin(eta1);
- double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
- double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
-
- double anglePerSegment = sweep / numSegments;
- for (int i = 0; i < numSegments; i++) {
- double eta2 = eta1 + anglePerSegment;
- double sinEta2 = Math.sin(eta2);
- double cosEta2 = Math.cos(eta2);
- double e2x = cx + (a * cosTheta * cosEta2)
- - (b * sinTheta * sinEta2);
- double e2y = cy + (a * sinTheta * cosEta2)
- + (b * cosTheta * sinEta2);
- double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
- double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
- double tanDiff2 = Math.tan((eta2 - eta1) / 2);
- double alpha = Math.sin(eta2 - eta1)
- * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
- double q1x = e1x + alpha * ep1x;
- double q1y = e1y + alpha * ep1y;
- double q2x = e2x - alpha * ep2x;
- double q2y = e2y - alpha * ep2y;
-
- p.cubicTo((float) q1x,
- (float) q1y,
- (float) q2x,
- (float) q2y,
- (float) e2x,
- (float) e2y);
- eta1 = eta2;
- e1x = e2x;
- e1y = e2y;
- ep1x = ep2x;
- ep1y = ep2y;
- }
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/util/Xml_Delegate.java b/tools/layoutlib/bridge/src/android/util/Xml_Delegate.java
deleted file mode 100644
index 213e848..0000000
--- a/tools/layoutlib/bridge/src/android/util/Xml_Delegate.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2014 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.util;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.layoutlib.bridge.impl.ParserFactory;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-/**
- * Delegate overriding some methods of android.util.Xml
- *
- * Through the layoutlib_create tool, the original methods of Xml have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
- * around to map int to instance of the delegate.
- */
-public class Xml_Delegate {
-
- @LayoutlibDelegate
- /*package*/ static XmlPullParser newPullParser() {
- try {
- return ParserFactory.instantiateParser(null);
- } catch (XmlPullParserException e) {
- throw new AssertionError();
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java b/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java
deleted file mode 100644
index 4445a22..0000000
--- a/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import com.android.layoutlib.bridge.android.BridgeWindow;
-import com.android.layoutlib.bridge.android.BridgeWindowSession;
-
-import android.content.Context;
-import android.os.Handler;
-import android.view.View.AttachInfo;
-
-/**
- * Class allowing access to package-protected methods/fields.
- */
-public class AttachInfo_Accessor {
-
- public static void setAttachInfo(View view) {
- Context context = view.getContext();
- WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
- Display display = wm.getDefaultDisplay();
- ViewRootImpl root = new ViewRootImpl(context, display);
- AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(),
- display, root, new Handler(), null, context);
- info.mHasWindowFocus = true;
- info.mWindowVisibility = View.VISIBLE;
- info.mInTouchMode = false; // this is so that we can display selections.
- info.mHardwareAccelerated = false;
- view.dispatchAttachedToWindow(info, 0);
- }
-
- public static void dispatchOnPreDraw(View view) {
- view.mAttachInfo.mTreeObserver.dispatchOnPreDraw();
- }
-
- public static void detachFromWindow(View view) {
- if (view != null) {
- view.dispatchDetachedFromWindow();
- }
- }
-
- public static ViewRootImpl getRootView(View view) {
- return view.mAttachInfo != null ? view.mAttachInfo.mViewRootImpl : null;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
deleted file mode 100644
index b6e6ec0..0000000
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ /dev/null
@@ -1,496 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.LayoutlibCallback;
-import com.android.ide.common.rendering.api.MergeCookie;
-import com.android.ide.common.rendering.api.ResourceReference;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.BridgeConstants;
-import com.android.layoutlib.bridge.MockView;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
-import com.android.layoutlib.bridge.android.support.DrawerLayoutUtil;
-import com.android.layoutlib.bridge.android.support.RecyclerViewUtil;
-import com.android.layoutlib.bridge.impl.ParserFactory;
-import com.android.layoutlib.bridge.util.ReflectionUtils;
-import com.android.resources.ResourceType;
-import com.android.util.Pair;
-
-import org.xmlpull.v1.XmlPullParser;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.drawable.Animatable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-import android.widget.NumberPicker;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import static com.android.SdkConstants.AUTO_COMPLETE_TEXT_VIEW;
-import static com.android.SdkConstants.BUTTON;
-import static com.android.SdkConstants.CHECKED_TEXT_VIEW;
-import static com.android.SdkConstants.CHECK_BOX;
-import static com.android.SdkConstants.EDIT_TEXT;
-import static com.android.SdkConstants.IMAGE_BUTTON;
-import static com.android.SdkConstants.IMAGE_VIEW;
-import static com.android.SdkConstants.MULTI_AUTO_COMPLETE_TEXT_VIEW;
-import static com.android.SdkConstants.RADIO_BUTTON;
-import static com.android.SdkConstants.SEEK_BAR;
-import static com.android.SdkConstants.SPINNER;
-import static com.android.SdkConstants.TEXT_VIEW;
-import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext;
-
-/**
- * Custom implementation of {@link LayoutInflater} to handle custom views.
- */
-public final class BridgeInflater extends LayoutInflater {
-
- private final LayoutlibCallback mLayoutlibCallback;
- /**
- * If true, the inflater will try to replace the framework widgets with the AppCompat versions.
- * Ideally, this should be based on the activity being an AppCompat activity but since that is
- * not trivial to check from layoutlib, we currently base the decision on the current theme
- * being an AppCompat theme.
- */
- private boolean mLoadAppCompatViews;
- /**
- * This set contains the framework views that have an AppCompat version but failed to load.
- * This might happen because not all widgets are contained in all versions of the support
- * library.
- * This will help us to avoid trying to load the AppCompat version multiple times if it
- * doesn't exist.
- */
- private Set<String> mFailedAppCompatViews = new HashSet<>();
- private boolean mIsInMerge = false;
- private ResourceReference mResourceReference;
- private Map<View, String> mOpenDrawerLayouts;
-
- // Keep in sync with the same value in LayoutInflater.
- private static final int[] ATTRS_THEME = new int[] {com.android.internal.R.attr.theme };
-
- private static final String APPCOMPAT_WIDGET_PREFIX = "android.support.v7.widget.AppCompat";
- /** List of platform widgets that have an AppCompat version */
- private static final Set<String> APPCOMPAT_VIEWS = Collections.unmodifiableSet(
- new HashSet<>(
- Arrays.asList(TEXT_VIEW, IMAGE_VIEW, BUTTON, EDIT_TEXT, SPINNER,
- IMAGE_BUTTON, CHECK_BOX, RADIO_BUTTON, CHECKED_TEXT_VIEW,
- AUTO_COMPLETE_TEXT_VIEW, MULTI_AUTO_COMPLETE_TEXT_VIEW, "RatingBar",
- SEEK_BAR)));
-
- /**
- * List of class prefixes which are tried first by default.
- * <p/>
- * This should match the list in com.android.internal.policy.impl.PhoneLayoutInflater.
- */
- private static final String[] sClassPrefixList = {
- "android.widget.",
- "android.webkit.",
- "android.app."
- };
-
- public static String[] getClassPrefixList() {
- return sClassPrefixList;
- }
-
- private BridgeInflater(LayoutInflater original, Context newContext) {
- super(original, newContext);
- newContext = getBaseContext(newContext);
- if (newContext instanceof BridgeContext) {
- mLayoutlibCallback = ((BridgeContext) newContext).getLayoutlibCallback();
- mLoadAppCompatViews = ((BridgeContext) newContext).isAppCompatTheme();
- } else {
- mLayoutlibCallback = null;
- mLoadAppCompatViews = false;
- }
- }
-
- /**
- * Instantiate a new BridgeInflater with an {@link LayoutlibCallback} object.
- *
- * @param context The Android application context.
- * @param layoutlibCallback the {@link LayoutlibCallback} object.
- */
- public BridgeInflater(BridgeContext context, LayoutlibCallback layoutlibCallback) {
- super(context);
- mLayoutlibCallback = layoutlibCallback;
- mConstructorArgs[0] = context;
- mLoadAppCompatViews = context.isAppCompatTheme();
- }
-
- @Override
- public View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
- View view = null;
-
- try {
- if (mLoadAppCompatViews
- && APPCOMPAT_VIEWS.contains(name)
- && !mFailedAppCompatViews.contains(name)) {
- // We are using an AppCompat theme so try to load the appcompat views
- view = loadCustomView(APPCOMPAT_WIDGET_PREFIX + name, attrs, true);
-
- if (view == null) {
- mFailedAppCompatViews.add(name); // Do not try this one anymore
- }
- }
-
- if (view == null) {
- // First try to find a class using the default Android prefixes
- for (String prefix : sClassPrefixList) {
- try {
- view = createView(name, prefix, attrs);
- if (view != null) {
- break;
- }
- } catch (ClassNotFoundException e) {
- // Ignore. We'll try again using the base class below.
- }
- }
-
- // Next try using the parent loader. This will most likely only work for
- // fully-qualified class names.
- try {
- if (view == null) {
- view = super.onCreateView(name, attrs);
- }
- } catch (ClassNotFoundException e) {
- // Ignore. We'll try again using the custom view loader below.
- }
- }
-
- // Finally try again using the custom view loader
- if (view == null) {
- view = loadCustomView(name, attrs);
- }
- } catch (InflateException e) {
- // Don't catch the InflateException below as that results in hiding the real cause.
- throw e;
- } catch (Exception e) {
- // Wrap the real exception in a ClassNotFoundException, so that the calling method
- // can deal with it.
- throw new ClassNotFoundException("onCreateView", e);
- }
-
- setupViewInContext(view, attrs);
-
- return view;
- }
-
- @Override
- public View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
- boolean ignoreThemeAttr) {
- View view = null;
- if (name.equals("view")) {
- // This is usually done by the superclass but this allows us catching the error and
- // reporting something useful.
- name = attrs.getAttributeValue(null, "class");
-
- if (name == null) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Unable to inflate view tag without " +
- "class attribute", null);
- // We weren't able to resolve the view so we just pass a mock View to be able to
- // continue rendering.
- view = new MockView(context, attrs);
- ((MockView) view).setText("view");
- }
- }
-
- try {
- if (view == null) {
- view = super.createViewFromTag(parent, name, context, attrs, ignoreThemeAttr);
- }
- } catch (InflateException e) {
- // Creation of ContextThemeWrapper code is same as in the super method.
- // Apply a theme wrapper, if allowed and one is specified.
- if (!ignoreThemeAttr) {
- final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
- final int themeResId = ta.getResourceId(0, 0);
- if (themeResId != 0) {
- context = new ContextThemeWrapper(context, themeResId);
- }
- ta.recycle();
- }
- if (!(e.getCause() instanceof ClassNotFoundException)) {
- // There is some unknown inflation exception in inflating a View that was found.
- view = new MockView(context, attrs);
- ((MockView) view).setText(name);
- Bridge.getLog().error(LayoutLog.TAG_BROKEN, e.getMessage(), e, null);
- } else {
- final Object lastContext = mConstructorArgs[0];
- mConstructorArgs[0] = context;
- // try to load the class from using the custom view loader
- try {
- view = loadCustomView(name, attrs);
- } catch (Exception e2) {
- // Wrap the real exception in an InflateException so that the calling
- // method can deal with it.
- InflateException exception = new InflateException();
- if (!e2.getClass().equals(ClassNotFoundException.class)) {
- exception.initCause(e2);
- } else {
- exception.initCause(e);
- }
- throw exception;
- } finally {
- mConstructorArgs[0] = lastContext;
- }
- }
- }
-
- setupViewInContext(view, attrs);
-
- return view;
- }
-
- @Override
- public View inflate(int resource, ViewGroup root) {
- Context context = getContext();
- context = getBaseContext(context);
- if (context instanceof BridgeContext) {
- BridgeContext bridgeContext = (BridgeContext)context;
-
- ResourceValue value = null;
-
- @SuppressWarnings("deprecation")
- Pair<ResourceType, String> layoutInfo = Bridge.resolveResourceId(resource);
- if (layoutInfo != null) {
- value = bridgeContext.getRenderResources().getFrameworkResource(
- ResourceType.LAYOUT, layoutInfo.getSecond());
- } else {
- layoutInfo = mLayoutlibCallback.resolveResourceId(resource);
-
- if (layoutInfo != null) {
- value = bridgeContext.getRenderResources().getProjectResource(
- ResourceType.LAYOUT, layoutInfo.getSecond());
- }
- }
-
- if (value != null) {
- File f = new File(value.getValue());
- if (f.isFile()) {
- try {
- XmlPullParser parser = ParserFactory.create(f, true);
-
- BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
- parser, bridgeContext, value.isFramework());
-
- return inflate(bridgeParser, root);
- } catch (Exception e) {
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
- "Failed to parse file " + f.getAbsolutePath(), e, null);
-
- return null;
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Instantiates the given view name and returns the instance. If the view doesn't exist, a
- * MockView or null might be returned.
- * @param name the custom view name
- * @param attrs the {@link AttributeSet} to be passed to the view constructor
- * @param silent if true, errors while loading the view won't be reported and, if the view
- * doesn't exist, null will be returned.
- */
- private View loadCustomView(String name, AttributeSet attrs, boolean silent) throws Exception {
- if (mLayoutlibCallback != null) {
- // first get the classname in case it's not the node name
- if (name.equals("view")) {
- name = attrs.getAttributeValue(null, "class");
- if (name == null) {
- return null;
- }
- }
-
- mConstructorArgs[1] = attrs;
-
- Object customView = silent ?
- mLayoutlibCallback.loadClass(name, mConstructorSignature, mConstructorArgs)
- : mLayoutlibCallback.loadView(name, mConstructorSignature, mConstructorArgs);
-
- if (customView instanceof View) {
- return (View)customView;
- }
- }
-
- return null;
- }
-
- private View loadCustomView(String name, AttributeSet attrs) throws Exception {
- return loadCustomView(name, attrs, false);
- }
-
- private void setupViewInContext(View view, AttributeSet attrs) {
- Context context = getContext();
- context = getBaseContext(context);
- if (context instanceof BridgeContext) {
- BridgeContext bc = (BridgeContext) context;
- // get the view key
- Object viewKey = getViewKeyFromParser(attrs, bc, mResourceReference, mIsInMerge);
- if (viewKey != null) {
- bc.addViewKey(view, viewKey);
- }
- String scrollPosX = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollX");
- if (scrollPosX != null && scrollPosX.endsWith("px")) {
- int value = Integer.parseInt(scrollPosX.substring(0, scrollPosX.length() - 2));
- bc.setScrollXPos(view, value);
- }
- String scrollPosY = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollY");
- if (scrollPosY != null && scrollPosY.endsWith("px")) {
- int value = Integer.parseInt(scrollPosY.substring(0, scrollPosY.length() - 2));
- bc.setScrollYPos(view, value);
- }
- if (ReflectionUtils.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) {
- Integer resourceId = null;
- String attrVal = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI,
- BridgeConstants.ATTR_LIST_ITEM);
- if (attrVal != null && !attrVal.isEmpty()) {
- ResourceValue resValue = bc.getRenderResources().findResValue(attrVal, false);
- if (resValue.isFramework()) {
- resourceId = Bridge.getResourceId(resValue.getResourceType(),
- resValue.getName());
- } else {
- resourceId = mLayoutlibCallback.getResourceId(resValue.getResourceType(),
- resValue.getName());
- }
- }
- if (resourceId == null) {
- resourceId = 0;
- }
- RecyclerViewUtil.setAdapter(view, bc, mLayoutlibCallback, resourceId);
- } else if (ReflectionUtils.isInstanceOf(view, DrawerLayoutUtil.CN_DRAWER_LAYOUT)) {
- String attrVal = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI,
- BridgeConstants.ATTR_OPEN_DRAWER);
- if (attrVal != null) {
- getDrawerLayoutMap().put(view, attrVal);
- }
- }
- else if (view instanceof NumberPicker) {
- NumberPicker numberPicker = (NumberPicker) view;
- String minValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, "minValue");
- if (minValue != null) {
- numberPicker.setMinValue(Integer.parseInt(minValue));
- }
- String maxValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, "maxValue");
- if (maxValue != null) {
- numberPicker.setMaxValue(Integer.parseInt(maxValue));
- }
- }
- else if (view instanceof ImageView) {
- ImageView img = (ImageView) view;
- Drawable drawable = img.getDrawable();
- if (drawable instanceof Animatable) {
- if (!((Animatable) drawable).isRunning()) {
- ((Animatable) drawable).start();
- }
- }
- }
-
- }
- }
-
- public void setIsInMerge(boolean isInMerge) {
- mIsInMerge = isInMerge;
- }
-
- public void setResourceReference(ResourceReference reference) {
- mResourceReference = reference;
- }
-
- @Override
- public LayoutInflater cloneInContext(Context newContext) {
- return new BridgeInflater(this, newContext);
- }
-
- /*package*/ static Object getViewKeyFromParser(AttributeSet attrs, BridgeContext bc,
- ResourceReference resourceReference, boolean isInMerge) {
-
- if (!(attrs instanceof BridgeXmlBlockParser)) {
- return null;
- }
- BridgeXmlBlockParser parser = ((BridgeXmlBlockParser) attrs);
-
- // get the view key
- Object viewKey = parser.getViewCookie();
-
- if (viewKey == null) {
- int currentDepth = parser.getDepth();
-
- // test whether we are in an included file or in a adapter binding view.
- BridgeXmlBlockParser previousParser = bc.getPreviousParser();
- if (previousParser != null) {
- // looks like we are inside an embedded layout.
- // only apply the cookie of the calling node (<include>) if we are at the
- // top level of the embedded layout. If there is a merge tag, then
- // skip it and look for the 2nd level
- int testDepth = isInMerge ? 2 : 1;
- if (currentDepth == testDepth) {
- viewKey = previousParser.getViewCookie();
- // if we are in a merge, wrap the cookie in a MergeCookie.
- if (viewKey != null && isInMerge) {
- viewKey = new MergeCookie(viewKey);
- }
- }
- } else if (resourceReference != null && currentDepth == 1) {
- // else if there's a resource reference, this means we are in an adapter
- // binding case. Set the resource ref as the view cookie only for the top
- // level view.
- viewKey = resourceReference;
- }
- }
-
- return viewKey;
- }
-
- public void postInflateProcess(View view) {
- if (mOpenDrawerLayouts != null) {
- String gravity = mOpenDrawerLayouts.get(view);
- if (gravity != null) {
- DrawerLayoutUtil.openDrawer(view, gravity);
- }
- mOpenDrawerLayouts.remove(view);
- }
- }
-
- @NonNull
- private Map<View, String> getDrawerLayoutMap() {
- if (mOpenDrawerLayouts == null) {
- mOpenDrawerLayouts = new HashMap<View, String>(4);
- }
- return mOpenDrawerLayouts;
- }
-
- public void onDoneInflation() {
- if (mOpenDrawerLayouts != null) {
- mOpenDrawerLayouts.clear();
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
deleted file mode 100644
index 494ffa1..0000000
--- a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.view;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-import com.android.tools.layoutlib.java.System_Delegate;
-
-import java.lang.reflect.Field;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Delegate used to provide new implementation of a select few methods of {@link Choreographer}
- *
- * Through the layoutlib_create tool, the original methods of Choreographer have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- */
-public class Choreographer_Delegate {
- static final AtomicReference<Choreographer> mInstance = new AtomicReference<Choreographer>();
-
- @LayoutlibDelegate
- public static Choreographer getInstance() {
- if (mInstance.get() == null) {
- mInstance.compareAndSet(null, Choreographer.getInstance_Original());
- }
-
- return mInstance.get();
- }
-
- @LayoutlibDelegate
- public static float getRefreshRate() {
- return 60.f;
- }
-
- @LayoutlibDelegate
- static void scheduleVsyncLocked(Choreographer thisChoreographer) {
- // do nothing
- }
-
- public static void doFrame(long frameTimeNanos) {
- Choreographer thisChoreographer = Choreographer.getInstance();
-
- thisChoreographer.mLastFrameTimeNanos = frameTimeNanos - thisChoreographer
- .getFrameIntervalNanos();
- thisChoreographer.mFrameInfo.markInputHandlingStart();
- thisChoreographer.doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
-
- thisChoreographer.mFrameInfo.markAnimationsStart();
- thisChoreographer.doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
-
- thisChoreographer.mFrameInfo.markPerformTraversalsStart();
- thisChoreographer.doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
-
- thisChoreographer.doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
- }
-
- public static void dispose() {
- try {
- Field threadInstanceField = Choreographer.class.getDeclaredField("sThreadInstance");
- threadInstanceField.setAccessible(true);
- @SuppressWarnings("unchecked") ThreadLocal<Choreographer> threadInstance =
- (ThreadLocal<Choreographer>) threadInstanceField.get(null);
- threadInstance.remove();
- } catch (ReflectiveOperationException e) {
- assert false;
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Unable to clear Choreographer memory.", e, null);
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/Display_Delegate.java b/tools/layoutlib/bridge/src/android/view/Display_Delegate.java
deleted file mode 100644
index 53dc821..0000000
--- a/tools/layoutlib/bridge/src/android/view/Display_Delegate.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-
-/**
- * Delegate used to provide new implementation of a select few methods of {@link Display}
- *
- * Through the layoutlib_create tool, the original methods of Display have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class Display_Delegate {
-
- @LayoutlibDelegate
- static void updateDisplayInfoLocked(Display theDisplay) {
- // do nothing
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/android/view/HandlerActionQueue_Delegate.java b/tools/layoutlib/bridge/src/android/view/HandlerActionQueue_Delegate.java
deleted file mode 100644
index e580ed0..0000000
--- a/tools/layoutlib/bridge/src/android/view/HandlerActionQueue_Delegate.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-/**
- * Delegate used to provide new implementation of a select few methods of
- * {@link HandlerActionQueue}
- *
- * Through the layoutlib_create tool, the original methods of ViewRootImpl.RunQueue have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- */
-public class HandlerActionQueue_Delegate {
-
- @LayoutlibDelegate
- /*package*/ static void postDelayed(HandlerActionQueue thisQueue, Runnable action, long
- delayMillis) {
- // The actual HandlerActionQueue is never run and therefore never cleared. This method
- // avoids runnables to be added to the RunQueue so they do not leak resources.
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
deleted file mode 100644
index deb0e09..0000000
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ /dev/null
@@ -1,529 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.content.res.Configuration;
-import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.IRemoteCallback;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.util.DisplayMetrics;
-
-import com.android.internal.app.IAssistScreenshotReceiver;
-import com.android.internal.os.IResultReceiver;
-import com.android.internal.policy.IKeyguardDismissCallback;
-import com.android.internal.policy.IShortcutService;
-import com.android.internal.view.IInputContext;
-import com.android.internal.view.IInputMethodClient;
-
-/**
- * Basic implementation of {@link IWindowManager} so that {@link Display} (and
- * {@link Display_Delegate}) can return a valid instance.
- */
-public class IWindowManagerImpl implements IWindowManager {
-
- private final Configuration mConfig;
- private final DisplayMetrics mMetrics;
- private final int mRotation;
- private final boolean mHasNavigationBar;
-
- public IWindowManagerImpl(Configuration config, DisplayMetrics metrics, int rotation,
- boolean hasNavigationBar) {
- mConfig = config;
- mMetrics = metrics;
- mRotation = rotation;
- mHasNavigationBar = hasNavigationBar;
- }
-
- // custom API.
-
- public DisplayMetrics getMetrics() {
- return mMetrics;
- }
-
- // ---- implementation of IWindowManager that we care about ----
-
- @Override
- public int getDefaultDisplayRotation() throws RemoteException {
- return mRotation;
- }
-
- @Override
- public boolean hasNavigationBar() {
- return mHasNavigationBar;
- }
-
- // ---- unused implementation of IWindowManager ----
-
- @Override
- public void addWindowToken(IBinder arg0, int arg1, int arg2) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void clearForcedDisplaySize(int displayId) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void clearForcedDisplayDensityForUser(int displayId, int userId) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setOverscan(int displayId, int left, int top, int right, int bottom)
- throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void closeSystemDialogs(String arg0) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void startFreezingScreen(int exitAnim, int enterAnim) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void stopFreezingScreen() {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void disableKeyguard(IBinder arg0, String arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void executeAppTransition() throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void exitKeyguardSecurely(IOnKeyguardExitResult arg0) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void freezeRotation(int arg0) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public float getAnimationScale(int arg0) throws RemoteException {
- // TODO Auto-generated method stub
- return 0;
- }
-
- @Override
- public float[] getAnimationScales() throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public int getPendingAppTransition() throws RemoteException {
- // TODO Auto-generated method stub
- return 0;
- }
-
- @Override
- public boolean inKeyguardRestrictedInputMode() throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean inputMethodClientHasFocus(IInputMethodClient arg0) throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean isKeyguardLocked() throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean isKeyguardSecure() throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean isViewServerRunning() throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public IWindowSession openSession(IWindowSessionCallback argn1, IInputMethodClient arg0,
- IInputContext arg1) throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public void overridePendingAppTransition(String arg0, int arg1, int arg2,
- IRemoteCallback startedCallback) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
- int startHeight) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void overridePendingAppTransitionClipReveal(int startX, int startY,
- int startWidth, int startHeight) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void overridePendingAppTransitionThumb(GraphicBuffer srcThumb, int startX, int startY,
- IRemoteCallback startedCallback, boolean scaleUp) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void overridePendingAppTransitionAspectScaledThumb(GraphicBuffer srcThumb, int startX,
- int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback,
- boolean scaleUp) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void overridePendingAppTransitionInPlace(String packageName, int anim) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void overridePendingAppTransitionMultiThumbFuture(
- IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback startedCallback,
- boolean scaleUp) throws RemoteException {
-
- }
-
- @Override
- public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
- IRemoteCallback callback0, IRemoteCallback callback1, boolean scaleUp) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void prepareAppTransition(int arg0, boolean arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void reenableKeyguard(IBinder arg0) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void removeWindowToken(IBinder arg0, int arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver)
- throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public void setAnimationScale(int arg0, float arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void setAnimationScales(float[] arg0) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public float getCurrentAnimatorScale() throws RemoteException {
- return 0;
- }
-
- @Override
- public void setEventDispatching(boolean arg0) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setFocusedApp(IBinder arg0, boolean arg1) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void getInitialDisplaySize(int displayId, Point size) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void getBaseDisplaySize(int displayId, Point size) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setForcedDisplaySize(int displayId, int arg0, int arg1) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public int getInitialDisplayDensity(int displayId) {
- return -1;
- }
-
- @Override
- public int getBaseDisplayDensity(int displayId) {
- return -1;
- }
-
- @Override
- public void setForcedDisplayDensityForUser(int displayId, int density, int userId)
- throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setForcedDisplayScalingMode(int displayId, int mode) {
- }
-
- @Override
- public void setInTouchMode(boolean arg0) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public int[] setNewDisplayOverrideConfiguration(Configuration arg0, int displayId)
- throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public void setScreenCaptureDisabled(int userId, boolean disabled) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void updateRotation(boolean arg0, boolean arg1) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setStrictModeVisualIndicatorPreference(String arg0) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void showStrictModeViolation(boolean arg0) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public boolean startViewServer(int arg0) throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public void statusBarVisibilityChanged(int arg0) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setRecentsVisibility(boolean visible) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setPipVisibility(boolean visible) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public boolean stopViewServer() throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public void thawRotation() throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public Configuration updateOrientationFromAppTokens(Configuration arg0, IBinder arg1, int arg2)
- throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public int watchRotation(IRotationWatcher arg0, int arg1) throws RemoteException {
- // TODO Auto-generated method stub
- return 0;
- }
-
- @Override
- public void removeRotationWatcher(IRotationWatcher arg0) throws RemoteException {
- }
-
- @Override
- public IBinder asBinder() {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public int getPreferredOptionsPanelGravity() throws RemoteException {
- return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
- }
-
- @Override
- public void dismissKeyguard(IKeyguardDismissCallback callback) throws RemoteException {
- }
-
- @Override
- public void setSwitchingUser(boolean switching) throws RemoteException {
- }
-
- @Override
- public void lockNow(Bundle options) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public boolean isSafeModeEnabled() {
- return false;
- }
-
- @Override
- public boolean isRotationFrozen() throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public void enableScreenIfNeeded() throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public boolean clearWindowContentFrameStats(IBinder token) throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public WindowContentFrameStats getWindowContentFrameStats(IBinder token)
- throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public int getDockedStackSide() throws RemoteException {
- return 0;
- }
-
- @Override
- public void setDockedStackResizing(boolean resizing) throws RemoteException {
- }
-
- @Override
- public void endProlongedAnimations() {
- }
-
- @Override
- public void registerDockedStackListener(IDockedStackListener listener) throws RemoteException {
- }
-
- @Override
- public void registerPinnedStackListener(int displayId, IPinnedStackListener listener) throws RemoteException {
- }
-
- @Override
- public void setResizeDimLayer(boolean visible, int targetStackId, float alpha)
- throws RemoteException {
- }
-
- @Override
- public void setDockedStackDividerTouchRegion(Rect touchableRegion) throws RemoteException {
- }
-
- @Override
- public void requestAppKeyboardShortcuts(
- IResultReceiver receiver, int deviceId) throws RemoteException {
- }
-
- @Override
- public void getStableInsets(int displayId, Rect outInsets) throws RemoteException {
- }
-
- @Override
- public void registerShortcutKey(long shortcutCode, IShortcutService service)
- throws RemoteException {}
-
- @Override
- public void createInputConsumer(String name, InputChannel inputChannel)
- throws RemoteException {}
-
- @Override
- public boolean destroyInputConsumer(String name) throws RemoteException {
- return false;
- }
-
- @Override
- public Bitmap screenshotWallpaper() throws RemoteException {
- return null;
- }
-
- @Override
- public void enableSurfaceTrace(ParcelFileDescriptor fd) throws RemoteException {
- }
-
- @Override
- public void disableSurfaceTrace() throws RemoteException {
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java
deleted file mode 100644
index cec6bb3..0000000
--- a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.util.Xml;
-
-import java.io.IOException;
-
-/**
- * Delegate used to provide new implementation of a select few methods of {@link LayoutInflater}
- *
- * Through the layoutlib_create tool, the original methods of LayoutInflater have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class LayoutInflater_Delegate {
- private static final String TAG_MERGE = "merge";
-
- private static final String ATTR_LAYOUT = "layout";
-
- private static final int[] ATTRS_THEME = new int[] {
- com.android.internal.R.attr.theme };
-
- public static boolean sIsInInclude = false;
-
- /**
- * Recursive method used to descend down the xml hierarchy and instantiate
- * views, instantiate their children, and then call onFinishInflate().
- *
- * This implementation just records the merge status before calling the default implementation.
- */
- @LayoutlibDelegate
- /* package */ static void rInflate(LayoutInflater thisInflater, XmlPullParser parser,
- View parent, Context context, AttributeSet attrs, boolean finishInflate)
- throws XmlPullParserException, IOException {
-
- if (finishInflate == false) {
- // this is a merge rInflate!
- if (thisInflater instanceof BridgeInflater) {
- ((BridgeInflater) thisInflater).setIsInMerge(true);
- }
- }
-
- // ---- START DEFAULT IMPLEMENTATION.
-
- thisInflater.rInflate_Original(parser, parent, context, attrs, finishInflate);
-
- // ---- END DEFAULT IMPLEMENTATION.
-
- if (finishInflate == false) {
- // this is a merge rInflate!
- if (thisInflater instanceof BridgeInflater) {
- ((BridgeInflater) thisInflater).setIsInMerge(false);
- }
- }
- }
-
- @LayoutlibDelegate
- public static void parseInclude(LayoutInflater thisInflater, XmlPullParser parser,
- Context context, View parent, AttributeSet attrs)
- throws XmlPullParserException, IOException {
- int type;
-
- if (parent instanceof ViewGroup) {
- // Apply a theme wrapper, if requested. This is sort of a weird
- // edge case, since developers think the <include> overwrites
- // values in the AttributeSet of the included View. So, if the
- // included View has a theme attribute, we'll need to ignore it.
- final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
- final int themeResId = ta.getResourceId(0, 0);
- final boolean hasThemeOverride = themeResId != 0;
- if (hasThemeOverride) {
- context = new ContextThemeWrapper(context, themeResId);
- }
- ta.recycle();
-
- // If the layout is pointing to a theme attribute, we have to
- // massage the value to get a resource identifier out of it.
- int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
- if (layout == 0) {
- final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
- if (value == null || value.length() <= 0) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN, "You must specify a layout in the"
- + " include tag: <include layout=\"@layout/layoutID\" />", null);
- LayoutInflater.consumeChildElements(parser);
- return;
- }
-
- // Attempt to resolve the "?attr/name" string to an identifier.
- layout = context.getResources().getIdentifier(value.substring(1), null, null);
- }
-
- // The layout might be referencing a theme attribute.
- // ---- START CHANGES
- if (layout != 0) {
- final TypedValue tempValue = new TypedValue();
- if (context.getTheme().resolveAttribute(layout, tempValue, true)) {
- layout = tempValue.resourceId;
- }
- }
- // ---- END CHANGES
-
- if (layout == 0) {
- final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
- if (value == null) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN, "You must specify a layout in the"
- + " include tag: <include layout=\"@layout/layoutID\" />", null);
- } else {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN, "You must specify a valid layout "
- + "reference. The layout ID " + value + " is not valid.", null);
- }
- } else {
- final XmlResourceParser childParser =
- thisInflater.getContext().getResources().getLayout(layout);
-
- try {
- final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
-
- while ((type = childParser.next()) != XmlPullParser.START_TAG &&
- type != XmlPullParser.END_DOCUMENT) {
- // Empty.
- }
-
- if (type != XmlPullParser.START_TAG) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- childParser.getPositionDescription() + ": No start tag found!",
- null);
- LayoutInflater.consumeChildElements(parser);
- return;
- }
-
- final String childName = childParser.getName();
-
- if (TAG_MERGE.equals(childName)) {
- // Inflate all children.
- thisInflater.rInflate(childParser, parent, context, childAttrs, false);
- } else {
- final View view = thisInflater.createViewFromTag(parent, childName,
- context, childAttrs, hasThemeOverride);
- final ViewGroup group = (ViewGroup) parent;
-
- final TypedArray a = context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.Include);
- final int id = a.getResourceId(
- com.android.internal.R.styleable.Include_id, View.NO_ID);
- final int visibility = a.getInt(
- com.android.internal.R.styleable.Include_visibility, -1);
- a.recycle();
-
- // We try to load the layout params set in the <include /> tag. If
- // they don't exist, we will rely on the layout params set in the
- // included XML file.
- // During a layoutparams generation, a runtime exception is thrown
- // if either layout_width or layout_height is missing. We catch
- // this exception and set localParams accordingly: true means we
- // successfully loaded layout params from the <include /> tag,
- // false means we need to rely on the included layout params.
- ViewGroup.LayoutParams params = null;
- try {
- // ---- START CHANGES
- sIsInInclude = true;
- // ---- END CHANGES
-
- params = group.generateLayoutParams(attrs);
- } catch (RuntimeException ignored) {
- // Ignore, just fail over to child attrs.
- } finally {
- // ---- START CHANGES
- sIsInInclude = false;
- // ---- END CHANGES
- }
- if (params == null) {
- params = group.generateLayoutParams(childAttrs);
- }
- view.setLayoutParams(params);
-
- // Inflate all children.
- thisInflater.rInflateChildren(childParser, view, childAttrs, true);
-
- if (id != View.NO_ID) {
- view.setId(id);
- }
-
- switch (visibility) {
- case 0:
- view.setVisibility(View.VISIBLE);
- break;
- case 1:
- view.setVisibility(View.INVISIBLE);
- break;
- case 2:
- view.setVisibility(View.GONE);
- break;
- }
-
- group.addView(view);
- }
- } finally {
- childParser.close();
- }
- }
- } else {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "<include /> can only be used inside of a ViewGroup",
- null);
- }
-
- LayoutInflater.consumeChildElements(parser);
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java
deleted file mode 100644
index 08a97d6..0000000
--- a/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.content.Context;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.ViewInfo;
-import com.android.internal.view.menu.BridgeMenuItemImpl;
-import com.android.internal.view.menu.MenuView;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.util.AttributeSet;
-
-/**
- * Delegate used to provide new implementation of a select few methods of {@link MenuInflater}
- * <p/>
- * Through the layoutlib_create tool, the original methods of MenuInflater have been
- * replaced by calls to methods of the same name in this delegate class.
- * <p/>
- * The main purpose of the class is to get the view key from the menu xml parser and add it to
- * the menu item. The view key is used by the IDE to match the individual view elements to the
- * corresponding xml tag in the menu/layout file.
- * <p/>
- * For Menus, the views may be reused and the {@link MenuItem} is a better object to hold the
- * view key than the {@link MenuView.ItemView}. At the time of computation of the rest of {@link
- * ViewInfo}, we check the corresponding view key in the menu item for the view and add it
- */
-public class MenuInflater_Delegate {
-
- @LayoutlibDelegate
- /*package*/ static void registerMenu(MenuInflater thisInflater, MenuItem menuItem,
- AttributeSet attrs) {
- if (menuItem instanceof BridgeMenuItemImpl) {
- Context context = thisInflater.getContext();
- context = BridgeContext.getBaseContext(context);
- if (context instanceof BridgeContext) {
- Object viewKey = BridgeInflater.getViewKeyFromParser(
- attrs, ((BridgeContext) context), null, false);
- ((BridgeMenuItemImpl) menuItem).setViewCookie(viewKey);
- return;
- }
- }
- // This means that Bridge did not take over the instantiation of some object properly.
- // This is most likely a bug in the LayoutLib code.
- Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
- "Action Bar Menu rendering may be incorrect.", null);
-
- }
-
- @LayoutlibDelegate
- /*package*/ static void registerMenu(MenuInflater thisInflater, SubMenu subMenu,
- AttributeSet parser) {
- registerMenu(thisInflater, subMenu.getItem(), parser);
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/android/view/PointerIcon_Delegate.java b/tools/layoutlib/bridge/src/android/view/PointerIcon_Delegate.java
deleted file mode 100644
index 4a5ea9b..0000000
--- a/tools/layoutlib/bridge/src/android/view/PointerIcon_Delegate.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.content.Context;
-import android.content.res.Resources;
-
-public class PointerIcon_Delegate {
-
- @LayoutlibDelegate
- /*package*/ static void loadResource(PointerIcon icon, Context context, Resources resources,
- int resourceId) {
- // HACK: This bypasses the problem of having an enum resolved as a resourceId.
- // PointerIcon would not be displayed by layoutlib anyway, so we always return the null
- // icon.
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
deleted file mode 100644
index 8ae212c..0000000
--- a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2015, 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.annotation.NonNull;
-import android.graphics.Canvas;
-import android.graphics.Outline;
-import android.graphics.Rect;
-import com.android.layoutlib.bridge.shadowutil.SpotShadow;
-import com.android.layoutlib.bridge.shadowutil.ShadowBuffer;
-
-public class RectShadowPainter {
-
- private static final float SHADOW_STRENGTH = 0.1f;
- private static final int LIGHT_POINTS = 8;
-
- private static final int QUADRANT_DIVIDED_COUNT = 8;
-
- private static final int RAY_TRACING_RAYS = 180;
- private static final int RAY_TRACING_LAYERS = 10;
-
- public static void paintShadow(@NonNull Outline viewOutline, float elevation,
- @NonNull Canvas canvas) {
- Rect outline = new Rect();
- if (!viewOutline.getRect(outline)) {
- assert false : "Outline is not a rect shadow";
- return;
- }
-
- Rect originCanvasRect = canvas.getClipBounds();
- int saved = modifyCanvas(canvas);
- if (saved == -1) {
- return;
- }
- try {
- float radius = viewOutline.getRadius();
- if (radius <= 0) {
- // We can not paint a shadow with radius 0
- return;
- }
-
- // view's absolute position in this canvas.
- int viewLeft = -originCanvasRect.left + outline.left;
- int viewTop = -originCanvasRect.top + outline.top;
- int viewRight = viewLeft + outline.width();
- int viewBottom = viewTop + outline.height();
-
- float[][] rectangleCoordinators = generateRectangleCoordinates(viewLeft, viewTop,
- viewRight, viewBottom, radius, elevation);
-
- // TODO: get these values from resources.
- float lightPosX = canvas.getWidth() / 2;
- float lightPosY = 0;
- float lightHeight = 1800;
- float lightSize = 200;
-
- paintGeometricShadow(rectangleCoordinators, lightPosX, lightPosY, lightHeight,
- lightSize, canvas);
- } finally {
- canvas.restoreToCount(saved);
- }
- }
-
- private static int modifyCanvas(@NonNull Canvas canvas) {
- Rect rect = canvas.getClipBounds();
- canvas.translate(rect.left, rect.top);
- return canvas.save();
- }
-
- @NonNull
- private static float[][] generateRectangleCoordinates(float left, float top, float right,
- float bottom, float radius, float elevation) {
- left = left + radius;
- top = top + radius;
- right = right - radius;
- bottom = bottom - radius;
-
- final double RADIANS_STEP = 2 * Math.PI / 4 / QUADRANT_DIVIDED_COUNT;
-
- float[][] ret = new float[QUADRANT_DIVIDED_COUNT * 4][3];
-
- int points = 0;
- // left-bottom points
- for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) {
- ret[points][0] = (float) (left - radius + radius * Math.cos(RADIANS_STEP * i));
- ret[points][1] = (float) (bottom + radius - radius * Math.cos(RADIANS_STEP * i));
- ret[points][2] = elevation;
- points++;
- }
- // left-top points
- for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) {
- ret[points][0] = (float) (left + radius - radius * Math.cos(RADIANS_STEP * i));
- ret[points][1] = (float) (top + radius - radius * Math.cos(RADIANS_STEP * i));
- ret[points][2] = elevation;
- points++;
- }
- // right-top points
- for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) {
- ret[points][0] = (float) (right + radius - radius * Math.cos(RADIANS_STEP * i));
- ret[points][1] = (float) (top + radius + radius * Math.cos(RADIANS_STEP * i));
- ret[points][2] = elevation;
- points++;
- }
- // right-bottom point
- for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) {
- ret[points][0] = (float) (right - radius + radius * Math.cos(RADIANS_STEP * i));
- ret[points][1] = (float) (bottom - radius + radius * Math.cos(RADIANS_STEP * i));
- ret[points][2] = elevation;
- points++;
- }
-
- return ret;
- }
-
- private static void paintGeometricShadow(@NonNull float[][] coordinates, float lightPosX,
- float lightPosY, float lightHeight, float lightSize, Canvas canvas) {
- if (canvas == null || canvas.getWidth() == 0 || canvas.getHeight() == 0) {
- return;
- }
-
- // The polygon of shadow (same as the original item)
- float[] shadowPoly = new float[coordinates.length * 3];
- for (int i = 0; i < coordinates.length; i++) {
- shadowPoly[i * 3 + 0] = coordinates[i][0];
- shadowPoly[i * 3 + 1] = coordinates[i][1];
- shadowPoly[i * 3 + 2] = coordinates[i][2];
- }
-
- // TODO: calculate the ambient shadow and mix with Spot shadow.
-
- // Calculate the shadow of SpotLight
- float[] light = SpotShadow.calculateLight(lightSize, LIGHT_POINTS, lightPosX,
- lightPosY, lightHeight);
-
- int stripSize = 3 * SpotShadow.getStripSize(RAY_TRACING_RAYS, RAY_TRACING_LAYERS);
- if (stripSize < 9) {
- return;
- }
- float[] strip = new float[stripSize];
- SpotShadow.calcShadow(light, LIGHT_POINTS, shadowPoly, coordinates.length, RAY_TRACING_RAYS,
- RAY_TRACING_LAYERS, 1f, strip);
-
- ShadowBuffer buff = new ShadowBuffer(canvas.getWidth(), canvas.getHeight());
- buff.generateTriangles(strip, SHADOW_STRENGTH);
- buff.draw(canvas);
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
deleted file mode 100644
index 152878b..0000000
--- a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.graphics.Matrix;
-
-import libcore.util.NativeAllocationRegistry_Delegate;
-
-/**
- * Delegate implementing the native methods of {@link RenderNode}
- * <p/>
- * Through the layoutlib_create tool, some native methods of RenderNode have been replaced by calls
- * to methods of the same name in this delegate class.
- *
- * @see DelegateManager
- */
-public class RenderNode_Delegate {
-
-
- // ---- delegate manager ----
- private static final DelegateManager<RenderNode_Delegate> sManager =
- new DelegateManager<RenderNode_Delegate>(RenderNode_Delegate.class);
- private static long sFinalizer = -1;
-
- private float mLift;
- private float mTranslationX;
- private float mTranslationY;
- private float mTranslationZ;
- private float mRotation;
- private float mScaleX = 1;
- private float mScaleY = 1;
- private float mPivotX;
- private float mPivotY;
- private boolean mPivotExplicitlySet;
- private int mLeft;
- private int mRight;
- private int mTop;
- private int mBottom;
- @SuppressWarnings("UnusedDeclaration")
- private String mName;
-
- @LayoutlibDelegate
- /*package*/ static long nCreate(String name) {
- RenderNode_Delegate renderNodeDelegate = new RenderNode_Delegate();
- renderNodeDelegate.mName = name;
- return sManager.addNewDelegate(renderNodeDelegate);
- }
-
- @LayoutlibDelegate
- /*package*/ static long nGetNativeFinalizer() {
- synchronized (RenderNode_Delegate.class) {
- if (sFinalizer == -1) {
- sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor);
- }
- }
- return sFinalizer;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nSetElevation(long renderNode, float lift) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null && delegate.mLift != lift) {
- delegate.mLift = lift;
- return true;
- }
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetElevation(long renderNode) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null) {
- return delegate.mLift;
- }
- return 0f;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nSetTranslationX(long renderNode, float translationX) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null && delegate.mTranslationX != translationX) {
- delegate.mTranslationX = translationX;
- return true;
- }
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetTranslationX(long renderNode) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null) {
- return delegate.mTranslationX;
- }
- return 0f;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nSetTranslationY(long renderNode, float translationY) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null && delegate.mTranslationY != translationY) {
- delegate.mTranslationY = translationY;
- return true;
- }
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetTranslationY(long renderNode) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null) {
- return delegate.mTranslationY;
- }
- return 0f;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nSetTranslationZ(long renderNode, float translationZ) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null && delegate.mTranslationZ != translationZ) {
- delegate.mTranslationZ = translationZ;
- return true;
- }
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetTranslationZ(long renderNode) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null) {
- return delegate.mTranslationZ;
- }
- return 0f;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nSetRotation(long renderNode, float rotation) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null && delegate.mRotation != rotation) {
- delegate.mRotation = rotation;
- return true;
- }
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetRotation(long renderNode) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null) {
- return delegate.mRotation;
- }
- return 0f;
- }
-
- @LayoutlibDelegate
- /*package*/ static void getMatrix(RenderNode renderNode, Matrix outMatrix) {
- outMatrix.reset();
- if (renderNode != null) {
- float rotation = renderNode.getRotation();
- float translationX = renderNode.getTranslationX();
- float translationY = renderNode.getTranslationY();
- float pivotX = renderNode.getPivotX();
- float pivotY = renderNode.getPivotY();
- float scaleX = renderNode.getScaleX();
- float scaleY = renderNode.getScaleY();
-
- outMatrix.setTranslate(translationX, translationY);
- outMatrix.preRotate(rotation, pivotX, pivotY);
- outMatrix.preScale(scaleX, scaleY, pivotX, pivotY);
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nSetLeft(long renderNode, int left) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null && delegate.mLeft != left) {
- delegate.mLeft = left;
- return true;
- }
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nSetTop(long renderNode, int top) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null && delegate.mTop != top) {
- delegate.mTop = top;
- return true;
- }
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nSetRight(long renderNode, int right) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null && delegate.mRight != right) {
- delegate.mRight = right;
- return true;
- }
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nSetBottom(long renderNode, int bottom) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null && delegate.mBottom != bottom) {
- delegate.mBottom = bottom;
- return true;
- }
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nSetLeftTopRightBottom(long renderNode, int left, int top, int right,
- int bottom) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null && (delegate.mLeft != left || delegate.mTop != top || delegate
- .mRight != right || delegate.mBottom != bottom)) {
- delegate.mLeft = left;
- delegate.mTop = top;
- delegate.mRight = right;
- delegate.mBottom = bottom;
- return true;
- }
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nIsPivotExplicitlySet(long renderNode) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- return delegate != null && delegate.mPivotExplicitlySet;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nSetPivotX(long renderNode, float pivotX) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null) {
- delegate.mPivotX = pivotX;
- delegate.mPivotExplicitlySet = true;
- return true;
- }
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetPivotX(long renderNode) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null) {
- if (delegate.mPivotExplicitlySet) {
- return delegate.mPivotX;
- } else {
- return (delegate.mRight - delegate.mLeft) / 2.0f;
- }
- }
- return 0f;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nSetPivotY(long renderNode, float pivotY) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null) {
- delegate.mPivotY = pivotY;
- delegate.mPivotExplicitlySet = true;
- return true;
- }
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetPivotY(long renderNode) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null) {
- if (delegate.mPivotExplicitlySet) {
- return delegate.mPivotY;
- } else {
- return (delegate.mBottom - delegate.mTop) / 2.0f;
- }
- }
- return 0f;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nSetScaleX(long renderNode, float scaleX) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null && delegate.mScaleX != scaleX) {
- delegate.mScaleX = scaleX;
- return true;
- }
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetScaleX(long renderNode) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null) {
- return delegate.mScaleX;
- }
- return 0f;
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean nSetScaleY(long renderNode, float scaleY) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null && delegate.mScaleY != scaleY) {
- delegate.mScaleY = scaleY;
- return true;
- }
- return false;
- }
-
- @LayoutlibDelegate
- /*package*/ static float nGetScaleY(long renderNode) {
- RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
- if (delegate != null) {
- return delegate.mScaleY;
- }
- return 0f;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/ShadowPainter.java b/tools/layoutlib/bridge/src/android/view/ShadowPainter.java
deleted file mode 100644
index f09fffd..0000000
--- a/tools/layoutlib/bridge/src/android/view/ShadowPainter.java
+++ /dev/null
@@ -1,422 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.annotation.NonNull;
-
-import java.awt.Graphics2D;
-import java.awt.Image;
-import java.awt.image.BufferedImage;
-import java.awt.image.DataBufferInt;
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.imageio.ImageIO;
-
-public class ShadowPainter {
-
- /**
- * Adds a drop shadow to a semi-transparent image (of an arbitrary shape) and returns it as a
- * new image. This method attempts to mimic the same visual characteristics as the rectangular
- * shadow painting methods in this class, {@link #createRectangularDropShadow(java.awt.image.BufferedImage)}
- * and {@link #createSmallRectangularDropShadow(java.awt.image.BufferedImage)}.
- * <p/>
- * If shadowSize is less or equals to 1, no shadow will be painted and the source image will be
- * returned instead.
- *
- * @param source the source image
- * @param shadowSize the size of the shadow, normally {@link #SHADOW_SIZE or {@link
- * #SMALL_SHADOW_SIZE}}
- *
- * @return an image with the shadow painted in or the source image if shadowSize <= 1
- */
- @NonNull
- public static BufferedImage createDropShadow(BufferedImage source, int shadowSize) {
- shadowSize /= 2; // make shadow size have the same meaning as in the other shadow paint methods in this class
-
- return createDropShadow(source, shadowSize, 0.7f, 0);
- }
-
- /**
- * Creates a drop shadow of a given image and returns a new image which shows the input image on
- * top of its drop shadow.
- * <p/>
- * <b>NOTE: If the shape is rectangular and opaque, consider using {@link
- * #drawRectangleShadow(Graphics2D, int, int, int, int)} instead.</b>
- *
- * @param source the source image to be shadowed
- * @param shadowSize the size of the shadow in pixels
- * @param shadowOpacity the opacity of the shadow, with 0=transparent and 1=opaque
- * @param shadowRgb the RGB int to use for the shadow color
- *
- * @return a new image with the source image on top of its shadow when shadowSize > 0 or the
- * source image otherwise
- */
- @SuppressWarnings({"SuspiciousNameCombination", "UnnecessaryLocalVariable"}) // Imported code
- public static BufferedImage createDropShadow(BufferedImage source, int shadowSize,
- float shadowOpacity, int shadowRgb) {
- if (shadowSize <= 0) {
- return source;
- }
-
- // This code is based on
- // http://www.jroller.com/gfx/entry/non_rectangular_shadow
-
- BufferedImage image;
- int width = source.getWidth();
- int height = source.getHeight();
- image = new BufferedImage(width + SHADOW_SIZE, height + SHADOW_SIZE,
- BufferedImage.TYPE_INT_ARGB);
-
- Graphics2D g2 = image.createGraphics();
- g2.drawImage(image, shadowSize, shadowSize, null);
-
- int dstWidth = image.getWidth();
- int dstHeight = image.getHeight();
-
- int left = (shadowSize - 1) >> 1;
- int right = shadowSize - left;
- int xStart = left;
- int xStop = dstWidth - right;
- int yStart = left;
- int yStop = dstHeight - right;
-
- shadowRgb &= 0x00FFFFFF;
-
- int[] aHistory = new int[shadowSize];
- int historyIdx;
-
- int aSum;
-
- int[] dataBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
- int lastPixelOffset = right * dstWidth;
- float sumDivider = shadowOpacity / shadowSize;
-
- // horizontal pass
- for (int y = 0, bufferOffset = 0; y < dstHeight; y++, bufferOffset = y * dstWidth) {
- aSum = 0;
- historyIdx = 0;
- for (int x = 0; x < shadowSize; x++, bufferOffset++) {
- int a = dataBuffer[bufferOffset] >>> 24;
- aHistory[x] = a;
- aSum += a;
- }
-
- bufferOffset -= right;
-
- for (int x = xStart; x < xStop; x++, bufferOffset++) {
- int a = (int) (aSum * sumDivider);
- dataBuffer[bufferOffset] = a << 24 | shadowRgb;
-
- // subtract the oldest pixel from the sum
- aSum -= aHistory[historyIdx];
-
- // get the latest pixel
- a = dataBuffer[bufferOffset + right] >>> 24;
- aHistory[historyIdx] = a;
- aSum += a;
-
- if (++historyIdx >= shadowSize) {
- historyIdx -= shadowSize;
- }
- }
- }
- // vertical pass
- for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {
- aSum = 0;
- historyIdx = 0;
- for (int y = 0; y < shadowSize; y++, bufferOffset += dstWidth) {
- int a = dataBuffer[bufferOffset] >>> 24;
- aHistory[y] = a;
- aSum += a;
- }
-
- bufferOffset -= lastPixelOffset;
-
- for (int y = yStart; y < yStop; y++, bufferOffset += dstWidth) {
- int a = (int) (aSum * sumDivider);
- dataBuffer[bufferOffset] = a << 24 | shadowRgb;
-
- // subtract the oldest pixel from the sum
- aSum -= aHistory[historyIdx];
-
- // get the latest pixel
- a = dataBuffer[bufferOffset + lastPixelOffset] >>> 24;
- aHistory[historyIdx] = a;
- aSum += a;
-
- if (++historyIdx >= shadowSize) {
- historyIdx -= shadowSize;
- }
- }
- }
-
- g2.drawImage(source, null, 0, 0);
- g2.dispose();
-
- return image;
- }
-
- /**
- * Draws a rectangular drop shadow (of size {@link #SHADOW_SIZE} by {@link #SHADOW_SIZE} around
- * the given source and returns a new image with both combined
- *
- * @param source the source image
- *
- * @return the source image with a drop shadow on the bottom and right
- */
- @SuppressWarnings("UnusedDeclaration")
- public static BufferedImage createRectangularDropShadow(BufferedImage source) {
- int type = source.getType();
- if (type == BufferedImage.TYPE_CUSTOM) {
- type = BufferedImage.TYPE_INT_ARGB;
- }
-
- int width = source.getWidth();
- int height = source.getHeight();
- BufferedImage image;
- image = new BufferedImage(width + SHADOW_SIZE, height + SHADOW_SIZE, type);
- Graphics2D g = image.createGraphics();
- g.drawImage(source, 0, 0, null);
- drawRectangleShadow(image, 0, 0, width, height);
- g.dispose();
-
- return image;
- }
-
- /**
- * Draws a small rectangular drop shadow (of size {@link #SMALL_SHADOW_SIZE} by {@link
- * #SMALL_SHADOW_SIZE} around the given source and returns a new image with both combined
- *
- * @param source the source image
- *
- * @return the source image with a drop shadow on the bottom and right
- */
- @SuppressWarnings("UnusedDeclaration")
- public static BufferedImage createSmallRectangularDropShadow(BufferedImage source) {
- int type = source.getType();
- if (type == BufferedImage.TYPE_CUSTOM) {
- type = BufferedImage.TYPE_INT_ARGB;
- }
-
- int width = source.getWidth();
- int height = source.getHeight();
-
- BufferedImage image;
- image = new BufferedImage(width + SMALL_SHADOW_SIZE, height + SMALL_SHADOW_SIZE, type);
-
- Graphics2D g = image.createGraphics();
- g.drawImage(source, 0, 0, null);
- drawSmallRectangleShadow(image, 0, 0, width, height);
- g.dispose();
-
- return image;
- }
-
- /**
- * Draws a drop shadow for the given rectangle into the given context. It will not draw anything
- * if the rectangle is smaller than a minimum determined by the assets used to draw the shadow
- * graphics. The size of the shadow is {@link #SHADOW_SIZE}.
- *
- * @param image the image to draw the shadow into
- * @param x the left coordinate of the left hand side of the rectangle
- * @param y the top coordinate of the top of the rectangle
- * @param width the width of the rectangle
- * @param height the height of the rectangle
- */
- public static void drawRectangleShadow(BufferedImage image,
- int x, int y, int width, int height) {
- Graphics2D gc = image.createGraphics();
- try {
- drawRectangleShadow(gc, x, y, width, height);
- } finally {
- gc.dispose();
- }
- }
-
- /**
- * Draws a small drop shadow for the given rectangle into the given context. It will not draw
- * anything if the rectangle is smaller than a minimum determined by the assets used to draw the
- * shadow graphics. The size of the shadow is {@link #SMALL_SHADOW_SIZE}.
- *
- * @param image the image to draw the shadow into
- * @param x the left coordinate of the left hand side of the rectangle
- * @param y the top coordinate of the top of the rectangle
- * @param width the width of the rectangle
- * @param height the height of the rectangle
- */
- public static void drawSmallRectangleShadow(BufferedImage image,
- int x, int y, int width, int height) {
- Graphics2D gc = image.createGraphics();
- try {
- drawSmallRectangleShadow(gc, x, y, width, height);
- } finally {
- gc.dispose();
- }
- }
-
- /**
- * The width and height of the drop shadow painted by
- * {@link #drawRectangleShadow(Graphics2D, int, int, int, int)}
- */
- public static final int SHADOW_SIZE = 20; // DO NOT EDIT. This corresponds to bitmap graphics
-
- /**
- * The width and height of the drop shadow painted by
- * {@link #drawSmallRectangleShadow(Graphics2D, int, int, int, int)}
- */
- public static final int SMALL_SHADOW_SIZE = 10; // DO NOT EDIT. Corresponds to bitmap graphics
-
- /**
- * Draws a drop shadow for the given rectangle into the given context. It will not draw anything
- * if the rectangle is smaller than a minimum determined by the assets used to draw the shadow
- * graphics.
- *
- * @param gc the graphics context to draw into
- * @param x the left coordinate of the left hand side of the rectangle
- * @param y the top coordinate of the top of the rectangle
- * @param width the width of the rectangle
- * @param height the height of the rectangle
- */
- public static void drawRectangleShadow(Graphics2D gc, int x, int y, int width, int height) {
- assert ShadowBottomLeft != null;
- assert ShadowBottomRight.getWidth(null) == SHADOW_SIZE;
- assert ShadowBottomRight.getHeight(null) == SHADOW_SIZE;
-
- int blWidth = ShadowBottomLeft.getWidth(null);
- int trHeight = ShadowTopRight.getHeight(null);
- if (width < blWidth) {
- return;
- }
- if (height < trHeight) {
- return;
- }
-
- gc.drawImage(ShadowBottomLeft, x - ShadowBottomLeft.getWidth(null), y + height, null);
- gc.drawImage(ShadowBottomRight, x + width, y + height, null);
- gc.drawImage(ShadowTopRight, x + width, y, null);
- gc.drawImage(ShadowTopLeft, x - ShadowTopLeft.getWidth(null), y, null);
- gc.drawImage(ShadowBottom,
- x, y + height, x + width, y + height + ShadowBottom.getHeight(null),
- 0, 0, ShadowBottom.getWidth(null), ShadowBottom.getHeight(null), null);
- gc.drawImage(ShadowRight,
- x + width, y + ShadowTopRight.getHeight(null), x + width + ShadowRight.getWidth(null), y + height,
- 0, 0, ShadowRight.getWidth(null), ShadowRight.getHeight(null), null);
- gc.drawImage(ShadowLeft,
- x - ShadowLeft.getWidth(null), y + ShadowTopLeft.getHeight(null), x, y + height,
- 0, 0, ShadowLeft.getWidth(null), ShadowLeft.getHeight(null), null);
- }
-
- /**
- * Draws a small drop shadow for the given rectangle into the given context. It will not draw
- * anything if the rectangle is smaller than a minimum determined by the assets used to draw the
- * shadow graphics.
- * <p/>
- *
- * @param gc the graphics context to draw into
- * @param x the left coordinate of the left hand side of the rectangle
- * @param y the top coordinate of the top of the rectangle
- * @param width the width of the rectangle
- * @param height the height of the rectangle
- */
- public static void drawSmallRectangleShadow(Graphics2D gc, int x, int y, int width,
- int height) {
- assert Shadow2BottomLeft != null;
- assert Shadow2TopRight != null;
- assert Shadow2BottomRight.getWidth(null) == SMALL_SHADOW_SIZE;
- assert Shadow2BottomRight.getHeight(null) == SMALL_SHADOW_SIZE;
-
- int blWidth = Shadow2BottomLeft.getWidth(null);
- int trHeight = Shadow2TopRight.getHeight(null);
- if (width < blWidth) {
- return;
- }
- if (height < trHeight) {
- return;
- }
-
- gc.drawImage(Shadow2BottomLeft, x - Shadow2BottomLeft.getWidth(null), y + height, null);
- gc.drawImage(Shadow2BottomRight, x + width, y + height, null);
- gc.drawImage(Shadow2TopRight, x + width, y, null);
- gc.drawImage(Shadow2TopLeft, x - Shadow2TopLeft.getWidth(null), y, null);
- gc.drawImage(Shadow2Bottom,
- x, y + height, x + width, y + height + Shadow2Bottom.getHeight(null),
- 0, 0, Shadow2Bottom.getWidth(null), Shadow2Bottom.getHeight(null), null);
- gc.drawImage(Shadow2Right,
- x + width, y + Shadow2TopRight.getHeight(null), x + width + Shadow2Right.getWidth(null), y + height,
- 0, 0, Shadow2Right.getWidth(null), Shadow2Right.getHeight(null), null);
- gc.drawImage(Shadow2Left,
- x - Shadow2Left.getWidth(null), y + Shadow2TopLeft.getHeight(null), x, y + height,
- 0, 0, Shadow2Left.getWidth(null), Shadow2Left.getHeight(null), null);
- }
-
- private static Image loadIcon(String name) {
- InputStream inputStream = ShadowPainter.class.getResourceAsStream(name);
- if (inputStream == null) {
- throw new RuntimeException("Unable to load image for shadow: " + name);
- }
- try {
- return ImageIO.read(inputStream);
- } catch (IOException e) {
- throw new RuntimeException("Unable to load image for shadow:" + name, e);
- } finally {
- try {
- inputStream.close();
- } catch (IOException e) {
- // ignore.
- }
- }
- }
-
- // Shadow graphics. This was generated by creating a drop shadow in
- // Gimp, using the parameters x offset=10, y offset=10, blur radius=10,
- // (for the small drop shadows x offset=10, y offset=10, blur radius=10)
- // color=black, and opacity=51. These values attempt to make a shadow
- // that is legible both for dark and light themes, on top of the
- // canvas background (rgb(150,150,150). Darker shadows would tend to
- // blend into the foreground for a dark holo screen, and lighter shadows
- // would be hard to spot on the canvas background. If you make adjustments,
- // make sure to check the shadow with both dark and light themes.
- //
- // After making the graphics, I cut out the top right, bottom left
- // and bottom right corners as 20x20 images, and these are reproduced by
- // painting them in the corresponding places in the target graphics context.
- // I then grabbed a single horizontal gradient line from the middle of the
- // right edge,and a single vertical gradient line from the bottom. These
- // are then painted scaled/stretched in the target to fill the gaps between
- // the three corner images.
- //
- // Filenames: bl=bottom left, b=bottom, br=bottom right, r=right, tr=top right
-
- // Normal Drop Shadow
- private static final Image ShadowBottom = loadIcon("/icons/shadow-b.png");
- private static final Image ShadowBottomLeft = loadIcon("/icons/shadow-bl.png");
- private static final Image ShadowBottomRight = loadIcon("/icons/shadow-br.png");
- private static final Image ShadowRight = loadIcon("/icons/shadow-r.png");
- private static final Image ShadowTopRight = loadIcon("/icons/shadow-tr.png");
- private static final Image ShadowTopLeft = loadIcon("/icons/shadow-tl.png");
- private static final Image ShadowLeft = loadIcon("/icons/shadow-l.png");
-
- // Small Drop Shadow
- private static final Image Shadow2Bottom = loadIcon("/icons/shadow2-b.png");
- private static final Image Shadow2BottomLeft = loadIcon("/icons/shadow2-bl.png");
- private static final Image Shadow2BottomRight = loadIcon("/icons/shadow2-br.png");
- private static final Image Shadow2Right = loadIcon("/icons/shadow2-r.png");
- private static final Image Shadow2TopRight = loadIcon("/icons/shadow2-tr.png");
- private static final Image Shadow2TopLeft = loadIcon("/icons/shadow2-tl.png");
- private static final Image Shadow2Left = loadIcon("/icons/shadow2-l.png");
-}
diff --git a/tools/layoutlib/bridge/src/android/view/SurfaceView.java b/tools/layoutlib/bridge/src/android/view/SurfaceView.java
deleted file mode 100644
index ebb2af4..0000000
--- a/tools/layoutlib/bridge/src/android/view/SurfaceView.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import com.android.layoutlib.bridge.MockView;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.util.AttributeSet;
-
-/**
- * Mock version of the SurfaceView.
- * Only non override public methods from the real SurfaceView have been added in there.
- * Methods that take an unknown class as parameter or as return object, have been removed for now.
- *
- * TODO: generate automatically.
- *
- */
-public class SurfaceView extends MockView {
-
- public SurfaceView(Context context) {
- this(context, null);
- }
-
- public SurfaceView(Context context, AttributeSet attrs) {
- this(context, attrs , 0);
- }
-
- public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- public boolean gatherTransparentRegion(Region region) {
- return false;
- }
-
- public void setZOrderMediaOverlay(boolean isMediaOverlay) {
- }
-
- public void setZOrderOnTop(boolean onTop) {
- }
-
- public void setSecure(boolean isSecure) {
- }
-
- public SurfaceHolder getHolder() {
- return mSurfaceHolder;
- }
-
- private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
-
- @Override
- public boolean isCreating() {
- return false;
- }
-
- @Override
- public void addCallback(Callback callback) {
- }
-
- @Override
- public void removeCallback(Callback callback) {
- }
-
- @Override
- public void setFixedSize(int width, int height) {
- }
-
- @Override
- public void setSizeFromLayout() {
- }
-
- @Override
- public void setFormat(int format) {
- }
-
- @Override
- public void setType(int type) {
- }
-
- @Override
- public void setKeepScreenOn(boolean screenOn) {
- }
-
- @Override
- public Canvas lockCanvas() {
- return null;
- }
-
- @Override
- public Canvas lockCanvas(Rect dirty) {
- return null;
- }
-
- @Override
- public void unlockCanvasAndPost(Canvas canvas) {
- }
-
- @Override
- public Surface getSurface() {
- return null;
- }
-
- @Override
- public Rect getSurfaceFrame() {
- return null;
- }
- };
-}
-
diff --git a/tools/layoutlib/bridge/src/android/view/ViewConfiguration_Accessor.java b/tools/layoutlib/bridge/src/android/view/ViewConfiguration_Accessor.java
deleted file mode 100644
index c3533e0..0000000
--- a/tools/layoutlib/bridge/src/android/view/ViewConfiguration_Accessor.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-/**
- * Class allowing access to package-protected methods/fields.
- */
-public class ViewConfiguration_Accessor {
-
- public static void clearConfigurations() {
- // clear the stored ViewConfiguration since the map is per density and not per context.
- ViewConfiguration.sConfigurations.clear();
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
deleted file mode 100644
index 23caaf8..0000000
--- a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import com.android.resources.Density;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.graphics.Bitmap;
-import android.graphics.Bitmap_Delegate;
-import android.graphics.Canvas;
-import android.graphics.Outline;
-import android.graphics.Path_Delegate;
-import android.graphics.Rect;
-import android.graphics.Region.Op;
-import android.view.animation.Transformation;
-
-import java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
-
-/**
- * Delegate used to provide new implementation of a select few methods of {@link ViewGroup}
- * <p/>
- * Through the layoutlib_create tool, the original methods of ViewGroup have been replaced by calls
- * to methods of the same name in this delegate class.
- */
-public class ViewGroup_Delegate {
-
- /**
- * Overrides the original drawChild call in ViewGroup to draw the shadow.
- */
- @LayoutlibDelegate
- /*package*/ static boolean drawChild(ViewGroup thisVG, Canvas canvas, View child,
- long drawingTime) {
- if (child.getZ() > thisVG.getZ()) {
- // The background's bounds are set lazily. Make sure they are set correctly so that
- // the outline obtained is correct.
- child.setBackgroundBounds();
- ViewOutlineProvider outlineProvider = child.getOutlineProvider();
- Outline outline = child.mAttachInfo.mTmpOutline;
- outlineProvider.getOutline(child, outline);
- if (outline.mPath != null || (outline.mRect != null && !outline.mRect.isEmpty())) {
- int restoreTo = transformCanvas(thisVG, canvas, child);
- drawShadow(thisVG, canvas, child, outline);
- canvas.restoreToCount(restoreTo);
- }
- }
- return thisVG.drawChild_Original(canvas, child, drawingTime);
- }
-
- private static void drawShadow(ViewGroup parent, Canvas canvas, View child,
- Outline outline) {
- float elevation = getElevation(child, parent);
- if(outline.mMode == Outline.MODE_ROUND_RECT && outline.mRect != null) {
- RectShadowPainter.paintShadow(outline, elevation, canvas);
- return;
- }
- BufferedImage shadow = null;
- if (outline.mPath != null) {
- shadow = getPathShadow(outline, canvas, elevation);
- }
- if (shadow == null) {
- return;
- }
- Bitmap bitmap = Bitmap_Delegate.createBitmap(shadow, false,
- Density.getEnum(canvas.getDensity()));
- Rect clipBounds = canvas.getClipBounds();
- Rect newBounds = new Rect(clipBounds);
- newBounds.inset((int)-elevation, (int)-elevation);
- canvas.clipRect(newBounds, Op.REPLACE);
- canvas.drawBitmap(bitmap, 0, 0, null);
- canvas.clipRect(clipBounds, Op.REPLACE);
- }
-
- private static float getElevation(View child, ViewGroup parent) {
- return child.getZ() - parent.getZ();
- }
-
- private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation) {
- Rect clipBounds = canvas.getClipBounds();
- if (clipBounds.isEmpty()) {
- return null;
- }
- BufferedImage image = new BufferedImage(clipBounds.width(), clipBounds.height(),
- BufferedImage.TYPE_INT_ARGB);
- Graphics2D graphics = image.createGraphics();
- graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape());
- graphics.dispose();
- return ShadowPainter.createDropShadow(image, (int) elevation);
- }
-
- // Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths
- // which were never taken. Ideally, we should hook up the shadow code in the same method so
- // that we don't have to transform the canvas twice.
- private static int transformCanvas(ViewGroup thisVG, Canvas canvas, View child) {
- final int restoreTo = canvas.save();
- final boolean childHasIdentityMatrix = child.hasIdentityMatrix();
- int flags = thisVG.mGroupFlags;
- Transformation transformToApply = null;
- boolean concatMatrix = false;
- if ((flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
- final Transformation t = thisVG.getChildTransformation();
- final boolean hasTransform = thisVG.getChildStaticTransformation(child, t);
- if (hasTransform) {
- final int transformType = t.getTransformationType();
- transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null;
- concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
- }
- }
- concatMatrix |= childHasIdentityMatrix;
-
- child.computeScroll();
- int sx = child.mScrollX;
- int sy = child.mScrollY;
-
- canvas.translate(child.mLeft - sx, child.mTop - sy);
- float alpha = child.getAlpha() * child.getTransitionAlpha();
-
- if (transformToApply != null || alpha < 1 || !childHasIdentityMatrix) {
- if (transformToApply != null || !childHasIdentityMatrix) {
- int transX = -sx;
- int transY = -sy;
-
- if (transformToApply != null) {
- if (concatMatrix) {
- // Undo the scroll translation, apply the transformation matrix,
- // then redo the scroll translate to get the correct result.
- canvas.translate(-transX, -transY);
- canvas.concat(transformToApply.getMatrix());
- canvas.translate(transX, transY);
- }
- if (!childHasIdentityMatrix) {
- canvas.translate(-transX, -transY);
- canvas.concat(child.getMatrix());
- canvas.translate(transX, transY);
- }
- }
-
- }
- }
- return restoreTo;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/ViewRootImpl_Accessor.java b/tools/layoutlib/bridge/src/android/view/ViewRootImpl_Accessor.java
deleted file mode 100644
index 0e15b97..0000000
--- a/tools/layoutlib/bridge/src/android/view/ViewRootImpl_Accessor.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-/**
- * Accessor to allow layoutlib to call {@link ViewRootImpl#dispatchApplyInsets} directly.
- */
-public class ViewRootImpl_Accessor {
- public static void dispatchApplyInsets(ViewRootImpl viewRoot, View host) {
- viewRoot.dispatchApplyInsets(host);
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/ViewRootImpl_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewRootImpl_Delegate.java
deleted file mode 100644
index 14b84ef..0000000
--- a/tools/layoutlib/bridge/src/android/view/ViewRootImpl_Delegate.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-/**
- * Delegate used to provide new implementation of a select few methods of {@link ViewRootImpl}
- *
- * Through the layoutlib_create tool, the original methods of ViewRootImpl have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class ViewRootImpl_Delegate {
-
- @LayoutlibDelegate
- /*package*/ static boolean isInTouchMode() {
- return false; // this allows displaying selection.
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/View_Delegate.java b/tools/layoutlib/bridge/src/android/view/View_Delegate.java
deleted file mode 100644
index 408ec54..0000000
--- a/tools/layoutlib/bridge/src/android/view/View_Delegate.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.content.Context;
-import android.os.IBinder;
-
-/**
- * Delegate used to provide new implementation of a select few methods of {@link View}
- *
- * Through the layoutlib_create tool, the original methods of View have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class View_Delegate {
-
- @LayoutlibDelegate
- /*package*/ static boolean isInEditMode(View thisView) {
- return true;
- }
-
- @LayoutlibDelegate
- /*package*/ static IBinder getWindowToken(View thisView) {
- Context baseContext = BridgeContext.getBaseContext(thisView.getContext());
- if (baseContext instanceof BridgeContext) {
- return ((BridgeContext) baseContext).getBinder();
- }
- return null;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/WindowCallback.java b/tools/layoutlib/bridge/src/android/view/WindowCallback.java
deleted file mode 100644
index 1ea8a9f..0000000
--- a/tools/layoutlib/bridge/src/android/view/WindowCallback.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.annotation.Nullable;
-import android.view.ActionMode.Callback;
-import android.view.WindowManager.LayoutParams;
-import android.view.accessibility.AccessibilityEvent;
-
-import java.util.List;
-
-/**
- * An empty implementation of {@link Window.Callback} that always returns null/false.
- */
-public class WindowCallback implements Window.Callback {
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- return false;
- }
-
- @Override
- public boolean dispatchKeyShortcutEvent(KeyEvent event) {
- return false;
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- return false;
- }
-
- @Override
- public boolean dispatchTrackballEvent(MotionEvent event) {
- return false;
- }
-
- @Override
- public boolean dispatchGenericMotionEvent(MotionEvent event) {
- return false;
- }
-
- @Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- return false;
- }
-
- @Override
- public View onCreatePanelView(int featureId) {
- return null;
- }
-
- @Override
- public boolean onCreatePanelMenu(int featureId, Menu menu) {
- return false;
- }
-
- @Override
- public boolean onPreparePanel(int featureId, View view, Menu menu) {
- return false;
- }
-
- @Override
- public boolean onMenuOpened(int featureId, Menu menu) {
- return false;
- }
-
- @Override
- public boolean onMenuItemSelected(int featureId, MenuItem item) {
- return false;
- }
-
- @Override
- public void onWindowAttributesChanged(LayoutParams attrs) {
-
- }
-
- @Override
- public void onContentChanged() {
-
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
-
- }
-
- @Override
- public void onAttachedToWindow() {
-
- }
-
- @Override
- public void onDetachedFromWindow() {
-
- }
-
- @Override
- public void onPanelClosed(int featureId, Menu menu) {
-
- }
-
- @Override
- public boolean onSearchRequested(SearchEvent searchEvent) {
- return onSearchRequested();
- }
-
- @Override
- public boolean onSearchRequested() {
- return false;
- }
-
- @Override
- public ActionMode onWindowStartingActionMode(Callback callback) {
- return null;
- }
-
- @Override
- public ActionMode onWindowStartingActionMode(Callback callback, int type) {
- return null;
- }
-
- @Override
- public void onActionModeStarted(ActionMode mode) {
-
- }
-
- @Override
- public void onActionModeFinished(ActionMode mode) {
-
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/WindowManagerGlobal_Delegate.java b/tools/layoutlib/bridge/src/android/view/WindowManagerGlobal_Delegate.java
deleted file mode 100644
index 2606e55..0000000
--- a/tools/layoutlib/bridge/src/android/view/WindowManagerGlobal_Delegate.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-/**
- * Delegate used to provide new implementation of a select few methods of
- * {@link WindowManagerGlobal}
- *
- * Through the layoutlib_create tool, the original methods of WindowManagerGlobal have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- */
-public class WindowManagerGlobal_Delegate {
-
- private static IWindowManager sService;
-
- @LayoutlibDelegate
- public static IWindowManager getWindowManagerService() {
- return sService;
- }
-
- // ---- internal implementation stuff ----
-
- public static void setWindowManagerService(IWindowManager service) {
- sService = service;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java b/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java
deleted file mode 100644
index 672ff6d..0000000
--- a/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.accessibility;
-
-import android.accessibilityservice.AccessibilityServiceInfo;
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.pm.ServiceInfo;
-import android.view.IWindow;
-import android.view.View;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * System level service that serves as an event dispatch for {@link AccessibilityEvent}s.
- * Such events are generated when something notable happens in the user interface,
- * for example an {@link android.app.Activity} starts, the focus or selection of a
- * {@link android.view.View} changes etc. Parties interested in handling accessibility
- * events implement and register an accessibility service which extends
- * {@code android.accessibilityservice.AccessibilityService}.
- *
- * @see AccessibilityEvent
- * @see android.content.Context#getSystemService
- */
-@SuppressWarnings("UnusedDeclaration")
-public final class AccessibilityManager {
-
- private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0);
-
-
- /**
- * Listener for the accessibility state.
- */
- public interface AccessibilityStateChangeListener {
-
- /**
- * Called back on change in the accessibility state.
- *
- * @param enabled Whether accessibility is enabled.
- */
- public void onAccessibilityStateChanged(boolean enabled);
- }
-
- /**
- * Listener for the system touch exploration state. To listen for changes to
- * the touch exploration state on the device, implement this interface and
- * register it with the system by calling
- * {@link #addTouchExplorationStateChangeListener}.
- */
- public interface TouchExplorationStateChangeListener {
-
- /**
- * Called when the touch exploration enabled state changes.
- *
- * @param enabled Whether touch exploration is enabled.
- */
- public void onTouchExplorationStateChanged(boolean enabled);
- }
-
- /**
- * Listener for the system high text contrast state. To listen for changes to
- * the high text contrast state on the device, implement this interface and
- * register it with the system by calling
- * {@link #addHighTextContrastStateChangeListener}.
- */
- public interface HighTextContrastChangeListener {
-
- /**
- * Called when the high text contrast enabled state changes.
- *
- * @param enabled Whether high text contrast is enabled.
- */
- public void onHighTextContrastStateChanged(boolean enabled);
- }
-
- private final IAccessibilityManagerClient.Stub mClient =
- new IAccessibilityManagerClient.Stub() {
- public void setState(int state) {
- }
-
- public void notifyServicesStateChanged() {
- }
-
- public void setRelevantEventTypes(int eventTypes) {
- }
- };
-
- /**
- * Get an AccessibilityManager instance (create one if necessary).
- *
- */
- public static AccessibilityManager getInstance(Context context) {
- return sInstance;
- }
-
- /**
- * Create an instance.
- *
- * @param context A {@link Context}.
- */
- public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
- }
-
- public IAccessibilityManagerClient getClient() {
- return mClient;
- }
-
- /**
- * Returns if the {@link AccessibilityManager} is enabled.
- *
- * @return True if this {@link AccessibilityManager} is enabled, false otherwise.
- */
- public boolean isEnabled() {
- return false;
- }
-
- /**
- * Returns if the touch exploration in the system is enabled.
- *
- * @return True if touch exploration is enabled, false otherwise.
- */
- public boolean isTouchExplorationEnabled() {
- return true;
- }
-
- /**
- * Returns if the high text contrast in the system is enabled.
- * <p>
- * <strong>Note:</strong> You need to query this only if you application is
- * doing its own rendering and does not rely on the platform rendering pipeline.
- * </p>
- *
- */
- public boolean isHighTextContrastEnabled() {
- return false;
- }
-
- /**
- * Sends an {@link AccessibilityEvent}.
- */
- public void sendAccessibilityEvent(AccessibilityEvent event) {
- }
-
- /**
- * Requests interruption of the accessibility feedback from all accessibility services.
- */
- public void interrupt() {
- }
-
- /**
- * Returns the {@link ServiceInfo}s of the installed accessibility services.
- *
- * @return An unmodifiable list with {@link ServiceInfo}s.
- */
- @Deprecated
- public List<ServiceInfo> getAccessibilityServiceList() {
- return Collections.emptyList();
- }
-
- public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
- return Collections.emptyList();
- }
-
- /**
- * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
- * for a given feedback type.
- *
- * @param feedbackTypeFlags The feedback type flags.
- * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
- *
- * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
- * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
- * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
- * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
- * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
- */
- public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
- int feedbackTypeFlags) {
- return Collections.emptyList();
- }
-
- /**
- * Registers an {@link AccessibilityStateChangeListener} for changes in
- * the global accessibility state of the system.
- *
- * @param listener The listener.
- * @return True if successfully registered.
- */
- public boolean addAccessibilityStateChangeListener(
- AccessibilityStateChangeListener listener) {
- return true;
- }
-
- public boolean removeAccessibilityStateChangeListener(
- AccessibilityStateChangeListener listener) {
- return true;
- }
-
- /**
- * Registers a {@link TouchExplorationStateChangeListener} for changes in
- * the global touch exploration state of the system.
- *
- * @param listener The listener.
- * @return True if successfully registered.
- */
- public boolean addTouchExplorationStateChangeListener(
- @NonNull TouchExplorationStateChangeListener listener) {
- return true;
- }
-
- /**
- * Unregisters a {@link TouchExplorationStateChangeListener}.
- *
- * @param listener The listener.
- * @return True if successfully unregistered.
- */
- public boolean removeTouchExplorationStateChangeListener(
- @NonNull TouchExplorationStateChangeListener listener) {
- return true;
- }
-
- /**
- * Registers a {@link HighTextContrastChangeListener} for changes in
- * the global high text contrast state of the system.
- *
- * @param listener The listener.
- * @return True if successfully registered.
- *
- */
- public boolean addHighTextContrastStateChangeListener(
- @NonNull HighTextContrastChangeListener listener) {
- return true;
- }
-
- /**
- * Unregisters a {@link HighTextContrastChangeListener}.
- *
- * @param listener The listener.
- * @return True if successfully unregistered.
- *
- */
- public boolean removeHighTextContrastStateChangeListener(
- @NonNull HighTextContrastChangeListener listener) {
- return true;
- }
-
- /**
- * Sets the current state and notifies listeners, if necessary.
- *
- * @param stateFlags The state flags.
- */
- private void setStateLocked(int stateFlags) {
- }
-
- public int addAccessibilityInteractionConnection(IWindow windowToken,
- IAccessibilityInteractionConnection connection) {
- return View.NO_ID;
- }
-
- public void removeAccessibilityInteractionConnection(IWindow windowToken) {
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java b/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java
deleted file mode 100644
index dc4f9c8..0000000
--- a/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.inputmethod;
-
-/**
- * Class allowing access to package-protected methods/fields.
- */
-public class InputMethodManager_Accessor {
-
- public static void resetInstance() {
- InputMethodManager.sInstance = null;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java b/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java
deleted file mode 100644
index 7c98847..0000000
--- a/tools/layoutlib/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.inputmethod;
-
-import com.android.layoutlib.bridge.android.BridgeIInputMethodManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.content.Context;
-import android.os.Looper;
-
-
-/**
- * Delegate used to provide new implementation of a select few methods of {@link InputMethodManager}
- *
- * Through the layoutlib_create tool, the original methods of InputMethodManager have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class InputMethodManager_Delegate {
-
- // ---- Overridden methods ----
-
- @LayoutlibDelegate
- /*package*/ static InputMethodManager getInstance() {
- synchronized (InputMethodManager.class) {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm == null) {
- imm = new InputMethodManager(
- new BridgeIInputMethodManager(), Looper.getMainLooper());
- InputMethodManager.sInstance = imm;
- }
- return imm;
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java b/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java
deleted file mode 100644
index 8e1f218..0000000
--- a/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.textservice;
-
-import android.os.Bundle;
-import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
-
-import java.util.Locale;
-
-/**
- * A stub class of TextServicesManager for Layout-Lib.
- */
-public final class TextServicesManager {
- private static final TextServicesManager sInstance = new TextServicesManager();
- private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0];
-
- /**
- * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
- * @hide
- */
- public static TextServicesManager getInstance() {
- return sInstance;
- }
-
- public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale,
- SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) {
- return null;
- }
-
- /**
- * @hide
- */
- public SpellCheckerInfo[] getEnabledSpellCheckers() {
- return EMPTY_SPELL_CHECKER_INFO;
- }
-
- /**
- * @hide
- */
- public SpellCheckerInfo getCurrentSpellChecker() {
- return null;
- }
-
- /**
- * @hide
- */
- public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
- boolean allowImplicitlySelectedSubtype) {
- return null;
- }
-
- /**
- * @hide
- */
- public boolean isSpellCheckerEnabled() {
- return false;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/webkit/WebView.java b/tools/layoutlib/bridge/src/android/webkit/WebView.java
deleted file mode 100644
index 202f204..0000000
--- a/tools/layoutlib/bridge/src/android/webkit/WebView.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * Copyright (C) 2008 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.webkit;
-
-import com.android.layoutlib.bridge.MockView;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Picture;
-import android.os.Bundle;
-import android.os.Message;
-import android.util.AttributeSet;
-import android.view.View;
-
-/**
- * Mock version of the WebView.
- * Only non override public methods from the real WebView have been added in there.
- * Methods that take an unknown class as parameter or as return object, have been removed for now.
- *
- * TODO: generate automatically.
- *
- */
-public class WebView extends MockView {
-
- /**
- * Construct a new WebView with a Context object.
- * @param context A Context object used to access application assets.
- */
- public WebView(Context context) {
- this(context, null);
- }
-
- /**
- * Construct a new WebView with layout parameters.
- * @param context A Context object used to access application assets.
- * @param attrs An AttributeSet passed to our parent.
- */
- public WebView(Context context, AttributeSet attrs) {
- this(context, attrs, com.android.internal.R.attr.webViewStyle);
- }
-
- /**
- * Construct a new WebView with layout parameters and a default style.
- * @param context A Context object used to access application assets.
- * @param attrs An AttributeSet passed to our parent.
- * @param defStyle The default style resource ID.
- */
- public WebView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- // START FAKE PUBLIC METHODS
-
- public void setHorizontalScrollbarOverlay(boolean overlay) {
- }
-
- public void setVerticalScrollbarOverlay(boolean overlay) {
- }
-
- public boolean overlayHorizontalScrollbar() {
- return false;
- }
-
- public boolean overlayVerticalScrollbar() {
- return false;
- }
-
- public void savePassword(String host, String username, String password) {
- }
-
- public void setHttpAuthUsernamePassword(String host, String realm,
- String username, String password) {
- }
-
- public String[] getHttpAuthUsernamePassword(String host, String realm) {
- return null;
- }
-
- public void destroy() {
- }
-
- public static void enablePlatformNotifications() {
- }
-
- public static void disablePlatformNotifications() {
- }
-
- public void loadUrl(String url) {
- }
-
- public void loadData(String data, String mimeType, String encoding) {
- }
-
- public void loadDataWithBaseURL(String baseUrl, String data,
- String mimeType, String encoding, String failUrl) {
- }
-
- public void stopLoading() {
- }
-
- public void reload() {
- }
-
- public boolean canGoBack() {
- return false;
- }
-
- public void goBack() {
- }
-
- public boolean canGoForward() {
- return false;
- }
-
- public void goForward() {
- }
-
- public boolean canGoBackOrForward(int steps) {
- return false;
- }
-
- public void goBackOrForward(int steps) {
- }
-
- public boolean pageUp(boolean top) {
- return false;
- }
-
- public boolean pageDown(boolean bottom) {
- return false;
- }
-
- public void clearView() {
- }
-
- public Picture capturePicture() {
- return null;
- }
-
- public float getScale() {
- return 0;
- }
-
- public void setInitialScale(int scaleInPercent) {
- }
-
- public void invokeZoomPicker() {
- }
-
- public void requestFocusNodeHref(Message hrefMsg) {
- }
-
- public void requestImageRef(Message msg) {
- }
-
- public String getUrl() {
- return null;
- }
-
- public String getTitle() {
- return null;
- }
-
- public Bitmap getFavicon() {
- return null;
- }
-
- public int getProgress() {
- return 0;
- }
-
- public int getContentHeight() {
- return 0;
- }
-
- public void pauseTimers() {
- }
-
- public void resumeTimers() {
- }
-
- public void clearCache() {
- }
-
- public void clearFormData() {
- }
-
- public void clearHistory() {
- }
-
- public void clearSslPreferences() {
- }
-
- public static String findAddress(String addr) {
- return null;
- }
-
- public void documentHasImages(Message response) {
- }
-
- public void setWebViewClient(WebViewClient client) {
- }
-
- public void setDownloadListener(DownloadListener listener) {
- }
-
- public void setWebChromeClient(WebChromeClient client) {
- }
-
- public void addJavascriptInterface(Object obj, String interfaceName) {
- }
-
- public View getZoomControls() {
- return null;
- }
-
- public boolean zoomIn() {
- return false;
- }
-
- public boolean zoomOut() {
- return false;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/widget/Toolbar_Accessor.java b/tools/layoutlib/bridge/src/android/widget/Toolbar_Accessor.java
deleted file mode 100644
index fdd1779..0000000
--- a/tools/layoutlib/bridge/src/android/widget/Toolbar_Accessor.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2014 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.widget;
-
-import android.content.Context;
-
-/**
- * To access non public members of classes in {@link Toolbar}
- */
-public class Toolbar_Accessor {
- public static ActionMenuPresenter getActionMenuPresenter(Toolbar toolbar) {
- return toolbar.getOuterActionMenuPresenter();
- }
-
- public static Context getPopupContext(Toolbar toolbar) {
- return toolbar.getPopupContext();
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/util/VirtualRefBasePtr_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/util/VirtualRefBasePtr_Delegate.java
deleted file mode 100644
index 01fe45d..0000000
--- a/tools/layoutlib/bridge/src/com/android/internal/util/VirtualRefBasePtr_Delegate.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.util;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.util.LongSparseLongArray;
-
-/**
- * Delegate used to provide new implementation the native methods of {@link VirtualRefBasePtr}
- *
- * Through the layoutlib_create tool, the original native methods of VirtualRefBasePtr have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- */
-@SuppressWarnings("unused")
-public class VirtualRefBasePtr_Delegate {
- private static final DelegateManager<Object> sManager = new DelegateManager<>(Object.class);
- private static final LongSparseLongArray sRefCount = new LongSparseLongArray();
-
- @LayoutlibDelegate
- /*package*/ static synchronized void nIncStrong(long ptr) {
- long counter = sRefCount.get(ptr);
- sRefCount.put(ptr, ++counter);
- }
-
- @LayoutlibDelegate
- /*package*/ static synchronized void nDecStrong(long ptr) {
- long counter = sRefCount.get(ptr);
-
- if (counter > 1) {
- sRefCount.put(ptr, --counter);
- } else {
- sRefCount.delete(ptr);
- sManager.removeJavaReferenceFor(ptr);
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/util/XmlUtils_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/util/XmlUtils_Delegate.java
deleted file mode 100644
index bf998b8..0000000
--- a/tools/layoutlib/bridge/src/com/android/internal/util/XmlUtils_Delegate.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.util;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-
-/**
- * Delegate used to provide new implementation of a select few methods of {@link XmlUtils}
- *
- * Through the layoutlib_create tool, the original methods of XmlUtils have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class XmlUtils_Delegate {
-
- @LayoutlibDelegate
- /*package*/ static final int convertValueToInt(CharSequence charSeq, int defaultValue) {
- if (null == charSeq)
- return defaultValue;
-
- String nm = charSeq.toString();
-
- // This code is copied from the original implementation. The issue is that
- // The Dalvik libraries are able to handle Integer.parse("XXXXXXXX", 16) where XXXXXXX
- // is > 80000000 but the Java VM cannot.
-
- int sign = 1;
- int index = 0;
- int len = nm.length();
- int base = 10;
-
- if ('-' == nm.charAt(0)) {
- sign = -1;
- index++;
- }
-
- if ('0' == nm.charAt(index)) {
- // Quick check for a zero by itself
- if (index == (len - 1))
- return 0;
-
- char c = nm.charAt(index + 1);
-
- if ('x' == c || 'X' == c) {
- index += 2;
- base = 16;
- } else {
- index++;
- base = 8;
- }
- }
- else if ('#' == nm.charAt(index)) {
- index++;
- base = 16;
- }
-
- return ((int)Long.parseLong(nm.substring(index), base)) * sign;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/view/animation/NativeInterpolatorFactoryHelper_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/view/animation/NativeInterpolatorFactoryHelper_Delegate.java
deleted file mode 100644
index da1ab27..0000000
--- a/tools/layoutlib/bridge/src/com/android/internal/view/animation/NativeInterpolatorFactoryHelper_Delegate.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.view.animation;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.graphics.Path;
-import android.util.MathUtils;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.AnticipateInterpolator;
-import android.view.animation.AnticipateOvershootInterpolator;
-import android.view.animation.BaseInterpolator;
-import android.view.animation.BounceInterpolator;
-import android.view.animation.CycleInterpolator;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.OvershootInterpolator;
-import android.view.animation.PathInterpolator;
-
-/**
- * Delegate used to provide new implementation of a select few methods of {@link
- * NativeInterpolatorFactoryHelper}
- * <p>
- * Through the layoutlib_create tool, the original methods of NativeInterpolatorFactoryHelper have
- * been replaced by calls to methods of the same name in this delegate class.
- */
-@SuppressWarnings("unused")
-public class NativeInterpolatorFactoryHelper_Delegate {
- private static final DelegateManager<Interpolator> sManager = new DelegateManager<>
- (Interpolator.class);
-
- public static Interpolator getDelegate(long nativePtr) {
- return sManager.getDelegate(nativePtr);
- }
-
- @LayoutlibDelegate
- /*package*/ static long createAccelerateDecelerateInterpolator() {
- return sManager.addNewDelegate(new AccelerateDecelerateInterpolator());
- }
-
- @LayoutlibDelegate
- /*package*/ static long createAccelerateInterpolator(float factor) {
- return sManager.addNewDelegate(new AccelerateInterpolator(factor));
- }
-
- @LayoutlibDelegate
- /*package*/ static long createAnticipateInterpolator(float tension) {
- return sManager.addNewDelegate(new AnticipateInterpolator(tension));
- }
-
- @LayoutlibDelegate
- /*package*/ static long createAnticipateOvershootInterpolator(float tension) {
- return sManager.addNewDelegate(new AnticipateOvershootInterpolator(tension));
- }
-
- @LayoutlibDelegate
- /*package*/ static long createBounceInterpolator() {
- return sManager.addNewDelegate(new BounceInterpolator());
- }
-
- @LayoutlibDelegate
- /*package*/ static long createCycleInterpolator(float cycles) {
- return sManager.addNewDelegate(new CycleInterpolator(cycles));
- }
-
- @LayoutlibDelegate
- /*package*/ static long createDecelerateInterpolator(float factor) {
- return sManager.addNewDelegate(new DecelerateInterpolator(factor));
- }
-
- @LayoutlibDelegate
- /*package*/ static long createLinearInterpolator() {
- return sManager.addNewDelegate(new LinearInterpolator());
- }
-
- @LayoutlibDelegate
- /*package*/ static long createOvershootInterpolator(float tension) {
- return sManager.addNewDelegate(new OvershootInterpolator(tension));
- }
-
- @LayoutlibDelegate
- /*package*/ static long createPathInterpolator(float[] x, float[] y) {
- Path path = new Path();
- path.moveTo(x[0], y[0]);
- for (int i = 1; i < x.length; i++) {
- path.lineTo(x[i], y[i]);
- }
- return sManager.addNewDelegate(new PathInterpolator(path));
- }
-
- private static class LutInterpolator extends BaseInterpolator {
- private final float[] mValues;
- private final int mSize;
-
- private LutInterpolator(float[] values) {
- mValues = values;
- mSize = mValues.length;
- }
-
- @Override
- public float getInterpolation(float input) {
- float lutpos = input * (mSize - 1);
- if (lutpos >= (mSize - 1)) {
- return mValues[mSize - 1];
- }
-
- int ipart = (int) lutpos;
- float weight = lutpos - ipart;
-
- int i1 = ipart;
- int i2 = Math.min(i1 + 1, mSize - 1);
-
- assert i1 >= 0 && i2 >= 0 : "Negatives in the interpolation";
-
- return MathUtils.lerp(mValues[i1], mValues[i2], weight);
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static long createLutInterpolator(float[] values) {
- return sManager.addNewDelegate(new LutInterpolator(values));
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java b/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java
deleted file mode 100644
index bb95c4e..0000000
--- a/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2014 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.view.menu;
-
-import com.android.layoutlib.bridge.android.BridgeContext;
-
-import android.content.Context;
-import android.view.View;
-
-/**
- * An extension of the {@link MenuItemImpl} to store the view cookie also.
- */
-public class BridgeMenuItemImpl extends MenuItemImpl {
-
- /**
- * An object returned by the IDE that helps mapping each View to the corresponding XML tag in
- * the layout. For Menus, we store this cookie here and attach it to the corresponding view
- * at the time of rendering.
- */
- private Object viewCookie;
- private BridgeContext mContext;
-
- /**
- * Instantiates this menu item.
- */
- BridgeMenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering,
- CharSequence title, int showAsAction) {
- super(menu, group, id, categoryOrder, ordering, title, showAsAction);
- Context context = menu.getContext();
- context = BridgeContext.getBaseContext(context);
- if (context instanceof BridgeContext) {
- mContext = ((BridgeContext) context);
- }
- }
-
- public Object getViewCookie() {
- return viewCookie;
- }
-
- public void setViewCookie(Object viewCookie) {
- // If the menu item has an associated action provider view,
- // directly set the cookie in the view to cookie map stored in BridgeContext.
- View actionView = getActionView();
- if (actionView != null && mContext != null) {
- mContext.addViewKey(actionView, viewCookie);
- // We don't need to add the view cookie to the this item now. But there's no harm in
- // storing it, in case we need it in the future.
- }
- this.viewCookie = viewCookie;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java
deleted file mode 100644
index 505fb81..0000000
--- a/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2014 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.view.menu;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-/**
- * Delegate used to provide new implementation of a select few methods of {@link MenuBuilder}
- * <p/>
- * Through the layoutlib_create tool, the original methods of {@code MenuBuilder} have been
- * replaced by calls to methods of the same name in this delegate class.
- */
-public class MenuBuilder_Delegate {
- /**
- * The method overrides the instantiation of the {@link MenuItemImpl} with an instance of
- * {@link BridgeMenuItemImpl} so that view cookies may be stored.
- */
- @LayoutlibDelegate
- /*package*/ static MenuItemImpl createNewMenuItem(MenuBuilder thisMenu, int group, int id,
- int categoryOrder, int ordering, CharSequence title, int defaultShowAsAction) {
- return new BridgeMenuItemImpl(thisMenu, group, id, categoryOrder, ordering, title,
- defaultShowAsAction);
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/internal/widget/ActionBarAccessor.java b/tools/layoutlib/bridge/src/com/android/internal/widget/ActionBarAccessor.java
deleted file mode 100644
index 40b6220..0000000
--- a/tools/layoutlib/bridge/src/com/android/internal/widget/ActionBarAccessor.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2014 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.widget;
-
-import android.widget.ActionMenuPresenter;
-
-/**
- * To access non public members of AbsActionBarView
- */
-public class ActionBarAccessor {
-
- /**
- * Returns the {@link ActionMenuPresenter} associated with the {@link AbsActionBarView}
- */
- public static ActionMenuPresenter getActionMenuPresenter(AbsActionBarView view) {
- return view.mActionMenuPresenter;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
deleted file mode 100644
index 2e14974..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ /dev/null
@@ -1,737 +0,0 @@
-/*
- * Copyright (C) 2008 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.layoutlib.bridge;
-
-import com.android.ide.common.rendering.api.Capability;
-import com.android.ide.common.rendering.api.DrawableParams;
-import com.android.ide.common.rendering.api.Features;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.RenderSession;
-import com.android.ide.common.rendering.api.Result;
-import com.android.ide.common.rendering.api.Result.Status;
-import com.android.ide.common.rendering.api.SessionParams;
-import com.android.layoutlib.bridge.android.RenderParamsFlags;
-import com.android.layoutlib.bridge.impl.RenderDrawable;
-import com.android.layoutlib.bridge.impl.RenderSessionImpl;
-import com.android.layoutlib.bridge.util.DynamicIdMap;
-import com.android.ninepatch.NinePatchChunk;
-import com.android.resources.ResourceType;
-import com.android.tools.layoutlib.create.MethodAdapter;
-import com.android.tools.layoutlib.create.OverrideMethod;
-import com.android.util.Pair;
-
-import android.annotation.NonNull;
-import android.content.res.BridgeAssetManager;
-import android.graphics.Bitmap;
-import android.graphics.FontFamily_Delegate;
-import android.graphics.Typeface;
-import android.graphics.Typeface_Delegate;
-import android.icu.util.ULocale;
-import android.os.Looper;
-import android.os.Looper_Accessor;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-
-import java.io.File;
-import java.lang.ref.SoftReference;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.EnumMap;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.locks.ReentrantLock;
-
-import libcore.io.MemoryMappedFile_Delegate;
-
-import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
-import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
-
-/**
- * Main entry point of the LayoutLib Bridge.
- * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call
- * {@link #createSession(SessionParams)}
- */
-public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
-
- private static final String ICU_LOCALE_DIRECTION_RTL = "right-to-left";
-
- public static class StaticMethodNotImplementedException extends RuntimeException {
- private static final long serialVersionUID = 1L;
-
- public StaticMethodNotImplementedException(String msg) {
- super(msg);
- }
- }
-
- /**
- * Lock to ensure only one rendering/inflating happens at a time.
- * This is due to some singleton in the Android framework.
- */
- private final static ReentrantLock sLock = new ReentrantLock();
-
- /**
- * Maps from id to resource type/name. This is for com.android.internal.R
- */
- private final static Map<Integer, Pair<ResourceType, String>> sRMap =
- new HashMap<Integer, Pair<ResourceType, String>>();
-
- /**
- * Same as sRMap except for int[] instead of int resources. This is for android.R only.
- */
- private final static Map<IntArray, String> sRArrayMap = new HashMap<IntArray, String>(384);
- /**
- * Reverse map compared to sRMap, resource type -> (resource name -> id).
- * This is for com.android.internal.R.
- */
- private final static Map<ResourceType, Map<String, Integer>> sRevRMap =
- new EnumMap<ResourceType, Map<String,Integer>>(ResourceType.class);
-
- // framework resources are defined as 0x01XX#### where XX is the resource type (layout,
- // drawable, etc...). Using FF as the type allows for 255 resource types before we get a
- // collision which should be fine.
- private final static int DYNAMIC_ID_SEED_START = 0x01ff0000;
- private final static DynamicIdMap sDynamicIds = new DynamicIdMap(DYNAMIC_ID_SEED_START);
-
- private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache =
- new HashMap<Object, Map<String, SoftReference<Bitmap>>>();
- private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache =
- new HashMap<Object, Map<String, SoftReference<NinePatchChunk>>>();
-
- private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache =
- new HashMap<String, SoftReference<Bitmap>>();
- private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache =
- new HashMap<String, SoftReference<NinePatchChunk>>();
-
- private static Map<String, Map<String, Integer>> sEnumValueMap;
- private static Map<String, String> sPlatformProperties;
-
- /**
- * int[] wrapper to use as keys in maps.
- */
- private final static class IntArray {
- private int[] mArray;
-
- private IntArray() {
- // do nothing
- }
-
- private IntArray(int[] a) {
- mArray = a;
- }
-
- private void set(int[] a) {
- mArray = a;
- }
-
- @Override
- public int hashCode() {
- return Arrays.hashCode(mArray);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
- if (obj == null) return false;
- if (getClass() != obj.getClass()) return false;
-
- IntArray other = (IntArray) obj;
- return Arrays.equals(mArray, other.mArray);
- }
- }
-
- /** Instance of IntArrayWrapper to be reused in {@link #resolveResourceId(int[])}. */
- private final static IntArray sIntArrayWrapper = new IntArray();
-
- /**
- * A default log than prints to stdout/stderr.
- */
- private final static LayoutLog sDefaultLog = new LayoutLog() {
- @Override
- public void error(String tag, String message, Object data) {
- System.err.println(message);
- }
-
- @Override
- public void error(String tag, String message, Throwable throwable, Object data) {
- System.err.println(message);
- }
-
- @Override
- public void warning(String tag, String message, Object data) {
- System.out.println(message);
- }
- };
-
- /**
- * Current log.
- */
- private static LayoutLog sCurrentLog = sDefaultLog;
-
- private static final int LAST_SUPPORTED_FEATURE = Features.THEME_PREVIEW_NAVIGATION_BAR;
-
- @Override
- public int getApiLevel() {
- return com.android.ide.common.rendering.api.Bridge.API_CURRENT;
- }
-
- @Override
- @Deprecated
- public EnumSet<Capability> getCapabilities() {
- // The Capability class is deprecated and frozen. All Capabilities enumerated there are
- // supported by this version of LayoutLibrary. So, it's safe to use EnumSet.allOf()
- return EnumSet.allOf(Capability.class);
- }
-
- @Override
- public boolean supports(int feature) {
- return feature <= LAST_SUPPORTED_FEATURE;
- }
-
- @Override
- public boolean init(Map<String,String> platformProperties,
- File fontLocation,
- Map<String, Map<String, Integer>> enumValueMap,
- LayoutLog log) {
- sPlatformProperties = platformProperties;
- sEnumValueMap = enumValueMap;
-
- BridgeAssetManager.initSystem();
-
- // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener
- // on static (native) methods which prints the signature on the console and
- // throws an exception.
- // This is useful when testing the rendering in ADT to identify static native
- // methods that are ignored -- layoutlib_create makes them returns 0/false/null
- // which is generally OK yet might be a problem, so this is how you'd find out.
- //
- // Currently layoutlib_create only overrides static native method.
- // Static non-natives are not overridden and thus do not get here.
- final String debug = System.getenv("DEBUG_LAYOUT");
- if (debug != null && !debug.equals("0") && !debug.equals("false")) {
-
- OverrideMethod.setDefaultListener(new MethodAdapter() {
- @Override
- public void onInvokeV(String signature, boolean isNative, Object caller) {
- sDefaultLog.error(null, "Missing Stub: " + signature +
- (isNative ? " (native)" : ""), null /*data*/);
-
- if (debug.equalsIgnoreCase("throw")) {
- // Throwing this exception doesn't seem that useful. It breaks
- // the layout editor yet doesn't display anything meaningful to the
- // user. Having the error in the console is just as useful. We'll
- // throw it only if the environment variable is "throw" or "THROW".
- throw new StaticMethodNotImplementedException(signature);
- }
- }
- });
- }
-
- // load the fonts.
- FontFamily_Delegate.setFontLocation(fontLocation.getAbsolutePath());
- MemoryMappedFile_Delegate.setDataDir(fontLocation.getAbsoluteFile().getParentFile());
-
- // now parse com.android.internal.R (and only this one as android.R is a subset of
- // the internal version), and put the content in the maps.
- try {
- Class<?> r = com.android.internal.R.class;
- // Parse the styleable class first, since it may contribute to attr values.
- parseStyleable();
-
- for (Class<?> inner : r.getDeclaredClasses()) {
- if (inner == com.android.internal.R.styleable.class) {
- // Already handled the styleable case. Not skipping attr, as there may be attrs
- // that are not referenced from styleables.
- continue;
- }
- String resTypeName = inner.getSimpleName();
- ResourceType resType = ResourceType.getEnum(resTypeName);
- if (resType != null) {
- Map<String, Integer> fullMap = null;
- switch (resType) {
- case ATTR:
- fullMap = sRevRMap.get(ResourceType.ATTR);
- break;
- case STRING:
- case STYLE:
- // Slightly less than thousand entries in each.
- fullMap = new HashMap<String, Integer>(1280);
- // no break.
- default:
- if (fullMap == null) {
- fullMap = new HashMap<String, Integer>();
- }
- sRevRMap.put(resType, fullMap);
- }
-
- for (Field f : inner.getDeclaredFields()) {
- // only process static final fields. Since the final attribute may have
- // been altered by layoutlib_create, we only check static
- if (!isValidRField(f)) {
- continue;
- }
- Class<?> type = f.getType();
- if (type.isArray()) {
- // if the object is an int[] we put it in sRArrayMap using an IntArray
- // wrapper that properly implements equals and hashcode for the array
- // objects, as required by the map contract.
- sRArrayMap.put(new IntArray((int[]) f.get(null)), f.getName());
- } else {
- Integer value = (Integer) f.get(null);
- sRMap.put(value, Pair.of(resType, f.getName()));
- fullMap.put(f.getName(), value);
- }
- }
- }
- }
- } catch (Exception throwable) {
- if (log != null) {
- log.error(LayoutLog.TAG_BROKEN,
- "Failed to load com.android.internal.R from the layout library jar",
- throwable, null);
- }
- return false;
- }
-
- return true;
- }
-
- /**
- * Tests if the field is pubic, static and one of int or int[].
- */
- private static boolean isValidRField(Field field) {
- int modifiers = field.getModifiers();
- boolean isAcceptable = Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers);
- Class<?> type = field.getType();
- return isAcceptable && type == int.class ||
- (type.isArray() && type.getComponentType() == int.class);
-
- }
-
- private static void parseStyleable() throws Exception {
- // R.attr doesn't contain all the needed values. There are too many resources in the
- // framework for all to be in the R class. Only the ones specified manually in
- // res/values/symbols.xml are put in R class. Since, we need to create a map of all attr
- // values, we try and find them from the styleables.
-
- // There were 1500 elements in this map at M timeframe.
- Map<String, Integer> revRAttrMap = new HashMap<String, Integer>(2048);
- sRevRMap.put(ResourceType.ATTR, revRAttrMap);
- // There were 2000 elements in this map at M timeframe.
- Map<String, Integer> revRStyleableMap = new HashMap<String, Integer>(3072);
- sRevRMap.put(ResourceType.STYLEABLE, revRStyleableMap);
- Class<?> c = com.android.internal.R.styleable.class;
- Field[] fields = c.getDeclaredFields();
- // Sort the fields to bring all arrays to the beginning, so that indices into the array are
- // able to refer back to the arrays (i.e. no forward references).
- Arrays.sort(fields, new Comparator<Field>() {
- @Override
- public int compare(Field o1, Field o2) {
- if (o1 == o2) {
- return 0;
- }
- Class<?> t1 = o1.getType();
- Class<?> t2 = o2.getType();
- if (t1.isArray() && !t2.isArray()) {
- return -1;
- } else if (t2.isArray() && !t1.isArray()) {
- return 1;
- }
- return o1.getName().compareTo(o2.getName());
- }
- });
- Map<String, int[]> styleables = new HashMap<String, int[]>();
- for (Field field : fields) {
- if (!isValidRField(field)) {
- // Only consider public static fields that are int or int[].
- // Don't check the final flag as it may have been modified by layoutlib_create.
- continue;
- }
- String name = field.getName();
- if (field.getType().isArray()) {
- int[] styleableValue = (int[]) field.get(null);
- sRArrayMap.put(new IntArray(styleableValue), name);
- styleables.put(name, styleableValue);
- continue;
- }
- // Not an array.
- String arrayName = name;
- int[] arrayValue = null;
- int index;
- while ((index = arrayName.lastIndexOf('_')) >= 0) {
- // Find the name of the corresponding styleable.
- // Search in reverse order so that attrs like LinearLayout_Layout_layout_gravity
- // are mapped to LinearLayout_Layout and not to LinearLayout.
- arrayName = arrayName.substring(0, index);
- arrayValue = styleables.get(arrayName);
- if (arrayValue != null) {
- break;
- }
- }
- index = (Integer) field.get(null);
- if (arrayValue != null) {
- String attrName = name.substring(arrayName.length() + 1);
- int attrValue = arrayValue[index];
- sRMap.put(attrValue, Pair.of(ResourceType.ATTR, attrName));
- revRAttrMap.put(attrName, attrValue);
- }
- sRMap.put(index, Pair.of(ResourceType.STYLEABLE, name));
- revRStyleableMap.put(name, index);
- }
- }
-
- @Override
- public boolean dispose() {
- BridgeAssetManager.clearSystem();
-
- // dispose of the default typeface.
- Typeface_Delegate.resetDefaults();
- Typeface.sDynamicTypefaceCache.evictAll();
-
- return true;
- }
-
- /**
- * Starts a layout session by inflating and rendering it. The method returns a
- * {@link RenderSession} on which further actions can be taken.
- * <p/>
- * If {@link SessionParams} includes the {@link RenderParamsFlags#FLAG_DO_NOT_RENDER_ON_CREATE},
- * this method will only inflate the layout but will NOT render it.
- * @param params the {@link SessionParams} object with all the information necessary to create
- * the scene.
- * @return a new {@link RenderSession} object that contains the result of the layout.
- * @since 5
- */
- @Override
- public RenderSession createSession(SessionParams params) {
- try {
- Result lastResult = SUCCESS.createResult();
- RenderSessionImpl scene = new RenderSessionImpl(params);
- try {
- prepareThread();
- lastResult = scene.init(params.getTimeout());
- if (lastResult.isSuccess()) {
- lastResult = scene.inflate();
-
- boolean doNotRenderOnCreate = Boolean.TRUE.equals(
- params.getFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE));
- if (lastResult.isSuccess() && !doNotRenderOnCreate) {
- lastResult = scene.render(true /*freshRender*/);
- }
- }
- } finally {
- scene.release();
- cleanupThread();
- }
-
- return new BridgeRenderSession(scene, lastResult);
- } catch (Throwable t) {
- // get the real cause of the exception.
- Throwable t2 = t;
- while (t2.getCause() != null) {
- t2 = t.getCause();
- }
- return new BridgeRenderSession(null,
- ERROR_UNKNOWN.createResult(t2.getMessage(), t));
- }
- }
-
- @Override
- public Result renderDrawable(DrawableParams params) {
- try {
- Result lastResult = SUCCESS.createResult();
- RenderDrawable action = new RenderDrawable(params);
- try {
- prepareThread();
- lastResult = action.init(params.getTimeout());
- if (lastResult.isSuccess()) {
- lastResult = action.render();
- }
- } finally {
- action.release();
- cleanupThread();
- }
-
- return lastResult;
- } catch (Throwable t) {
- // get the real cause of the exception.
- Throwable t2 = t;
- while (t2.getCause() != null) {
- t2 = t.getCause();
- }
- return ERROR_UNKNOWN.createResult(t2.getMessage(), t);
- }
- }
-
- @Override
- public void clearCaches(Object projectKey) {
- if (projectKey != null) {
- sProjectBitmapCache.remove(projectKey);
- sProject9PatchCache.remove(projectKey);
- }
- }
-
- @Override
- public Result getViewParent(Object viewObject) {
- if (viewObject instanceof View) {
- return Status.SUCCESS.createResult(((View)viewObject).getParent());
- }
-
- throw new IllegalArgumentException("viewObject is not a View");
- }
-
- @Override
- public Result getViewIndex(Object viewObject) {
- if (viewObject instanceof View) {
- View view = (View) viewObject;
- ViewParent parentView = view.getParent();
-
- if (parentView instanceof ViewGroup) {
- Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view));
- }
-
- return Status.SUCCESS.createResult();
- }
-
- throw new IllegalArgumentException("viewObject is not a View");
- }
-
- @Override
- public boolean isRtl(String locale) {
- return isLocaleRtl(locale);
- }
-
- public static boolean isLocaleRtl(String locale) {
- if (locale == null) {
- locale = "";
- }
- ULocale uLocale = new ULocale(locale);
- return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL);
- }
-
- /**
- * Returns the lock for the bridge
- */
- public static ReentrantLock getLock() {
- return sLock;
- }
-
- /**
- * Prepares the current thread for rendering.
- *
- * Note that while this can be called several time, the first call to {@link #cleanupThread()}
- * will do the clean-up, and make the thread unable to do further scene actions.
- */
- public synchronized static void prepareThread() {
- // we need to make sure the Looper has been initialized for this thread.
- // this is required for View that creates Handler objects.
- if (Looper.myLooper() == null) {
- Looper.prepareMainLooper();
- }
- }
-
- /**
- * Cleans up thread-specific data. After this, the thread cannot be used for scene actions.
- * <p>
- * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single
- * call to this will prevent the thread from doing further scene actions
- */
- public synchronized static void cleanupThread() {
- // clean up the looper
- Looper_Accessor.cleanupThread();
- }
-
- public static LayoutLog getLog() {
- return sCurrentLog;
- }
-
- public static void setLog(LayoutLog log) {
- // check only the thread currently owning the lock can do this.
- if (!sLock.isHeldByCurrentThread()) {
- throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
- }
-
- if (log != null) {
- sCurrentLog = log;
- } else {
- sCurrentLog = sDefaultLog;
- }
- }
-
- /**
- * Returns details of a framework resource from its integer value.
- * @param value the integer value
- * @return a Pair containing the resource type and name, or null if the id
- * does not match any resource.
- */
- public static Pair<ResourceType, String> resolveResourceId(int value) {
- Pair<ResourceType, String> pair = sRMap.get(value);
- if (pair == null) {
- pair = sDynamicIds.resolveId(value);
- if (pair == null) {
- //System.out.println(String.format("Missing id: %1$08X (%1$d)", value));
- }
- }
- return pair;
- }
-
- /**
- * Returns the name of a framework resource whose value is an int array.
- */
- public static String resolveResourceId(int[] array) {
- sIntArrayWrapper.set(array);
- return sRArrayMap.get(sIntArrayWrapper);
- }
-
- /**
- * Returns the integer id of a framework resource, from a given resource type and resource name.
- * <p/>
- * If no resource is found, it creates a dynamic id for the resource.
- *
- * @param type the type of the resource
- * @param name the name of the resource.
- *
- * @return an {@link Integer} containing the resource id.
- */
- @NonNull
- public static Integer getResourceId(ResourceType type, String name) {
- Map<String, Integer> map = sRevRMap.get(type);
- Integer value = null;
- if (map != null) {
- value = map.get(name);
- }
-
- return value == null ? sDynamicIds.getId(type, name) : value;
-
- }
-
- /**
- * Returns the list of possible enums for a given attribute name.
- */
- public static Map<String, Integer> getEnumValues(String attributeName) {
- if (sEnumValueMap != null) {
- return sEnumValueMap.get(attributeName);
- }
-
- return null;
- }
-
- /**
- * Returns the platform build properties.
- */
- public static Map<String, String> getPlatformProperties() {
- return sPlatformProperties;
- }
-
- /**
- * Returns the bitmap for a specific path, from a specific project cache, or from the
- * framework cache.
- * @param value the path of the bitmap
- * @param projectKey the key of the project, or null to query the framework cache.
- * @return the cached Bitmap or null if not found.
- */
- public static Bitmap getCachedBitmap(String value, Object projectKey) {
- if (projectKey != null) {
- Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
- if (map != null) {
- SoftReference<Bitmap> ref = map.get(value);
- if (ref != null) {
- return ref.get();
- }
- }
- } else {
- SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value);
- if (ref != null) {
- return ref.get();
- }
- }
-
- return null;
- }
-
- /**
- * Sets a bitmap in a project cache or in the framework cache.
- * @param value the path of the bitmap
- * @param bmp the Bitmap object
- * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
- */
- public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) {
- if (projectKey != null) {
- Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
-
- if (map == null) {
- map = new HashMap<String, SoftReference<Bitmap>>();
- sProjectBitmapCache.put(projectKey, map);
- }
-
- map.put(value, new SoftReference<Bitmap>(bmp));
- } else {
- sFrameworkBitmapCache.put(value, new SoftReference<Bitmap>(bmp));
- }
- }
-
- /**
- * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the
- * framework cache.
- * @param value the path of the 9 patch
- * @param projectKey the key of the project, or null to query the framework cache.
- * @return the cached 9 patch or null if not found.
- */
- public static NinePatchChunk getCached9Patch(String value, Object projectKey) {
- if (projectKey != null) {
- Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
-
- if (map != null) {
- SoftReference<NinePatchChunk> ref = map.get(value);
- if (ref != null) {
- return ref.get();
- }
- }
- } else {
- SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value);
- if (ref != null) {
- return ref.get();
- }
- }
-
- return null;
- }
-
- /**
- * Sets a 9 patch chunk in a project cache or in the framework cache.
- * @param value the path of the 9 patch
- * @param ninePatch the 9 patch object
- * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
- */
- public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
- if (projectKey != null) {
- Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
-
- if (map == null) {
- map = new HashMap<String, SoftReference<NinePatchChunk>>();
- sProject9PatchCache.put(projectKey, map);
- }
-
- map.put(value, new SoftReference<NinePatchChunk>(ninePatch));
- } else {
- sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch));
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java
deleted file mode 100644
index 6228766..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2008 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.layoutlib.bridge;
-
-/**
- * Constant definition class.<br>
- * <br>
- * Most constants have a prefix defining the content.
- * <ul>
- * <li><code>WS_</code> Workspace path constant. Those are absolute paths,
- * from the project root.</li>
- * <li><code>OS_</code> OS path constant. These paths are different depending on the platform.</li>
- * <li><code>FN_</code> File name constant.</li>
- * <li><code>FD_</code> Folder name constant.</li>
- * <li><code>EXT_</code> File extension constant. This does NOT include a dot.</li>
- * <li><code>DOT_</code> File extension constant. This start with a dot.</li>
- * <li><code>RE_</code> Regexp constant.</li>
- * <li><code>NS_</code> Namespace constant.</li>
- * <li><code>CLASS_</code> Fully qualified class name.</li>
- * </ul>
- *
- */
-public class BridgeConstants {
-
- /** Namespace for the resource XML */
- public final static String NS_RESOURCES = "http://schemas.android.com/apk/res/android";
-
- /** App auto namespace */
- public final static String NS_APP_RES_AUTO = "http://schemas.android.com/apk/res-auto";
- public final static String NS_TOOLS_URI = "http://schemas.android.com/tools";
-
- public final static String R = "com.android.internal.R";
-
-
- public final static String MATCH_PARENT = "match_parent";
- public final static String FILL_PARENT = "fill_parent";
- public final static String WRAP_CONTENT = "wrap_content";
-
- // Should be kept in sync with LayoutMetadata.KEY_LV_ITEM in tools/adt/idea
- /** Attribute in the tools namespace used to specify layout manager for RecyclerView. */
- @SuppressWarnings("SpellCheckingInspection")
- public static final String ATTR_LIST_ITEM = "listitem";
- public static final String ATTR_OPEN_DRAWER = "openDrawer";
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
deleted file mode 100644
index fdf6d63..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2010 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.layoutlib.bridge;
-
-import com.android.ide.common.rendering.api.IAnimationListener;
-import com.android.ide.common.rendering.api.ILayoutPullParser;
-import com.android.ide.common.rendering.api.RenderParams;
-import com.android.ide.common.rendering.api.RenderSession;
-import com.android.ide.common.rendering.api.Result;
-import com.android.ide.common.rendering.api.ViewInfo;
-import com.android.layoutlib.bridge.impl.RenderSessionImpl;
-import com.android.tools.layoutlib.java.System_Delegate;
-import com.android.util.PropertiesMap;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.awt.image.BufferedImage;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * An implementation of {@link RenderSession}.
- *
- * This is a pretty basic class that does almost nothing. All of the work is done in
- * {@link RenderSessionImpl}.
- *
- */
-public class BridgeRenderSession extends RenderSession {
-
- @Nullable
- private final RenderSessionImpl mSession;
- @NonNull
- private Result mLastResult;
-
- @Override
- public Result getResult() {
- return mLastResult;
- }
-
- @Override
- public BufferedImage getImage() {
- return mSession != null ? mSession.getImage() :
- new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
- }
-
- @Override
- public boolean isAlphaChannelImage() {
- return mSession != null && mSession.isAlphaChannelImage();
- }
-
- @Override
- public List<ViewInfo> getRootViews() {
- return mSession != null ? mSession.getViewInfos() : Collections.emptyList();
- }
-
- @Override
- public List<ViewInfo> getSystemRootViews() {
- return mSession != null ? mSession.getSystemViewInfos() : Collections.emptyList();
- }
-
- @Override
- public Map<Object, PropertiesMap> getDefaultProperties() {
- return mSession != null ? mSession.getDefaultProperties() : Collections.emptyMap();
- }
-
- @Override
- public Result measure(long timeout) {
- if (mSession != null) {
- try {
- Bridge.prepareThread();
- mLastResult = mSession.acquire(timeout);
- if (mLastResult.isSuccess()) {
- mSession.invalidateRenderingSize();
- mLastResult = mSession.measure();
- }
- } finally {
- mSession.release();
- Bridge.cleanupThread();
- }
- }
-
- return mLastResult;
- }
-
- @Override
- public Result render(long timeout, boolean forceMeasure) {
- if (mSession != null) {
- try {
- Bridge.prepareThread();
- mLastResult = mSession.acquire(timeout);
- if (mLastResult.isSuccess()) {
- if (forceMeasure) {
- mSession.invalidateRenderingSize();
- }
- mLastResult = mSession.render(false /*freshRender*/);
- }
- } finally {
- mSession.release();
- Bridge.cleanupThread();
- }
- }
-
- return mLastResult;
- }
-
- @Override
- public Result animate(Object targetObject, String animationName,
- boolean isFrameworkAnimation, IAnimationListener listener) {
- if (mSession != null) {
- try {
- Bridge.prepareThread();
- mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT);
- if (mLastResult.isSuccess()) {
- mLastResult = mSession.animate(targetObject, animationName, isFrameworkAnimation,
- listener);
- }
- } finally {
- mSession.release();
- Bridge.cleanupThread();
- }
- }
-
- return mLastResult;
- }
-
- @Override
- public Result insertChild(Object parentView, ILayoutPullParser childXml, int index,
- IAnimationListener listener) {
- if (!(parentView instanceof ViewGroup)) {
- throw new IllegalArgumentException("parentView is not a ViewGroup");
- }
-
- if (mSession != null) {
- try {
- Bridge.prepareThread();
- mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT);
- if (mLastResult.isSuccess()) {
- mLastResult =
- mSession.insertChild((ViewGroup) parentView, childXml, index, listener);
- }
- } finally {
- mSession.release();
- Bridge.cleanupThread();
- }
- }
-
- return mLastResult;
- }
-
-
- @Override
- public Result moveChild(Object parentView, Object childView, int index,
- Map<String, String> layoutParams, IAnimationListener listener) {
- if (!(parentView instanceof ViewGroup)) {
- throw new IllegalArgumentException("parentView is not a ViewGroup");
- }
- if (!(childView instanceof View)) {
- throw new IllegalArgumentException("childView is not a View");
- }
-
- if (mSession != null) {
- try {
- Bridge.prepareThread();
- mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT);
- if (mLastResult.isSuccess()) {
- mLastResult = mSession.moveChild((ViewGroup) parentView, (View) childView, index,
- layoutParams, listener);
- }
- } finally {
- mSession.release();
- Bridge.cleanupThread();
- }
- }
-
- return mLastResult;
- }
-
- @Override
- public Result removeChild(Object childView, IAnimationListener listener) {
- if (!(childView instanceof View)) {
- throw new IllegalArgumentException("childView is not a View");
- }
-
- if (mSession != null) {
- try {
- Bridge.prepareThread();
- mLastResult = mSession.acquire(RenderParams.DEFAULT_TIMEOUT);
- if (mLastResult.isSuccess()) {
- mLastResult = mSession.removeChild((View) childView, listener);
- }
- } finally {
- mSession.release();
- Bridge.cleanupThread();
- }
- }
-
- return mLastResult;
- }
-
- @Override
- public void setSystemTimeNanos(long nanos) {
- System_Delegate.setNanosTime(nanos);
- }
-
- @Override
- public void setSystemBootTimeNanos(long nanos) {
- System_Delegate.setBootTimeNanos(nanos);
- }
-
- @Override
- public void setElapsedFrameTimeNanos(long nanos) {
- if (mSession != null) {
- mSession.setElapsedFrameTimeNanos(nanos);
- }
- }
-
- @Override
- public void dispose() {
- if (mSession != null) {
- mSession.dispose();
- }
- }
-
- /*package*/ BridgeRenderSession(@Nullable RenderSessionImpl scene, @NonNull Result lastResult) {
- mSession = scene;
- if (scene != null) {
- mSession.setScene(this);
- }
- mLastResult = lastResult;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
deleted file mode 100644
index e10f20d..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2008 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.layoutlib.bridge;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-/**
- * Base class for mocked views.
- * <p/>
- * FrameLayout with a single TextView. Doesn't allow adding any other views to itself.
- */
-public class MockView extends FrameLayout {
-
- private final TextView mView;
-
- public MockView(Context context) {
- this(context, null);
- }
-
- public MockView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public MockView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public MockView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- mView = new TextView(context, attrs);
- mView.setTextColor(0xFF000000);
- setGravity(Gravity.CENTER);
- setText(getClass().getSimpleName());
- addView(mView);
- setBackgroundColor(0xFF7F7F7F);
- }
-
- // Only allow adding one TextView.
- @Override
- public void addView(View child) {
- if (child == mView) {
- super.addView(child);
- }
- }
-
- @Override
- public void addView(View child, int index) {
- if (child == mView) {
- super.addView(child, index);
- }
- }
-
- @Override
- public void addView(View child, int width, int height) {
- if (child == mView) {
- super.addView(child, width, height);
- }
- }
-
- @Override
- public void addView(View child, ViewGroup.LayoutParams params) {
- if (child == mView) {
- super.addView(child, params);
- }
- }
-
- @Override
- public void addView(View child, int index, ViewGroup.LayoutParams params) {
- if (child == mView) {
- super.addView(child, index, params);
- }
- }
-
- // The following methods are called by the IDE via reflection, and should be considered part
- // of the API.
- // Historically, MockView used to be a textView and had these methods. Now, we simply delegate
- // them to the contained textView.
-
- public void setText(CharSequence text) {
- mView.setText(text);
- }
-
- @SuppressWarnings("WeakerAccess") // This method is used from Studio
- public void setGravity(int gravity) {
- mView.setGravity(gravity);
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java
deleted file mode 100644
index faaf105..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2014 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.layoutlib.bridge.android;
-
-import com.android.layoutlib.bridge.impl.RenderAction;
-
-import android.icu.util.ULocale;
-
-import java.util.Locale;
-
-/**
- * This class provides an alternate implementation for {@code java.util.Locale#toLanguageTag}
- * which is only available after Java 6.
- *
- * The create tool re-writes references to the above mentioned method to this one. Hence it's
- * imperative that this class is not deleted unless the create tool is modified.
- */
-@SuppressWarnings("UnusedDeclaration")
-public class AndroidLocale {
-
- public static String toLanguageTag(Locale locale) {
- return ULocale.forLocale(locale).toLanguageTag();
- }
-
- public static String adjustLanguageCode(String languageCode) {
- String adjusted = languageCode.toLowerCase(Locale.US);
- // Map new language codes to the obsolete language
- // codes so the correct resource bundles will be used.
- if (languageCode.equals("he")) {
- adjusted = "iw";
- } else if (languageCode.equals("id")) {
- adjusted = "in";
- } else if (languageCode.equals("yi")) {
- adjusted = "ji";
- }
-
- return adjusted;
- }
-
- public static Locale forLanguageTag(String tag) {
- return ULocale.forLanguageTag(tag).toLocale();
- }
-
- public static String getScript(Locale locale) {
- return ULocale.forLocale(locale).getScript();
- }
-
- public static Locale getDefault() {
- BridgeContext context = RenderAction.getCurrentContext();
- if (context != null) {
- Locale locale = context.getConfiguration().locale;
- if (locale != null) {
- return locale;
- }
- }
- return Locale.getDefault();
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java
deleted file mode 100644
index c827f17..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2010 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.layoutlib.bridge.android;
-
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-import android.content.ContentValues;
-import android.content.IContentProvider;
-import android.content.OperationApplicationException;
-import android.content.res.AssetFileDescriptor;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.ICancellationSignal;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-
-import java.io.FileNotFoundException;
-import java.util.ArrayList;
-
-/**
- * Mock implementation of {@link IContentProvider}.
- *
- * TODO: never return null when the method is not supposed to. Return fake data instead.
- */
-public final class BridgeContentProvider implements IContentProvider {
- @Override
- public ContentProviderResult[] applyBatch(String callingPackage,
- ArrayList<ContentProviderOperation> arg0)
- throws RemoteException, OperationApplicationException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public int bulkInsert(String callingPackage, Uri arg0, ContentValues[] arg1)
- throws RemoteException {
- // TODO Auto-generated method stub
- return 0;
- }
-
- @Override
- public Bundle call(String callingPackage, String arg0, String arg1, Bundle arg2)
- throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public int delete(String callingPackage, Uri arg0, String arg1, String[] arg2)
- throws RemoteException {
- // TODO Auto-generated method stub
- return 0;
- }
-
- @Override
- public String getType(Uri arg0) throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public Uri insert(String callingPackage, Uri arg0, ContentValues arg1) throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public AssetFileDescriptor openAssetFile(
- String callingPackage, Uri arg0, String arg1, ICancellationSignal signal)
- throws RemoteException, FileNotFoundException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public ParcelFileDescriptor openFile(
- String callingPackage, Uri arg0, String arg1, ICancellationSignal signal, IBinder token)
- throws RemoteException, FileNotFoundException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public Cursor query(String callingPackage, Uri arg0, String[] arg1,
- Bundle arg3, ICancellationSignal arg4) throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public int update(String callingPackage, Uri arg0, ContentValues arg1, String arg2,
- String[] arg3) throws RemoteException {
- // TODO Auto-generated method stub
- return 0;
- }
-
- @Override
- public IBinder asBinder() {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public String[] getStreamTypes(Uri arg0, String arg1) throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public AssetFileDescriptor openTypedAssetFile(String callingPackage, Uri arg0, String arg1,
- Bundle arg2, ICancellationSignal signal) throws RemoteException, FileNotFoundException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public ICancellationSignal createCancellationSignal() throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
-
- @Override
- public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException {
- return null;
- }
-
- @Override
- public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException {
- return null;
- }
-
- @Override
- public boolean refresh(String callingPkg, Uri url, Bundle args,
- ICancellationSignal cancellationSignal) throws RemoteException {
- return false;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java
deleted file mode 100644
index 8d259d7..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2009 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.layoutlib.bridge.android;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.IContentProvider;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Bundle;
-
-/**
- * A mock content resolver for the LayoutLib Bridge.
- * <p/>
- * It won't serve any actual data but it's good enough for all
- * the widgets which expect to have a content resolver available via
- * {@link BridgeContext#getContentResolver()}.
- */
-public class BridgeContentResolver extends ContentResolver {
-
- private BridgeContentProvider mProvider = null;
-
- public BridgeContentResolver(Context context) {
- super(context);
- }
-
- @Override
- public IContentProvider acquireProvider(Context c, String name) {
- if (mProvider == null) {
- mProvider = new BridgeContentProvider();
- }
-
- return mProvider;
- }
-
- @Override
- public IContentProvider acquireExistingProvider(Context c, String name) {
- if (mProvider == null) {
- mProvider = new BridgeContentProvider();
- }
-
- return mProvider;
- }
-
- @Override
- public boolean releaseProvider(IContentProvider icp) {
- // ignore
- return false;
- }
-
- @Override
- protected IContentProvider acquireUnstableProvider(Context c, String name) {
- return acquireProvider(c, name);
- }
-
- @Override
- public boolean releaseUnstableProvider(IContentProvider icp) {
- return releaseProvider(icp);
- }
-
- /** @hide */
- @Override
- public void unstableProviderDied(IContentProvider icp) {
- }
-
- /**
- * Stub for the layoutlib bridge content resolver.
- */
- @Override
- public void registerContentObserver(Uri uri, boolean notifyForDescendents,
- ContentObserver observer) {
- // pass
- }
-
- /**
- * Stub for the layoutlib bridge content resolver.
- */
- @Override
- public void unregisterContentObserver(ContentObserver observer) {
- // pass
- }
-
- /**
- * Stub for the layoutlib bridge content resolver.
- */
- @Override
- public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
- // pass
- }
-
- /**
- * Stub for the layoutlib bridge content resolver.
- */
- @Override
- public void startSync(Uri uri, Bundle extras) {
- // pass
- }
-
- /**
- * Stub for the layoutlib bridge content resolver.
- */
- @Override
- public void cancelSync(Uri uri) {
- // pass
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
deleted file mode 100644
index 8bd924e..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ /dev/null
@@ -1,2059 +0,0 @@
-/*
- * Copyright (C) 2008 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.layoutlib.bridge.android;
-
-import com.android.SdkConstants;
-import com.android.ide.common.rendering.api.AssetRepository;
-import com.android.ide.common.rendering.api.ILayoutPullParser;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.LayoutlibCallback;
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.ResourceReference;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.rendering.api.StyleResourceValue;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.BridgeConstants;
-import com.android.layoutlib.bridge.android.view.WindowManagerImpl;
-import com.android.layoutlib.bridge.impl.ParserFactory;
-import com.android.layoutlib.bridge.impl.Stack;
-import com.android.resources.ResourceType;
-import com.android.util.Pair;
-import com.android.util.PropertiesMap;
-import com.android.util.PropertiesMap.Property;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Notification;
-import android.app.SystemServiceRegistry_Accessor;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentSender;
-import android.content.ServiceConnection;
-import android.content.SharedPreferences;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.res.AssetManager;
-import android.content.res.BridgeAssetManager;
-import android.content.res.BridgeTypedArray;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.Resources.Theme;
-import android.content.res.Resources_Delegate;
-import android.database.DatabaseErrorHandler;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteDatabase.CursorFactory;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.hardware.display.DisplayManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IInterface;
-import android.os.Looper;
-import android.os.Parcel;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.ShellCallback;
-import android.os.UserHandle;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
-import android.view.BridgeInflater;
-import android.view.Display;
-import android.view.DisplayAdjustments;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-import android.view.textservice.TextServicesManager;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-
-import static android.os._Original_Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_APPLICATION_PACKAGE;
-
-/**
- * Custom implementation of Context/Activity to handle non compiled resources.
- */
-@SuppressWarnings("deprecation") // For use of Pair.
-public class BridgeContext extends Context {
- private static final String PREFIX_THEME_APPCOMPAT = "Theme.AppCompat";
-
- private static final Map<String, ResourceValue> FRAMEWORK_PATCHED_VALUES = new HashMap<>(2);
- private static final Map<String, ResourceValue> FRAMEWORK_REPLACE_VALUES = new HashMap<>(3);
-
- static {
- FRAMEWORK_PATCHED_VALUES.put("animateFirstView", new ResourceValue(
- ResourceType.BOOL, "animateFirstView", "false", false));
- FRAMEWORK_PATCHED_VALUES.put("animateLayoutChanges",
- new ResourceValue(ResourceType.BOOL, "animateLayoutChanges", "false", false));
-
-
- FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionItemLayout",
- new ResourceValue(ResourceType.LAYOUT, "textEditSuggestionItemLayout",
- "text_edit_suggestion_item", true));
- FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionContainerLayout",
- new ResourceValue(ResourceType.LAYOUT, "textEditSuggestionContainerLayout",
- "text_edit_suggestion_container", true));
- FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionHighlightStyle",
- new ResourceValue(ResourceType.STYLE, "textEditSuggestionHighlightStyle",
- "TextAppearance.Holo.SuggestionHighlight", true));
-
- }
-
- /** The map adds cookies to each view so that IDE can link xml tags to views. */
- private final HashMap<View, Object> mViewKeyMap = new HashMap<>();
- /**
- * In some cases, when inflating an xml, some objects are created. Then later, the objects are
- * converted to views. This map stores the mapping from objects to cookies which can then be
- * used to populate the mViewKeyMap.
- */
- private final HashMap<Object, Object> mViewKeyHelpMap = new HashMap<>();
- private final BridgeAssetManager mAssets;
- private Resources mSystemResources;
- private final Object mProjectKey;
- private final DisplayMetrics mMetrics;
- private final RenderResources mRenderResources;
- private final Configuration mConfig;
- private final ApplicationInfo mApplicationInfo;
- private final LayoutlibCallback mLayoutlibCallback;
- private final WindowManager mWindowManager;
- private final DisplayManager mDisplayManager;
- private final HashMap<View, Integer> mScrollYPos = new HashMap<>();
- private final HashMap<View, Integer> mScrollXPos = new HashMap<>();
-
- private Resources.Theme mTheme;
-
- private final Map<Object, PropertiesMap> mDefaultPropMaps = new IdentityHashMap<>();
-
- // maps for dynamically generated id representing style objects (StyleResourceValue)
- @Nullable
- private Map<Integer, StyleResourceValue> mDynamicIdToStyleMap;
- private Map<StyleResourceValue, Integer> mStyleToDynamicIdMap;
- private int mDynamicIdGenerator = 0x02030000; // Base id for R.style in custom namespace
-
- // cache for TypedArray generated from StyleResourceValue object
- private TypedArrayCache mTypedArrayCache;
- private BridgeInflater mBridgeInflater;
-
- private BridgeContentResolver mContentResolver;
-
- private final Stack<BridgeXmlBlockParser> mParserStack = new Stack<>();
- private SharedPreferences mSharedPreferences;
- private ClassLoader mClassLoader;
- private IBinder mBinder;
- private PackageManager mPackageManager;
- private Boolean mIsThemeAppCompat;
-
- /**
- * Some applications that target both pre API 17 and post API 17, set the newer attrs to
- * reference the older ones. For example, android:paddingStart will resolve to
- * android:paddingLeft. This way the apps need to only define paddingLeft at any other place.
- * This a map from value to attribute name. Warning for missing references shouldn't be logged
- * if value and attr name pair is the same as an entry in this map.
- */
- private static Map<String, String> RTL_ATTRS = new HashMap<>(10);
-
- static {
- RTL_ATTRS.put("?android:attr/paddingLeft", "paddingStart");
- RTL_ATTRS.put("?android:attr/paddingRight", "paddingEnd");
- RTL_ATTRS.put("?android:attr/layout_marginLeft", "layout_marginStart");
- RTL_ATTRS.put("?android:attr/layout_marginRight", "layout_marginEnd");
- RTL_ATTRS.put("?android:attr/layout_toLeftOf", "layout_toStartOf");
- RTL_ATTRS.put("?android:attr/layout_toRightOf", "layout_toEndOf");
- RTL_ATTRS.put("?android:attr/layout_alignParentLeft", "layout_alignParentStart");
- RTL_ATTRS.put("?android:attr/layout_alignParentRight", "layout_alignParentEnd");
- RTL_ATTRS.put("?android:attr/drawableLeft", "drawableStart");
- RTL_ATTRS.put("?android:attr/drawableRight", "drawableEnd");
- }
-
- /**
- * @param projectKey An Object identifying the project. This is used for the cache mechanism.
- * @param metrics the {@link DisplayMetrics}.
- * @param renderResources the configured resources (both framework and projects) for this
- * render.
- * @param config the Configuration object for this render.
- * @param targetSdkVersion the targetSdkVersion of the application.
- */
- public BridgeContext(Object projectKey, DisplayMetrics metrics,
- RenderResources renderResources,
- AssetRepository assets,
- LayoutlibCallback layoutlibCallback,
- Configuration config,
- int targetSdkVersion,
- boolean hasRtlSupport) {
- mProjectKey = projectKey;
- mMetrics = metrics;
- mLayoutlibCallback = layoutlibCallback;
-
- mRenderResources = renderResources;
- mConfig = config;
- AssetManager systemAssetManager = AssetManager.getSystem();
- if (systemAssetManager instanceof BridgeAssetManager) {
- mAssets = (BridgeAssetManager) systemAssetManager;
- } else {
- throw new AssertionError("Creating BridgeContext without initializing Bridge");
- }
- mAssets.setAssetRepository(assets);
-
- mApplicationInfo = new ApplicationInfo();
- mApplicationInfo.targetSdkVersion = targetSdkVersion;
- if (hasRtlSupport) {
- mApplicationInfo.flags = mApplicationInfo.flags | ApplicationInfo.FLAG_SUPPORTS_RTL;
- }
-
- mWindowManager = new WindowManagerImpl(mMetrics);
- mDisplayManager = new DisplayManager(this);
- }
-
- /**
- * Initializes the {@link Resources} singleton to be linked to this {@link Context}, its
- * {@link DisplayMetrics}, {@link Configuration}, and {@link LayoutlibCallback}.
- *
- * @see #disposeResources()
- */
- public void initResources() {
- AssetManager assetManager = AssetManager.getSystem();
-
- mSystemResources = Resources_Delegate.initSystem(
- this,
- assetManager,
- mMetrics,
- mConfig,
- mLayoutlibCallback);
- mTheme = mSystemResources.newTheme();
- }
-
- /**
- * Disposes the {@link Resources} singleton.
- */
- public void disposeResources() {
- Resources_Delegate.disposeSystem();
- }
-
- public void setBridgeInflater(BridgeInflater inflater) {
- mBridgeInflater = inflater;
- }
-
- public void addViewKey(View view, Object viewKey) {
- mViewKeyMap.put(view, viewKey);
- }
-
- public Object getViewKey(View view) {
- return mViewKeyMap.get(view);
- }
-
- public void addCookie(Object o, Object cookie) {
- mViewKeyHelpMap.put(o, cookie);
- }
-
- public Object getCookie(Object o) {
- return mViewKeyHelpMap.get(o);
- }
-
- public Object getProjectKey() {
- return mProjectKey;
- }
-
- public DisplayMetrics getMetrics() {
- return mMetrics;
- }
-
- public LayoutlibCallback getLayoutlibCallback() {
- return mLayoutlibCallback;
- }
-
- public RenderResources getRenderResources() {
- return mRenderResources;
- }
-
- public Map<Object, PropertiesMap> getDefaultProperties() {
- return mDefaultPropMaps;
- }
-
- public Configuration getConfiguration() {
- return mConfig;
- }
-
- /**
- * Adds a parser to the stack.
- * @param parser the parser to add.
- */
- public void pushParser(BridgeXmlBlockParser parser) {
- if (ParserFactory.LOG_PARSER) {
- System.out.println("PUSH " + parser.getParser().toString());
- }
- mParserStack.push(parser);
- }
-
- /**
- * Removes the parser at the top of the stack
- */
- public void popParser() {
- BridgeXmlBlockParser parser = mParserStack.pop();
- if (ParserFactory.LOG_PARSER) {
- System.out.println("POPD " + parser.getParser().toString());
- }
- }
-
- /**
- * Returns the current parser at the top the of the stack.
- * @return a parser or null.
- */
- private BridgeXmlBlockParser getCurrentParser() {
- return mParserStack.peek();
- }
-
- /**
- * Returns the previous parser.
- * @return a parser or null if there isn't any previous parser
- */
- public BridgeXmlBlockParser getPreviousParser() {
- if (mParserStack.size() < 2) {
- return null;
- }
- return mParserStack.get(mParserStack.size() - 2);
- }
-
- public boolean resolveThemeAttribute(int resId, TypedValue outValue, boolean resolveRefs) {
- Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(resId);
- boolean isFrameworkRes = true;
- if (resourceInfo == null) {
- resourceInfo = mLayoutlibCallback.resolveResourceId(resId);
- isFrameworkRes = false;
- }
-
- if (resourceInfo == null) {
- return false;
- }
-
- ResourceValue value = mRenderResources.findItemInTheme(resourceInfo.getSecond(),
- isFrameworkRes);
- if (resolveRefs) {
- value = mRenderResources.resolveResValue(value);
- }
-
- if (value == null) {
- // unable to find the attribute.
- return false;
- }
-
- // check if this is a style resource
- if (value instanceof StyleResourceValue) {
- // get the id that will represent this style.
- outValue.resourceId = getDynamicIdByStyle((StyleResourceValue) value);
- return true;
- }
-
- String stringValue = value.getValue();
- if (!stringValue.isEmpty()) {
- if (stringValue.charAt(0) == '#') {
- outValue.type = TypedValue.TYPE_INT_COLOR_ARGB8;
- outValue.data = Color.parseColor(value.getValue());
- }
- else if (stringValue.charAt(0) == '@') {
- outValue.type = TypedValue.TYPE_REFERENCE;
- }
-
- }
-
- int a;
- // if this is a framework value.
- if (value.isFramework()) {
- // look for idName in the android R classes.
- // use 0 a default res value as it's not a valid id value.
- a = getFrameworkResourceValue(value.getResourceType(), value.getName(), 0 /*defValue*/);
- } else {
- // look for idName in the project R class.
- // use 0 a default res value as it's not a valid id value.
- a = getProjectResourceValue(value.getResourceType(), value.getName(), 0 /*defValue*/);
- }
-
- if (a != 0) {
- outValue.resourceId = a;
- return true;
- }
-
- // If the value is not a valid reference, fallback to pass the value as a string.
- outValue.string = stringValue;
- return true;
- }
-
-
- public ResourceReference resolveId(int id) {
- // first get the String related to this id in the framework
- Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
-
- if (resourceInfo != null) {
- return new ResourceReference(resourceInfo.getSecond(), true);
- }
-
- // didn't find a match in the framework? look in the project.
- if (mLayoutlibCallback != null) {
- resourceInfo = mLayoutlibCallback.resolveResourceId(id);
-
- if (resourceInfo != null) {
- return new ResourceReference(resourceInfo.getSecond(), false);
- }
- }
-
- // The base value for R.style is 0x01030000 and the custom style is 0x02030000.
- // So, if the second byte is 03, it's probably a style.
- if ((id >> 16 & 0xFF) == 0x03) {
- return getStyleByDynamicId(id);
- }
- return null;
- }
-
- public Pair<View, Boolean> inflateView(ResourceReference resource, ViewGroup parent,
- @SuppressWarnings("SameParameterValue") boolean attachToRoot,
- boolean skipCallbackParser) {
- boolean isPlatformLayout = resource.isFramework();
-
- if (!isPlatformLayout && !skipCallbackParser) {
- // check if the project callback can provide us with a custom parser.
- ILayoutPullParser parser = getParser(resource);
-
- if (parser != null) {
- BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(parser,
- this, resource.isFramework());
- try {
- pushParser(blockParser);
- return Pair.of(
- mBridgeInflater.inflate(blockParser, parent, attachToRoot),
- Boolean.TRUE);
- } finally {
- popParser();
- }
- }
- }
-
- ResourceValue resValue;
- if (resource instanceof ResourceValue) {
- resValue = (ResourceValue) resource;
- } else {
- if (isPlatformLayout) {
- resValue = mRenderResources.getFrameworkResource(ResourceType.LAYOUT,
- resource.getName());
- } else {
- resValue = mRenderResources.getProjectResource(ResourceType.LAYOUT,
- resource.getName());
- }
- }
-
- if (resValue != null) {
-
- File xml = new File(resValue.getValue());
- if (xml.isFile()) {
- // we need to create a pull parser around the layout XML file, and then
- // give that to our XmlBlockParser
- try {
- XmlPullParser parser = ParserFactory.create(xml, true);
-
- // set the resource ref to have correct view cookies
- mBridgeInflater.setResourceReference(resource);
-
- BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(parser,
- this, resource.isFramework());
- try {
- pushParser(blockParser);
- return Pair.of(
- mBridgeInflater.inflate(blockParser, parent, attachToRoot),
- Boolean.FALSE);
- } finally {
- popParser();
- }
- } catch (XmlPullParserException e) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Failed to configure parser for " + xml, e, null /*data*/);
- // we'll return null below.
- } catch (FileNotFoundException e) {
- // this shouldn't happen since we check above.
- } finally {
- mBridgeInflater.setResourceReference(null);
- }
- } else {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- String.format("File %s is missing!", xml), null);
- }
- } else {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- String.format("Layout %s%s does not exist.", isPlatformLayout ? "android:" : "",
- resource.getName()), null);
- }
-
- return Pair.of(null, Boolean.FALSE);
- }
-
- /**
- * Returns whether the current selected theme is based on AppCompat
- */
- public boolean isAppCompatTheme() {
- // If a cached value exists, return it.
- if (mIsThemeAppCompat != null) {
- return mIsThemeAppCompat;
- }
- // Ideally, we should check if the corresponding activity extends
- // android.support.v7.app.ActionBarActivity, and not care about the theme name at all.
- StyleResourceValue defaultTheme = mRenderResources.getDefaultTheme();
- // We can't simply check for parent using resources.themeIsParentOf() since the
- // inheritance structure isn't really what one would expect. The first common parent
- // between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21).
- boolean isThemeAppCompat = false;
- for (int i = 0; i < 50; i++) {
- if (defaultTheme == null) {
- break;
- }
- // for loop ensures that we don't run into cyclic theme inheritance.
- if (defaultTheme.getName().startsWith(PREFIX_THEME_APPCOMPAT)) {
- isThemeAppCompat = true;
- break;
- }
- defaultTheme = mRenderResources.getParent(defaultTheme);
- }
- mIsThemeAppCompat = isThemeAppCompat;
- return isThemeAppCompat;
- }
-
- @SuppressWarnings("deprecation")
- private ILayoutPullParser getParser(ResourceReference resource) {
- ILayoutPullParser parser;
- if (resource instanceof ResourceValue) {
- parser = mLayoutlibCallback.getParser((ResourceValue) resource);
- } else {
- parser = mLayoutlibCallback.getParser(resource.getName());
- }
- return parser;
- }
-
- // ------------ Context methods
-
- @Override
- public Resources getResources() {
- return mSystemResources;
- }
-
- @Override
- public Theme getTheme() {
- return mTheme;
- }
-
- @Override
- public ClassLoader getClassLoader() {
- // The documentation for this method states that it should return a class loader one can
- // use to retrieve classes in this package. However, when called by LayoutInflater, we do
- // not want the class loader to return app's custom views.
- // This is so that the IDE can instantiate the custom views and also generate proper error
- // messages in case of failure. This also enables the IDE to fallback to MockView in case
- // there's an exception thrown when trying to inflate the custom view.
- // To work around this issue, LayoutInflater is modified via LayoutLib Create tool to
- // replace invocations of this method to a new method: getFrameworkClassLoader(). Also,
- // the method is injected into Context. The implementation of getFrameworkClassLoader() is:
- // "return getClass().getClassLoader();". This means that when LayoutInflater asks for
- // the context ClassLoader, it gets only LayoutLib's ClassLoader which doesn't have
- // access to the apps's custom views.
- // This method can now return the right ClassLoader, which CustomViews can use to do the
- // right thing.
- if (mClassLoader == null) {
- mClassLoader = new ClassLoader(getClass().getClassLoader()) {
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- for (String prefix : BridgeInflater.getClassPrefixList()) {
- if (name.startsWith(prefix)) {
- // These are framework classes and should not be loaded from the app.
- throw new ClassNotFoundException(name + " not found");
- }
- }
- return BridgeContext.this.mLayoutlibCallback.findClass(name);
- }
- };
- }
- return mClassLoader;
- }
-
- @Override
- public Object getSystemService(String service) {
- if (LAYOUT_INFLATER_SERVICE.equals(service)) {
- return mBridgeInflater;
- }
-
- if (TEXT_SERVICES_MANAGER_SERVICE.equals(service)) {
- // we need to return a valid service to avoid NPE
- return TextServicesManager.getInstance();
- }
-
- if (WINDOW_SERVICE.equals(service)) {
- return mWindowManager;
- }
-
- // needed by SearchView
- if (INPUT_METHOD_SERVICE.equals(service)) {
- return null;
- }
-
- if (POWER_SERVICE.equals(service)) {
- return new PowerManager(this, new BridgePowerManager(), new Handler());
- }
-
- if (DISPLAY_SERVICE.equals(service)) {
- return mDisplayManager;
- }
-
- if (ACCESSIBILITY_SERVICE.equals(service)) {
- return AccessibilityManager.getInstance(this);
- }
-
- if (AUTOFILL_MANAGER_SERVICE.equals(service)) {
- return null;
- }
-
- if (AUDIO_SERVICE.equals(service)) {
- return null;
- }
-
- assert false : "Unsupported Service: " + service;
- return null;
- }
-
- @Override
- public String getSystemServiceName(Class<?> serviceClass) {
- return SystemServiceRegistry_Accessor.getSystemServiceName(serviceClass);
- }
-
- @Override
- public final BridgeTypedArray obtainStyledAttributes(int[] attrs) {
- return obtainStyledAttributes(0, attrs);
- }
-
- @Override
- public final BridgeTypedArray obtainStyledAttributes(int resId, int[] attrs)
- throws Resources.NotFoundException {
- StyleResourceValue style = null;
- // get the StyleResourceValue based on the resId;
- if (resId != 0) {
- style = getStyleByDynamicId(resId);
-
- if (style == null) {
- // In some cases, style may not be a dynamic id, so we do a full search.
- ResourceReference ref = resolveId(resId);
- if (ref != null) {
- style = mRenderResources.getStyle(ref.getName(), ref.isFramework());
- }
- }
-
- if (style == null) {
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
- "Failed to find style with " + resId, null);
- return null;
- }
- }
-
- if (mTypedArrayCache == null) {
- mTypedArrayCache = new TypedArrayCache();
- }
-
- List<StyleResourceValue> currentThemes = mRenderResources.getAllThemes();
-
- Pair<BridgeTypedArray, PropertiesMap> typeArrayAndPropertiesPair =
- mTypedArrayCache.get(attrs, currentThemes, resId);
-
- if (typeArrayAndPropertiesPair == null) {
- typeArrayAndPropertiesPair = createStyleBasedTypedArray(style, attrs);
- mTypedArrayCache.put(attrs, currentThemes, resId, typeArrayAndPropertiesPair);
- }
- // Add value to defaultPropsMap if needed
- if (typeArrayAndPropertiesPair.getSecond() != null) {
- BridgeXmlBlockParser parser = getCurrentParser();
- Object key = parser != null ? parser.getViewCookie() : null;
- if (key != null) {
- PropertiesMap defaultPropMap = mDefaultPropMaps.get(key);
- if (defaultPropMap == null) {
- defaultPropMap = typeArrayAndPropertiesPair.getSecond();
- mDefaultPropMaps.put(key, defaultPropMap);
- } else {
- defaultPropMap.putAll(typeArrayAndPropertiesPair.getSecond());
- }
- }
- }
- return typeArrayAndPropertiesPair.getFirst();
- }
-
- @Override
- public final BridgeTypedArray obtainStyledAttributes(AttributeSet set, int[] attrs) {
- return obtainStyledAttributes(set, attrs, 0, 0);
- }
-
- @Override
- public BridgeTypedArray obtainStyledAttributes(AttributeSet set, int[] attrs,
- int defStyleAttr, int defStyleRes) {
-
- PropertiesMap defaultPropMap = null;
- boolean isPlatformFile = true;
-
- // Hint: for XmlPullParser, attach source //DEVICE_SRC/dalvik/libcore/xml/src/java
- if (set instanceof BridgeXmlBlockParser) {
- BridgeXmlBlockParser parser;
- parser = (BridgeXmlBlockParser)set;
-
- isPlatformFile = parser.isPlatformFile();
-
- Object key = parser.getViewCookie();
- if (key != null) {
- defaultPropMap = mDefaultPropMaps.computeIfAbsent(key, k -> new PropertiesMap());
- }
-
- } else if (set instanceof BridgeLayoutParamsMapAttributes) {
- // this is only for temp layout params generated dynamically, so this is never
- // platform content.
- isPlatformFile = false;
- } else if (set != null) { // null parser is ok
- // really this should not be happening since its instantiated in Bridge
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Parser is not a BridgeXmlBlockParser!", null);
- return null;
- }
-
- List<Pair<String, Boolean>> attributeList = searchAttrs(attrs);
-
- BridgeTypedArray ta =
- Resources_Delegate.newTypeArray(mSystemResources, attrs.length, isPlatformFile);
-
- // look for a custom style.
- String customStyle = null;
- if (set != null) {
- customStyle = set.getAttributeValue(null, "style");
- }
-
- StyleResourceValue customStyleValues = null;
- if (customStyle != null) {
- ResourceValue item = mRenderResources.findResValue(customStyle,
- isPlatformFile /*forceFrameworkOnly*/);
-
- // resolve it in case it links to something else
- item = mRenderResources.resolveResValue(item);
-
- if (item instanceof StyleResourceValue) {
- customStyleValues = (StyleResourceValue)item;
- }
- }
-
- // resolve the defStyleAttr value into a IStyleResourceValue
- StyleResourceValue defStyleValues = null;
-
- if (defStyleAttr != 0) {
- // get the name from the int.
- Pair<String, Boolean> defStyleAttribute = searchAttr(defStyleAttr);
-
- if (defStyleAttribute == null) {
- // This should be rare. Happens trying to map R.style.foo to @style/foo fails.
- // This will happen if the user explicitly used a non existing int value for
- // defStyleAttr or there's something wrong with the project structure/build.
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
- "Failed to find the style corresponding to the id " + defStyleAttr, null);
- } else {
- String defStyleName = defStyleAttribute.getFirst();
-
- // look for the style in the current theme, and its parent:
- ResourceValue item = mRenderResources.findItemInTheme(defStyleName,
- defStyleAttribute.getSecond());
-
- if (item != null) {
- // item is a reference to a style entry. Search for it.
- item = mRenderResources.findResValue(item.getValue(), item.isFramework());
- item = mRenderResources.resolveResValue(item);
- if (item instanceof StyleResourceValue) {
- defStyleValues = (StyleResourceValue) item;
- }
- if (defaultPropMap != null) {
- if (defStyleAttribute.getSecond()) {
- defStyleName = "android:" + defStyleName;
- }
- defaultPropMap.put("style", new Property(defStyleName, item.getValue()));
- }
- } else {
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR,
- String.format(
- "Failed to find style '%s' in current theme",
- defStyleAttribute.getFirst()),
- null);
- }
- }
- } else if (defStyleRes != 0) {
- StyleResourceValue item = getStyleByDynamicId(defStyleRes);
- if (item != null) {
- defStyleValues = item;
- } else {
- boolean isFrameworkRes = true;
- Pair<ResourceType, String> value = Bridge.resolveResourceId(defStyleRes);
- if (value == null) {
- value = mLayoutlibCallback.resolveResourceId(defStyleRes);
- isFrameworkRes = false;
- }
-
- if (value != null) {
- if ((value.getFirst() == ResourceType.STYLE)) {
- // look for the style in all resources:
- item = mRenderResources.getStyle(value.getSecond(), isFrameworkRes);
- if (item != null) {
- if (defaultPropMap != null) {
- String name = item.getName();
- defaultPropMap.put("style", new Property(name, name));
- }
-
- defStyleValues = item;
- } else {
- Bridge.getLog().error(null,
- String.format(
- "Style with id 0x%x (resolved to '%s') does not exist.",
- defStyleRes, value.getSecond()),
- null);
- }
- } else {
- Bridge.getLog().error(null,
- String.format(
- "Resource id 0x%x is not of type STYLE (instead %s)",
- defStyleRes, value.getFirst().toString()),
- null);
- }
- } else {
- Bridge.getLog().error(null,
- String.format(
- "Failed to find style with id 0x%x in current theme",
- defStyleRes),
- null);
- }
- }
- }
-
- String appNamespace = mLayoutlibCallback.getNamespace();
-
- if (attributeList != null) {
- for (int index = 0 ; index < attributeList.size() ; index++) {
- Pair<String, Boolean> attribute = attributeList.get(index);
-
- if (attribute == null) {
- continue;
- }
-
- String attrName = attribute.getFirst();
- boolean frameworkAttr = attribute.getSecond();
- String value = null;
- if (set != null) {
- value = set.getAttributeValue(
- frameworkAttr ? BridgeConstants.NS_RESOURCES : appNamespace,
- attrName);
-
- // if this is an app attribute, and the first get fails, try with the
- // new res-auto namespace as well
- if (!frameworkAttr && value == null) {
- value = set.getAttributeValue(BridgeConstants.NS_APP_RES_AUTO, attrName);
- }
- }
-
- // Calculate the default value from the Theme in two cases:
- // - If defaultPropMap is not null, get the default value to add it to the list
- // of default values of properties.
- // - If value is null, it means that the attribute is not directly set as an
- // attribute in the XML so try to get the default value.
- ResourceValue defaultValue = null;
- if (defaultPropMap != null || value == null) {
- // look for the value in the custom style first (and its parent if needed)
- if (customStyleValues != null) {
- defaultValue = mRenderResources.findItemInStyle(customStyleValues, attrName,
- frameworkAttr);
- }
-
- // then look for the value in the default Style (and its parent if needed)
- if (defaultValue == null && defStyleValues != null) {
- defaultValue = mRenderResources.findItemInStyle(defStyleValues, attrName,
- frameworkAttr);
- }
-
- // if the item is not present in the defStyle, we look in the main theme (and
- // its parent themes)
- if (defaultValue == null) {
- defaultValue = mRenderResources.findItemInTheme(attrName, frameworkAttr);
- }
-
- // if we found a value, we make sure this doesn't reference another value.
- // So we resolve it.
- if (defaultValue != null) {
- String preResolve = defaultValue.getValue();
- defaultValue = mRenderResources.resolveResValue(defaultValue);
-
- if (defaultPropMap != null) {
- defaultPropMap.put(
- frameworkAttr ? SdkConstants.PREFIX_ANDROID + attrName :
- attrName, new Property(preResolve, defaultValue.getValue()));
- }
- }
- }
- // Done calculating the defaultValue
-
- // if there's no direct value for this attribute in the XML, we look for default
- // values in the widget defStyle, and then in the theme.
- if (value == null) {
- if (frameworkAttr) {
- // For some framework values, layoutlib patches the actual value in the
- // theme when it helps to improve the final preview. In most cases
- // we just disable animations.
- ResourceValue patchedValue = FRAMEWORK_PATCHED_VALUES.get(attrName);
- if (patchedValue != null) {
- defaultValue = patchedValue;
- }
- }
-
- // if we found a value, we make sure this doesn't reference another value.
- // So we resolve it.
- if (defaultValue != null) {
- // If the value is a reference to another theme attribute that doesn't
- // exist, we should log a warning and omit it.
- String val = defaultValue.getValue();
- if (val != null && val.startsWith(SdkConstants.PREFIX_THEME_REF)) {
- // Because we always use the latest framework code, some resources might
- // fail to resolve when using old themes (they haven't been backported).
- // Since this is an artifact caused by us using always the latest
- // code, we check for some of those values and replace them here.
- defaultValue = FRAMEWORK_REPLACE_VALUES.get(attrName);
-
- if (defaultValue == null &&
- (getApplicationInfo().targetSdkVersion < JELLY_BEAN_MR1 ||
- !attrName.equals(RTL_ATTRS.get(val)))) {
- // Only log a warning if the referenced value isn't one of the RTL
- // attributes, or the app targets old API.
- Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR,
- String.format("Failed to find '%s' in current theme.", val),
- val);
- }
- }
- }
-
- ta.bridgeSetValue(index, attrName, frameworkAttr, defaultValue);
- } else {
- // there is a value in the XML, but we need to resolve it in case it's
- // referencing another resource or a theme value.
- ta.bridgeSetValue(index, attrName, frameworkAttr,
- mRenderResources.resolveValue(null, attrName, value, isPlatformFile));
- }
- }
- }
-
- ta.sealArray();
-
- return ta;
- }
-
- @Override
- public Looper getMainLooper() {
- return Looper.myLooper();
- }
-
-
- @Override
- public String getPackageName() {
- if (mApplicationInfo.packageName == null) {
- mApplicationInfo.packageName = mLayoutlibCallback.getFlag(FLAG_KEY_APPLICATION_PACKAGE);
- }
- return mApplicationInfo.packageName;
- }
-
- @Override
- public PackageManager getPackageManager() {
- if (mPackageManager == null) {
- mPackageManager = new BridgePackageManager();
- }
- return mPackageManager;
- }
-
- // ------------- private new methods
-
- /**
- * Creates a {@link BridgeTypedArray} by filling the values defined by the int[] with the
- * values found in the given style. If no style is specified, the default theme, along with the
- * styles applied to it are used.
- *
- * @see #obtainStyledAttributes(int, int[])
- */
- private Pair<BridgeTypedArray, PropertiesMap> createStyleBasedTypedArray(
- @Nullable StyleResourceValue style, int[] attrs) throws Resources.NotFoundException {
- List<Pair<String, Boolean>> attributes = searchAttrs(attrs);
-
- BridgeTypedArray ta =
- Resources_Delegate.newTypeArray(mSystemResources, attrs.length, false);
-
- PropertiesMap defaultPropMap = new PropertiesMap();
- // for each attribute, get its name so that we can search it in the style
- for (int i = 0; i < attrs.length; i++) {
- Pair<String, Boolean> attribute = attributes.get(i);
-
- if (attribute != null) {
- // look for the value in the given style
- ResourceValue resValue;
- String attrName = attribute.getFirst();
- boolean frameworkAttr = attribute.getSecond();
- if (style != null) {
- resValue = mRenderResources.findItemInStyle(style, attrName, frameworkAttr);
- } else {
- resValue = mRenderResources.findItemInTheme(attrName, frameworkAttr);
- }
-
- if (resValue != null) {
- // Add it to defaultPropMap before resolving
- String preResolve = resValue.getValue();
- // resolve it to make sure there are no references left.
- resValue = mRenderResources.resolveResValue(resValue);
- ta.bridgeSetValue(i, attrName, frameworkAttr, resValue);
- defaultPropMap.put(
- frameworkAttr ? SdkConstants.ANDROID_PREFIX + attrName : attrName,
- new Property(preResolve, resValue.getValue()));
- }
- }
- }
-
- ta.sealArray();
-
- return Pair.of(ta, defaultPropMap);
- }
-
- /**
- * The input int[] attrs is a list of attributes. The returns a list of information about
- * each attributes. The information is (name, isFramework)
- * <p/>
- *
- * @param attrs An attribute array reference given to obtainStyledAttributes.
- * @return List of attribute information.
- */
- private List<Pair<String, Boolean>> searchAttrs(int[] attrs) {
- List<Pair<String, Boolean>> results = new ArrayList<>(attrs.length);
-
- // for each attribute, get its name so that we can search it in the style
- for (int attr : attrs) {
- Pair<ResourceType, String> resolvedResource = Bridge.resolveResourceId(attr);
- boolean isFramework = false;
- if (resolvedResource != null) {
- isFramework = true;
- } else {
- resolvedResource = mLayoutlibCallback.resolveResourceId(attr);
- }
-
- if (resolvedResource != null) {
- results.add(Pair.of(resolvedResource.getSecond(), isFramework));
- } else {
- results.add(null);
- }
- }
-
- return results;
- }
-
- /**
- * Searches for the attribute referenced by its internal id.
- *
- * @param attr An attribute reference given to obtainStyledAttributes such as defStyle.
- * @return A (name, isFramework) pair describing the attribute if found. Returns null
- * if nothing is found.
- */
- private Pair<String, Boolean> searchAttr(int attr) {
- Pair<ResourceType, String> info = Bridge.resolveResourceId(attr);
- if (info != null) {
- return Pair.of(info.getSecond(), Boolean.TRUE);
- }
-
- info = mLayoutlibCallback.resolveResourceId(attr);
- if (info != null) {
- return Pair.of(info.getSecond(), Boolean.FALSE);
- }
-
- return null;
- }
-
- public int getDynamicIdByStyle(StyleResourceValue resValue) {
- if (mDynamicIdToStyleMap == null) {
- // create the maps.
- mDynamicIdToStyleMap = new HashMap<>();
- mStyleToDynamicIdMap = new HashMap<>();
- }
-
- // look for an existing id
- Integer id = mStyleToDynamicIdMap.get(resValue);
-
- if (id == null) {
- // generate a new id
- id = ++mDynamicIdGenerator;
-
- // and add it to the maps.
- mDynamicIdToStyleMap.put(id, resValue);
- mStyleToDynamicIdMap.put(resValue, id);
- }
-
- return id;
- }
-
- private StyleResourceValue getStyleByDynamicId(int i) {
- if (mDynamicIdToStyleMap != null) {
- return mDynamicIdToStyleMap.get(i);
- }
-
- return null;
- }
-
- public int getFrameworkResourceValue(ResourceType resType, String resName, int defValue) {
- if (getRenderResources().getFrameworkResource(resType, resName) != null) {
- // Bridge.getResourceId creates a new resource id if an existing one isn't found. So,
- // we check for the existence of the resource before calling it.
- return Bridge.getResourceId(resType, resName);
- }
-
- return defValue;
- }
-
- public int getProjectResourceValue(ResourceType resType, String resName, int defValue) {
- // getResourceId creates a new resource id if an existing resource id isn't found. So, we
- // check for the existence of the resource before calling it.
- if (getRenderResources().getProjectResource(resType, resName) != null) {
- if (mLayoutlibCallback != null) {
- Integer value = mLayoutlibCallback.getResourceId(resType, resName);
- if (value != null) {
- return value;
- }
- }
- }
-
- return defValue;
- }
-
- public static Context getBaseContext(Context context) {
- while (context instanceof ContextWrapper) {
- context = ((ContextWrapper) context).getBaseContext();
- }
- return context;
- }
-
- public IBinder getBinder() {
- if (mBinder == null) {
- // create a dummy binder. We only need it be not null.
- mBinder = new IBinder() {
- @Override
- public String getInterfaceDescriptor() throws RemoteException {
- return null;
- }
-
- @Override
- public boolean pingBinder() {
- return false;
- }
-
- @Override
- public boolean isBinderAlive() {
- return false;
- }
-
- @Override
- public IInterface queryLocalInterface(String descriptor) {
- return null;
- }
-
- @Override
- public void dump(FileDescriptor fd, String[] args) throws RemoteException {
-
- }
-
- @Override
- public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
-
- }
-
- @Override
- public boolean transact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- return false;
- }
-
- @Override
- public void linkToDeath(DeathRecipient recipient, int flags)
- throws RemoteException {
-
- }
-
- @Override
- public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
- return false;
- }
-
- @Override
- public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ShellCallback shellCallback, ResultReceiver resultReceiver) {
- }
- };
- }
- return mBinder;
- }
-
- //------------ NOT OVERRIDEN --------------------
-
- @Override
- public boolean bindService(Intent arg0, ServiceConnection arg1, int arg2) {
- // pass
- return false;
- }
-
- @Override
- public int checkCallingOrSelfPermission(String arg0) {
- // pass
- return 0;
- }
-
- @Override
- public int checkCallingOrSelfUriPermission(Uri arg0, int arg1) {
- // pass
- return 0;
- }
-
- @Override
- public int checkCallingPermission(String arg0) {
- // pass
- return 0;
- }
-
- @Override
- public int checkCallingUriPermission(Uri arg0, int arg1) {
- // pass
- return 0;
- }
-
- @Override
- public int checkPermission(String arg0, int arg1, int arg2) {
- // pass
- return 0;
- }
-
- @Override
- public int checkSelfPermission(String arg0) {
- // pass
- return 0;
- }
-
- @Override
- public int checkPermission(String arg0, int arg1, int arg2, IBinder arg3) {
- // pass
- return 0;
- }
-
- @Override
- public int checkUriPermission(Uri arg0, int arg1, int arg2, int arg3) {
- // pass
- return 0;
- }
-
- @Override
- public int checkUriPermission(Uri arg0, int arg1, int arg2, int arg3, IBinder arg4) {
- // pass
- return 0;
- }
-
- @Override
- public int checkUriPermission(Uri arg0, String arg1, String arg2, int arg3,
- int arg4, int arg5) {
- // pass
- return 0;
- }
-
- @Override
- public void clearWallpaper() {
- // pass
-
- }
-
- @Override
- public Context createPackageContext(String arg0, int arg1) {
- // pass
- return null;
- }
-
- @Override
- public Context createPackageContextAsUser(String arg0, int arg1, UserHandle user) {
- // pass
- return null;
- }
-
- @Override
- public Context createConfigurationContext(Configuration overrideConfiguration) {
- // pass
- return null;
- }
-
- @Override
- public Context createDisplayContext(Display display) {
- // pass
- return null;
- }
-
- @Override
- public Context createContextForSplit(String splitName) {
- // pass
- return null;
- }
-
- @Override
- public String[] databaseList() {
- // pass
- return null;
- }
-
- @Override
- public Context createApplicationContext(ApplicationInfo application, int flags)
- throws PackageManager.NameNotFoundException {
- return null;
- }
-
- @Override
- public boolean moveDatabaseFrom(Context sourceContext, String name) {
- // pass
- return false;
- }
-
- @Override
- public boolean deleteDatabase(String arg0) {
- // pass
- return false;
- }
-
- @Override
- public boolean deleteFile(String arg0) {
- // pass
- return false;
- }
-
- @Override
- public void enforceCallingOrSelfPermission(String arg0, String arg1) {
- // pass
-
- }
-
- @Override
- public void enforceCallingOrSelfUriPermission(Uri arg0, int arg1,
- String arg2) {
- // pass
-
- }
-
- @Override
- public void enforceCallingPermission(String arg0, String arg1) {
- // pass
-
- }
-
- @Override
- public void enforceCallingUriPermission(Uri arg0, int arg1, String arg2) {
- // pass
-
- }
-
- @Override
- public void enforcePermission(String arg0, int arg1, int arg2, String arg3) {
- // pass
-
- }
-
- @Override
- public void enforceUriPermission(Uri arg0, int arg1, int arg2, int arg3,
- String arg4) {
- // pass
-
- }
-
- @Override
- public void enforceUriPermission(Uri arg0, String arg1, String arg2,
- int arg3, int arg4, int arg5, String arg6) {
- // pass
-
- }
-
- @Override
- public String[] fileList() {
- // pass
- return null;
- }
-
- @Override
- public BridgeAssetManager getAssets() {
- return mAssets;
- }
-
- @Override
- public File getCacheDir() {
- // pass
- return null;
- }
-
- @Override
- public File getCodeCacheDir() {
- // pass
- return null;
- }
-
- @Override
- public File getExternalCacheDir() {
- // pass
- return null;
- }
-
- @Override
- public File getPreloadsFileCache() {
- // pass
- return null;
- }
-
- @Override
- public ContentResolver getContentResolver() {
- if (mContentResolver == null) {
- mContentResolver = new BridgeContentResolver(this);
- }
- return mContentResolver;
- }
-
- @Override
- public File getDatabasePath(String arg0) {
- // pass
- return null;
- }
-
- @Override
- public File getDir(String arg0, int arg1) {
- // pass
- return null;
- }
-
- @Override
- public File getFileStreamPath(String arg0) {
- // pass
- return null;
- }
-
- @Override
- public File getSharedPreferencesPath(String name) {
- // pass
- return null;
- }
-
- @Override
- public File getDataDir() {
- // pass
- return null;
- }
-
- @Override
- public File getFilesDir() {
- // pass
- return null;
- }
-
- @Override
- public File getNoBackupFilesDir() {
- // pass
- return null;
- }
-
- @Override
- public File getExternalFilesDir(String type) {
- // pass
- return null;
- }
-
- @Override
- public String getPackageCodePath() {
- // pass
- return null;
- }
-
- @Override
- public String getBasePackageName() {
- // pass
- return null;
- }
-
- @Override
- public String getOpPackageName() {
- // pass
- return null;
- }
-
- @Override
- public ApplicationInfo getApplicationInfo() {
- return mApplicationInfo;
- }
-
- @Override
- public String getPackageResourcePath() {
- // pass
- return null;
- }
-
- @Override
- public SharedPreferences getSharedPreferences(String arg0, int arg1) {
- if (mSharedPreferences == null) {
- mSharedPreferences = new BridgeSharedPreferences();
- }
- return mSharedPreferences;
- }
-
- @Override
- public SharedPreferences getSharedPreferences(File arg0, int arg1) {
- if (mSharedPreferences == null) {
- mSharedPreferences = new BridgeSharedPreferences();
- }
- return mSharedPreferences;
- }
-
- @Override
- public boolean moveSharedPreferencesFrom(Context sourceContext, String name) {
- // pass
- return false;
- }
-
- @Override
- public boolean deleteSharedPreferences(String name) {
- // pass
- return false;
- }
-
- @Override
- public Drawable getWallpaper() {
- // pass
- return null;
- }
-
- @Override
- public int getWallpaperDesiredMinimumWidth() {
- return -1;
- }
-
- @Override
- public int getWallpaperDesiredMinimumHeight() {
- return -1;
- }
-
- @Override
- public void grantUriPermission(String arg0, Uri arg1, int arg2) {
- // pass
-
- }
-
- @Override
- public FileInputStream openFileInput(String arg0) throws FileNotFoundException {
- // pass
- return null;
- }
-
- @Override
- public FileOutputStream openFileOutput(String arg0, int arg1) throws FileNotFoundException {
- // pass
- return null;
- }
-
- @Override
- public SQLiteDatabase openOrCreateDatabase(String arg0, int arg1, CursorFactory arg2) {
- // pass
- return null;
- }
-
- @Override
- public SQLiteDatabase openOrCreateDatabase(String arg0, int arg1,
- CursorFactory arg2, DatabaseErrorHandler arg3) {
- // pass
- return null;
- }
-
- @Override
- public Drawable peekWallpaper() {
- // pass
- return null;
- }
-
- @Override
- public Intent registerReceiver(BroadcastReceiver arg0, IntentFilter arg1) {
- // pass
- return null;
- }
-
- @Override
- public Intent registerReceiver(BroadcastReceiver arg0, IntentFilter arg1, int arg2) {
- // pass
- return null;
- }
-
- @Override
- public Intent registerReceiver(BroadcastReceiver arg0, IntentFilter arg1,
- String arg2, Handler arg3) {
- // pass
- return null;
- }
-
- @Override
- public Intent registerReceiver(BroadcastReceiver arg0, IntentFilter arg1,
- String arg2, Handler arg3, int arg4) {
- // pass
- return null;
- }
-
- @Override
- public Intent registerReceiverAsUser(BroadcastReceiver arg0, UserHandle arg0p5,
- IntentFilter arg1, String arg2, Handler arg3) {
- // pass
- return null;
- }
-
- @Override
- public void removeStickyBroadcast(Intent arg0) {
- // pass
-
- }
-
- @Override
- public void revokeUriPermission(Uri arg0, int arg1) {
- // pass
-
- }
-
- @Override
- public void revokeUriPermission(String arg0, Uri arg1, int arg2) {
- // pass
-
- }
-
- @Override
- public void sendBroadcast(Intent arg0) {
- // pass
-
- }
-
- @Override
- public void sendBroadcast(Intent arg0, String arg1) {
- // pass
-
- }
-
- @Override
- public void sendBroadcastMultiplePermissions(Intent intent, String[] receiverPermissions) {
- // pass
-
- }
-
- @Override
- public void sendBroadcast(Intent arg0, String arg1, Bundle arg2) {
- // pass
-
- }
-
- @Override
- public void sendBroadcast(Intent intent, String receiverPermission, int appOp) {
- // pass
- }
-
- @Override
- public void sendOrderedBroadcast(Intent arg0, String arg1) {
- // pass
-
- }
-
- @Override
- public void sendOrderedBroadcast(Intent arg0, String arg1,
- BroadcastReceiver arg2, Handler arg3, int arg4, String arg5,
- Bundle arg6) {
- // pass
-
- }
-
- @Override
- public void sendOrderedBroadcast(Intent arg0, String arg1,
- Bundle arg7, BroadcastReceiver arg2, Handler arg3, int arg4, String arg5,
- Bundle arg6) {
- // pass
-
- }
-
- @Override
- public void sendOrderedBroadcast(Intent intent, String receiverPermission, int appOp,
- BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
- String initialData, Bundle initialExtras) {
- // pass
- }
-
- @Override
- public void sendBroadcastAsUser(Intent intent, UserHandle user) {
- // pass
- }
-
- @Override
- public void sendBroadcastAsUser(Intent intent, UserHandle user,
- String receiverPermission) {
- // pass
- }
-
- @Override
- public void sendBroadcastAsUser(Intent intent, UserHandle user,
- String receiverPermission, Bundle options) {
- // pass
- }
-
- public void sendBroadcastAsUser(Intent intent, UserHandle user,
- String receiverPermission, int appOp) {
- // pass
- }
-
- @Override
- public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
- String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
- int initialCode, String initialData, Bundle initialExtras) {
- // pass
- }
-
- @Override
- public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
- String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
- Handler scheduler,
- int initialCode, String initialData, Bundle initialExtras) {
- // pass
- }
-
- @Override
- public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
- String receiverPermission, int appOp, Bundle options, BroadcastReceiver resultReceiver,
- Handler scheduler,
- int initialCode, String initialData, Bundle initialExtras) {
- // pass
- }
-
- @Override
- public void sendStickyBroadcast(Intent arg0) {
- // pass
-
- }
-
- @Override
- public void sendStickyOrderedBroadcast(Intent intent,
- BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData,
- Bundle initialExtras) {
- // pass
- }
-
- @Override
- public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) {
- // pass
- }
-
- @Override
- public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) {
- // pass
- }
-
- @Override
- public void sendStickyOrderedBroadcastAsUser(Intent intent,
- UserHandle user, BroadcastReceiver resultReceiver,
- Handler scheduler, int initialCode, String initialData,
- Bundle initialExtras) {
- // pass
- }
-
- @Override
- public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) {
- // pass
- }
-
- @Override
- public void setTheme(int arg0) {
- // pass
-
- }
-
- @Override
- public void setWallpaper(Bitmap arg0) throws IOException {
- // pass
-
- }
-
- @Override
- public void setWallpaper(InputStream arg0) throws IOException {
- // pass
-
- }
-
- @Override
- public void startActivity(Intent arg0) {
- // pass
- }
-
- @Override
- public void startActivity(Intent arg0, Bundle arg1) {
- // pass
- }
-
- @Override
- public void startIntentSender(IntentSender intent,
- Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
- throws IntentSender.SendIntentException {
- // pass
- }
-
- @Override
- public void startIntentSender(IntentSender intent,
- Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
- Bundle options) throws IntentSender.SendIntentException {
- // pass
- }
-
- @Override
- public boolean startInstrumentation(ComponentName arg0, String arg1,
- Bundle arg2) {
- // pass
- return false;
- }
-
- @Override
- public ComponentName startService(Intent arg0) {
- // pass
- return null;
- }
-
- @Override
- public ComponentName startForegroundService(Intent service) {
- // pass
- return null;
- }
-
- @Override
- public ComponentName startForegroundServiceAsUser(Intent service, UserHandle user) {
- // pass
- return null;
- }
-
- @Override
- public boolean stopService(Intent arg0) {
- // pass
- return false;
- }
-
- @Override
- public ComponentName startServiceAsUser(Intent arg0, UserHandle arg1) {
- // pass
- return null;
- }
-
- @Override
- public boolean stopServiceAsUser(Intent arg0, UserHandle arg1) {
- // pass
- return false;
- }
-
- @Override
- public void unbindService(ServiceConnection arg0) {
- // pass
-
- }
-
- @Override
- public void unregisterReceiver(BroadcastReceiver arg0) {
- // pass
-
- }
-
- @Override
- public Context getApplicationContext() {
- return this;
- }
-
- @Override
- public void startActivities(Intent[] arg0) {
- // pass
-
- }
-
- @Override
- public void startActivities(Intent[] arg0, Bundle arg1) {
- // pass
-
- }
-
- @Override
- public boolean isRestricted() {
- return false;
- }
-
- @Override
- public File getObbDir() {
- Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "OBB not supported", null);
- return null;
- }
-
- @Override
- public DisplayAdjustments getDisplayAdjustments(int displayId) {
- // pass
- return null;
- }
-
- @Override
- public Display getDisplay() {
- // pass
- return null;
- }
-
- @Override
- public void updateDisplay(int displayId) {
- // pass
- }
-
- @Override
- public int getUserId() {
- return 0; // not used
- }
-
- @Override
- public File[] getExternalFilesDirs(String type) {
- // pass
- return new File[0];
- }
-
- @Override
- public File[] getObbDirs() {
- // pass
- return new File[0];
- }
-
- @Override
- public File[] getExternalCacheDirs() {
- // pass
- return new File[0];
- }
-
- @Override
- public File[] getExternalMediaDirs() {
- // pass
- return new File[0];
- }
-
- public void setScrollYPos(@NonNull View view, int scrollPos) {
- mScrollYPos.put(view, scrollPos);
- }
-
- public int getScrollYPos(@NonNull View view) {
- Integer pos = mScrollYPos.get(view);
- return pos != null ? pos : 0;
- }
-
- public void setScrollXPos(@NonNull View view, int scrollPos) {
- mScrollXPos.put(view, scrollPos);
- }
-
- public int getScrollXPos(@NonNull View view) {
- Integer pos = mScrollXPos.get(view);
- return pos != null ? pos : 0;
- }
-
- @Override
- public Context createDeviceProtectedStorageContext() {
- // pass
- return null;
- }
-
- @Override
- public Context createCredentialProtectedStorageContext() {
- // pass
- return null;
- }
-
- @Override
- public boolean isDeviceProtectedStorage() {
- return false;
- }
-
- @Override
- public boolean isCredentialProtectedStorage() {
- return false;
- }
-
- /**
- * The cached value depends on
- * <ol>
- * <li>{@code int[]}: the attributes for which TypedArray is created </li>
- * <li>{@code List<StyleResourceValue>}: the themes set on the context at the time of
- * creation of the TypedArray</li>
- * <li>{@code Integer}: the default style used at the time of creation</li>
- * </ol>
- *
- * The class is created by using nested maps resolving one dependency at a time.
- * <p/>
- * The final value of the nested maps is a pair of the typed array and a map of properties
- * that should be added to {@link #mDefaultPropMaps}, if needed.
- */
- private static class TypedArrayCache {
-
- private Map<int[],
- Map<List<StyleResourceValue>,
- Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>>> mCache;
-
- private TypedArrayCache() {
- mCache = new IdentityHashMap<>();
- }
-
- public Pair<BridgeTypedArray, PropertiesMap> get(int[] attrs,
- List<StyleResourceValue> themes, int resId) {
- Map<List<StyleResourceValue>, Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>>
- cacheFromThemes = mCache.get(attrs);
- if (cacheFromThemes != null) {
- Map<Integer, Pair<BridgeTypedArray, PropertiesMap>> cacheFromResId =
- cacheFromThemes.get(themes);
- if (cacheFromResId != null) {
- return cacheFromResId.get(resId);
- }
- }
- return null;
- }
-
- public void put(int[] attrs, List<StyleResourceValue> themes, int resId,
- Pair<BridgeTypedArray, PropertiesMap> value) {
- Map<List<StyleResourceValue>, Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>>
- cacheFromThemes = mCache.computeIfAbsent(attrs, k -> new HashMap<>());
- Map<Integer, Pair<BridgeTypedArray, PropertiesMap>> cacheFromResId =
- cacheFromThemes.computeIfAbsent(themes, k -> new HashMap<>());
- cacheFromResId.put(resId, value);
- }
-
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
deleted file mode 100644
index 4805ed1..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2011 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.layoutlib.bridge.android;
-
-import com.android.internal.inputmethod.IInputContentUriToken;
-import com.android.internal.view.IInputContext;
-import com.android.internal.view.IInputMethodClient;
-import com.android.internal.view.IInputMethodManager;
-import com.android.internal.view.InputBindResult;
-
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.text.style.SuggestionSpan;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodSubtype;
-
-import java.util.List;
-
-/**
- * Basic implementation of IInputMethodManager that does nothing.
- *
- */
-public class BridgeIInputMethodManager implements IInputMethodManager {
-
- @Override
- public void addClient(IInputMethodClient arg0, IInputContext arg1, int arg2, int arg3)
- throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void finishInput(IInputMethodClient arg0) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public InputMethodSubtype getCurrentInputMethodSubtype() throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public List<InputMethodInfo> getEnabledInputMethodList() throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String arg0,
- boolean arg1) throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public List<InputMethodInfo> getInputMethodList() throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public InputMethodSubtype getLastInputMethodSubtype() throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public List getShortcutInputMethodsAndSubtypes() throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public void hideMySoftInput(IBinder arg0, int arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public boolean hideSoftInput(IInputMethodClient arg0, int arg1, ResultReceiver arg2)
- throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean notifySuggestionPicked(SuggestionSpan arg0, String arg1, int arg2)
- throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public void registerSuggestionSpansForNotification(SuggestionSpan[] arg0)
- throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void removeClient(IInputMethodClient arg0) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void setAdditionalInputMethodSubtypes(String arg0, InputMethodSubtype[] arg1)
- throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public boolean setCurrentInputMethodSubtype(InputMethodSubtype arg0) throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public void setImeWindowStatus(IBinder arg0, IBinder arg1, int arg2, int arg3)
- throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setInputMethod(IBinder arg0, String arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void setInputMethodAndSubtype(IBinder arg0, String arg1, InputMethodSubtype arg2)
- throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public boolean setInputMethodEnabled(String arg0, boolean arg1) throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public void showInputMethodAndSubtypeEnablerFromClient(IInputMethodClient arg0, String arg1)
- throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void showInputMethodPickerFromClient(IInputMethodClient arg0,
- int arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void showMySoftInput(IBinder arg0, int arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public boolean showSoftInput(IInputMethodClient arg0, int arg1, ResultReceiver arg2)
- throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean switchToLastInputMethod(IBinder arg0) throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean switchToNextInputMethod(IBinder arg0, boolean arg1) throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean shouldOfferSwitchingToNextInputMethod(IBinder arg0) throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public int getInputMethodWindowVisibleHeight() throws RemoteException {
- // TODO Auto-generated method stub
- return 0;
- }
-
- @Override
- public void notifyUserAction(int sequenceNumber) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void updateStatusIcon(IBinder arg0, String arg1, int arg2) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void clearLastInputMethodWindowForTransition(IBinder arg0) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public InputBindResult startInputOrWindowGainedFocus(
- /* @InputMethodClient.StartInputReason */ int startInputReason,
- IInputMethodClient client, IBinder windowToken, int controlFlags,
- /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
- int windowFlags, EditorInfo attribute, IInputContext inputContext,
- /* @InputConnectionInspector.MissingMethodFlags */ int missingMethodFlags)
- throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public IBinder asBinder() {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public IInputContentUriToken createInputContentUriToken(IBinder token, Uri contentUri,
- String packageName) {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public void reportFullscreenMode(IBinder token, boolean fullscreen) {
- // TODO Auto-generated method stub
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java
deleted file mode 100644
index f5912e7..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2010 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.layoutlib.bridge.android;
-
-import com.android.layoutlib.bridge.BridgeConstants;
-
-import android.util.AttributeSet;
-
-import java.util.Map;
-
-/**
- * An implementation of the {@link AttributeSet} interface on top of a map of attribute in the form
- * of (name, value).
- *
- * This is meant to be called only from {@link BridgeContext#obtainStyledAttributes(AttributeSet, int[], int, int)}
- * in the case of LayoutParams and therefore isn't a full implementation.
- */
-public class BridgeLayoutParamsMapAttributes implements AttributeSet {
-
- private final Map<String, String> mAttributes;
-
- public BridgeLayoutParamsMapAttributes(Map<String, String> attributes) {
- mAttributes = attributes;
- }
-
- @Override
- public String getAttributeValue(String namespace, String name) {
- if (BridgeConstants.NS_RESOURCES.equals(namespace)) {
- return mAttributes.get(name);
- }
-
- return null;
- }
-
- // ---- the following methods are not called from
- // BridgeContext#obtainStyledAttributes(AttributeSet, int[], int, int)
- // Should they ever be called, we'll just implement them on a need basis.
-
- @Override
- public int getAttributeCount() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getAttributeName(int index) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getAttributeValue(int index) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getPositionDescription() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getAttributeNameResource(int index) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getAttributeListValue(String namespace, String attribute,
- String[] options, int defaultValue) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean getAttributeBooleanValue(String namespace, String attribute,
- boolean defaultValue) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getAttributeResourceValue(String namespace, String attribute,
- int defaultValue) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getAttributeIntValue(String namespace, String attribute,
- int defaultValue) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getAttributeUnsignedIntValue(String namespace, String attribute,
- int defaultValue) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public float getAttributeFloatValue(String namespace, String attribute,
- float defaultValue) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getAttributeListValue(int index,
- String[] options, int defaultValue) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getAttributeResourceValue(int index, int defaultValue) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getAttributeIntValue(int index, int defaultValue) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getAttributeUnsignedIntValue(int index, int defaultValue) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public float getAttributeFloatValue(int index, float defaultValue) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getIdAttribute() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getClassAttribute() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getIdAttributeResourceValue(int defaultValue) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getStyleAttribute() {
- throw new UnsupportedOperationException();
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
deleted file mode 100644
index 1b5a54d..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ /dev/null
@@ -1,958 +0,0 @@
-/*
- * Copyright (C) 2015 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.layoutlib.bridge.android;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.PackageInstallObserver;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentSender;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.ChangedPackages;
-import android.content.pm.InstantAppInfo;
-import android.content.pm.FeatureInfo;
-import android.content.pm.IPackageDataObserver;
-import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallObserver;
-import android.content.pm.IPackageStatsObserver;
-import android.content.pm.InstrumentationInfo;
-import android.content.pm.IntentFilterVerificationInfo;
-import android.content.pm.KeySet;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageItemInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PermissionGroupInfo;
-import android.content.pm.PermissionInfo;
-import android.content.pm.ProviderInfo;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.pm.SharedLibraryInfo;
-import android.content.pm.VerifierDeviceIdentity;
-import android.content.pm.VersionedPackage;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
-import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.os.storage.VolumeInfo;
-import java.util.List;
-
-/**
- * An implementation of {@link PackageManager} that does nothing.
- */
-@SuppressWarnings("deprecation")
-public class BridgePackageManager extends PackageManager {
- @Override
- public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException {
- return null;
- }
-
- @Override
- public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
- throws NameNotFoundException {
- return null;
- }
-
- @Override
- public PackageInfo getPackageInfo(VersionedPackage versionedPackage,
- @PackageInfoFlags int flags) throws NameNotFoundException {
- return null;
- }
-
- @Override
- public List<SharedLibraryInfo> getSharedLibraries(@InstallFlags int flags) {
- return null;
- }
-
- @Override
- public List<SharedLibraryInfo> getSharedLibrariesAsUser(@InstallFlags int flags,
- int userId) {
- return null;
- }
-
- @Override
- public String[] currentToCanonicalPackageNames(String[] names) {
- return new String[0];
- }
-
- @Override
- public String[] canonicalToCurrentPackageNames(String[] names) {
- return new String[0];
- }
-
- @Override
- public Intent getLaunchIntentForPackage(String packageName) {
- return null;
- }
-
- @Override
- public Intent getLeanbackLaunchIntentForPackage(String packageName) {
- return null;
- }
-
- @Override
- public int[] getPackageGids(String packageName) throws NameNotFoundException {
- return new int[0];
- }
-
- @Override
- public int[] getPackageGids(String packageName, int flags) throws NameNotFoundException {
- return new int[0];
- }
-
- @Override
- public int getPackageUid(String packageName, int flags) throws NameNotFoundException {
- return 0;
- }
-
- @Override
- public int getPackageUidAsUser(String packageName, int userHandle) throws NameNotFoundException {
- return 0;
- }
-
- @Override
- public int getPackageUidAsUser(String packageName, int flags, int userHandle) throws NameNotFoundException {
- return 0;
- }
-
- @Override
- public PermissionInfo getPermissionInfo(String name, int flags) throws NameNotFoundException {
- return null;
- }
-
- @Override
- public List<PermissionInfo> queryPermissionsByGroup(String group, int flags)
- throws NameNotFoundException {
- return null;
- }
-
- @Override
- public boolean isPermissionReviewModeEnabled() {
- return false;
- }
-
- @Override
- public PermissionGroupInfo getPermissionGroupInfo(String name, int flags)
- throws NameNotFoundException {
- return null;
- }
-
- @Override
- public List<PermissionGroupInfo> getAllPermissionGroups(int flags) {
- return null;
- }
-
- @Override
- public ApplicationInfo getApplicationInfo(String packageName, int flags)
- throws NameNotFoundException {
- return null;
- }
-
- @Override
- public ApplicationInfo getApplicationInfoAsUser(String packageName, int flags, int userId)
- throws NameNotFoundException {
- return null;
- }
-
- @Override
- public ActivityInfo getActivityInfo(ComponentName component, int flags)
- throws NameNotFoundException {
- return null;
- }
-
- @Override
- public ActivityInfo getReceiverInfo(ComponentName component, int flags)
- throws NameNotFoundException {
- return null;
- }
-
- @Override
- public ServiceInfo getServiceInfo(ComponentName component, int flags)
- throws NameNotFoundException {
- return null;
- }
-
- @Override
- public ProviderInfo getProviderInfo(ComponentName component, int flags)
- throws NameNotFoundException {
- return null;
- }
-
- @Override
- public List<PackageInfo> getInstalledPackages(int flags) {
- return null;
- }
-
- @Override
- public List<PackageInfo> getPackagesHoldingPermissions(String[] permissions, int flags) {
- return null;
- }
-
- @Override
- public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
- return null;
- }
-
- @Override
- public int checkPermission(String permName, String pkgName) {
- return 0;
- }
-
- @Override
- public boolean isPermissionRevokedByPolicy(String permName, String pkgName) {
- return false;
- }
-
- @Override
- public String getPermissionControllerPackageName() {
- return null;
- }
-
- @Override
- public boolean addPermission(PermissionInfo info) {
- return false;
- }
-
- @Override
- public boolean addPermissionAsync(PermissionInfo info) {
- return false;
- }
-
- @Override
- public void removePermission(String name) {
- }
-
- @Override
- public void grantRuntimePermission(String packageName, String permissionName, UserHandle user) {
- }
-
- @Override
- public void revokeRuntimePermission(String packageName, String permissionName,
- UserHandle user) {
- }
-
- @Override
- public int getPermissionFlags(String permissionName, String packageName, UserHandle user) {
- return 0;
- }
-
- @Override
- public void updatePermissionFlags(String permissionName, String packageName, int flagMask,
- int flagValues, UserHandle user) {
- }
-
- @Override
- public boolean shouldShowRequestPermissionRationale(String permission) {
- return false;
- }
-
- @Override
- public int checkSignatures(String pkg1, String pkg2) {
- return 0;
- }
-
- @Override
- public int checkSignatures(int uid1, int uid2) {
- return 0;
- }
-
- @Override
- public String[] getPackagesForUid(int uid) {
- return new String[0];
- }
-
- @Override
- public String getNameForUid(int uid) {
- return null;
- }
-
- @Override
- public int getUidForSharedUser(String sharedUserName) throws NameNotFoundException {
- return 0;
- }
-
- @Override
- public List<ApplicationInfo> getInstalledApplications(int flags) {
- return null;
- }
-
- @Override
- public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) {
- return null;
- }
-
- @Override
- public List<InstantAppInfo> getInstantApps() {
- return null;
- }
-
- @Override
- public Drawable getInstantAppIcon(String packageName) {
- assert false : "Unsupported operation";
- return new ColorDrawable();
- }
-
- @Override
- public byte[] getInstantAppCookie() {
- return new byte[0];
- }
-
- @Override
- public boolean isInstantApp() {
- return false;
- }
-
- @Override
- public boolean isInstantApp(String packageName) {
- return false;
- }
-
- @Override
- public int getInstantAppCookieMaxBytes() {
- return 0;
- }
-
- @Override
- public int getInstantAppCookieMaxSize() {
- return 0;
- }
-
- @Override
- public void clearInstantAppCookie() {;
-
- }
-
- @Override
- public void updateInstantAppCookie(@Nullable byte[] cookie) {
-
- }
-
- @Override
- public boolean setInstantAppCookie(@NonNull byte[] cookie) {
- return false;
- }
-
- @Override
- public String[] getSystemSharedLibraryNames() {
- return new String[0];
- }
-
- @Override
- public String getServicesSystemSharedLibraryPackageName() {
- return null;
- }
-
- @Override
- public @NonNull String getSharedSystemSharedLibraryPackageName() {
- return null;
- }
-
- @Override
- public FeatureInfo[] getSystemAvailableFeatures() {
- return new FeatureInfo[0];
- }
-
- @Override
- public boolean hasSystemFeature(String name) {
- return false;
- }
-
- @Override
- public boolean hasSystemFeature(String name, int version) {
- return false;
- }
-
- @Override
- public ResolveInfo resolveActivity(Intent intent, int flags) {
- return null;
- }
-
- @Override
- public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) {
- return null;
- }
-
- @Override
- public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
- return null;
- }
-
- @Override
- public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) {
- return null;
- }
-
- @Override
- public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller, Intent[] specifics,
- Intent intent, int flags) {
- return null;
- }
-
- @Override
- public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
- return null;
- }
-
- @Override
- public List<ResolveInfo> queryBroadcastReceiversAsUser(Intent intent, int flags, int userId) {
- return null;
- }
-
- @Override
- public ResolveInfo resolveService(Intent intent, int flags) {
- return null;
- }
-
- @Override
- public List<ResolveInfo> queryIntentServices(Intent intent, int flags) {
- return null;
- }
-
- @Override
- public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) {
- return null;
- }
-
- @Override
- public List<ResolveInfo> queryIntentContentProvidersAsUser(Intent intent, int flags,
- int userId) {
- return null;
- }
-
- @Override
- public List<ResolveInfo> queryIntentContentProviders(Intent intent, int flags) {
- return null;
- }
-
- @Override
- public ProviderInfo resolveContentProvider(String name, int flags) {
- return null;
- }
-
- @Override
- public ProviderInfo resolveContentProviderAsUser(String name, int flags, int userId) {
- return null;
- }
-
- @Override
- public List<ProviderInfo> queryContentProviders(String processName, int uid, int flags) {
- return null;
- }
-
- @Override
- public InstrumentationInfo getInstrumentationInfo(ComponentName className, int flags)
- throws NameNotFoundException {
- return null;
- }
-
- @Override
- public List<InstrumentationInfo> queryInstrumentation(String targetPackage, int flags) {
- return null;
- }
-
- @Override
- public Drawable getDrawable(String packageName, int resid, ApplicationInfo appInfo) {
- return null;
- }
-
- @Override
- public Drawable getActivityIcon(ComponentName activityName) throws NameNotFoundException {
- return null;
- }
-
- @Override
- public Drawable getActivityIcon(Intent intent) throws NameNotFoundException {
- return null;
- }
-
- @Override
- public Drawable getActivityBanner(ComponentName activityName) throws NameNotFoundException {
- return null;
- }
-
- @Override
- public Drawable getActivityBanner(Intent intent) throws NameNotFoundException {
- return null;
- }
-
- @Override
- public Drawable getDefaultActivityIcon() {
- return null;
- }
-
- @Override
- public Drawable getApplicationIcon(ApplicationInfo info) {
- return null;
- }
-
- @Override
- public Drawable getApplicationIcon(String packageName) throws NameNotFoundException {
- return null;
- }
-
- @Override
- public Drawable getApplicationBanner(ApplicationInfo info) {
- return null;
- }
-
- @Override
- public Drawable getApplicationBanner(String packageName) throws NameNotFoundException {
- return null;
- }
-
- @Override
- public Drawable getActivityLogo(ComponentName activityName) throws NameNotFoundException {
- return null;
- }
-
- @Override
- public Drawable getActivityLogo(Intent intent) throws NameNotFoundException {
- return null;
- }
-
- @Override
- public Drawable getApplicationLogo(ApplicationInfo info) {
- return null;
- }
-
- @Override
- public Drawable getApplicationLogo(String packageName) throws NameNotFoundException {
- return null;
- }
-
- @Override
- public Drawable getUserBadgedIcon(Drawable icon, UserHandle user) {
- return null;
- }
-
- @Override
- public Drawable getUserBadgedDrawableForDensity(Drawable drawable, UserHandle user,
- Rect badgeLocation, int badgeDensity) {
- return null;
- }
-
- @Override
- public Drawable getUserBadgeForDensity(UserHandle user, int density) {
- return null;
- }
-
- @Override
- public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) {
- return null;
- }
-
- @Override
- public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) {
- return null;
- }
-
- @Override
- public CharSequence getText(String packageName, int resid, ApplicationInfo appInfo) {
- return null;
- }
-
- @Override
- public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) {
- return null;
- }
-
- @Override
- public CharSequence getApplicationLabel(ApplicationInfo info) {
- return null;
- }
-
- @Override
- public Resources getResourcesForActivity(ComponentName activityName)
- throws NameNotFoundException {
- return null;
- }
-
- @Override
- public Resources getResourcesForApplication(ApplicationInfo app) throws NameNotFoundException {
- return null;
- }
-
- @Override
- public Resources getResourcesForApplication(String appPackageName)
- throws NameNotFoundException {
- return null;
- }
-
- @Override
- public Resources getResourcesForApplicationAsUser(String appPackageName, int userId)
- throws NameNotFoundException {
- return null;
- }
-
- @Override
- public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags,
- String installerPackageName) {
- }
-
- @Override
- public void installPackage(Uri packageURI, PackageInstallObserver observer, int flags,
- String installerPackageName) {
- }
-
- @Override
- public int installExistingPackage(String packageName) throws NameNotFoundException {
- return 0;
- }
-
- @Override
- public int installExistingPackage(String packageName, int installReason)
- throws NameNotFoundException {
- return 0;
- }
-
- @Override
- public int installExistingPackageAsUser(String packageName, int userId)
- throws NameNotFoundException {
- return 0;
- }
-
- @Override
- public void verifyPendingInstall(int id, int verificationCode) {
- }
-
- @Override
- public void extendVerificationTimeout(int id, int verificationCodeAtTimeout,
- long millisecondsToDelay) {
- }
-
- @Override
- public void verifyIntentFilter(int verificationId, int verificationCode,
- List<String> outFailedDomains) {
- }
-
- @Override
- public int getIntentVerificationStatusAsUser(String packageName, int userId) {
- return 0;
- }
-
- @Override
- public boolean updateIntentVerificationStatusAsUser(String packageName, int status, int userId) {
- return false;
- }
-
- @Override
- public List<IntentFilterVerificationInfo> getIntentFilterVerifications(String packageName) {
- return null;
- }
-
- @Override
- public List<IntentFilter> getAllIntentFilters(String packageName) {
- return null;
- }
-
- @Override
- public String getDefaultBrowserPackageNameAsUser(int userId) {
- return null;
- }
-
- @Override
- public boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId) {
- return false;
- }
-
- @Override
- public void setInstallerPackageName(String targetPackage, String installerPackageName) {
- }
-
- @Override
- public void setUpdateAvailable(String packageName, boolean updateAvailable) {
- }
-
- @Override
- public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) {
- }
-
- @Override
- public void deletePackageAsUser(String packageName, IPackageDeleteObserver observer, int flags,
- int userId) {
- }
-
- @Override
- public String getInstallerPackageName(String packageName) {
- return null;
- }
-
- @Override
- public void clearApplicationUserData(String packageName, IPackageDataObserver observer) {
- }
-
- @Override
- public void deleteApplicationCacheFiles(String packageName, IPackageDataObserver observer) {
- }
-
- @Override
- public void deleteApplicationCacheFilesAsUser(String packageName, int userId,
- IPackageDataObserver observer) {
- }
-
- @Override
- public void freeStorageAndNotify(String volumeUuid, long freeStorageSize,
- IPackageDataObserver observer) {
- }
-
- @Override
- public void freeStorage(String volumeUuid, long freeStorageSize, IntentSender pi) {
- }
-
- @Override
- public void getPackageSizeInfoAsUser(String packageName, int userHandle,
- IPackageStatsObserver observer) {
- }
-
- @Override
- public void addPackageToPreferred(String packageName) {
- }
-
- @Override
- public void removePackageFromPreferred(String packageName) {
- }
-
- @Override
- public List<PackageInfo> getPreferredPackages(int flags) {
- return null;
- }
-
- @Override
- public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set,
- ComponentName activity) {
- }
-
- @Override
- public void replacePreferredActivity(IntentFilter filter, int match, ComponentName[] set,
- ComponentName activity) {
- }
-
- @Override
- public void clearPackagePreferredActivities(String packageName) {
- }
-
- @Override
- public int getPreferredActivities(List<IntentFilter> outFilters,
- List<ComponentName> outActivities, String packageName) {
- return 0;
- }
-
- @Override
- public ComponentName getHomeActivities(List<ResolveInfo> outActivities) {
- return null;
- }
-
- @Override
- public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) {
- }
-
- @Override
- public int getComponentEnabledSetting(ComponentName componentName) {
- return 0;
- }
-
- @Override
- public void setApplicationEnabledSetting(String packageName, int newState, int flags) {
- }
-
- @Override
- public int getApplicationEnabledSetting(String packageName) {
- return 0;
- }
-
- @Override
- public void flushPackageRestrictionsAsUser(int userId) {
- }
-
- @Override
- public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden,
- UserHandle userHandle) {
- return false;
- }
-
- @Override
- public boolean getApplicationHiddenSettingAsUser(String packageName, UserHandle userHandle) {
- return false;
- }
-
- @Override
- public boolean isSafeMode() {
- return false;
- }
-
- @Override
- public void addOnPermissionsChangeListener(OnPermissionsChangedListener listener) {
- }
-
- @Override
- public void removeOnPermissionsChangeListener(OnPermissionsChangedListener listener) {
- }
-
- @Override
- public KeySet getKeySetByAlias(String packageName, String alias) {
- return null;
- }
-
- @Override
- public KeySet getSigningKeySet(String packageName) {
- return null;
- }
-
- @Override
- public boolean isSignedBy(String packageName, KeySet ks) {
- return false;
- }
-
- @Override
- public boolean isSignedByExactly(String packageName, KeySet ks) {
- return false;
- }
-
- @Override
- public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
- int userId) {
- return new String[]{};
- }
-
- @Override
- public boolean isPackageSuspendedForUser(String packageName, int userId) {
- return false;
- }
-
- @Override
- public void setApplicationCategoryHint(String packageName, int categoryHint) {
- }
-
- @Override
- public int getMoveStatus(int moveId) {
- return 0;
- }
-
- @Override
- public void registerMoveCallback(MoveCallback callback, Handler handler) {
- }
-
- @Override
- public void unregisterMoveCallback(MoveCallback callback) {
- }
-
- @Override
- public int movePackage(String packageName, VolumeInfo vol) {
- return 0;
- }
-
- @Override
- public VolumeInfo getPackageCurrentVolume(ApplicationInfo app) {
- return null;
- }
-
- @Override
- public List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app) {
- return null;
- }
-
- @Override
- public int movePrimaryStorage(VolumeInfo vol) {
- return 0;
- }
-
- @Override
- public VolumeInfo getPrimaryStorageCurrentVolume() {
- return null;
- }
-
- @Override
- public List<VolumeInfo> getPrimaryStorageCandidateVolumes() {
- return null;
- }
-
- @Override
- public VerifierDeviceIdentity getVerifierDeviceIdentity() {
- return null;
- }
-
- @Override
- public ChangedPackages getChangedPackages(int sequenceNumber) {
- return null;
- }
-
- @Override
- public boolean isUpgrade() {
- return false;
- }
-
- @Override
- public PackageInstaller getPackageInstaller() {
- return null;
- }
-
- @Override
- public void addCrossProfileIntentFilter(IntentFilter filter, int sourceUserId, int targetUserId,
- int flags) {
- }
-
- @Override
- public void clearCrossProfileIntentFilters(int sourceUserId) {
- }
-
- @Override
- public Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo) {
- return null;
- }
-
- @Override
- public Drawable loadUnbadgedItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo) {
- return null;
- }
-
- @Override
- public boolean isPackageAvailable(String packageName) {
- return false;
- }
-
- @Override
- public int getInstallReason(String packageName, UserHandle user) {
- return INSTALL_REASON_UNKNOWN;
- }
-
- @Override
- public boolean canRequestPackageInstalls() {
- return false;
- }
-
- @Override
- public ComponentName getInstantAppResolverSettingsComponent() {
- return null;
- }
-
- @Override
- public ComponentName getInstantAppInstallerComponent() {
- return null;
- }
-
- @Override
- public String getInstantAppAndroidId(String packageName, UserHandle user) {
- return null;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
deleted file mode 100644
index ed428ec..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2012 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.layoutlib.bridge.android;
-
-import android.os.IBinder;
-import android.os.IPowerManager;
-import android.os.PowerManager;
-import android.os.PowerSaveState;
-import android.os.RemoteException;
-import android.os.WorkSource;
-
-/**
- * Fake implementation of IPowerManager.
- *
- */
-public class BridgePowerManager implements IPowerManager {
-
- @Override
- public boolean isInteractive() throws RemoteException {
- return true;
- }
-
- @Override
- public boolean isPowerSaveMode() throws RemoteException {
- return false;
- }
-
- @Override
- public boolean setPowerSaveMode(boolean mode) throws RemoteException {
- return false;
- }
-
- public PowerSaveState getPowerSaveState(int serviceType) {
- return null;
- }
-
- @Override
- public IBinder asBinder() {
- // pass for now.
- return null;
- }
-
- @Override
- public void acquireWakeLock(IBinder arg0, int arg1, String arg2, String arg2_5, WorkSource arg3, String arg4)
- throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void acquireWakeLockWithUid(IBinder arg0, int arg1, String arg2, String arg2_5, int arg3)
- throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void powerHint(int hintId, int data) {
- // pass for now.
- }
-
- @Override
- public void crash(String arg0) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void goToSleep(long arg0, int arg1, int arg2) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void nap(long arg0) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void reboot(boolean confirm, String reason, boolean wait) {
- // pass for now.
- }
-
- @Override
- public void rebootSafeMode(boolean confirm, boolean wait) {
- // pass for now.
- }
-
- @Override
- public void shutdown(boolean confirm, String reason, boolean wait) {
- // pass for now.
- }
-
- @Override
- public void releaseWakeLock(IBinder arg0, int arg1) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void updateWakeLockUids(IBinder arg0, int[] arg1) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void setAttentionLight(boolean arg0, int arg1) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(float arg0) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void setTemporaryScreenBrightnessSettingOverride(int arg0) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void setStayOnSetting(int arg0) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void updateWakeLockWorkSource(IBinder arg0, WorkSource arg1, String arg2) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public boolean isWakeLockLevelSupported(int level) throws RemoteException {
- // pass for now.
- return true;
- }
-
- @Override
- public void userActivity(long time, int event, int flags) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void wakeUp(long time, String reason, String opPackageName) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void boostScreenBrightness(long time) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public boolean isDeviceIdleMode() throws RemoteException {
- return false;
- }
-
- @Override
- public boolean isLightDeviceIdleMode() throws RemoteException {
- return false;
- }
-
- @Override
- public boolean isScreenBrightnessBoosted() throws RemoteException {
- return false;
- }
-
- @Override
- public int getLastShutdownReason() {
- return PowerManager.SHUTDOWN_REASON_UNKNOWN;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeSharedPreferences.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeSharedPreferences.java
deleted file mode 100644
index 132ff2f..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeSharedPreferences.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2014 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.layoutlib.bridge.android;
-
-import android.content.SharedPreferences;
-
-import java.util.Map;
-import java.util.Set;
-
-/**
- * An empty shared preferences implementation which doesn't store anything. It always returns
- * null, 0 or false.
- */
-public class BridgeSharedPreferences implements SharedPreferences {
- private Editor mEditor;
-
- @Override
- public Map<String, ?> getAll() {
- return null;
- }
-
- @Override
- public String getString(String key, String defValue) {
- return null;
- }
-
- @Override
- public Set<String> getStringSet(String key, Set<String> defValues) {
- return null;
- }
-
- @Override
- public int getInt(String key, int defValue) {
- return 0;
- }
-
- @Override
- public long getLong(String key, long defValue) {
- return 0;
- }
-
- @Override
- public float getFloat(String key, float defValue) {
- return 0;
- }
-
- @Override
- public boolean getBoolean(String key, boolean defValue) {
- return false;
- }
-
- @Override
- public boolean contains(String key) {
- return false;
- }
-
- @Override
- public Editor edit() {
- if (mEditor != null) {
- return mEditor;
- }
- mEditor = new Editor() {
- @Override
- public Editor putString(String key, String value) {
- return null;
- }
-
- @Override
- public Editor putStringSet(String key, Set<String> values) {
- return null;
- }
-
- @Override
- public Editor putInt(String key, int value) {
- return null;
- }
-
- @Override
- public Editor putLong(String key, long value) {
- return null;
- }
-
- @Override
- public Editor putFloat(String key, float value) {
- return null;
- }
-
- @Override
- public Editor putBoolean(String key, boolean value) {
- return null;
- }
-
- @Override
- public Editor remove(String key) {
- return null;
- }
-
- @Override
- public Editor clear() {
- return null;
- }
-
- @Override
- public boolean commit() {
- return false;
- }
-
- @Override
- public void apply() {
- }
- };
- return mEditor;
- }
-
- @Override
- public void registerOnSharedPreferenceChangeListener(
- OnSharedPreferenceChangeListener listener) {
- }
-
- @Override
- public void unregisterOnSharedPreferenceChangeListener(
- OnSharedPreferenceChangeListener listener) {
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
deleted file mode 100644
index ffbe7c4..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2010 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.layoutlib.bridge.android;
-
-import com.android.internal.os.IResultReceiver;
-
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.util.MergedConfiguration;
-import android.view.DragEvent;
-import android.view.IWindow;
-
-/**
- * Implementation of {@link IWindow} to pass to the AttachInfo.
- */
-public final class BridgeWindow implements IWindow {
-
- @Override
- public void dispatchAppVisibility(boolean arg0) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void dispatchGetNewSurface() throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void executeCommand(String arg0, String arg1, ParcelFileDescriptor arg2)
- throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void resized(Rect rect, Rect rect2, Rect rect3, Rect rect4, Rect rect5, Rect rect6,
- boolean b, MergedConfiguration mergedConfig, Rect rect7, boolean b2, boolean b3, int i0)
- throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void moved(int arg0, int arg1) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void windowFocusChanged(boolean arg0, boolean arg1) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep,
- boolean sync) {
- // pass for now.
- }
-
- @Override
- public void dispatchWallpaperCommand(String action, int x, int y,
- int z, Bundle extras, boolean sync) {
- // pass for now.
- }
-
- @Override
- public void closeSystemDialogs(String reason) {
- // pass for now.
- }
-
- @Override
- public void dispatchDragEvent(DragEvent event) {
- // pass for now.
- }
-
- @Override
- public void updatePointerIcon(float x, float y) {
- // pass for now
- }
-
- @Override
- public void dispatchSystemUiVisibilityChanged(int seq, int globalUi,
- int localValue, int localChanges) {
- // pass for now.
- }
-
- @Override
- public void dispatchWindowShown() {
- }
-
- @Override
- public void requestAppKeyboardShortcuts(
- IResultReceiver receiver, int deviceId) throws RemoteException {
- }
-
- @Override
- public void dispatchPointerCaptureChanged(boolean hasCapture) {
- }
-
- @Override
- public IBinder asBinder() {
- // pass for now.
- return null;
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
deleted file mode 100644
index 2c88394..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright (C) 2010 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.layoutlib.bridge.android;
-
-import android.content.ClipData;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.MergedConfiguration;
-import android.view.IWindow;
-import android.view.IWindowId;
-import android.view.IWindowSession;
-import android.view.InputChannel;
-import android.view.Surface;
-import android.view.SurfaceView;
-import android.view.WindowManager.LayoutParams;
-
-/**
- * Implementation of {@link IWindowSession} so that mSession is not null in
- * the {@link SurfaceView}.
- */
-public final class BridgeWindowSession implements IWindowSession {
-
- @Override
- public int add(IWindow arg0, int seq, LayoutParams arg1, int arg2, Rect arg3, Rect arg4,
- InputChannel outInputchannel)
- throws RemoteException {
- // pass for now.
- return 0;
- }
-
- @Override
- public int addToDisplay(IWindow arg0, int seq, LayoutParams arg1, int arg2, int displayId,
- Rect arg3, Rect arg4, Rect arg5, InputChannel outInputchannel)
- throws RemoteException {
- // pass for now.
- return 0;
- }
-
- @Override
- public int addWithoutInputChannel(IWindow arg0, int seq, LayoutParams arg1, int arg2,
- Rect arg3, Rect arg4)
- throws RemoteException {
- // pass for now.
- return 0;
- }
-
- @Override
- public int addToDisplayWithoutInputChannel(IWindow arg0, int seq, LayoutParams arg1, int arg2,
- int displayId, Rect arg3, Rect arg4)
- throws RemoteException {
- // pass for now.
- return 0;
- }
-
- @Override
- public void finishDrawing(IWindow arg0) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public boolean getInTouchMode() throws RemoteException {
- // pass for now.
- return false;
- }
-
- @Override
- public boolean performHapticFeedback(IWindow window, int effectId, boolean always) {
- // pass for now.
- return false;
- }
-
- @Override
- public int relayout(IWindow iWindow, int i, LayoutParams layoutParams, int i2,
- int i3, int i4, int i5, Rect rect, Rect rect2, Rect rect3, Rect rect4, Rect rect5,
- Rect rect6, Rect rect7, MergedConfiguration mergedConfig, Surface surface)
- throws RemoteException {
- // pass for now.
- return 0;
- }
-
- @Override
- public boolean outOfMemory(IWindow window) throws RemoteException {
- return false;
- }
-
- @Override
- public void getDisplayFrame(IWindow window, Rect outDisplayFrame) {
- // pass for now.
- }
-
- @Override
- public void remove(IWindow arg0) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void setInTouchMode(boolean arg0) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void setTransparentRegion(IWindow arg0, Region arg1) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void setInsets(IWindow window, int touchable, Rect contentInsets,
- Rect visibleInsets, Region touchableRegion) {
- // pass for now.
- }
-
- @Override
- public IBinder prepareDrag(IWindow window, int flags,
- int thumbnailWidth, int thumbnailHeight, Surface outSurface)
- throws RemoteException {
- // pass for now
- return null;
- }
-
- @Override
- public boolean performDrag(IWindow window, IBinder dragToken,
- int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
- ClipData data)
- throws RemoteException {
- // pass for now
- return false;
- }
-
- @Override
- public boolean startMovingTask(IWindow window, float startX, float startY)
- throws RemoteException {
- // pass for now
- return false;
- }
-
- @Override
- public void reportDropResult(IWindow window, boolean consumed) throws RemoteException {
- // pass for now
- }
-
- @Override
- public void cancelDragAndDrop(IBinder dragToken) throws RemoteException {
- // pass for now
- }
-
- @Override
- public void dragRecipientEntered(IWindow window) throws RemoteException {
- // pass for now
- }
-
- @Override
- public void dragRecipientExited(IWindow window) throws RemoteException {
- // pass for now
- }
-
- @Override
- public void setWallpaperPosition(IBinder window, float x, float y,
- float xStep, float yStep) {
- // pass for now.
- }
-
- @Override
- public void wallpaperOffsetsComplete(IBinder window) {
- // pass for now.
- }
-
- @Override
- public void setWallpaperDisplayOffset(IBinder windowToken, int x, int y) {
- // pass for now.
- }
-
- @Override
- public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
- int z, Bundle extras, boolean sync) {
- // pass for now.
- return null;
- }
-
- @Override
- public void wallpaperCommandComplete(IBinder window, Bundle result) {
- // pass for now.
- }
-
- @Override
- public IBinder asBinder() {
- // pass for now.
- return null;
- }
-
- @Override
- public void onRectangleOnScreenRequested(IBinder window, Rect rectangle) {
- // pass for now.
- }
-
- @Override
- public IWindowId getWindowId(IBinder window) throws RemoteException {
- // pass for now.
- return null;
- }
-
- @Override
- public void pokeDrawLock(IBinder window) {
- // pass for now.
- }
-
- @Override
- public void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly) {
- // pass for now.
- }
-
- @Override
- public void updatePointerIcon(IWindow window) {
- // pass for now.
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java
deleted file mode 100644
index ac8712e..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java
+++ /dev/null
@@ -1,494 +0,0 @@
-/*
- * Copyright (C) 2008 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.layoutlib.bridge.android;
-
-
-import com.android.ide.common.rendering.api.ILayoutPullParser;
-import com.android.layoutlib.bridge.impl.ParserFactory;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.content.res.XmlResourceParser;
-import android.util.AttributeSet;
-import android.util.BridgeXmlPullAttributes;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-
-/**
- * {@link BridgeXmlBlockParser} reimplements most of android.xml.XmlBlock.Parser.
- * It delegates to both an instance of {@link XmlPullParser} and an instance of
- * XmlPullAttributes (for the {@link AttributeSet} part).
- */
-public class BridgeXmlBlockParser implements XmlResourceParser {
-
- private final XmlPullParser mParser;
- private final BridgeXmlPullAttributes mAttrib;
- private final BridgeContext mContext;
- private final boolean mPlatformFile;
-
- private boolean mStarted = false;
- private int mEventType = START_DOCUMENT;
-
- private boolean mPopped = true; // default to true in case it's not pushed.
-
- /**
- * Builds a {@link BridgeXmlBlockParser}.
- * @param parser The XmlPullParser to get the content from.
- * @param context the Context.
- * @param platformFile Indicates whether the the file is a platform file or not.
- */
- public BridgeXmlBlockParser(XmlPullParser parser, BridgeContext context, boolean platformFile) {
- if (ParserFactory.LOG_PARSER) {
- System.out.println("CRTE " + parser.toString());
- }
-
- mParser = parser;
- mContext = context;
- mPlatformFile = platformFile;
- mAttrib = new BridgeXmlPullAttributes(parser, context, mPlatformFile);
-
- if (mContext != null) {
- mContext.pushParser(this);
- mPopped = false;
- }
- }
-
- public XmlPullParser getParser() {
- return mParser;
- }
-
- public boolean isPlatformFile() {
- return mPlatformFile;
- }
-
- public Object getViewCookie() {
- if (mParser instanceof ILayoutPullParser) {
- return ((ILayoutPullParser)mParser).getViewCookie();
- }
-
- return null;
- }
-
- public void ensurePopped() {
- if (mContext != null && mPopped == false) {
- mContext.popParser();
- mPopped = true;
- }
- }
-
- // ------- XmlResourceParser implementation
-
- @Override
- public void setFeature(String name, boolean state)
- throws XmlPullParserException {
- if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) {
- return;
- }
- if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) {
- return;
- }
- throw new XmlPullParserException("Unsupported feature: " + name);
- }
-
- @Override
- public boolean getFeature(String name) {
- if (FEATURE_PROCESS_NAMESPACES.equals(name)) {
- return true;
- }
- if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) {
- return true;
- }
- return false;
- }
-
- @Override
- public void setProperty(String name, Object value) throws XmlPullParserException {
- throw new XmlPullParserException("setProperty() not supported");
- }
-
- @Override
- public Object getProperty(String name) {
- return null;
- }
-
- @Override
- public void setInput(Reader in) throws XmlPullParserException {
- mParser.setInput(in);
- }
-
- @Override
- public void setInput(InputStream inputStream, String inputEncoding)
- throws XmlPullParserException {
- mParser.setInput(inputStream, inputEncoding);
- }
-
- @Override
- public void defineEntityReplacementText(String entityName,
- String replacementText) throws XmlPullParserException {
- throw new XmlPullParserException(
- "defineEntityReplacementText() not supported");
- }
-
- @Override
- public String getNamespacePrefix(int pos) throws XmlPullParserException {
- throw new XmlPullParserException("getNamespacePrefix() not supported");
- }
-
- @Override
- public String getInputEncoding() {
- return null;
- }
-
- @Override
- public String getNamespace(String prefix) {
- throw new RuntimeException("getNamespace() not supported");
- }
-
- @Override
- public int getNamespaceCount(int depth) throws XmlPullParserException {
- throw new XmlPullParserException("getNamespaceCount() not supported");
- }
-
- @Override
- public String getPositionDescription() {
- return "Binary XML file line #" + getLineNumber();
- }
-
- @Override
- public String getNamespaceUri(int pos) throws XmlPullParserException {
- throw new XmlPullParserException("getNamespaceUri() not supported");
- }
-
- @Override
- public int getColumnNumber() {
- return -1;
- }
-
- @Override
- public int getDepth() {
- return mParser.getDepth();
- }
-
- @Override
- public String getText() {
- return mParser.getText();
- }
-
- @Override
- public int getLineNumber() {
- return mParser.getLineNumber();
- }
-
- @Override
- public int getEventType() {
- return mEventType;
- }
-
- @Override
- public boolean isWhitespace() throws XmlPullParserException {
- // Original comment: whitespace was stripped by aapt.
- return mParser.isWhitespace();
- }
-
- @Override
- public String getPrefix() {
- throw new RuntimeException("getPrefix not supported");
- }
-
- @Override
- public char[] getTextCharacters(int[] holderForStartAndLength) {
- String txt = getText();
- char[] chars = null;
- if (txt != null) {
- holderForStartAndLength[0] = 0;
- holderForStartAndLength[1] = txt.length();
- chars = new char[txt.length()];
- txt.getChars(0, txt.length(), chars, 0);
- }
- return chars;
- }
-
- @Override
- public String getNamespace() {
- return mParser.getNamespace();
- }
-
- @Override
- public String getName() {
- return mParser.getName();
- }
-
- @Override
- public String getAttributeNamespace(int index) {
- return mParser.getAttributeNamespace(index);
- }
-
- @Override
- public String getAttributeName(int index) {
- return mParser.getAttributeName(index);
- }
-
- @Override
- public String getAttributePrefix(int index) {
- throw new RuntimeException("getAttributePrefix not supported");
- }
-
- @Override
- public boolean isEmptyElementTag() {
- // XXX Need to detect this.
- return false;
- }
-
- @Override
- public int getAttributeCount() {
- return mParser.getAttributeCount();
- }
-
- @Override
- public String getAttributeValue(int index) {
- return mParser.getAttributeValue(index);
- }
-
- @Override
- public String getAttributeType(int index) {
- return "CDATA";
- }
-
- @Override
- public boolean isAttributeDefault(int index) {
- return false;
- }
-
- @Override
- public int nextToken() throws XmlPullParserException, IOException {
- return next();
- }
-
- @Override
- public String getAttributeValue(String namespace, String name) {
- return mParser.getAttributeValue(namespace, name);
- }
-
- @Override
- public int next() throws XmlPullParserException, IOException {
- if (!mStarted) {
- mStarted = true;
-
- if (ParserFactory.LOG_PARSER) {
- System.out.println("STRT " + mParser.toString());
- }
-
- return START_DOCUMENT;
- }
-
- int ev = mParser.next();
-
- if (ParserFactory.LOG_PARSER) {
- System.out.println("NEXT " + mParser.toString() + " " +
- eventTypeToString(mEventType) + " -> " + eventTypeToString(ev));
- }
-
- if (ev == END_TAG && mParser.getDepth() == 1) {
- // done with parser remove it from the context stack.
- ensurePopped();
-
- if (ParserFactory.LOG_PARSER) {
- System.out.println("");
- }
- }
-
- mEventType = ev;
- return ev;
- }
-
- public static String eventTypeToString(int eventType) {
- switch (eventType) {
- case START_DOCUMENT:
- return "START_DOC";
- case END_DOCUMENT:
- return "END_DOC";
- case START_TAG:
- return "START_TAG";
- case END_TAG:
- return "END_TAG";
- case TEXT:
- return "TEXT";
- case CDSECT:
- return "CDSECT";
- case ENTITY_REF:
- return "ENTITY_REF";
- case IGNORABLE_WHITESPACE:
- return "IGNORABLE_WHITESPACE";
- case PROCESSING_INSTRUCTION:
- return "PROCESSING_INSTRUCTION";
- case COMMENT:
- return "COMMENT";
- case DOCDECL:
- return "DOCDECL";
- }
-
- return "????";
- }
-
- @Override
- public void require(int type, String namespace, String name)
- throws XmlPullParserException {
- if (type != getEventType()
- || (namespace != null && !namespace.equals(getNamespace()))
- || (name != null && !name.equals(getName())))
- throw new XmlPullParserException("expected " + TYPES[type]
- + getPositionDescription());
- }
-
- @Override
- public String nextText() throws XmlPullParserException, IOException {
- if (getEventType() != START_TAG) {
- throw new XmlPullParserException(getPositionDescription()
- + ": parser must be on START_TAG to read next text", this,
- null);
- }
- int eventType = next();
- if (eventType == TEXT) {
- String result = getText();
- eventType = next();
- if (eventType != END_TAG) {
- throw new XmlPullParserException(
- getPositionDescription()
- + ": event TEXT it must be immediately followed by END_TAG",
- this, null);
- }
- return result;
- } else if (eventType == END_TAG) {
- return "";
- } else {
- throw new XmlPullParserException(getPositionDescription()
- + ": parser must be on START_TAG or TEXT to read text",
- this, null);
- }
- }
-
- @Override
- public int nextTag() throws XmlPullParserException, IOException {
- int eventType = next();
- if (eventType == TEXT && isWhitespace()) { // skip whitespace
- eventType = next();
- }
- if (eventType != START_TAG && eventType != END_TAG) {
- throw new XmlPullParserException(getPositionDescription()
- + ": expected start or end tag", this, null);
- }
- return eventType;
- }
-
- // AttributeSet implementation
-
-
- @Override
- public void close() {
- // pass
- }
-
- @Override
- public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
- return mAttrib.getAttributeBooleanValue(index, defaultValue);
- }
-
- @Override
- public boolean getAttributeBooleanValue(String namespace, String attribute,
- boolean defaultValue) {
- return mAttrib.getAttributeBooleanValue(namespace, attribute, defaultValue);
- }
-
- @Override
- public float getAttributeFloatValue(int index, float defaultValue) {
- return mAttrib.getAttributeFloatValue(index, defaultValue);
- }
-
- @Override
- public float getAttributeFloatValue(String namespace, String attribute, float defaultValue) {
- return mAttrib.getAttributeFloatValue(namespace, attribute, defaultValue);
- }
-
- @Override
- public int getAttributeIntValue(int index, int defaultValue) {
- return mAttrib.getAttributeIntValue(index, defaultValue);
- }
-
- @Override
- public int getAttributeIntValue(String namespace, String attribute, int defaultValue) {
- return mAttrib.getAttributeIntValue(namespace, attribute, defaultValue);
- }
-
- @Override
- public int getAttributeListValue(int index, String[] options, int defaultValue) {
- return mAttrib.getAttributeListValue(index, options, defaultValue);
- }
-
- @Override
- public int getAttributeListValue(String namespace, String attribute,
- String[] options, int defaultValue) {
- return mAttrib.getAttributeListValue(namespace, attribute, options, defaultValue);
- }
-
- @Override
- public int getAttributeNameResource(int index) {
- return mAttrib.getAttributeNameResource(index);
- }
-
- @Override
- public int getAttributeResourceValue(int index, int defaultValue) {
- return mAttrib.getAttributeResourceValue(index, defaultValue);
- }
-
- @Override
- public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
- return mAttrib.getAttributeResourceValue(namespace, attribute, defaultValue);
- }
-
- @Override
- public int getAttributeUnsignedIntValue(int index, int defaultValue) {
- return mAttrib.getAttributeUnsignedIntValue(index, defaultValue);
- }
-
- @Override
- public int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue) {
- return mAttrib.getAttributeUnsignedIntValue(namespace, attribute, defaultValue);
- }
-
- @Override
- public String getClassAttribute() {
- return mAttrib.getClassAttribute();
- }
-
- @Override
- public String getIdAttribute() {
- return mAttrib.getIdAttribute();
- }
-
- @Override
- public int getIdAttributeResourceValue(int defaultValue) {
- return mAttrib.getIdAttributeResourceValue(defaultValue);
- }
-
- @Override
- public int getStyleAttribute() {
- return mAttrib.getStyleAttribute();
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
deleted file mode 100644
index 051de90..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2014 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.layoutlib.bridge.android;
-
-import com.android.ide.common.rendering.api.LayoutlibCallback;
-import com.android.ide.common.rendering.api.RenderParams;
-import com.android.ide.common.rendering.api.SessionParams.Key;
-
-/**
- * This contains all known keys for the {@link RenderParams#getFlag(Key)}.
- * <p/>
- * The IDE has its own copy of this class which may be newer or older than this one.
- * <p/>
- * Constants should never be modified or removed from this class.
- */
-public final class RenderParamsFlags {
-
- public static final Key<String> FLAG_KEY_ROOT_TAG =
- new Key<String>("rootTag", String.class);
- public static final Key<Boolean> FLAG_KEY_DISABLE_BITMAP_CACHING =
- new Key<Boolean>("disableBitmapCaching", Boolean.class);
- public static final Key<Boolean> FLAG_KEY_RENDER_ALL_DRAWABLE_STATES =
- new Key<Boolean>("renderAllDrawableStates", Boolean.class);
- /**
- * To tell LayoutLib that the IDE supports RecyclerView.
- * <p/>
- * Default is false.
- */
- public static final Key<Boolean> FLAG_KEY_RECYCLER_VIEW_SUPPORT =
- new Key<Boolean>("recyclerViewSupport", Boolean.class);
- /**
- * The application package name. Used via {@link LayoutlibCallback#getFlag(Key)}
- */
- public static final Key<String> FLAG_KEY_APPLICATION_PACKAGE =
- new Key<String>("applicationPackage", String.class);
- /**
- * To tell LayoutLib that IDE supports providing XML Parser for a file (useful for getting in
- * memory contents of the file). Used via {@link LayoutlibCallback#getFlag(Key)}
- */
- public static final Key<Boolean> FLAG_KEY_XML_FILE_PARSER_SUPPORT =
- new Key<Boolean>("xmlFileParser", Boolean.class);
- /**
- * To tell LayoutLib to not render when creating a new session. This allows controlling when the first
- * layout rendering will happen.
- */
- public static final Key<Boolean> FLAG_DO_NOT_RENDER_ON_CREATE =
- new Key<Boolean>("doNotRenderOnCreate", Boolean.class);
-
- // Disallow instances.
- private RenderParamsFlags() {}
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/graphics/NopCanvas.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/graphics/NopCanvas.java
deleted file mode 100644
index 131aa17..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/graphics/NopCanvas.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.android.graphics;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.NinePatch;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Picture;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-
-/**
- * Canvas implementation that does not do any rendering
- */
-public class NopCanvas extends Canvas {
- private boolean mIsInitialized = false;
-
- public NopCanvas() {
- super();
- mIsInitialized = true;
- }
-
- @Override
- public boolean isHardwareAccelerated() {
- // We return true the first time so there are no allocations for the software renderer in
- // the constructor
- return !mIsInitialized;
- }
-
- @Override
- public int save() {
- return 0;
- }
-
- @Override
- public int save(int saveFlags) {
- return 0;
- }
-
- @Override
- public int saveLayer(RectF bounds, Paint paint, int saveFlags) {
- return 0;
- }
-
- @Override
- public int saveLayer(RectF bounds, Paint paint) {
- return 0;
- }
-
- @Override
- public int saveLayer(float left, float top, float right, float bottom, Paint paint,
- int saveFlags) {
- return 0;
- }
-
- @Override
- public int saveLayer(float left, float top, float right, float bottom, Paint paint) {
- return 0;
- }
-
- @Override
- public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags) {
- return 0;
- }
-
- @Override
- public int saveLayerAlpha(RectF bounds, int alpha) {
- return 0;
- }
-
- @Override
- public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha,
- int saveFlags) {
- return 0;
- }
-
- @Override
- public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha) {
- return 0;
- }
-
- @Override
- public void restore() {
- }
-
- @Override
- public int getSaveCount() {
- return 0;
- }
-
- @Override
- public void restoreToCount(int saveCount) {
- }
-
- @Override
- public void drawRGB(int r, int g, int b) {
- }
-
- @Override
- public void drawARGB(int a, int r, int g, int b) {
- }
-
- @Override
- public void drawColor(int color) {
- }
-
- @Override
- public void drawColor(int color, Mode mode) {
- }
-
- @Override
- public void drawPaint(Paint paint) {
- }
-
- @Override
- public void drawPoints(float[] pts, int offset, int count, Paint paint) {
- }
-
- @Override
- public void drawPoints(float[] pts, Paint paint) {
- }
-
- @Override
- public void drawPoint(float x, float y, Paint paint) {
- }
-
- @Override
- public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) {
- }
-
- @Override
- public void drawLines(float[] pts, int offset, int count, Paint paint) {
- }
-
- @Override
- public void drawLines(float[] pts, Paint paint) {
- }
-
- @Override
- public void drawRect(RectF rect, Paint paint) {
- }
-
- @Override
- public void drawRect(Rect r, Paint paint) {
- }
-
- @Override
- public void drawRect(float left, float top, float right, float bottom, Paint paint) {
- }
-
- @Override
- public void drawOval(RectF oval, Paint paint) {
- }
-
- @Override
- public void drawOval(float left, float top, float right, float bottom, Paint paint) {
- }
-
- @Override
- public void drawCircle(float cx, float cy, float radius, Paint paint) {
- }
-
- @Override
- public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,
- Paint paint) {
- }
-
- @Override
- public void drawArc(float left, float top, float right, float bottom, float startAngle,
- float sweepAngle, boolean useCenter, Paint paint) {
- }
-
- @Override
- public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) {
- }
-
- @Override
- public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
- Paint paint) {
- }
-
- @Override
- public void drawPath(Path path, Paint paint) {
- }
-
- @Override
- protected void throwIfCannotDraw(Bitmap bitmap) {
- }
-
- @Override
- public void drawPatch(NinePatch patch, Rect dst, Paint paint) {
- }
-
- @Override
- public void drawPatch(NinePatch patch, RectF dst, Paint paint) {
- }
-
- @Override
- public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
- }
-
- @Override
- public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) {
- }
-
- @Override
- public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
- }
-
- @Override
- public void drawBitmap(int[] colors, int offset, int stride, float x, float y, int width,
- int height, boolean hasAlpha, Paint paint) {
- }
-
- @Override
- public void drawBitmap(int[] colors, int offset, int stride, int x, int y, int width,
- int height, boolean hasAlpha, Paint paint) {
- }
-
- @Override
- public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {
- }
-
- @Override
- public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts,
- int vertOffset, int[] colors, int colorOffset, Paint paint) {
- }
-
- @Override
- public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset,
- float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices,
- int indexOffset, int indexCount, Paint paint) {
- }
-
- @Override
- public void drawText(char[] text, int index, int count, float x, float y, Paint paint) {
- }
-
- @Override
- public void drawText(String text, float x, float y, Paint paint) {
- }
-
- @Override
- public void drawText(String text, int start, int end, float x, float y, Paint paint) {
- }
-
- @Override
- public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
- }
-
- @Override
- public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount,
- float x, float y, boolean isRtl, Paint paint) {
- }
-
- @Override
- public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd,
- float x, float y, boolean isRtl, Paint paint) {
- }
-
- @Override
- public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) {
- }
-
- @Override
- public void drawPosText(String text, float[] pos, Paint paint) {
- }
-
- @Override
- public void drawTextOnPath(char[] text, int index, int count, Path path, float hOffset,
- float vOffset, Paint paint) {
- }
-
- @Override
- public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) {
- }
-
- @Override
- public void drawPicture(Picture picture) {
- }
-
- @Override
- public void drawPicture(Picture picture, RectF dst) {
- }
-
- @Override
- public void drawPicture(Picture picture, Rect dst) {
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/DesignLibUtil.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/DesignLibUtil.java
deleted file mode 100644
index aa873a6..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/DesignLibUtil.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2015 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.layoutlib.bridge.android.support;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.view.View;
-
-import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod;
-import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke;
-
-/**
- * Utility class for working with the design support lib.
- */
-public class DesignLibUtil {
-
- private static final String PKG_PREFIX = "android.support.design.widget.";
- public static final String CN_COORDINATOR_LAYOUT = PKG_PREFIX + "CoordinatorLayout";
- public static final String CN_APPBAR_LAYOUT = PKG_PREFIX + "AppBarLayout";
- public static final String CN_COLLAPSING_TOOLBAR_LAYOUT =
- PKG_PREFIX + "CollapsingToolbarLayout";
- public static final String CN_TOOLBAR = "android.support.v7.widget.Toolbar";
- public static final int SCROLL_AXIS_VERTICAL = 1 << 1;
-
- /**
- * Tries to set the title of a view. This is used to set the title in a
- * CollapsingToolbarLayout.
- * <p/>
- * Any exceptions thrown during the process are logged in {@link Bridge#getLog()}
- */
- public static void setTitle(@NonNull View view, @Nullable String title) {
- if (title == null) {
- return;
- }
- try {
- invoke(getMethod(view.getClass(), "setTitle", CharSequence.class), view, title);
- } catch (ReflectionException e) {
- Bridge.getLog().warning(LayoutLog.TAG_INFO,
- "Error occurred while trying to set title.", e);
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/DrawerLayoutUtil.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/DrawerLayoutUtil.java
deleted file mode 100644
index 40d3811..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/DrawerLayoutUtil.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2015 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.layoutlib.bridge.android.support;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException;
-
-import android.annotation.Nullable;
-import android.view.View;
-
-import static android.view.Gravity.END;
-import static android.view.Gravity.LEFT;
-import static android.view.Gravity.RIGHT;
-import static android.view.Gravity.START;
-import static com.android.layoutlib.bridge.util.ReflectionUtils.getCause;
-import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod;
-import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke;
-
-public class DrawerLayoutUtil {
-
- public static final String CN_DRAWER_LAYOUT = "android.support.v4.widget.DrawerLayout";
-
- public static void openDrawer(View drawerLayout, @Nullable String drawerGravity) {
- int gravity = -1;
- if ("left".equals(drawerGravity)) {
- gravity = LEFT;
- } else if ("right".equals(drawerGravity)) {
- gravity = RIGHT;
- } else if ("start".equals(drawerGravity)) {
- gravity = START;
- } else if ("end".equals(drawerGravity)) {
- gravity = END;
- }
- if (gravity > 0) {
- openDrawer(drawerLayout, gravity);
- }
- }
-
- private static void openDrawer(View drawerLayout, int gravity) {
- try {
- invoke(getMethod(drawerLayout.getClass(), "openDrawer", int.class), drawerLayout,
- gravity);
- } catch (ReflectionException e) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Unable to open navigation drawer",
- getCause(e), null);
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java
deleted file mode 100644
index ab27819..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2015 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.layoutlib.bridge.android.support;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.LayoutlibCallback;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.android.RenderParamsFlags;
-import com.android.layoutlib.bridge.util.ReflectionUtils;
-import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.view.View;
-
-import static com.android.layoutlib.bridge.util.ReflectionUtils.getCause;
-import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod;
-import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke;
-
-/**
- * Utility class for working with android.support.v7.widget.RecyclerView
- */
-public class RecyclerViewUtil {
-
- private static final String RV_PKG_PREFIX = "android.support.v7.widget.";
- public static final String CN_RECYCLER_VIEW = RV_PKG_PREFIX + "RecyclerView";
- private static final String CN_LAYOUT_MANAGER = CN_RECYCLER_VIEW + "$LayoutManager";
- private static final String CN_ADAPTER = CN_RECYCLER_VIEW + "$Adapter";
-
- // LinearLayoutManager related constants.
- private static final String CN_LINEAR_LAYOUT_MANAGER = RV_PKG_PREFIX + "LinearLayoutManager";
- private static final Class<?>[] LLM_CONSTRUCTOR_SIGNATURE = new Class<?>[]{Context.class};
-
- /**
- * Tries to create an Adapter ({@code android.support.v7.widget.RecyclerView.Adapter} and a
- * LayoutManager {@code RecyclerView.LayoutManager} and assign these to the {@code RecyclerView}
- * that is passed.
- * <p/>
- * Any exceptions thrown during the process are logged in {@link Bridge#getLog()}
- */
- public static void setAdapter(@NonNull View recyclerView, @NonNull BridgeContext context,
- @NonNull LayoutlibCallback layoutlibCallback, int adapterLayout) {
- try {
- setLayoutManager(recyclerView, context, layoutlibCallback);
- Object adapter = createAdapter(layoutlibCallback);
- if (adapter != null) {
- setProperty(recyclerView, CN_ADAPTER, adapter, "setAdapter");
- setProperty(adapter, int.class, adapterLayout, "setLayoutId");
- }
- } catch (ReflectionException e) {
- Throwable cause = getCause(e);
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Error occurred while trying to setup RecyclerView.", cause, null);
- }
- }
-
- private static void setLayoutManager(@NonNull View recyclerView, @NonNull BridgeContext context,
- @NonNull LayoutlibCallback callback) throws ReflectionException {
- if (getLayoutManager(recyclerView) == null) {
- // Only set the layout manager if not already set by the recycler view.
- Object layoutManager = createLayoutManager(context, callback);
- if (layoutManager != null) {
- setProperty(recyclerView, CN_LAYOUT_MANAGER, layoutManager, "setLayoutManager");
- }
- }
- }
-
- /** Creates a LinearLayoutManager using the provided context. */
- @Nullable
- private static Object createLayoutManager(@NonNull Context context,
- @NonNull LayoutlibCallback callback)
- throws ReflectionException {
- try {
- return callback.loadView(CN_LINEAR_LAYOUT_MANAGER, LLM_CONSTRUCTOR_SIGNATURE,
- new Object[]{context});
- } catch (Exception e) {
- throw new ReflectionException(e);
- }
- }
-
- @Nullable
- private static Object getLayoutManager(View recyclerView) throws ReflectionException {
- return invoke(getMethod(recyclerView.getClass(), "getLayoutManager"), recyclerView);
- }
-
- @Nullable
- private static Object createAdapter(@NonNull LayoutlibCallback layoutlibCallback)
- throws ReflectionException {
- Boolean ideSupport =
- layoutlibCallback.getFlag(RenderParamsFlags.FLAG_KEY_RECYCLER_VIEW_SUPPORT);
- if (ideSupport != Boolean.TRUE) {
- return null;
- }
- try {
- return layoutlibCallback.loadClass(CN_ADAPTER, new Class[0], new Object[0]);
- } catch (Exception e) {
- throw new ReflectionException(e);
- }
- }
-
- private static void setProperty(@NonNull Object object, @NonNull String propertyClassName,
- @NonNull Object propertyValue, @NonNull String propertySetter)
- throws ReflectionException {
- Class<?> propertyClass = ReflectionUtils.getClassInstance(propertyValue, propertyClassName);
- setProperty(object, propertyClass, propertyValue, propertySetter);
- }
-
- private static void setProperty(@NonNull Object object, @NonNull Class<?> propertyClass,
- @Nullable Object propertyValue, @NonNull String propertySetter)
- throws ReflectionException {
- invoke(getMethod(object.getClass(), propertySetter, propertyClass), object, propertyValue);
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java
deleted file mode 100644
index 6ad9efc..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.android.support;
-
-import com.android.ide.common.rendering.api.LayoutlibCallback;
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.rendering.api.StyleResourceValue;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
-import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.view.ContextThemeWrapper;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-import android.widget.LinearLayout.LayoutParams;
-import android.widget.ScrollView;
-
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-
-import static com.android.layoutlib.bridge.util.ReflectionUtils.getAccessibleMethod;
-import static com.android.layoutlib.bridge.util.ReflectionUtils.getClassInstance;
-import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod;
-import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke;
-
-/**
- * Class with utility methods to instantiate Preferences provided by the support library.
- * This class uses reflection to access the support preference objects so it heavily depends on
- * the API being stable.
- */
-public class SupportPreferencesUtil {
- private static final String PREFERENCE_PKG = "android.support.v7.preference";
- private static final String PREFERENCE_MANAGER = PREFERENCE_PKG + ".PreferenceManager";
- private static final String PREFERENCE_GROUP = PREFERENCE_PKG + ".PreferenceGroup";
- private static final String PREFERENCE_GROUP_ADAPTER =
- PREFERENCE_PKG + ".PreferenceGroupAdapter";
- private static final String PREFERENCE_INFLATER = PREFERENCE_PKG + ".PreferenceInflater";
-
- private SupportPreferencesUtil() {
- }
-
- @NonNull
- private static Object instantiateClass(@NonNull LayoutlibCallback callback,
- @NonNull String className, @Nullable Class[] constructorSignature,
- @Nullable Object[] constructorArgs) throws ReflectionException {
- try {
- Object instance = callback.loadClass(className, constructorSignature, constructorArgs);
- if (instance == null) {
- throw new ClassNotFoundException(className + " class not found");
- }
- return instance;
- } catch (ClassNotFoundException e) {
- throw new ReflectionException(e);
- }
- }
-
- @NonNull
- private static Object createPreferenceGroupAdapter(@NonNull LayoutlibCallback callback,
- @NonNull Object preferenceScreen) throws ReflectionException {
- Class<?> preferenceGroupClass = getClassInstance(preferenceScreen, PREFERENCE_GROUP);
-
- return instantiateClass(callback, PREFERENCE_GROUP_ADAPTER,
- new Class[]{preferenceGroupClass}, new Object[]{preferenceScreen});
- }
-
- @NonNull
- private static Object createInflatedPreference(@NonNull LayoutlibCallback callback,
- @NonNull Context context, @NonNull XmlPullParser parser, @NonNull Object preferenceScreen,
- @NonNull Object preferenceManager) throws ReflectionException {
- Class<?> preferenceGroupClass = getClassInstance(preferenceScreen, PREFERENCE_GROUP);
- Object preferenceInflater = instantiateClass(callback, PREFERENCE_INFLATER,
- new Class[]{Context.class, preferenceManager.getClass()},
- new Object[]{context, preferenceManager});
- Object inflatedPreference =
- invoke(getAccessibleMethod(preferenceInflater.getClass(), "inflate",
- XmlPullParser.class, preferenceGroupClass), preferenceInflater, parser,
- null);
-
- if (inflatedPreference == null) {
- throw new ReflectionException("inflate method returned null");
- }
-
- return inflatedPreference;
- }
-
- /**
- * Returns a themed wrapper context of {@link BridgeContext} with the theme specified in
- * ?attr/preferenceTheme applied to it.
- */
- @Nullable
- private static Context getThemedContext(@NonNull BridgeContext bridgeContext) {
- RenderResources resources = bridgeContext.getRenderResources();
- ResourceValue preferenceTheme = resources.findItemInTheme("preferenceTheme", false);
-
- if (preferenceTheme != null) {
- // resolve it, if needed.
- preferenceTheme = resources.resolveResValue(preferenceTheme);
- }
- if (preferenceTheme instanceof StyleResourceValue) {
- int styleId = bridgeContext.getDynamicIdByStyle(((StyleResourceValue) preferenceTheme));
- if (styleId != 0) {
- return new ContextThemeWrapper(bridgeContext, styleId);
- }
- }
-
- return null;
- }
-
- /**
- * Returns a {@link LinearLayout} containing all the UI widgets representing the preferences
- * passed in the group adapter.
- */
- @Nullable
- private static LinearLayout setUpPreferencesListView(@NonNull BridgeContext bridgeContext,
- @NonNull Context themedContext, @NonNull ArrayList<Object> viewCookie,
- @NonNull Object preferenceGroupAdapter) throws ReflectionException {
- // Setup the LinearLayout that will contain the preferences
- LinearLayout listView = new LinearLayout(themedContext);
- listView.setOrientation(LinearLayout.VERTICAL);
- listView.setLayoutParams(
- new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-
- if (!viewCookie.isEmpty()) {
- bridgeContext.addViewKey(listView, viewCookie.get(0));
- }
-
- // Get all the preferences and add them to the LinearLayout
- Integer preferencesCount =
- (Integer) invoke(getMethod(preferenceGroupAdapter.getClass(), "getItemCount"),
- preferenceGroupAdapter);
- if (preferencesCount == null) {
- return listView;
- }
-
- Method getItemId = getMethod(preferenceGroupAdapter.getClass(), "getItemId", int.class);
- Method getItemViewType =
- getMethod(preferenceGroupAdapter.getClass(), "getItemViewType", int.class);
- Method onCreateViewHolder =
- getMethod(preferenceGroupAdapter.getClass(), "onCreateViewHolder", ViewGroup.class,
- int.class);
- for (int i = 0; i < preferencesCount; i++) {
- Long id = (Long) invoke(getItemId, preferenceGroupAdapter, i);
- if (id == null) {
- continue;
- }
-
- // Get the type of the preference layout and bind it to a newly created view holder
- Integer type = (Integer) invoke(getItemViewType, preferenceGroupAdapter, i);
- Object viewHolder =
- invoke(onCreateViewHolder, preferenceGroupAdapter, listView, type);
- if (viewHolder == null) {
- continue;
- }
- invoke(getMethod(preferenceGroupAdapter.getClass(), "onBindViewHolder",
- viewHolder.getClass(), int.class), preferenceGroupAdapter, viewHolder, i);
-
- try {
- // Get the view from the view holder and add it to our layout
- View itemView =
- (View) viewHolder.getClass().getField("itemView").get(viewHolder);
-
- int arrayPosition = id.intValue() - 1; // IDs are 1 based
- if (arrayPosition >= 0 && arrayPosition < viewCookie.size()) {
- bridgeContext.addViewKey(itemView, viewCookie.get(arrayPosition));
- }
- listView.addView(itemView);
- } catch (IllegalAccessException | NoSuchFieldException ignored) {
- }
- }
-
- return listView;
- }
-
- /**
- * Inflates a preferences layout using the support library. If the support library is not
- * available, this method will return null without advancing the parsers.
- */
- @Nullable
- public static View inflatePreference(@NonNull BridgeContext bridgeContext,
- @NonNull XmlPullParser parser, @Nullable ViewGroup root) {
- try {
- LayoutlibCallback callback = bridgeContext.getLayoutlibCallback();
-
- Context context = getThemedContext(bridgeContext);
- if (context == null) {
- // Probably we couldn't find the "preferenceTheme" in the theme
- return null;
- }
-
- // Create PreferenceManager
- Object preferenceManager =
- instantiateClass(callback, PREFERENCE_MANAGER, new Class[]{Context.class},
- new Object[]{context});
-
- // From this moment on, we can assume that we found the support library and that
- // nothing should fail
-
- // Create PreferenceScreen
- Object preferenceScreen =
- invoke(getMethod(preferenceManager.getClass(), "createPreferenceScreen",
- Context.class), preferenceManager, context);
- if (preferenceScreen == null) {
- return null;
- }
-
- // Setup a parser that stores the list of cookies in the same order as the preferences
- // are inflated. That way we can later reconstruct the list using the preference id
- // since they are sequential and start in 1.
- ArrayList<Object> viewCookie = new ArrayList<>();
- if (parser instanceof BridgeXmlBlockParser) {
- // Setup a parser that stores the XmlTag
- parser = new BridgeXmlBlockParser(parser, null, false) {
- @Override
- public Object getViewCookie() {
- return ((BridgeXmlBlockParser) getParser()).getViewCookie();
- }
-
- @Override
- public int next() throws XmlPullParserException, IOException {
- int ev = super.next();
- if (ev == XmlPullParser.START_TAG) {
- viewCookie.add(this.getViewCookie());
- }
-
- return ev;
- }
- };
- }
-
- // Create the PreferenceInflater
- Object inflatedPreference =
- createInflatedPreference(callback, context, parser, preferenceScreen,
- preferenceManager);
-
- // Setup the RecyclerView (set adapter and layout manager)
- Object preferenceGroupAdapter =
- createPreferenceGroupAdapter(callback, inflatedPreference);
-
- // Instead of just setting the group adapter as adapter for a RecyclerView, we manually
- // get all the items and add them to a LinearLayout. This allows us to set the view
- // cookies so the preferences are correctly linked to their XML.
- LinearLayout listView = setUpPreferencesListView(bridgeContext, context, viewCookie,
- preferenceGroupAdapter);
-
- ScrollView scrollView = new ScrollView(context);
- scrollView.setLayoutParams(
- new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
- scrollView.addView(listView);
-
- if (root != null) {
- root.addView(scrollView);
- }
-
- return scrollView;
- } catch (ReflectionException e) {
- return null;
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
deleted file mode 100644
index d7d16cf..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2012 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.layoutlib.bridge.android.view;
-
-import android.util.DisplayMetrics;
-import android.view.Display;
-import android.view.Display.Mode;
-import android.view.DisplayAdjustments;
-import android.view.DisplayInfo;
-import android.view.View;
-import android.view.WindowManager;
-
-public class WindowManagerImpl implements WindowManager {
-
- private final DisplayMetrics mMetrics;
- private final Display mDisplay;
-
- public WindowManagerImpl(DisplayMetrics metrics) {
- mMetrics = metrics;
-
- DisplayInfo info = new DisplayInfo();
- info.logicalHeight = mMetrics.heightPixels;
- info.logicalWidth = mMetrics.widthPixels;
- info.supportedModes = new Mode[] {
- new Mode(0, mMetrics.widthPixels, mMetrics.heightPixels, 60f)
- };
- mDisplay = new Display(null, Display.DEFAULT_DISPLAY, info,
- DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
- }
-
- @Override
- public Display getDefaultDisplay() {
- return mDisplay;
- }
-
-
- @Override
- public void addView(View arg0, android.view.ViewGroup.LayoutParams arg1) {
- // pass
- }
-
- @Override
- public void removeView(View arg0) {
- // pass
- }
-
- @Override
- public void updateViewLayout(View arg0, android.view.ViewGroup.LayoutParams arg1) {
- // pass
- }
-
-
- @Override
- public void removeViewImmediate(View arg0) {
- // pass
- }
-
- @Override
- public void requestAppKeyboardShortcuts(
- KeyboardShortcutsReceiver receiver, int deviceId) {
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
deleted file mode 100644
index cdcf0ea..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2015 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.layoutlib.bridge.bars;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.LayoutlibCallback;
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.rendering.api.SessionParams;
-import com.android.ide.common.rendering.api.StyleResourceValue;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.impl.ResourceHelper;
-import com.android.resources.ResourceType;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.view.ContextThemeWrapper;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-
-/**
- * Assumes that the AppCompat library is present in the project's classpath and creates an
- * actionbar around it.
- */
-public class AppCompatActionBar extends BridgeActionBar {
-
- private Object mWindowDecorActionBar;
- private static final String WINDOW_ACTION_BAR_CLASS = "android.support.v7.internal.app.WindowDecorActionBar";
- // This is used on v23.1.1 and later.
- private static final String WINDOW_ACTION_BAR_CLASS_NEW = "android.support.v7.app.WindowDecorActionBar";
- private Class<?> mWindowActionBarClass;
-
- /**
- * Inflate the action bar and attach it to {@code parentView}
- */
- public AppCompatActionBar(@NonNull BridgeContext context, @NonNull SessionParams params) {
- super(context, params);
- int contentRootId = context.getProjectResourceValue(ResourceType.ID,
- "action_bar_activity_content", 0);
- View contentView = getDecorContent().findViewById(contentRootId);
- if (contentView != null) {
- assert contentView instanceof FrameLayout;
- setContentRoot((FrameLayout) contentView);
- } else {
- // Something went wrong. Create a new FrameLayout in the enclosing layout.
- FrameLayout contentRoot = new FrameLayout(context);
- setMatchParent(contentRoot);
- if (mEnclosingLayout != null) {
- mEnclosingLayout.addView(contentRoot);
- }
- setContentRoot(contentRoot);
- }
- try {
- Class[] constructorParams = {View.class};
- Object[] constructorArgs = {getDecorContent()};
- LayoutlibCallback callback = params.getLayoutlibCallback();
-
- // Check if the old action bar class is present.
- String actionBarClass = WINDOW_ACTION_BAR_CLASS;
- try {
- callback.findClass(actionBarClass);
- } catch (ClassNotFoundException expected) {
- // Failed to find the old class, use the newer one.
- actionBarClass = WINDOW_ACTION_BAR_CLASS_NEW;
- }
-
- mWindowDecorActionBar = callback.loadView(actionBarClass,
- constructorParams, constructorArgs);
- mWindowActionBarClass = mWindowDecorActionBar == null ? null :
- mWindowDecorActionBar.getClass();
- setupActionBar();
- } catch (Exception e) {
- Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
- "Failed to load AppCompat ActionBar with unknown error.", e);
- }
- }
-
- @Override
- protected ResourceValue getLayoutResource(BridgeContext context) {
- // We always assume that the app has requested the action bar.
- return context.getRenderResources().getProjectResource(ResourceType.LAYOUT,
- "abc_screen_toolbar");
- }
-
- @Override
- protected LayoutInflater getInflater(BridgeContext context) {
- // Other than the resource resolution part, the code has been taken from the support
- // library. see code from line 269 onwards in
- // https://android.googlesource.com/platform/frameworks/support/+/android-5.1.0_r1/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java
- Context themedContext = context;
- RenderResources resources = context.getRenderResources();
- ResourceValue actionBarTheme = resources.findItemInTheme("actionBarTheme", false);
- if (actionBarTheme != null) {
- // resolve it, if needed.
- actionBarTheme = resources.resolveResValue(actionBarTheme);
- }
- if (actionBarTheme instanceof StyleResourceValue) {
- int styleId = context.getDynamicIdByStyle(((StyleResourceValue) actionBarTheme));
- if (styleId != 0) {
- themedContext = new ContextThemeWrapper(context, styleId);
- }
- }
- return LayoutInflater.from(themedContext);
- }
-
- @Override
- protected void setTitle(CharSequence title) {
- if (title != null && mWindowDecorActionBar != null) {
- Method setTitle = getMethod(mWindowActionBarClass, "setTitle", CharSequence.class);
- invoke(setTitle, mWindowDecorActionBar, title);
- }
- }
-
- @Override
- protected void setSubtitle(CharSequence subtitle) {
- if (subtitle != null && mWindowDecorActionBar != null) {
- Method setSubtitle = getMethod(mWindowActionBarClass, "setSubtitle", CharSequence.class);
- invoke(setSubtitle, mWindowDecorActionBar, subtitle);
- }
- }
-
- @Override
- protected void setIcon(String icon) {
- // Do this only if the action bar doesn't already have an icon.
- if (icon != null && !icon.isEmpty() && mWindowDecorActionBar != null) {
- if (invoke(getMethod(mWindowActionBarClass, "hasIcon"), mWindowDecorActionBar)
- == Boolean.TRUE) {
- Drawable iconDrawable = getDrawable(icon, false);
- if (iconDrawable != null) {
- Method setIcon = getMethod(mWindowActionBarClass, "setIcon", Drawable.class);
- invoke(setIcon, mWindowDecorActionBar, iconDrawable);
- }
- }
- }
- }
-
- @Override
- protected void setHomeAsUp(boolean homeAsUp) {
- if (mWindowDecorActionBar != null) {
- Method setHomeAsUp = getMethod(mWindowActionBarClass,
- "setDefaultDisplayHomeAsUpEnabled", boolean.class);
- invoke(setHomeAsUp, mWindowDecorActionBar, homeAsUp);
- }
- }
-
- @Override
- public void createMenuPopup() {
- // it's hard to add menus to appcompat's actionbar, since it'll use a lot of reflection.
- // so we skip it for now.
- }
-
- @Nullable
- private static Method getMethod(Class<?> owner, String name, Class<?>... parameterTypes) {
- try {
- return owner == null ? null : owner.getMethod(name, parameterTypes);
- } catch (NoSuchMethodException e) {
- e.printStackTrace();
- }
- return null;
- }
-
- @Nullable
- private static Object invoke(Method method, Object owner, Object... args) {
- try {
- return method == null ? null : method.invoke(owner, args);
- } catch (InvocationTargetException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- return null;
- }
-
- // TODO: this is duplicated from FrameworkActionBarWrapper$WindowActionBarWrapper
- @Nullable
- private Drawable getDrawable(@NonNull String name, boolean isFramework) {
- RenderResources res = mBridgeContext.getRenderResources();
- ResourceValue value = res.findResValue(name, isFramework);
- value = res.resolveResValue(value);
- if (value != null) {
- return ResourceHelper.getDrawable(value, mBridgeContext);
- }
- return null;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java
deleted file mode 100644
index a439e7d..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2015 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.layoutlib.bridge.bars;
-
-import com.android.ide.common.rendering.api.ActionBarCallback;
-import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle;
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.rendering.api.SessionParams;
-import com.android.layoutlib.bridge.MockView;
-import com.android.layoutlib.bridge.android.BridgeContext;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.FrameLayout;
-import android.widget.RelativeLayout;
-
-/**
- * An abstraction over two implementations of the ActionBar - framework and appcompat.
- */
-public abstract class BridgeActionBar {
- // Store a reference to the context so that we don't have to cast it repeatedly.
- @NonNull protected final BridgeContext mBridgeContext;
- @NonNull protected final SessionParams mParams;
- // A Layout that contains the inflated action bar. The menu popup is added to this layout.
- @Nullable protected final ViewGroup mEnclosingLayout;
-
- private final View mDecorContent;
- private final ActionBarCallback mCallback;
-
- @SuppressWarnings("NullableProblems") // Should be initialized by subclasses.
- @NonNull private FrameLayout mContentRoot;
-
- public BridgeActionBar(@NonNull BridgeContext context, @NonNull SessionParams params) {
- mBridgeContext = context;
- mParams = params;
- mCallback = params.getLayoutlibCallback().getActionBarCallback();
- ResourceValue layoutName = getLayoutResource(context);
-
- int layoutId = 0;
- if (layoutName == null) {
- assert false : "Unable to find the layout for Action Bar.";
- }
- else {
- if (layoutName.isFramework()) {
- layoutId = context.getFrameworkResourceValue(layoutName.getResourceType(),
- layoutName.getName(), 0);
- } else {
- layoutId = context.getProjectResourceValue(layoutName.getResourceType(),
- layoutName.getName(), 0);
-
- }
- }
- if (layoutId == 0) {
- assert false : String.format("Unable to resolve attribute \"%1$s\" of type \"%2$s\"",
- layoutName.getName(), layoutName.getResourceType());
- mDecorContent = new MockView(context);
- mEnclosingLayout = null;
- }
- else {
- if (mCallback.isOverflowPopupNeeded()) {
- // Create a RelativeLayout around the action bar, to which the overflow popup may be
- // added.
- mEnclosingLayout = new RelativeLayout(mBridgeContext);
- setMatchParent(mEnclosingLayout);
- } else {
- mEnclosingLayout = null;
- }
-
- // Inflate action bar layout.
- mDecorContent = getInflater(context).inflate(layoutId, mEnclosingLayout,
- mEnclosingLayout != null);
- }
- }
-
- /**
- * Returns the Layout Resource that should be used to inflate the action bar. This layout
- * should cover the complete screen, and have a FrameLayout included, where the content will
- * be inflated.
- */
- protected abstract ResourceValue getLayoutResource(BridgeContext context);
-
- protected LayoutInflater getInflater(BridgeContext context) {
- return LayoutInflater.from(context);
- }
-
- protected void setContentRoot(@NonNull FrameLayout contentRoot) {
- mContentRoot = contentRoot;
- }
-
- @NonNull
- public FrameLayout getContentRoot() {
- return mContentRoot;
- }
-
- /**
- * Returns the view inflated. This should contain both the ActionBar and the app content in it.
- */
- protected View getDecorContent() {
- return mDecorContent;
- }
-
- /** Setup things like the title, subtitle, icon etc. */
- protected void setupActionBar() {
- setTitle();
- setSutTitle();
- setIcon();
- setHomeAsUp(mCallback.getHomeButtonStyle() == HomeButtonStyle.SHOW_HOME_AS_UP);
- }
-
- protected abstract void setTitle(CharSequence title);
- protected abstract void setSubtitle(CharSequence subtitle);
- protected abstract void setIcon(String icon);
- protected abstract void setHomeAsUp(boolean homeAsUp);
-
- private void setTitle() {
- RenderResources res = mBridgeContext.getRenderResources();
-
- String title = mParams.getAppLabel();
- ResourceValue titleValue = res.findResValue(title, false);
- if (titleValue != null && titleValue.getValue() != null) {
- setTitle(titleValue.getValue());
- } else {
- setTitle(title);
- }
- }
-
- private void setSutTitle() {
- String subTitle = mCallback.getSubTitle();
- if (subTitle != null) {
- setSubtitle(subTitle);
- }
- }
-
- private void setIcon() {
- String appIcon = mParams.getAppIcon();
- if (appIcon != null) {
- setIcon(appIcon);
- }
- }
-
- public abstract void createMenuPopup();
-
- /**
- * The root view that represents the action bar and possibly the content included in it.
- */
- public View getRootView() {
- return mEnclosingLayout == null ? mDecorContent : mEnclosingLayout;
- }
-
- public ActionBarCallback getCallBack() {
- return mCallback;
- }
-
- protected static void setMatchParent(View view) {
- view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/Config.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/Config.java
deleted file mode 100644
index 7f8d992..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/Config.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2014 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.layoutlib.bridge.bars;
-
-import android.os._Original_Build.VERSION_CODES;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import static android.os._Original_Build.VERSION_CODES.*;
-
-/**
- * Various helper methods to simulate older versions of platform.
- */
-public class Config {
-
- // each of these resource dirs must end in '/'
- private static final String GINGERBREAD_DIR = "/bars/v9/";
- private static final String JELLYBEAN_DIR = "/bars/v18/";
- private static final String KITKAT_DIR = "/bars/v19/";
- private static final String DEFAULT_RESOURCE_DIR = "/bars/v21/";
-
- private static final List<String> sDefaultResourceDir =
- Collections.singletonList(DEFAULT_RESOURCE_DIR);
-
- private static final int WHITE = 0xFFFFFFFF;
- private static final int BLACK = 0xFF000000;
-
- public static boolean showOnScreenNavBar(int platformVersion) {
- return isGreaterOrEqual(platformVersion, ICE_CREAM_SANDWICH);
- }
-
- public static int getStatusBarColor(int platformVersion) {
- // return white for froyo and earlier; black otherwise.
- return isGreaterOrEqual(platformVersion, GINGERBREAD) ? BLACK : WHITE;
- }
-
- public static List<String> getResourceDirs(int platformVersion) {
- // Special case the most used scenario.
- if (platformVersion == 0) {
- return sDefaultResourceDir;
- }
- List<String> list = new ArrayList<String>(4);
- // Gingerbread - uses custom battery and wifi icons.
- if (platformVersion <= GINGERBREAD) {
- list.add(GINGERBREAD_DIR);
- }
- // ICS - JellyBean uses custom battery, wifi.
- if (platformVersion <= JELLY_BEAN_MR2) {
- list.add(JELLYBEAN_DIR);
- }
- // KitKat - uses custom wifi and nav icons.
- if (platformVersion <= KITKAT) {
- list.add(KITKAT_DIR);
- }
- list.add(DEFAULT_RESOURCE_DIR);
-
- return Collections.unmodifiableList(list);
- }
-
- public static String getTime(int platformVersion) {
- if (isGreaterOrEqual(platformVersion, N)) {
- return "7:00";
- }
- if (platformVersion < GINGERBREAD) {
- return "2:20";
- }
- if (platformVersion < ICE_CREAM_SANDWICH) {
- return "2:30";
- }
- if (platformVersion < JELLY_BEAN) {
- return "4:00";
- }
- if (platformVersion < KITKAT) {
- return "4:30";
- }
- if (platformVersion < LOLLIPOP) {
- return "4:40";
- }
- if (platformVersion < LOLLIPOP_MR1) {
- return "5:00";
- }
- if (platformVersion < M) {
- return "5:10";
- }
- if (platformVersion < N) {
- return "6:00";
- }
- // Should never happen.
- return "4:04";
- }
-
- public static int getTimeColor(int platformVersion) {
- if (isGreaterOrEqual(platformVersion, KITKAT) ||
- platformVersion > FROYO && platformVersion < HONEYCOMB) {
- // Gingerbread and KitKat onwards.
- return WHITE;
- }
- // Black for froyo.
- if (platformVersion < GINGERBREAD) {
- return BLACK;
- } else if (platformVersion < KITKAT) {
- // Honeycomb to JB-mr2: Holo blue light.
- return 0xff33b5e5;
- }
- // Should never happen.
- return WHITE;
- }
-
- public static String getWifiIconType(int platformVersion) {
- return isGreaterOrEqual(platformVersion, LOLLIPOP) ? "xml" : "png";
- }
-
- /**
- * Compare simulated platform version and code from {@link VERSION_CODES} to check if
- * the simulated platform is greater than or equal to the version code.
- */
- public static boolean isGreaterOrEqual(int platformVersion, int code) {
- // simulated platform version = 0 means that we use the latest.
- return platformVersion == 0 || platformVersion >= code;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
deleted file mode 100644
index 2984fc0..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright (C) 2011 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.layoutlib.bridge.bars;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.rendering.api.StyleResourceValue;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
-import com.android.layoutlib.bridge.impl.ParserFactory;
-import com.android.layoutlib.bridge.impl.ResourceHelper;
-import com.android.resources.Density;
-import com.android.resources.LayoutDirection;
-import com.android.resources.ResourceType;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.annotation.NonNull;
-import android.content.res.ColorStateList;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap_Delegate;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import static android.os._Original_Build.VERSION_CODES.LOLLIPOP;
-
-/**
- * Base "bar" class for the window decor around the the edited layout.
- * This is basically an horizontal layout that loads a given layout on creation (it is read
- * through {@link Class#getResourceAsStream(String)}).
- *
- * The given layout should be a merge layout so that all the children belong to this class directly.
- *
- * It also provides a few utility methods to configure the content of the layout.
- */
-abstract class CustomBar extends LinearLayout {
-
-
- private final int mSimulatedPlatformVersion;
-
- protected abstract TextView getStyleableTextView();
-
- protected CustomBar(BridgeContext context, int orientation, String layoutPath,
- String name, int simulatedPlatformVersion) {
- super(context);
- mSimulatedPlatformVersion = simulatedPlatformVersion;
- setOrientation(orientation);
- if (orientation == LinearLayout.HORIZONTAL) {
- setGravity(Gravity.CENTER_VERTICAL);
- } else {
- setGravity(Gravity.CENTER_HORIZONTAL);
- }
-
- LayoutInflater inflater = LayoutInflater.from(mContext);
-
- XmlPullParser parser;
- try {
- parser = ParserFactory.create(getClass().getResourceAsStream(layoutPath), name);
-
- BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(parser, context, false);
-
- try {
- inflater.inflate(bridgeParser, this, true);
- } finally {
- bridgeParser.ensurePopped();
- }
- } catch (XmlPullParserException e) {
- // Should not happen as the resource is bundled with the jar, and ParserFactory should
- // have been initialized.
- assert false;
- }
- }
-
- protected void loadIcon(int index, String iconName, Density density) {
- loadIcon(index, iconName, density, false);
- }
-
- protected void loadIcon(int index, String iconName, Density density, boolean isRtl) {
- View child = getChildAt(index);
- if (child instanceof ImageView) {
- ImageView imageView = (ImageView) child;
-
- LayoutDirection dir = isRtl ? LayoutDirection.RTL : null;
- IconLoader iconLoader = new IconLoader(iconName, density, mSimulatedPlatformVersion,
- dir);
- InputStream stream = iconLoader.getIcon();
-
- if (stream != null) {
- density = iconLoader.getDensity();
- String path = iconLoader.getPath();
- // look for a cached bitmap
- Bitmap bitmap = Bridge.getCachedBitmap(path, Boolean.TRUE /*isFramework*/);
- if (bitmap == null) {
- try {
- bitmap = Bitmap_Delegate.createBitmap(stream, false /*isMutable*/, density);
- Bridge.setCachedBitmap(path, bitmap, Boolean.TRUE /*isFramework*/);
- } catch (IOException e) {
- return;
- }
- }
-
- if (bitmap != null) {
- BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(),
- bitmap);
- imageView.setImageDrawable(drawable);
- }
- }
- }
- }
-
- protected TextView setText(int index, String string, boolean reference) {
- View child = getChildAt(index);
- if (child instanceof TextView) {
- TextView textView = (TextView) child;
- setText(textView, string, reference);
- return textView;
- }
-
- return null;
- }
-
- private void setText(TextView textView, String string, boolean reference) {
- if (reference) {
- ResourceValue value = getResourceValue(string);
- if (value != null) {
- string = value.getValue();
- }
- }
- textView.setText(string);
- }
-
- protected void setStyle(String themeEntryName) {
-
- BridgeContext bridgeContext = getContext();
- RenderResources res = bridgeContext.getRenderResources();
-
- ResourceValue value = res.findItemInTheme(themeEntryName, true /*isFrameworkAttr*/);
- value = res.resolveResValue(value);
-
- if (!(value instanceof StyleResourceValue)) {
- return;
- }
-
- StyleResourceValue style = (StyleResourceValue) value;
-
- // get the background
- ResourceValue backgroundValue = res.findItemInStyle(style, "background",
- true /*isFrameworkAttr*/);
- backgroundValue = res.resolveResValue(backgroundValue);
- if (backgroundValue != null) {
- Drawable d = ResourceHelper.getDrawable(backgroundValue, bridgeContext);
- if (d != null) {
- setBackground(d);
- }
- }
-
- TextView textView = getStyleableTextView();
- if (textView != null) {
- // get the text style
- ResourceValue textStyleValue = res.findItemInStyle(style, "titleTextStyle",
- true /*isFrameworkAttr*/);
- textStyleValue = res.resolveResValue(textStyleValue);
- if (textStyleValue instanceof StyleResourceValue) {
- StyleResourceValue textStyle = (StyleResourceValue) textStyleValue;
-
- ResourceValue textSize = res.findItemInStyle(textStyle, "textSize",
- true /*isFrameworkAttr*/);
- textSize = res.resolveResValue(textSize);
-
- if (textSize != null) {
- TypedValue out = new TypedValue();
- if (ResourceHelper.parseFloatAttribute("textSize", textSize.getValue(), out,
- true /*requireUnit*/)) {
- textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- out.getDimension(bridgeContext.getResources().getDisplayMetrics()));
- }
- }
-
-
- ResourceValue textColor = res.findItemInStyle(textStyle, "textColor",
- true);
- textColor = res.resolveResValue(textColor);
- if (textColor != null) {
- ColorStateList stateList = ResourceHelper.getColorStateList(
- textColor, bridgeContext);
- if (stateList != null) {
- textView.setTextColor(stateList);
- }
- }
- }
- }
- }
-
- @Override
- public BridgeContext getContext() {
- return (BridgeContext) mContext;
- }
-
- /**
- * Find the background color for this bar from the theme attributes. Only relevant to StatusBar
- * and NavigationBar.
- * <p/>
- * Returns 0 if not found.
- *
- * @param colorAttrName the attribute name for the background color
- * @param translucentAttrName the attribute name for the translucency property of the bar.
- *
- * @throws NumberFormatException if color resolved to an invalid string.
- */
- protected int getBarColor(@NonNull String colorAttrName, @NonNull String translucentAttrName) {
- if (!Config.isGreaterOrEqual(mSimulatedPlatformVersion, LOLLIPOP)) {
- return 0;
- }
- RenderResources renderResources = getContext().getRenderResources();
- // First check if the bar is translucent.
- boolean translucent = ResourceHelper.getBooleanThemeValue(renderResources,
- translucentAttrName, true, false);
- if (translucent) {
- // Keep in sync with R.color.system_bar_background_semi_transparent from system ui.
- return 0x66000000; // 40% black.
- }
- boolean transparent = ResourceHelper.getBooleanThemeValue(renderResources,
- "windowDrawsSystemBarBackgrounds", true, false);
- if (transparent) {
- return getColor(renderResources, colorAttrName);
- }
- return 0;
- }
-
- private static int getColor(RenderResources renderResources, String attr) {
- // From ?attr/foo to @color/bar. This is most likely an ItemResourceValue.
- ResourceValue resource = renderResources.findItemInTheme(attr, true);
- // Form @color/bar to the #AARRGGBB
- resource = renderResources.resolveResValue(resource);
- if (resource != null) {
- ResourceType type = resource.getResourceType();
- if (type == null || type == ResourceType.COLOR) {
- // if no type is specified, the value may have been specified directly in the style
- // file, rather than referencing a color resource value.
- try {
- return ResourceHelper.getColor(resource.getValue());
- } catch (NumberFormatException e) {
- // Conversion failed.
- Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
- "Theme attribute @android:" + attr +
- " does not reference a color, instead is '" +
- resource.getValue() + "'.", resource);
- }
- }
- }
- return 0;
- }
-
- private ResourceValue getResourceValue(String reference) {
- RenderResources res = getContext().getRenderResources();
-
- // find the resource
- ResourceValue value = res.findResValue(reference, false);
-
- // resolve it if needed
- return res.resolveResValue(value);
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBar.java
deleted file mode 100644
index fd49c77..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBar.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2014 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.layoutlib.bridge.bars;
-
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.rendering.api.SessionParams;
-import com.android.internal.R;
-import com.android.internal.view.menu.MenuBuilder;
-import com.android.internal.view.menu.MenuItemImpl;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.impl.ResourceHelper;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
-import android.view.InflateException;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.ActionMenuPresenter;
-import android.widget.FrameLayout;
-import android.widget.ListAdapter;
-import android.widget.ListView;
-import android.widget.RelativeLayout;
-
-import java.util.ArrayList;
-
-/**
- * Creates the ActionBar as done by the framework.
- */
-public class FrameworkActionBar extends BridgeActionBar {
-
- private static final String LAYOUT_ATTR_NAME = "windowActionBarFullscreenDecorLayout";
-
- // The Action Bar
- @NonNull private FrameworkActionBarWrapper mActionBar;
-
- // A fake parent for measuring views.
- @Nullable private ViewGroup mMeasureParent;
-
- /**
- * Inflate the action bar and attach it to {@code parentView}
- */
- public FrameworkActionBar(@NonNull BridgeContext context, @NonNull SessionParams params) {
- super(context, params);
-
- View decorContent = getDecorContent();
-
- mActionBar = FrameworkActionBarWrapper.getActionBarWrapper(context, getCallBack(),
- decorContent);
-
- FrameLayout contentRoot = (FrameLayout) decorContent.findViewById(android.R.id.content);
-
- // If something went wrong and we were not able to initialize the content root,
- // just add a frame layout inside this and return.
- if (contentRoot == null) {
- contentRoot = new FrameLayout(context);
- setMatchParent(contentRoot);
- if (mEnclosingLayout != null) {
- mEnclosingLayout.addView(contentRoot);
- }
- setContentRoot(contentRoot);
- } else {
- setContentRoot(contentRoot);
- setupActionBar();
- mActionBar.inflateMenus();
- }
- }
-
- @Override
- protected ResourceValue getLayoutResource(BridgeContext context) {
- ResourceValue layoutName =
- context.getRenderResources().findItemInTheme(LAYOUT_ATTR_NAME, true);
- if (layoutName != null) {
- // We may need to resolve the reference obtained.
- layoutName = context.getRenderResources().findResValue(layoutName.getValue(),
- layoutName.isFramework());
- }
- if (layoutName == null) {
- throw new InflateException("Unable to find action bar layout (" + LAYOUT_ATTR_NAME
- + ") in the current theme.");
- }
- return layoutName;
- }
-
- @Override
- protected void setupActionBar() {
- super.setupActionBar();
- mActionBar.setupActionBar();
- }
-
- @Override
- protected void setHomeAsUp(boolean homeAsUp) {
- mActionBar.setHomeAsUp(homeAsUp);
- }
-
- @Override
- protected void setTitle(CharSequence title) {
- mActionBar.setTitle(title);
- }
-
- @Override
- protected void setSubtitle(CharSequence subtitle) {
- mActionBar.setSubTitle(subtitle);
- }
-
- @Override
- protected void setIcon(String icon) {
- mActionBar.setIcon(icon);
- }
-
- /**
- * Creates a Popup and adds it to the content frame. It also adds another {@link FrameLayout} to
- * the content frame which shall serve as the new content root.
- */
- @Override
- public void createMenuPopup() {
- if (!isOverflowPopupNeeded()) {
- return;
- }
-
- DisplayMetrics metrics = mBridgeContext.getMetrics();
- MenuBuilder menu = mActionBar.getMenuBuilder();
- OverflowMenuAdapter adapter = new OverflowMenuAdapter(menu, mActionBar.getPopupContext());
-
- ListView listView = new ListView(mActionBar.getPopupContext(), null,
- R.attr.dropDownListViewStyle);
- RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
- measureContentWidth(adapter), LayoutParams.WRAP_CONTENT);
- layoutParams.addRule(RelativeLayout.ALIGN_PARENT_END);
- if (mActionBar.isSplit()) {
- layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
- layoutParams.bottomMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin();
- } else {
- layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
- layoutParams.topMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin();
- }
- layoutParams.setMarginEnd(getPixelValue("5dp", metrics));
- listView.setLayoutParams(layoutParams);
- listView.setAdapter(adapter);
- final TypedArray a = mActionBar.getPopupContext().obtainStyledAttributes(null,
- R.styleable.PopupWindow, R.attr.popupMenuStyle, 0);
- listView.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground));
- listView.setDivider(a.getDrawable(R.attr.actionBarDivider));
- a.recycle();
- listView.setElevation(mActionBar.getMenuPopupElevation());
- assert mEnclosingLayout != null : "Unable to find view to attach ActionMenuPopup.";
- mEnclosingLayout.addView(listView);
- }
-
- private boolean isOverflowPopupNeeded() {
- boolean needed = mActionBar.isOverflowPopupNeeded();
- if (!needed) {
- return false;
- }
- // Copied from android.widget.ActionMenuPresenter.updateMenuView()
- ArrayList<MenuItemImpl> menus = mActionBar.getMenuBuilder().getNonActionItems();
- ActionMenuPresenter presenter = mActionBar.getActionMenuPresenter();
- if (presenter == null) {
- assert false : "Failed to create a Presenter for Action Bar Menus.";
- return false;
- }
- if (presenter.isOverflowReserved() &&
- menus != null) {
- final int count = menus.size();
- if (count == 1) {
- needed = !menus.get(0).isActionViewExpanded();
- } else {
- needed = count > 0;
- }
- }
- return needed;
- }
-
- // Copied from com.android.internal.view.menu.MenuPopHelper.measureContentWidth()
- private int measureContentWidth(@NonNull ListAdapter adapter) {
- // Menus don't tend to be long, so this is more sane than it looks.
- int maxWidth = 0;
- View itemView = null;
- int itemType = 0;
-
- Context context = mActionBar.getPopupContext();
- final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- final int count = adapter.getCount();
- for (int i = 0; i < count; i++) {
- final int positionType = adapter.getItemViewType(i);
- if (positionType != itemType) {
- itemType = positionType;
- itemView = null;
- }
-
- if (mMeasureParent == null) {
- mMeasureParent = new FrameLayout(context);
- }
-
- itemView = adapter.getView(i, itemView, mMeasureParent);
- itemView.measure(widthMeasureSpec, heightMeasureSpec);
-
- final int itemWidth = itemView.getMeasuredWidth();
- int popupMaxWidth = Math.max(mBridgeContext.getMetrics().widthPixels / 2,
- context.getResources().getDimensionPixelSize(R.dimen.config_prefDialogWidth));
- if (itemWidth >= popupMaxWidth) {
- return popupMaxWidth;
- } else if (itemWidth > maxWidth) {
- maxWidth = itemWidth;
- }
- }
-
- return maxWidth;
- }
-
- static int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) {
- TypedValue typedValue = ResourceHelper.getValue(null, value, false /*requireUnit*/);
- return (int) typedValue.getDimension(metrics);
- }
-
- // TODO: This is duplicated from RenderSessionImpl.
- private int getActionBarHeight() {
- RenderResources resources = mBridgeContext.getRenderResources();
- DisplayMetrics metrics = mBridgeContext.getMetrics();
- ResourceValue value = resources.findItemInTheme("actionBarSize", true);
-
- // resolve it
- value = resources.resolveResValue(value);
-
- if (value != null) {
- // get the numerical value, if available
- TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(),
- true);
- if (typedValue != null) {
- // compute the pixel value based on the display metrics
- return (int) typedValue.getDimension(metrics);
-
- }
- }
- return 0;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java
deleted file mode 100644
index 2cdc647..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java
+++ /dev/null
@@ -1,379 +0,0 @@
-/*
- * Copyright (C) 2014 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.layoutlib.bridge.bars;
-
-import com.android.ide.common.rendering.api.ActionBarCallback;
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.internal.R;
-import com.android.internal.app.ToolbarActionBar;
-import com.android.internal.app.WindowDecorActionBar;
-import com.android.internal.view.menu.MenuBuilder;
-import com.android.internal.widget.ActionBarAccessor;
-import com.android.internal.widget.ActionBarView;
-import com.android.internal.widget.DecorToolbar;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.impl.ResourceHelper;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActionBar;
-import android.app.ActionBar.Tab;
-import android.app.ActionBar.TabListener;
-import android.app.FragmentTransaction;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.view.MenuInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowCallback;
-import android.widget.ActionMenuPresenter;
-import android.widget.ActionMenuView;
-import android.widget.Toolbar;
-import android.widget.Toolbar_Accessor;
-
-import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
-import static com.android.resources.ResourceType.MENU;
-
-/**
- * A common API to access {@link ToolbarActionBar} and {@link WindowDecorActionBar}.
- */
-public abstract class FrameworkActionBarWrapper {
-
- @NonNull protected ActionBar mActionBar;
- @NonNull protected ActionBarCallback mCallback;
- @NonNull protected BridgeContext mContext;
-
- /**
- * Returns a wrapper around different implementations of the Action Bar to provide a common API.
- *
- * @param decorContent the top level view returned by inflating
- * ?attr/windowActionBarFullscreenDecorLayout
- */
- @NonNull
- public static FrameworkActionBarWrapper getActionBarWrapper(@NonNull BridgeContext context,
- @NonNull ActionBarCallback callback, @NonNull View decorContent) {
- View view = decorContent.findViewById(R.id.action_bar);
- if (view instanceof Toolbar) {
- return new ToolbarWrapper(context, callback, (Toolbar) view);
- } else if (view instanceof ActionBarView) {
- return new WindowActionBarWrapper(context, callback, decorContent,
- (ActionBarView) view);
- } else {
- throw new IllegalStateException("Can't make an action bar out of " +
- view.getClass().getSimpleName());
- }
- }
-
- FrameworkActionBarWrapper(@NonNull BridgeContext context, @NonNull ActionBarCallback callback,
- @NonNull ActionBar actionBar) {
- mActionBar = actionBar;
- mCallback = callback;
- mContext = context;
- }
-
- /** A call to setup any custom properties. */
- protected void setupActionBar() {
- // Nothing to do here.
- }
-
- public void setTitle(CharSequence title) {
- mActionBar.setTitle(title);
- }
-
- public void setSubTitle(CharSequence subTitle) {
- if (subTitle != null) {
- mActionBar.setSubtitle(subTitle);
- }
- }
-
- public void setHomeAsUp(boolean homeAsUp) {
- mActionBar.setDisplayHomeAsUpEnabled(homeAsUp);
- }
-
- public void setIcon(String icon) {
- // Nothing to do.
- }
-
- protected boolean isSplit() {
- return getDecorToolbar().isSplit();
- }
-
- protected boolean isOverflowPopupNeeded() {
- return mCallback.isOverflowPopupNeeded();
- }
-
- /**
- * Gets the menus to add to the action bar from the callback, resolves them, inflates them and
- * adds them to the action bar.
- */
- protected void inflateMenus() {
- MenuInflater inflater = new MenuInflater(getActionMenuContext());
- MenuBuilder menuBuilder = getMenuBuilder();
- for (String name : mCallback.getMenuIdNames()) {
- int id;
- if (name.startsWith(ANDROID_NS_NAME_PREFIX)) {
- // Framework menu.
- name = name.substring(ANDROID_NS_NAME_PREFIX.length());
- id = mContext.getFrameworkResourceValue(MENU, name, -1);
- } else {
- // Project menu.
- id = mContext.getProjectResourceValue(MENU, name, -1);
- }
- if (id > -1) {
- inflater.inflate(id, menuBuilder);
- }
- }
- }
-
- /**
- * The context used for the ActionBar and the menus in the ActionBarView.
- */
- @NonNull
- protected Context getActionMenuContext() {
- return mActionBar.getThemedContext();
- }
-
- /**
- * The context used to inflate the popup menu.
- */
- @NonNull
- abstract Context getPopupContext();
-
- /**
- * The Menu in which to inflate the user's menus.
- */
- @NonNull
- abstract MenuBuilder getMenuBuilder();
-
- @Nullable
- abstract ActionMenuPresenter getActionMenuPresenter();
-
- /**
- * Framework's wrapper over two ActionBar implementations.
- */
- @NonNull
- abstract DecorToolbar getDecorToolbar();
-
- abstract int getMenuPopupElevation();
-
- /**
- * Margin between the menu popup and the action bar.
- */
- abstract int getMenuPopupMargin();
-
- // ---- The implementations ----
-
- /**
- * Material theme uses {@link Toolbar} as the action bar. This wrapper provides access to
- * Toolbar using a common API.
- */
- private static class ToolbarWrapper extends FrameworkActionBarWrapper {
-
- @NonNull
- private final Toolbar mToolbar; // This is the view.
-
- ToolbarWrapper(@NonNull BridgeContext context, @NonNull ActionBarCallback callback,
- @NonNull Toolbar toolbar) {
- super(context, callback, new ToolbarActionBar(toolbar, "", new WindowCallback()));
- mToolbar = toolbar;
- }
-
- @Override
- protected void inflateMenus() {
- super.inflateMenus();
- // Inflating the menus isn't enough. ActionMenuPresenter needs to be initialized too.
- MenuBuilder menu = getMenuBuilder();
- DecorToolbar decorToolbar = getDecorToolbar();
- // Setting a menu different from the above initializes the presenter.
- decorToolbar.setMenu(new MenuBuilder(getActionMenuContext()), null);
- // ActionMenuView needs to be recreated to be able to set the menu back.
- ActionMenuPresenter presenter = getActionMenuPresenter();
- if (presenter != null) {
- presenter.setMenuView(new ActionMenuView(getPopupContext()));
- }
- decorToolbar.setMenu(menu, null);
- }
-
- @NonNull
- @Override
- Context getPopupContext() {
- return Toolbar_Accessor.getPopupContext(mToolbar);
- }
-
- @NonNull
- @Override
- MenuBuilder getMenuBuilder() {
- return (MenuBuilder) mToolbar.getMenu();
- }
-
- @Nullable
- @Override
- ActionMenuPresenter getActionMenuPresenter() {
- return Toolbar_Accessor.getActionMenuPresenter(mToolbar);
- }
-
- @NonNull
- @Override
- DecorToolbar getDecorToolbar() {
- return mToolbar.getWrapper();
- }
-
- @Override
- int getMenuPopupElevation() {
- return 10;
- }
-
- @Override
- int getMenuPopupMargin() {
- return 0;
- }
- }
-
- /**
- * Holo theme uses {@link WindowDecorActionBar} as the action bar. This wrapper provides
- * access to it using a common API.
- */
- private static class WindowActionBarWrapper extends FrameworkActionBarWrapper {
-
- @NonNull private final WindowDecorActionBar mActionBar;
- @NonNull private final ActionBarView mActionBarView;
- @NonNull private final View mDecorContentRoot;
- private MenuBuilder mMenuBuilder;
-
- public WindowActionBarWrapper(@NonNull BridgeContext context,
- @NonNull ActionBarCallback callback, @NonNull View decorContentRoot,
- @NonNull ActionBarView actionBarView) {
- super(context, callback, new WindowDecorActionBar(decorContentRoot));
- mActionBarView = actionBarView;
- mActionBar = (WindowDecorActionBar) super.mActionBar;
- mDecorContentRoot = decorContentRoot;
- }
-
- @Override
- protected void setupActionBar() {
-
- // Set the navigation mode.
- int navMode = mCallback.getNavigationMode();
- mActionBar.setNavigationMode(navMode);
- //noinspection deprecation
- if (navMode == ActionBar.NAVIGATION_MODE_TABS) {
- setupTabs(3);
- }
-
- // Set action bar to be split, if needed.
- ViewGroup splitView = (ViewGroup) mDecorContentRoot.findViewById(R.id.split_action_bar);
- if (splitView != null) {
- mActionBarView.setSplitView(splitView);
- Resources res = mContext.getResources();
- boolean split = res.getBoolean(R.bool.split_action_bar_is_narrow)
- && mCallback.getSplitActionBarWhenNarrow();
- mActionBarView.setSplitToolbar(split);
- }
- }
-
- @Override
- public void setIcon(String icon) {
- // Set the icon only if the action bar doesn't specify an icon.
- if (!mActionBar.hasIcon() && icon != null) {
- Drawable iconDrawable = getDrawable(icon, false);
- if (iconDrawable != null) {
- mActionBar.setIcon(iconDrawable);
- }
- }
- }
-
- @Override
- protected void inflateMenus() {
- super.inflateMenus();
- // The super implementation doesn't set the menu on the view. Set it here.
- mActionBarView.setMenu(getMenuBuilder(), null);
- }
-
- @NonNull
- @Override
- Context getPopupContext() {
- return getActionMenuContext();
- }
-
- @NonNull
- @Override
- MenuBuilder getMenuBuilder() {
- if (mMenuBuilder == null) {
- mMenuBuilder = new MenuBuilder(getActionMenuContext());
- }
- return mMenuBuilder;
- }
-
- @Nullable
- @Override
- ActionMenuPresenter getActionMenuPresenter() {
- return ActionBarAccessor.getActionMenuPresenter(mActionBarView);
- }
-
- @NonNull
- @Override
- ActionBarView getDecorToolbar() {
- return mActionBarView;
- }
-
- @Override
- int getMenuPopupElevation() {
- return 0;
- }
-
- @Override
- int getMenuPopupMargin() {
- return -FrameworkActionBar.getPixelValue("10dp", mContext.getMetrics());
- }
-
- // TODO: Use an adapter, like List View to set up tabs.
- @SuppressWarnings("deprecation") // For Tab
- private void setupTabs(int num) {
- for (int i = 1; i <= num; i++) {
- Tab tab = mActionBar.newTab().setText("Tab" + i).setTabListener(new TabListener() {
- @Override
- public void onTabUnselected(Tab t, FragmentTransaction ft) {
- // pass
- }
- @Override
- public void onTabSelected(Tab t, FragmentTransaction ft) {
- // pass
- }
- @Override
- public void onTabReselected(Tab t, FragmentTransaction ft) {
- // pass
- }
- });
- mActionBar.addTab(tab);
- }
- }
-
- @Nullable
- private Drawable getDrawable(@NonNull String name, boolean isFramework) {
- RenderResources res = mContext.getRenderResources();
- ResourceValue value = res.findResValue(name, isFramework);
- value = res.resolveResValue(value);
- if (value != null) {
- return ResourceHelper.getDrawable(value, mContext);
- }
- return null;
- }
-
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/IconLoader.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/IconLoader.java
deleted file mode 100644
index 9ab2e82..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/IconLoader.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2014 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.layoutlib.bridge.bars;
-
-import com.android.resources.Density;
-import com.android.resources.LayoutDirection;
-
-import java.io.InputStream;
-
-public class IconLoader {
-
- private final String mIconName;
- private final Density mDesiredDensity;
- private final int mPlatformVersion;
- private final LayoutDirection mDirection;
-
- private Density mCurrentDensity;
- private StringBuilder mCurrentPath;
-
- IconLoader(String iconName, Density density, int platformVersion, LayoutDirection direction) {
- mIconName = iconName;
- mDesiredDensity = density;
- mPlatformVersion = platformVersion;
- mDirection = direction;
- // An upper bound on the length of the path for the icon: /bars/v21/ldrtl-xxxhdpi/
- final int iconPathLength = 24;
- mCurrentPath = new StringBuilder(iconPathLength + iconName.length());
- }
-
- public InputStream getIcon() {
- for (String resourceDir : Config.getResourceDirs(mPlatformVersion)) {
- mCurrentDensity = null;
- InputStream stream = getIcon(resourceDir);
- if (stream != null) {
- return stream;
- }
- }
- return null;
- }
-
- /**
- * Should only be called after {@link #getIcon()}. Returns the density of the icon, if found by
- * {@code getIcon()}. If no icon was found, then the return value has no meaning.
- */
- public Density getDensity() {
- return mCurrentDensity;
- }
-
- /**
- * Should only be called after {@link #getIcon()}. Returns the path to the icon, if found by
- * {@code getIcon()}. If no icon was found, then the return value has no meaning.
- */
- public String getPath() {
- return mCurrentPath.toString();
- }
-
- /**
- * Search for icon in the resource directory. This iterates over all densities.
- * If a match is found, mCurrentDensity will be set to the icon's density.
- */
- private InputStream getIcon(String resourceDir) {
- // First check for the desired density.
- InputStream stream = getIcon(resourceDir, mDesiredDensity);
- if (stream != null) {
- mCurrentDensity = mDesiredDensity;
- return stream;
- }
- // Didn't find in the desired density. Search in all.
- for (Density density : Density.values()) {
- if (density == mDesiredDensity) {
- // Skip the desired density since it's already been checked.
- continue;
- }
- stream = getIcon(resourceDir, density);
- if (stream != null) {
- mCurrentDensity = density;
- return stream;
- }
- }
- return null;
- }
-
- /**
- * Returns the icon for given density present in the given resource directory, taking layout
- * direction into consideration.
- */
- private InputStream getIcon(String resourceDir, Density density) {
- mCurrentPath.setLength(0);
- // Currently we don't have any LTR only resources and hence the check is skipped. If they
- // are ever added, change to:
- // if (mDirection == LayoutDirection.RTL || mDirection == LayoutDirection.LTR) {
- if (mDirection == LayoutDirection.RTL) {
- mCurrentPath.append(resourceDir)
- .append(mDirection.getResourceValue())
- .append('-')
- .append(density.getResourceValue())
- .append('/')
- .append(mIconName);
- InputStream stream = getClass().getResourceAsStream(mCurrentPath.toString());
- if (stream != null) {
- return stream;
- }
- mCurrentPath.setLength(0);
- }
- mCurrentPath.append(resourceDir)
- .append(density.getResourceValue())
- .append('/')
- .append(mIconName);
- return getClass().getResourceAsStream(mCurrentPath.toString());
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
deleted file mode 100644
index dfbc69b..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2011 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.layoutlib.bridge.bars;
-
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.resources.Density;
-
-import android.util.DisplayMetrics;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-public class NavigationBar extends CustomBar {
-
- /** Navigation bar background color attribute name. */
- private static final String ATTR_COLOR = "navigationBarColor";
- /** Attribute for translucency property. */
- public static final String ATTR_TRANSLUCENT = "windowTranslucentNavigation";
- // These correspond to @dimen/navigation_side_padding in the system ui code.
- private static final int PADDING_WIDTH_DEFAULT = 36;
- private static final int PADDING_WIDTH_SW360 = 40;
- private static final int PADDING_WIDTH_SW400 = 50;
- // These corresponds to @dimen/navigation_key_width in the system ui code.
- private static final int WIDTH_DEFAULT = 36;
- private static final int WIDTH_SW360 = 40;
- private static final int WIDTH_SW600 = 48;
- protected static final String LAYOUT_XML = "/bars/navigation_bar.xml";
- private static final String LAYOUT_600DP_XML = "/bars/navigation_bar600dp.xml";
-
- public NavigationBar(BridgeContext context, Density density, int orientation, boolean isRtl,
- boolean rtlEnabled, int simulatedPlatformVersion) {
- this(context, density, orientation, isRtl, rtlEnabled, simulatedPlatformVersion,
- getShortestWidth(context)>= 600 ? LAYOUT_600DP_XML : LAYOUT_XML);
- }
-
- protected NavigationBar(BridgeContext context, Density density, int orientation, boolean isRtl,
- boolean rtlEnabled, int simulatedPlatformVersion, String layoutPath) {
- super(context, orientation, layoutPath, "navigation_bar.xml", simulatedPlatformVersion);
-
- int color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT);
- setBackgroundColor(color == 0 ? 0xFF000000 : color);
-
- // Cannot access the inside items through id because no R.id values have been
- // created for them.
- // We do know the order though.
- // 0 is a spacer.
- int back = 1;
- int recent = 5;
- if (orientation == LinearLayout.VERTICAL || (isRtl && !rtlEnabled)) {
- // If RTL is enabled, then layoutlib mirrors the layout for us.
- back = 5;
- recent = 1;
- }
-
- //noinspection SpellCheckingInspection
- loadIcon(back, "ic_sysbar_back.png", density, isRtl);
- //noinspection SpellCheckingInspection
- loadIcon(3, "ic_sysbar_home.png", density, isRtl);
- //noinspection SpellCheckingInspection
- loadIcon(recent, "ic_sysbar_recent.png", density, isRtl);
- setupNavBar(context, orientation);
- }
-
- private void setupNavBar(BridgeContext context, int orientation) {
- float sw = getShortestWidth(context);
- View leftPadding = getChildAt(0);
- View rightPadding = getChildAt(6);
- setSize(context, leftPadding, orientation, getSidePadding(sw));
- setSize(context, rightPadding, orientation, getSidePadding(sw));
- int navButtonWidth = getWidth(sw);
- for (int i = 1; i < 6; i += 2) {
- View navButton = getChildAt(i);
- setSize(context, navButton, orientation, navButtonWidth);
- }
- if (sw >= 600) {
- setSize(context, getChildAt(2), orientation, 128);
- setSize(context, getChildAt(4), orientation, 128);
- }
- }
-
- private static void setSize(BridgeContext context, View view, int orientation, int size) {
- size *= context.getMetrics().density;
- LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
- if (orientation == HORIZONTAL) {
- layoutParams.width = size;
- } else {
- layoutParams.height = size;
- }
- view.setLayoutParams(layoutParams);
- }
-
- protected int getSidePadding(float sw) {
- if (sw >= 400) {
- return PADDING_WIDTH_SW400;
- }
- if (sw >= 360) {
- return PADDING_WIDTH_SW360;
- }
- return PADDING_WIDTH_DEFAULT;
- }
-
- private static int getWidth(float sw) {
- if (sw >= 600) {
- return WIDTH_SW600;
- }
- if (sw >= 360) {
- return WIDTH_SW360;
- }
- return WIDTH_DEFAULT;
- }
-
- private static float getShortestWidth(BridgeContext context) {
- DisplayMetrics metrics = context.getMetrics();
- float sw = metrics.widthPixels < metrics.heightPixels ?
- metrics.widthPixels : metrics.heightPixels;
- sw /= metrics.density;
- return sw;
- }
-
- @Override
- protected TextView getStyleableTextView() {
- return null;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/OverflowMenuAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/OverflowMenuAdapter.java
deleted file mode 100644
index 778305d..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/OverflowMenuAdapter.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2014 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.layoutlib.bridge.bars;
-
-import com.android.internal.view.menu.MenuBuilder;
-import com.android.internal.view.menu.MenuItemImpl;
-import com.android.internal.view.menu.MenuView;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-
-import java.util.ArrayList;
-
-/**
- * Provides an adapter for Overflow menu popup. This is very similar to
- * {@code MenuPopupHelper.MenuAdapter}
- */
-public class OverflowMenuAdapter extends BaseAdapter {
-
- private final MenuBuilder mMenu;
- private int mExpandedIndex = -1;
- private final Context context;
-
- public OverflowMenuAdapter(MenuBuilder menu, Context context) {
- mMenu = menu;
- findExpandedIndex();
- this.context = context;
- }
-
- @Override
- public int getCount() {
- ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
- if (mExpandedIndex < 0) {
- return items.size();
- }
- return items.size() - 1;
- }
-
- @Override
- public MenuItemImpl getItem(int position) {
- ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
- if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
- position++;
- }
- return items.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- // Since a menu item's ID is optional, we'll use the position as an
- // ID for the item in the AdapterView
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- LayoutInflater mInflater = LayoutInflater.from(context);
- convertView = mInflater.inflate(com.android.internal.R.layout.popup_menu_item_layout,
- parent, false);
- }
-
- MenuView.ItemView itemView = (MenuView.ItemView) convertView;
- itemView.initialize(getItem(position), 0);
- return convertView;
- }
-
- private void findExpandedIndex() {
- final MenuItemImpl expandedItem = mMenu.getExpandedItem();
- if (expandedItem != null) {
- final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
- final int count = items.size();
- for (int i = 0; i < count; i++) {
- final MenuItemImpl item = items.get(i);
- if (item == expandedItem) {
- mExpandedIndex = i;
- return;
- }
- }
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
deleted file mode 100644
index 2dc7c65..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2011 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.layoutlib.bridge.bars;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
-import com.android.layoutlib.bridge.impl.ParserFactory;
-import com.android.resources.Density;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-public class StatusBar extends CustomBar {
-
- private final int mSimulatedPlatformVersion;
- /** Status bar background color attribute name. */
- private static final String ATTR_COLOR = "statusBarColor";
- /** Attribute for translucency property. */
- public static final String ATTR_TRANSLUCENT = "windowTranslucentStatus";
-
- /**
- * Constructor to be used when creating the {@link StatusBar} as a regular control. This
- * is currently used by the theme editor.
- */
- @SuppressWarnings("UnusedParameters")
- public StatusBar(Context context, AttributeSet attrs) {
- this((BridgeContext) context,
- Density.getEnum(((BridgeContext) context).getMetrics().densityDpi),
- ((BridgeContext) context).getConfiguration().getLayoutDirection() ==
- View.LAYOUT_DIRECTION_RTL,
- (context.getApplicationInfo().flags & ApplicationInfo.FLAG_SUPPORTS_RTL) != 0,
- context.getApplicationInfo().targetSdkVersion);
- }
-
- @SuppressWarnings("UnusedParameters")
- public StatusBar(BridgeContext context, Density density, boolean isRtl, boolean rtlEnabled,
- int simulatedPlatformVersion) {
- // FIXME: if direction is RTL but it's not enabled in application manifest, mirror this bar.
- super(context, LinearLayout.HORIZONTAL, "/bars/status_bar.xml", "status_bar.xml",
- simulatedPlatformVersion);
- mSimulatedPlatformVersion = simulatedPlatformVersion;
-
- // FIXME: use FILL_H?
- setGravity(Gravity.START | Gravity.TOP | Gravity.RIGHT);
-
- int color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT);
- setBackgroundColor(color == 0 ? Config.getStatusBarColor(simulatedPlatformVersion) : color);
-
- // Cannot access the inside items through id because no R.id values have been
- // created for them.
- // We do know the order though.
- // 0 is the spacer
- loadIcon(1, "stat_sys_wifi_signal_4_fully."
- + Config.getWifiIconType(simulatedPlatformVersion), density);
- loadIcon(2, "stat_sys_battery_100.png", density);
- setText(3, Config.getTime(simulatedPlatformVersion), false)
- .setTextColor(Config.getTimeColor(simulatedPlatformVersion));
- }
-
- @Override
- protected void loadIcon(int index, String iconName, Density density) {
- if (!iconName.endsWith(".xml")) {
- super.loadIcon(index, iconName, density);
- return;
- }
- View child = getChildAt(index);
- if (child instanceof ImageView) {
- ImageView imageView = (ImageView) child;
- // The xml is stored only in xhdpi.
- IconLoader iconLoader = new IconLoader(iconName, Density.XHIGH,
- mSimulatedPlatformVersion, null);
- InputStream stream = iconLoader.getIcon();
-
- if (stream != null) {
- try {
- BridgeXmlBlockParser parser = new BridgeXmlBlockParser(
- ParserFactory.create(stream, null), (BridgeContext) mContext, true);
- imageView.setImageDrawable(
- Drawable.createFromXml(mContext.getResources(), parser));
- } catch (XmlPullParserException e) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Unable to draw wifi icon", e,
- null);
- } catch (IOException e) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Unable to draw wifi icon", e,
- null);
- }
- }
- }
- }
-
- @Override
- protected TextView getStyleableTextView() {
- return null;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ThemePreviewNavigationBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ThemePreviewNavigationBar.java
deleted file mode 100644
index 0435280..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ThemePreviewNavigationBar.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2015 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.layoutlib.bridge.bars;
-
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.resources.Density;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-
-/**
- * Navigation Bar for the Theme Editor preview.
- *
- * For small bars, it is identical to {@link NavigationBar}.
- * But wide bars from {@link NavigationBar} are too wide for the Theme Editor preview.
- * To solve that problem, {@link ThemePreviewNavigationBar} use the layout for small bars,
- * and have no padding on the sides. That way, they have a similar look as the true ones,
- * and they fit in the Theme Editor preview.
- */
-public class ThemePreviewNavigationBar extends NavigationBar {
- private static final int PADDING_WIDTH_SW600 = 0;
-
- @SuppressWarnings("unused")
- public ThemePreviewNavigationBar(Context context, AttributeSet attrs) {
- super((BridgeContext) context,
- Density.getEnum(((BridgeContext) context).getMetrics().densityDpi),
- LinearLayout.HORIZONTAL, // In this mode, it doesn't need to be render vertically
- ((BridgeContext) context).getConfiguration().getLayoutDirection() ==
- View.LAYOUT_DIRECTION_RTL,
- (context.getApplicationInfo().flags & ApplicationInfo.FLAG_SUPPORTS_RTL) != 0,
- 0, LAYOUT_XML);
- }
-
- @Override
- protected int getSidePadding(float sw) {
- if (sw >= 600) {
- return PADDING_WIDTH_SW600;
- }
- return super.getSidePadding(sw);
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java
deleted file mode 100644
index 4fe1001..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2011 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.layoutlib.bridge.bars;
-
-import com.android.layoutlib.bridge.android.BridgeContext;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-public class TitleBar extends CustomBar {
-
- private TextView mTextView;
-
- public TitleBar(BridgeContext context, String label, int simulatedPlatformVersion) {
- super(context, LinearLayout.HORIZONTAL, "/bars/title_bar.xml", "title_bar.xml",
- simulatedPlatformVersion);
-
- // Cannot access the inside items through id because no R.id values have been
- // created for them.
- // We do know the order though.
- mTextView = setText(0, label, true);
-
- setStyle("windowTitleBackgroundStyle");
- }
-
- @Override
- protected TextView getStyleableTextView() {
- return mTextView;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java
deleted file mode 100644
index 11da445..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2010 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.layoutlib.bridge.impl;
-
-import com.android.layoutlib.bridge.util.Debug;
-import com.android.layoutlib.bridge.util.SparseWeakArray;
-
-import android.annotation.Nullable;
-import android.util.SparseArray;
-
-import java.io.PrintStream;
-import java.lang.ref.WeakReference;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * Manages native delegates.
- *
- * This is used in conjunction with layoublib_create: certain Android java classes are mere
- * wrappers around a heavily native based implementation, and we need a way to run these classes
- * in our Android Studio rendering framework without bringing all the native code from the Android
- * platform.
- *
- * Thus we instruct layoutlib_create to modify the bytecode of these classes to replace their
- * native methods by "delegate calls".
- *
- * For example, a native method android.graphics.Matrix.init(...) will actually become
- * a call to android.graphics.Matrix_Delegate.init(...).
- *
- * The Android java classes that use native code uses an int (Java side) to reference native
- * objects. This int is generally directly the pointer to the C structure counterpart.
- * Typically a creation method will return such an int, and then this int will be passed later
- * to a Java method to identify the C object to manipulate.
- *
- * Since we cannot use the Java object reference as the int directly, DelegateManager manages the
- * int -> Delegate class link.
- *
- * Native methods usually always have the int as parameters. The first thing the delegate method
- * will do is call {@link #getDelegate(long)} to get the Java object matching the int.
- *
- * Typical native init methods are returning a new int back to the Java class, so
- * {@link #addNewDelegate(Object)} does the same.
- *
- * The JNI references are counted, so we do the same through a {@link WeakReference}. Because
- * the Java object needs to count as a reference (even though it only holds an int), we use the
- * following mechanism:
- *
- * - {@link #addNewDelegate(Object)} and {@link #removeJavaReferenceFor(long)} adds and removes
- * the delegate to/from a set. This set holds the reference and prevents the GC from reclaiming
- * the delegate.
- *
- * - {@link #addNewDelegate(Object)} also adds the delegate to a {@link SparseArray} that holds a
- * {@link WeakReference} to the delegate. This allows the delegate to be deleted automatically
- * when nothing references it. This means that any class that holds a delegate (except for the
- * Java main class) must not use the int but the Delegate class instead. The integers must
- * only be used in the API between the main Java class and the Delegate.
- *
- * @param <T> the delegate class to manage
- */
-public final class DelegateManager<T> {
- @SuppressWarnings("FieldCanBeLocal")
- private final Class<T> mClass;
- private static final SparseWeakArray<Object> sDelegates = new SparseWeakArray<>();
- /** Set used to store delegates when their main object holds a reference to them.
- * This is to ensure that the WeakReference in the SparseWeakArray doesn't get GC'ed
- * @see #addNewDelegate(Object)
- * @see #removeJavaReferenceFor(long)
- */
- private static final Set<Object> sJavaReferences = new HashSet<>();
- private static final AtomicLong sDelegateCounter = new AtomicLong(1);
-
- public DelegateManager(Class<T> theClass) {
- mClass = theClass;
- }
-
- /**
- * Returns the delegate from the given native int.
- * <p>
- * If the int is zero, then this will always return null.
- * <p>
- * If the int is non zero and the delegate is not found, this will throw an assert.
- *
- * @param native_object the native int.
- * @return the delegate or null if not found.
- */
- @Nullable
- public T getDelegate(long native_object) {
- if (native_object > 0) {
- Object delegate;
- synchronized (DelegateManager.class) {
- delegate = sDelegates.get(native_object);
- }
-
- if (Debug.DEBUG) {
- if (delegate == null) {
- System.err.println("Unknown " + mClass.getSimpleName() + " with int " +
- native_object);
- }
- }
-
- assert delegate != null;
- //noinspection unchecked
- return (T)delegate;
- }
- return null;
- }
-
- /**
- * Adds a delegate to the manager and returns the native int used to identify it.
- * @param newDelegate the delegate to add
- * @return a unique native int to identify the delegate
- */
- public long addNewDelegate(T newDelegate) {
- long native_object = sDelegateCounter.getAndIncrement();
- synchronized (DelegateManager.class) {
- sDelegates.put(native_object, newDelegate);
- // Only for development: assert !sJavaReferences.contains(newDelegate);
- sJavaReferences.add(newDelegate);
- }
-
- if (Debug.DEBUG) {
- System.out.println(
- "New " + mClass.getSimpleName() + " " +
- "with int " +
- native_object);
- }
-
- return native_object;
- }
-
- /**
- * Removes the main reference on the given delegate.
- * @param native_object the native integer representing the delegate.
- */
- public void removeJavaReferenceFor(long native_object) {
- synchronized (DelegateManager.class) {
- T delegate = getDelegate(native_object);
-
- if (Debug.DEBUG) {
- System.out.println("Removing main Java ref on " + mClass.getSimpleName() +
- " with int " + native_object);
- }
-
- sJavaReferences.remove(delegate);
- }
- }
-
- public synchronized static void dump(PrintStream out) {
- for (Object reference : sJavaReferences) {
- int idx = sDelegates.indexOfValue(reference);
- out.printf("[%d] %s\n", sDelegates.keyAt(idx), reference.getClass().getSimpleName());
- }
- out.printf("\nTotal number of objects: %d\n", sJavaReferences.size());
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
deleted file mode 100644
index 7526e09..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
+++ /dev/null
@@ -1,885 +0,0 @@
-/*
- * Copyright (C) 2010 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.layoutlib.bridge.impl;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-
-import android.graphics.Bitmap_Delegate;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter_Delegate;
-import android.graphics.Paint;
-import android.graphics.Paint_Delegate;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.graphics.Region_Delegate;
-import android.graphics.Shader_Delegate;
-
-import java.awt.AlphaComposite;
-import java.awt.Color;
-import java.awt.Composite;
-import java.awt.Graphics2D;
-import java.awt.Rectangle;
-import java.awt.RenderingHints;
-import java.awt.Shape;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Area;
-import java.awt.geom.NoninvertibleTransformException;
-import java.awt.geom.Rectangle2D;
-import java.awt.image.BufferedImage;
-import java.util.ArrayList;
-
-/**
- * Class representing a graphics context snapshot, as well as a context stack as a linked list.
- * <p>
- * This is based on top of {@link Graphics2D} but can operate independently if none are available
- * yet when setting transforms and clip information.
- * <p>
- * This allows for drawing through {@link #draw(Drawable, Paint_Delegate, boolean, boolean)} and
- * {@link #draw(Drawable)}
- *
- * Handling of layers (created with {@link Canvas#saveLayer(RectF, Paint, int)}) is handled through
- * a list of Graphics2D for each layers. The class actually maintains a list of {@link Layer}
- * for each layer. Doing a save() will duplicate this list so that each graphics2D object
- * ({@link Layer#getGraphics()}) is configured only for the new snapshot.
- */
-public class GcSnapshot {
-
- private final GcSnapshot mPrevious;
- private final int mFlags;
-
- /** list of layers. The first item in the list is always the */
- private final ArrayList<Layer> mLayers = new ArrayList<Layer>();
-
- /** temp transform in case transformation are set before a Graphics2D exists */
- private AffineTransform mTransform = null;
- /** temp clip in case clipping is set before a Graphics2D exists */
- private Area mClip = null;
-
- // local layer data
- /** a local layer created with {@link Canvas#saveLayer(RectF, Paint, int)}.
- * If this is null, this does not mean there's no layer, just that the snapshot is not the
- * one that created the layer.
- * @see #getLayerSnapshot()
- */
- private final Layer mLocalLayer;
- private final Paint_Delegate mLocalLayerPaint;
- private final Rect mLayerBounds;
-
- public interface Drawable {
- void draw(Graphics2D graphics, Paint_Delegate paint);
- }
-
- /**
- * Class containing information about a layer.
- *
- * This contains graphics, bitmap and layer information.
- */
- private static class Layer {
- private final Graphics2D mGraphics;
- private final Bitmap_Delegate mBitmap;
- private final BufferedImage mImage;
- /** the flags that were used to configure the layer. This is never changed, and passed
- * as is when {@link #makeCopy()} is called */
- private final int mFlags;
- /** the original content of the layer when the next object was created. This is not
- * passed in {@link #makeCopy()} and instead is recreated when a new layer is added
- * (depending on its flags) */
- private BufferedImage mOriginalCopy;
-
- /**
- * Creates a layer with a graphics and a bitmap. This is only used to create
- * the base layer.
- *
- * @param graphics the graphics
- * @param bitmap the bitmap
- */
- Layer(Graphics2D graphics, Bitmap_Delegate bitmap) {
- mGraphics = graphics;
- mBitmap = bitmap;
- mImage = mBitmap.getImage();
- mFlags = 0;
- }
-
- /**
- * Creates a layer with a graphics and an image. If the image belongs to a
- * {@link Bitmap_Delegate} (case of the base layer), then
- * {@link Layer#Layer(Graphics2D, Bitmap_Delegate)} should be used.
- *
- * @param graphics the graphics the new graphics for this layer
- * @param image the image the image from which the graphics came
- * @param flags the flags that were used to save this layer
- */
- Layer(Graphics2D graphics, BufferedImage image, int flags) {
- mGraphics = graphics;
- mBitmap = null;
- mImage = image;
- mFlags = flags;
- }
-
- /** The Graphics2D, guaranteed to be non null */
- Graphics2D getGraphics() {
- return mGraphics;
- }
-
- /** The BufferedImage, guaranteed to be non null */
- BufferedImage getImage() {
- return mImage;
- }
-
- /** Returns the layer save flags. This is only valid for additional layers.
- * For the base layer this will always return 0;
- * For a given layer, all further copies of this {@link Layer} object in new snapshots
- * will always return the same value.
- */
- int getFlags() {
- return mFlags;
- }
-
- Layer makeCopy() {
- if (mBitmap != null) {
- return new Layer((Graphics2D) mGraphics.create(), mBitmap);
- }
-
- return new Layer((Graphics2D) mGraphics.create(), mImage, mFlags);
- }
-
- /** sets an optional copy of the original content to be used during restore */
- void setOriginalCopy(BufferedImage image) {
- mOriginalCopy = image;
- }
-
- BufferedImage getOriginalCopy() {
- return mOriginalCopy;
- }
-
- void change() {
- if (mBitmap != null) {
- mBitmap.change();
- }
- }
-
- /**
- * Sets the clip for the graphics2D object associated with the layer.
- * This should be used over the normal Graphics2D setClip method.
- *
- * @param clipShape the shape to use a the clip shape.
- */
- void setClip(Shape clipShape) {
- // because setClip is only guaranteed to work with rectangle shape,
- // first reset the clip to max and then intersect the current (empty)
- // clip with the shap.
- mGraphics.setClip(null);
- mGraphics.clip(clipShape);
- }
-
- /**
- * Clips the layer with the given shape. This performs an intersect between the current
- * clip shape and the given shape.
- * @param shape the new clip shape.
- */
- public void clip(Shape shape) {
- mGraphics.clip(shape);
- }
- }
-
- /**
- * Creates the root snapshot associating it with a given bitmap.
- * <p>
- * If <var>bitmap</var> is null, then {@link GcSnapshot#setBitmap(Bitmap_Delegate)} must be
- * called before the snapshot can be used to draw. Transform and clip operations are permitted
- * before.
- *
- * @param bitmap the image to associate to the snapshot or null.
- * @return the root snapshot
- */
- public static GcSnapshot createDefaultSnapshot(Bitmap_Delegate bitmap) {
- GcSnapshot snapshot = new GcSnapshot();
- if (bitmap != null) {
- snapshot.setBitmap(bitmap);
- }
-
- return snapshot;
- }
-
- /**
- * Saves the current state according to the given flags and returns the new current snapshot.
- * <p/>
- * This is the equivalent of {@link Canvas#save(int)}
- *
- * @param flags the save flags.
- * @return the new snapshot
- *
- * @see Canvas#save(int)
- */
- public GcSnapshot save(int flags) {
- return new GcSnapshot(this, null /*layerbounds*/, null /*paint*/, flags);
- }
-
- /**
- * Saves the current state and creates a new layer, and returns the new current snapshot.
- * <p/>
- * This is the equivalent of {@link Canvas#saveLayer(RectF, Paint, int)}
- *
- * @param layerBounds the layer bounds
- * @param paint the Paint information used to blit the layer back into the layers underneath
- * upon restore
- * @param flags the save flags.
- * @return the new snapshot
- *
- * @see Canvas#saveLayer(RectF, Paint, int)
- */
- public GcSnapshot saveLayer(RectF layerBounds, Paint_Delegate paint, int flags) {
- return new GcSnapshot(this, layerBounds, paint, flags);
- }
-
- /**
- * Creates the root snapshot.
- * {@link #setGraphics2D(Graphics2D)} will have to be called on it when possible.
- */
- private GcSnapshot() {
- mPrevious = null;
- mFlags = 0;
- mLocalLayer = null;
- mLocalLayerPaint = null;
- mLayerBounds = null;
- }
-
- /**
- * Creates a new {@link GcSnapshot} on top of another one, with a layer data to be restored
- * into the main graphics when {@link #restore()} is called.
- *
- * @param previous the previous snapshot head.
- * @param layerBounds the region of the layer. Optional, if null, this is a normal save()
- * @param paint the Paint information used to blit the layer back into the layers underneath
- * upon restore
- * @param flags the flags regarding what should be saved.
- */
- private GcSnapshot(GcSnapshot previous, RectF layerBounds, Paint_Delegate paint, int flags) {
- assert previous != null;
- mPrevious = previous;
- mFlags = flags;
-
- // make a copy of the current layers before adding the new one.
- // This keeps the same BufferedImage reference but creates new Graphics2D for this
- // snapshot.
- // It does not copy whatever original copy the layers have, as they will be done
- // only if the new layer doesn't clip drawing to itself.
- for (Layer layer : mPrevious.mLayers) {
- mLayers.add(layer.makeCopy());
- }
-
- if (layerBounds != null) {
- // get the current transform
- AffineTransform matrix = mLayers.get(0).getGraphics().getTransform();
-
- // transform the layerBounds with the current transform and stores it into a int rect
- RectF rect2 = new RectF();
- mapRect(matrix, rect2, layerBounds);
- mLayerBounds = new Rect();
- rect2.round(mLayerBounds);
-
- // get the base layer (always at index 0)
- Layer baseLayer = mLayers.get(0);
-
- // create the image for the layer
- BufferedImage layerImage = new BufferedImage(
- baseLayer.getImage().getWidth(),
- baseLayer.getImage().getHeight(),
- (mFlags & Canvas.HAS_ALPHA_LAYER_SAVE_FLAG) != 0 ?
- BufferedImage.TYPE_INT_ARGB :
- BufferedImage.TYPE_INT_RGB);
-
- // create a graphics for it so that drawing can be done.
- Graphics2D layerGraphics = layerImage.createGraphics();
-
- // because this layer inherits the current context for transform and clip,
- // set them to one from the base layer.
- AffineTransform currentMtx = baseLayer.getGraphics().getTransform();
- layerGraphics.setTransform(currentMtx);
-
- // create a new layer for this new layer and add it to the list at the end.
- mLayers.add(mLocalLayer = new Layer(layerGraphics, layerImage, flags));
-
- // set the clip on it.
- Shape currentClip = baseLayer.getGraphics().getClip();
- mLocalLayer.setClip(currentClip);
-
- // if the drawing is not clipped to the local layer only, we save the current content
- // of all other layers. We are only interested in the part that will actually
- // be drawn, so we create as small bitmaps as we can.
- // This is so that we can erase the drawing that goes in the layers below that will
- // be coming from the layer itself.
- if ((mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0) {
- int w = mLayerBounds.width();
- int h = mLayerBounds.height();
- for (int i = 0 ; i < mLayers.size() - 1 ; i++) {
- Layer layer = mLayers.get(i);
- BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
- Graphics2D graphics = image.createGraphics();
- graphics.drawImage(layer.getImage(),
- 0, 0, w, h,
- mLayerBounds.left, mLayerBounds.top,
- mLayerBounds.right, mLayerBounds.bottom,
- null);
- graphics.dispose();
- layer.setOriginalCopy(image);
- }
- }
- } else {
- mLocalLayer = null;
- mLayerBounds = null;
- }
-
- mLocalLayerPaint = paint;
- }
-
- public void dispose() {
- for (Layer layer : mLayers) {
- layer.getGraphics().dispose();
- }
-
- if (mPrevious != null) {
- mPrevious.dispose();
- }
- }
-
- /**
- * Restores the top {@link GcSnapshot}, and returns the next one.
- */
- public GcSnapshot restore() {
- return doRestore();
- }
-
- /**
- * Restores the {@link GcSnapshot} to <var>saveCount</var>.
- * @param saveCount the saveCount or -1 to only restore 1.
- *
- * @return the new head of the Gc snapshot stack.
- */
- public GcSnapshot restoreTo(int saveCount) {
- return doRestoreTo(size(), saveCount);
- }
-
- public int size() {
- if (mPrevious != null) {
- return mPrevious.size() + 1;
- }
-
- return 1;
- }
-
- /**
- * Link the snapshot to a Bitmap_Delegate.
- * <p/>
- * This is only for the case where the snapshot was created with a null image when calling
- * {@link #createDefaultSnapshot(Bitmap_Delegate)}, and is therefore not yet linked to
- * a previous snapshot.
- * <p/>
- * If any transform or clip information was set before, they are put into the Graphics object.
- * @param bitmap the bitmap to link to.
- */
- public void setBitmap(Bitmap_Delegate bitmap) {
- // create a new Layer for the bitmap. This will be the base layer.
- Graphics2D graphics2D = bitmap.getImage().createGraphics();
- Layer baseLayer = new Layer(graphics2D, bitmap);
-
- // Set the current transform and clip which can either come from mTransform/mClip if they
- // were set when there was no bitmap/layers or from the current base layers if there is
- // one already.
-
- graphics2D.setTransform(getTransform());
- // reset mTransform in case there was one.
- mTransform = null;
-
- baseLayer.setClip(getClip());
- // reset mClip in case there was one.
- mClip = null;
-
- // replace whatever current layers we have with this.
- mLayers.clear();
- mLayers.add(baseLayer);
-
- }
-
- public void translate(float dx, float dy) {
- if (mLayers.size() > 0) {
- for (Layer layer : mLayers) {
- layer.getGraphics().translate(dx, dy);
- }
- } else {
- if (mTransform == null) {
- mTransform = new AffineTransform();
- }
- mTransform.translate(dx, dy);
- }
- }
-
- public void rotate(double radians) {
- if (mLayers.size() > 0) {
- for (Layer layer : mLayers) {
- layer.getGraphics().rotate(radians);
- }
- } else {
- if (mTransform == null) {
- mTransform = new AffineTransform();
- }
- mTransform.rotate(radians);
- }
- }
-
- public void scale(float sx, float sy) {
- if (mLayers.size() > 0) {
- for (Layer layer : mLayers) {
- layer.getGraphics().scale(sx, sy);
- }
- } else {
- if (mTransform == null) {
- mTransform = new AffineTransform();
- }
- mTransform.scale(sx, sy);
- }
- }
-
- public AffineTransform getTransform() {
- if (mLayers.size() > 0) {
- // all graphics2D in the list have the same transform
- return mLayers.get(0).getGraphics().getTransform();
- } else {
- if (mTransform == null) {
- mTransform = new AffineTransform();
- }
- return mTransform;
- }
- }
-
- public void setTransform(AffineTransform transform) {
- if (mLayers.size() > 0) {
- for (Layer layer : mLayers) {
- layer.getGraphics().setTransform(transform);
- }
- } else {
- if (mTransform == null) {
- mTransform = new AffineTransform();
- }
- mTransform.setTransform(transform);
- }
- }
-
- public boolean clip(Shape shape, int regionOp) {
- // Simple case of intersect with existing layers.
- // Because Graphics2D#setClip works a bit peculiarly, we optimize
- // the case of clipping by intersection, as it's supported natively.
- if (regionOp == Region.Op.INTERSECT.nativeInt && mLayers.size() > 0) {
- for (Layer layer : mLayers) {
- layer.clip(shape);
- }
-
- Shape currentClip = getClip();
- return currentClip != null && currentClip.getBounds().isEmpty() == false;
- }
-
- Area area = null;
-
- if (regionOp == Region.Op.REPLACE.nativeInt) {
- area = new Area(shape);
- } else {
- area = Region_Delegate.combineShapes(getClip(), shape, regionOp);
- }
-
- assert area != null;
-
- if (mLayers.size() > 0) {
- if (area != null) {
- for (Layer layer : mLayers) {
- layer.setClip(area);
- }
- }
-
- Shape currentClip = getClip();
- return currentClip != null && currentClip.getBounds().isEmpty() == false;
- } else {
- if (area != null) {
- mClip = area;
- } else {
- mClip = new Area();
- }
-
- return mClip.getBounds().isEmpty() == false;
- }
- }
-
- public boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
- return clip(new Rectangle2D.Float(left, top, right - left, bottom - top), regionOp);
- }
-
- /**
- * Returns the current clip, or null if none have been setup.
- */
- public Shape getClip() {
- if (mLayers.size() > 0) {
- // they all have the same clip
- return mLayers.get(0).getGraphics().getClip();
- } else {
- return mClip;
- }
- }
-
- private GcSnapshot doRestoreTo(int size, int saveCount) {
- if (size <= saveCount) {
- return this;
- }
-
- // restore the current one first.
- GcSnapshot previous = doRestore();
-
- if (size == saveCount + 1) { // this was the only one that needed restore.
- return previous;
- } else {
- return previous.doRestoreTo(size - 1, saveCount);
- }
- }
-
- /**
- * Executes the Drawable's draw method, with a null paint delegate.
- * <p/>
- * Note that the method can be called several times if there are more than one active layer.
- */
- public void draw(Drawable drawable) {
- draw(drawable, null, false /*compositeOnly*/, false /*forceSrcMode*/);
- }
-
- /**
- * Executes the Drawable's draw method.
- * <p/>
- * Note that the method can be called several times if there are more than one active layer.
- * @param compositeOnly whether the paint is used for composite only. This is typically
- * the case for bitmaps.
- * @param forceSrcMode if true, this overrides the composite to be SRC
- */
- public void draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly,
- boolean forceSrcMode) {
- int forceMode = forceSrcMode ? AlphaComposite.SRC : 0;
- // the current snapshot may not have a mLocalLayer (ie it was created on save() instead
- // of saveLayer(), but that doesn't mean there's no layer.
- // mLayers however saves all the information we need (flags).
- if (mLayers.size() == 1) {
- // no layer, only base layer. easy case.
- drawInLayer(mLayers.get(0), drawable, paint, compositeOnly, forceMode);
- } else {
- // draw in all the layers until the layer save flags tells us to stop (ie drawing
- // in that layer is limited to the layer itself.
- int flags;
- int i = mLayers.size() - 1;
-
- do {
- Layer layer = mLayers.get(i);
-
- drawInLayer(layer, drawable, paint, compositeOnly, forceMode);
-
- // then go to previous layer, only if there are any left, and its flags
- // doesn't restrict drawing to the layer itself.
- i--;
- flags = layer.getFlags();
- } while (i >= 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0);
- }
- }
-
- private void drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint,
- boolean compositeOnly, int forceMode) {
- Graphics2D originalGraphics = layer.getGraphics();
- if (paint == null) {
- drawOnGraphics((Graphics2D) originalGraphics.create(), drawable,
- null /*paint*/, layer);
- } else {
- ColorFilter_Delegate filter = paint.getColorFilter();
- if (filter == null || !filter.isSupported()) {
- // get a Graphics2D object configured with the drawing parameters.
- Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint,
- compositeOnly, forceMode);
- drawOnGraphics(configuredGraphics, drawable, paint, layer);
- return;
- }
-
- int x = 0;
- int y = 0;
- int width;
- int height;
- Rectangle clipBounds = originalGraphics.getClip() != null ? originalGraphics
- .getClipBounds() : null;
- if (clipBounds != null) {
- if (clipBounds.width == 0 || clipBounds.height == 0) {
- // Clip is 0 so no need to paint anything.
- return;
- }
- // If we have clipBounds available, use them as they will always be
- // smaller than the full layer size.
- x = clipBounds.x;
- y = clipBounds.y;
- width = clipBounds.width;
- height = clipBounds.height;
- } else {
- width = layer.getImage().getWidth();
- height = layer.getImage().getHeight();
- }
-
- // Create a temporary image to which the color filter will be applied.
- BufferedImage image = new BufferedImage(width, height,
- BufferedImage.TYPE_INT_ARGB);
- Graphics2D imageBaseGraphics = (Graphics2D) image.getGraphics();
- // Configure the Graphics2D object with drawing parameters and shader.
- Graphics2D imageGraphics = createCustomGraphics(
- imageBaseGraphics, paint, compositeOnly,
- AlphaComposite.SRC_OVER);
- // get a Graphics2D object configured with the drawing parameters, but no shader.
- Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint,
- true /*compositeOnly*/, forceMode);
- try {
- // The main draw operation.
- // We translate the operation to take into account that the rendering does not
- // know about the clipping area.
- imageGraphics.translate(-x, -y);
- drawable.draw(imageGraphics, paint);
-
- // Apply the color filter.
- // Restore the original coordinates system and apply the filter only to the
- // clipped area.
- imageGraphics.translate(x, y);
- filter.applyFilter(imageGraphics, width, height);
-
- // Draw the tinted image on the main layer using as start point the clipping
- // upper left coordinates.
- configuredGraphics.drawImage(image, x, y, null);
- layer.change();
- } finally {
- // dispose Graphics2D objects
- imageGraphics.dispose();
- imageBaseGraphics.dispose();
- configuredGraphics.dispose();
- }
- }
- }
-
- private void drawOnGraphics(Graphics2D g, Drawable drawable, Paint_Delegate paint,
- Layer layer) {
- try {
- drawable.draw(g, paint);
- layer.change();
- } finally {
- g.dispose();
- }
- }
-
- private GcSnapshot doRestore() {
- if (mPrevious != null) {
- if (mLocalLayer != null) {
- // prepare to blit the layers in which we have draw, in the layer beneath
- // them, starting with the top one (which is the current local layer).
- int i = mLayers.size() - 1;
- int flags;
- do {
- Layer dstLayer = mLayers.get(i - 1);
-
- restoreLayer(dstLayer);
-
- flags = dstLayer.getFlags();
- i--;
- } while (i > 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0);
- }
-
- // if this snapshot does not save everything, then set the previous snapshot
- // to this snapshot content
-
- // didn't save the matrix? set the current matrix on the previous snapshot
- if ((mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) {
- AffineTransform mtx = getTransform();
- for (Layer layer : mPrevious.mLayers) {
- layer.getGraphics().setTransform(mtx);
- }
- }
-
- // didn't save the clip? set the current clip on the previous snapshot
- if ((mFlags & Canvas.CLIP_SAVE_FLAG) == 0) {
- Shape clip = getClip();
- for (Layer layer : mPrevious.mLayers) {
- layer.setClip(clip);
- }
- }
- }
-
- for (Layer layer : mLayers) {
- layer.getGraphics().dispose();
- }
-
- return mPrevious;
- }
-
- private void restoreLayer(Layer dstLayer) {
-
- Graphics2D baseGfx = dstLayer.getImage().createGraphics();
-
- // if the layer contains an original copy this means the flags
- // didn't restrict drawing to the local layer and we need to make sure the
- // layer bounds in the layer beneath didn't receive any drawing.
- // so we use the originalCopy to erase the new drawings in there.
- BufferedImage originalCopy = dstLayer.getOriginalCopy();
- if (originalCopy != null) {
- Graphics2D g = (Graphics2D) baseGfx.create();
- g.setComposite(AlphaComposite.Src);
-
- g.drawImage(originalCopy,
- mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
- 0, 0, mLayerBounds.width(), mLayerBounds.height(),
- null);
- g.dispose();
- }
-
- // now draw put the content of the local layer onto the layer,
- // using the paint information
- Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint,
- true /*alphaOnly*/, 0 /*forceMode*/);
-
- g.drawImage(mLocalLayer.getImage(),
- mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
- mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
- null);
- g.dispose();
-
- baseGfx.dispose();
- }
-
- /**
- * Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
- * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
- */
- private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint,
- boolean compositeOnly, int forceMode) {
- // make new one graphics
- Graphics2D g = (Graphics2D) original.create();
-
- // configure it
-
- if (paint.isAntiAliased()) {
- g.setRenderingHint(
- RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
- g.setRenderingHint(
- RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
- }
-
- // set the shader first, as it'll replace the color if it can be used it.
- boolean customShader = false;
- if (!compositeOnly) {
- customShader = setShader(g, paint);
- // set the stroke
- g.setStroke(paint.getJavaStroke());
- }
- // set the composite.
- setComposite(g, paint, compositeOnly || customShader, forceMode);
-
- return g;
- }
-
- private boolean setShader(Graphics2D g, Paint_Delegate paint) {
- Shader_Delegate shaderDelegate = paint.getShader();
- if (shaderDelegate != null) {
- if (shaderDelegate.isSupported()) {
- java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint();
- assert shaderPaint != null;
- if (shaderPaint != null) {
- g.setPaint(shaderPaint);
- return true;
- }
- } else {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER,
- shaderDelegate.getSupportMessage(),
- null /*throwable*/, null /*data*/);
- }
- }
-
- // if no shader, use the paint color
- g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
-
- return false;
- }
-
- private void setComposite(Graphics2D g, Paint_Delegate paint, boolean usePaintAlpha,
- int forceMode) {
- // the alpha for the composite. Always opaque if the normal paint color is used since
- // it contains the alpha
- int alpha = usePaintAlpha ? paint.getAlpha() : 0xFF;
- if (forceMode != 0) {
- g.setComposite(AlphaComposite.getInstance(forceMode, (float) alpha / 255.f));
- return;
- }
- Mode mode = PorterDuff.intToMode(paint.getPorterDuffMode());
- Composite composite = PorterDuffUtility.getComposite(mode, alpha);
- g.setComposite(composite);
- }
-
- private void mapRect(AffineTransform matrix, RectF dst, RectF src) {
- // array with 4 corners
- float[] corners = new float[] {
- src.left, src.top,
- src.right, src.top,
- src.right, src.bottom,
- src.left, src.bottom,
- };
-
- // apply the transform to them.
- matrix.transform(corners, 0, corners, 0, 4);
-
- // now put the result in the rect. We take the min/max of Xs and min/max of Ys
- dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
- dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
-
- dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
- dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
- }
-
- /**
- * Returns the clip of the oldest snapshot of the stack, appropriately translated to be
- * expressed in the coordinate system of the latest snapshot.
- */
- public Rectangle getOriginalClip() {
- GcSnapshot originalSnapshot = this;
- while (originalSnapshot.mPrevious != null) {
- originalSnapshot = originalSnapshot.mPrevious;
- }
- if (originalSnapshot.mLayers.isEmpty()) {
- return null;
- }
- Graphics2D graphics2D = originalSnapshot.mLayers.get(0).getGraphics();
- Rectangle bounds = graphics2D.getClipBounds();
- if (bounds == null) {
- return null;
- }
- try {
- AffineTransform originalTransform =
- ((Graphics2D) graphics2D.create()).getTransform().createInverse();
- AffineTransform latestTransform = getTransform().createInverse();
- bounds.x += latestTransform.getTranslateX() - originalTransform.getTranslateX();
- bounds.y += latestTransform.getTranslateY() - originalTransform.getTranslateY();
- } catch (NoninvertibleTransformException e) {
- Bridge.getLog().warning(null, "Non invertible transformation", null);
- }
- return bounds;
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
deleted file mode 100644
index 287334c..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- * Copyright (C) 2015 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.layoutlib.bridge.impl;
-
-import com.android.ide.common.rendering.api.HardwareConfig;
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.rendering.api.SessionParams;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.android.RenderParamsFlags;
-import com.android.layoutlib.bridge.bars.AppCompatActionBar;
-import com.android.layoutlib.bridge.bars.BridgeActionBar;
-import com.android.layoutlib.bridge.bars.Config;
-import com.android.layoutlib.bridge.bars.FrameworkActionBar;
-import com.android.layoutlib.bridge.bars.NavigationBar;
-import com.android.layoutlib.bridge.bars.StatusBar;
-import com.android.layoutlib.bridge.bars.TitleBar;
-import com.android.resources.Density;
-import com.android.resources.ResourceType;
-import com.android.resources.ScreenOrientation;
-
-import android.annotation.NonNull;
-import android.graphics.drawable.Drawable;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
-import android.view.AttachInfo_Accessor;
-import android.view.View;
-import android.view.ViewRootImpl;
-import android.view.ViewRootImpl_Accessor;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
-
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.widget.LinearLayout.VERTICAL;
-import static com.android.layoutlib.bridge.impl.ResourceHelper.getBooleanThemeValue;
-
-/**
- * The Layout used to create the system decor.
- *
- * The layout inflated will contain a content frame where the user's layout can be inflated.
- * <pre>
- * +-------------------------------------------------+---+
- * | Status bar | N |
- * +-------------------------------------------------+ a |
- * | Title/Action bar (optional) | v |
- * +-------------------------------------------------+ |
- * | Content, vertical extending | b |
- * | | a |
- * | | r |
- * +-------------------------------------------------+---+
- * </pre>
- * or
- * <pre>
- * +-------------------------------------+
- * | Status bar |
- * +-------------------------------------+
- * | Title/Action bar (optional) |
- * +-------------------------------------+
- * | Content, vertical extending |
- * | |
- * | |
- * +-------------------------------------+
- * | Nav bar |
- * +-------------------------------------+
- * </pre>
- *
- */
-class Layout extends RelativeLayout {
-
- // Theme attributes used for configuring appearance of the system decor.
- private static final String ATTR_WINDOW_FLOATING = "windowIsFloating";
- private static final String ATTR_WINDOW_BACKGROUND = "windowBackground";
- private static final String ATTR_WINDOW_FULL_SCREEN = "windowFullscreen";
- private static final String ATTR_NAV_BAR_HEIGHT = "navigation_bar_height";
- private static final String ATTR_NAV_BAR_WIDTH = "navigation_bar_width";
- private static final String ATTR_STATUS_BAR_HEIGHT = "status_bar_height";
- private static final String ATTR_WINDOW_ACTION_BAR = "windowActionBar";
- private static final String ATTR_ACTION_BAR_SIZE = "actionBarSize";
- private static final String ATTR_WINDOW_NO_TITLE = "windowNoTitle";
- private static final String ATTR_WINDOW_TITLE_SIZE = "windowTitleSize";
- private static final String ATTR_WINDOW_TRANSLUCENT_STATUS = StatusBar.ATTR_TRANSLUCENT;
- private static final String ATTR_WINDOW_TRANSLUCENT_NAV = NavigationBar.ATTR_TRANSLUCENT;
-
- // Default sizes
- private static final int DEFAULT_STATUS_BAR_HEIGHT = 25;
- private static final int DEFAULT_TITLE_BAR_HEIGHT = 25;
- private static final int DEFAULT_NAV_BAR_SIZE = 48;
-
- // Ids assigned to components created. This is so that we can refer to other components in
- // layout params.
- private static final String ID_NAV_BAR = "navBar";
- private static final String ID_STATUS_BAR = "statusBar";
- private static final String ID_TITLE_BAR = "titleBar";
- // Prefix used with the above ids in order to make them unique in framework namespace.
- private static final String ID_PREFIX = "android_layoutlib_";
-
- /**
- * Temporarily store the builder so that it doesn't have to be passed to all methods used
- * during inflation.
- */
- private Builder mBuilder;
-
- /**
- * This holds user's layout.
- */
- private FrameLayout mContentRoot;
-
- public Layout(@NonNull Builder builder) {
- super(builder.mContext);
- mBuilder = builder;
- if (builder.mWindowBackground != null) {
- Drawable d = ResourceHelper.getDrawable(builder.mWindowBackground, builder.mContext);
- setBackground(d);
- }
-
- int simulatedPlatformVersion = getParams().getSimulatedPlatformVersion();
- HardwareConfig hwConfig = getParams().getHardwareConfig();
- Density density = hwConfig.getDensity();
- boolean isRtl = Bridge.isLocaleRtl(getParams().getLocale());
- setLayoutDirection(isRtl? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR);
-
- NavigationBar navBar = null;
- if (mBuilder.hasNavBar()) {
- navBar = createNavBar(getContext(), density, isRtl, getParams().isRtlSupported(),
- simulatedPlatformVersion);
- }
-
- StatusBar statusBar = null;
- if (builder.mStatusBarSize > 0) {
- statusBar = createStatusBar(getContext(), density, isRtl, getParams().isRtlSupported(),
- simulatedPlatformVersion);
- }
-
- View actionBar = null;
- TitleBar titleBar = null;
- if (builder.mActionBarSize > 0) {
- BridgeActionBar bar = createActionBar(getContext(), getParams());
- mContentRoot = bar.getContentRoot();
- actionBar = bar.getRootView();
- } else if (mBuilder.mTitleBarSize > 0) {
- titleBar = createTitleBar(getContext(), getParams().getAppLabel(),
- simulatedPlatformVersion);
- }
-
- addViews(titleBar, mContentRoot == null ? (mContentRoot = createContentFrame()) : actionBar,
- statusBar, navBar);
- // Done with the builder. Don't hold a reference to it.
- mBuilder = null;
- }
-
- @NonNull
- private FrameLayout createContentFrame() {
- FrameLayout contentRoot = new FrameLayout(getContext());
- LayoutParams params = createLayoutParams(MATCH_PARENT, MATCH_PARENT);
- int rule = mBuilder.isNavBarVertical() ? START_OF : ABOVE;
- if (mBuilder.hasSolidNavBar()) {
- params.addRule(rule, getId(ID_NAV_BAR));
- }
- int below = -1;
- if (mBuilder.mActionBarSize <= 0 && mBuilder.mTitleBarSize > 0) {
- below = getId(ID_TITLE_BAR);
- } else if (mBuilder.hasSolidStatusBar()) {
- below = getId(ID_STATUS_BAR);
- }
- if (below != -1) {
- params.addRule(BELOW, below);
- }
- contentRoot.setLayoutParams(params);
- return contentRoot;
- }
-
- @NonNull
- private LayoutParams createLayoutParams(int width, int height) {
- DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
- if (width > 0) {
- width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, width, metrics);
- }
- if (height > 0) {
- height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height, metrics);
- }
- return new LayoutParams(width, height);
- }
-
- @NonNull
- public FrameLayout getContentRoot() {
- return mContentRoot;
- }
-
- @NonNull
- private SessionParams getParams() {
- return mBuilder.mParams;
- }
-
- @NonNull
- @Override
- public BridgeContext getContext(){
- return (BridgeContext) super.getContext();
- }
-
- /**
- * @param isRtl whether the current locale is an RTL locale.
- * @param isRtlSupported whether the applications supports RTL (i.e. has supportsRtl=true
- * in the manifest and targetSdkVersion >= 17.
- */
- @NonNull
- private StatusBar createStatusBar(BridgeContext context, Density density, boolean isRtl,
- boolean isRtlSupported, int simulatedPlatformVersion) {
- StatusBar statusBar =
- new StatusBar(context, density, isRtl, isRtlSupported, simulatedPlatformVersion);
- LayoutParams params = createLayoutParams(MATCH_PARENT, mBuilder.mStatusBarSize);
- if (mBuilder.isNavBarVertical()) {
- params.addRule(START_OF, getId(ID_NAV_BAR));
- }
- statusBar.setLayoutParams(params);
- statusBar.setId(getId(ID_STATUS_BAR));
- return statusBar;
- }
-
- private BridgeActionBar createActionBar(@NonNull BridgeContext context,
- @NonNull SessionParams params) {
- boolean isMenu = "menu".equals(params.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG));
-
- BridgeActionBar actionBar;
- if (context.isAppCompatTheme() && !isMenu) {
- actionBar = new AppCompatActionBar(context, params);
- } else {
- actionBar = new FrameworkActionBar(context, params);
- }
- LayoutParams layoutParams = createLayoutParams(MATCH_PARENT, MATCH_PARENT);
- int rule = mBuilder.isNavBarVertical() ? START_OF : ABOVE;
- if (mBuilder.hasSolidNavBar()) {
- layoutParams.addRule(rule, getId(ID_NAV_BAR));
- }
- if (mBuilder.hasSolidStatusBar()) {
- layoutParams.addRule(BELOW, getId(ID_STATUS_BAR));
- }
- actionBar.getRootView().setLayoutParams(layoutParams);
- actionBar.createMenuPopup();
- return actionBar;
- }
-
- @NonNull
- private TitleBar createTitleBar(BridgeContext context, String title,
- int simulatedPlatformVersion) {
- TitleBar titleBar = new TitleBar(context, title, simulatedPlatformVersion);
- LayoutParams params = createLayoutParams(MATCH_PARENT, mBuilder.mTitleBarSize);
- if (mBuilder.hasSolidStatusBar()) {
- params.addRule(BELOW, getId(ID_STATUS_BAR));
- }
- if (mBuilder.isNavBarVertical() && mBuilder.hasSolidNavBar()) {
- params.addRule(START_OF, getId(ID_NAV_BAR));
- }
- titleBar.setLayoutParams(params);
- titleBar.setId(getId(ID_TITLE_BAR));
- return titleBar;
- }
-
- /**
- * @param isRtl whether the current locale is an RTL locale.
- * @param isRtlSupported whether the applications supports RTL (i.e. has supportsRtl=true
- * in the manifest and targetSdkVersion >= 17.
- */
- @NonNull
- private NavigationBar createNavBar(BridgeContext context, Density density, boolean isRtl,
- boolean isRtlSupported, int simulatedPlatformVersion) {
- int orientation = mBuilder.mNavBarOrientation;
- int size = mBuilder.mNavBarSize;
- NavigationBar navBar = new NavigationBar(context, density, orientation, isRtl,
- isRtlSupported, simulatedPlatformVersion);
- boolean isVertical = mBuilder.isNavBarVertical();
- int w = isVertical ? size : MATCH_PARENT;
- int h = isVertical ? MATCH_PARENT : size;
- LayoutParams params = createLayoutParams(w, h);
- params.addRule(isVertical ? ALIGN_PARENT_END : ALIGN_PARENT_BOTTOM);
- navBar.setLayoutParams(params);
- navBar.setId(getId(ID_NAV_BAR));
- return navBar;
- }
-
- private void addViews(@NonNull View... views) {
- for (View view : views) {
- if (view != null) {
- addView(view);
- }
- }
- }
-
- private int getId(String name) {
- return Bridge.getResourceId(ResourceType.ID, ID_PREFIX + name);
- }
-
- @SuppressWarnings("deprecation")
- @Override
- public void requestFitSystemWindows() {
- // The framework call would usually bubble up to ViewRootImpl but, in layoutlib, Layout will
- // act as view root for most purposes. That way, we can also save going through the Handler
- // to dispatch the new applied insets.
- ViewRootImpl root = AttachInfo_Accessor.getRootView(this);
- if (root != null) {
- ViewRootImpl_Accessor.dispatchApplyInsets(root, this);
- }
- }
-
- /**
- * A helper class to help initialize the Layout.
- */
- static class Builder {
- @NonNull
- private final SessionParams mParams;
- @NonNull
- private final BridgeContext mContext;
- private final RenderResources mResources;
-
- private final boolean mWindowIsFloating;
- private ResourceValue mWindowBackground;
- private int mStatusBarSize;
- private int mNavBarSize;
- private int mNavBarOrientation;
- private int mActionBarSize;
- private int mTitleBarSize;
- private boolean mTranslucentStatus;
- private boolean mTranslucentNav;
-
- public Builder(@NonNull SessionParams params, @NonNull BridgeContext context) {
- mParams = params;
- mContext = context;
- mResources = mParams.getResources();
- mWindowIsFloating = getBooleanThemeValue(mResources, ATTR_WINDOW_FLOATING, true, true);
-
- findBackground();
-
- if (!mParams.isForceNoDecor()) {
- findStatusBar();
- findActionBar();
- findNavBar();
- }
- }
-
- private void findBackground() {
- if (!mParams.isBgColorOverridden()) {
- mWindowBackground = mResources.findItemInTheme(ATTR_WINDOW_BACKGROUND, true);
- mWindowBackground = mResources.resolveResValue(mWindowBackground);
- }
- }
-
- private void findStatusBar() {
- boolean windowFullScreen =
- getBooleanThemeValue(mResources, ATTR_WINDOW_FULL_SCREEN, true, false);
- if (!windowFullScreen && !mWindowIsFloating) {
- mStatusBarSize =
- getDimension(ATTR_STATUS_BAR_HEIGHT, true, DEFAULT_STATUS_BAR_HEIGHT);
- mTranslucentStatus = getBooleanThemeValue(mResources,
- ATTR_WINDOW_TRANSLUCENT_STATUS, true, false);
- }
- }
-
- private void findActionBar() {
- if (mWindowIsFloating) {
- return;
- }
- // Check if an actionbar is needed
- boolean isMenu = "menu".equals(mParams.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG));
- boolean windowActionBar = isMenu ||
- getBooleanThemeValue(mResources, ATTR_WINDOW_ACTION_BAR,
- !mContext.isAppCompatTheme(), true);
- if (windowActionBar) {
- mActionBarSize = getDimension(ATTR_ACTION_BAR_SIZE, true, DEFAULT_TITLE_BAR_HEIGHT);
- } else {
- // Maybe the gingerbread era title bar is needed
- boolean windowNoTitle =
- getBooleanThemeValue(mResources, ATTR_WINDOW_NO_TITLE, true, false);
- if (!windowNoTitle) {
- mTitleBarSize =
- getDimension(ATTR_WINDOW_TITLE_SIZE, true, DEFAULT_TITLE_BAR_HEIGHT);
- }
- }
- }
-
- private void findNavBar() {
- if (hasSoftwareButtons() && !mWindowIsFloating) {
-
- // get orientation
- HardwareConfig hwConfig = mParams.getHardwareConfig();
- boolean barOnBottom = true;
-
- if (hwConfig.getOrientation() == ScreenOrientation.LANDSCAPE) {
- int shortSize = hwConfig.getScreenHeight();
- int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT /
- hwConfig.getDensity().getDpiValue();
-
- // 0-599dp: "phone" UI with bar on the side
- // 600+dp: "tablet" UI with bar on the bottom
- barOnBottom = shortSizeDp >= 600;
- }
-
- mNavBarOrientation = barOnBottom ? LinearLayout.HORIZONTAL : VERTICAL;
- mNavBarSize = getDimension(barOnBottom ? ATTR_NAV_BAR_HEIGHT : ATTR_NAV_BAR_WIDTH,
- true, DEFAULT_NAV_BAR_SIZE);
- mTranslucentNav = getBooleanThemeValue(mResources,
- ATTR_WINDOW_TRANSLUCENT_NAV, true, false);
- }
- }
-
- @SuppressWarnings("SameParameterValue")
- private int getDimension(String attr, boolean isFramework, int defaultValue) {
- ResourceValue value = mResources.findItemInTheme(attr, isFramework);
- value = mResources.resolveResValue(value);
- if (value != null) {
- TypedValue typedValue = ResourceHelper.getValue(attr, value.getValue(), true);
- if (typedValue != null) {
- return (int) typedValue.getDimension(mContext.getMetrics());
- }
- }
- return defaultValue;
- }
-
- private boolean hasSoftwareButtons() {
- return mParams.getHardwareConfig().hasSoftwareButtons();
- }
-
- /**
- * Return true if the nav bar is present and not translucent
- */
- private boolean hasSolidNavBar() {
- return hasNavBar() && !mTranslucentNav;
- }
-
- /**
- * Return true if the status bar is present and not translucent
- */
- private boolean hasSolidStatusBar() {
- return hasStatusBar() && !mTranslucentStatus;
- }
-
- private boolean hasNavBar() {
- return Config.showOnScreenNavBar(mParams.getSimulatedPlatformVersion()) &&
- hasSoftwareButtons() && mNavBarSize > 0;
- }
-
- private boolean hasStatusBar() {
- return mStatusBarSize > 0;
- }
-
- /**
- * Return true if the nav bar is present and is vertical.
- */
- private boolean isNavBarVertical() {
- return hasNavBar() && mNavBarOrientation == VERTICAL;
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutParserWrapper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutParserWrapper.java
deleted file mode 100644
index 71e7fd2..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutParserWrapper.java
+++ /dev/null
@@ -1,377 +0,0 @@
-/*
- * Copyright (C) 2015 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.layoutlib.bridge.impl;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.annotation.Nullable;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A wrapper around XmlPullParser that can peek forward to inspect if the file is a data-binding
- * layout and some parts need to be stripped.
- */
-public class LayoutParserWrapper implements XmlPullParser {
-
- // Data binding constants.
- private static final String TAG_LAYOUT = "layout";
- private static final String TAG_DATA = "data";
- private static final String DEFAULT = "default=";
-
- private final XmlPullParser mDelegate;
-
- // Storage for peeked values.
- private boolean mPeeked;
- private int mEventType;
- private int mDepth;
- private int mNext;
- private List<Attribute> mAttributes;
- private String mText;
- private String mName;
-
- // Used to end the document before the actual parser ends.
- private int mFinalDepth = -1;
- private boolean mEndNow;
-
- public LayoutParserWrapper(XmlPullParser delegate) {
- mDelegate = delegate;
- }
-
- public LayoutParserWrapper peekTillLayoutStart() throws IOException, XmlPullParserException {
- final int STATE_LAYOUT_NOT_STARTED = 0; // <layout> tag not encountered yet.
- final int STATE_ROOT_NOT_STARTED = 1; // the main view root not found yet.
- final int STATE_INSIDE_DATA = 2; // START_TAG for <data> found, but not END_TAG.
-
- int state = STATE_LAYOUT_NOT_STARTED;
- int dataDepth = -1; // depth of the <data> tag. Should be two.
- while (true) {
- int peekNext = peekNext();
- switch (peekNext) {
- case START_TAG:
- if (state == STATE_LAYOUT_NOT_STARTED) {
- if (mName.equals(TAG_LAYOUT)) {
- state = STATE_ROOT_NOT_STARTED;
- } else {
- return this; // no layout tag in the file.
- }
- } else if (state == STATE_ROOT_NOT_STARTED) {
- if (mName.equals(TAG_DATA)) {
- state = STATE_INSIDE_DATA;
- dataDepth = mDepth;
- } else {
- mFinalDepth = mDepth;
- return this;
- }
- }
- break;
- case END_TAG:
- if (state == STATE_INSIDE_DATA) {
- if (mDepth <= dataDepth) {
- state = STATE_ROOT_NOT_STARTED;
- }
- }
- break;
- case END_DOCUMENT:
- // No layout start found.
- return this;
- }
- // consume the peeked tag.
- next();
- }
- }
-
- private int peekNext() throws IOException, XmlPullParserException {
- if (mPeeked) {
- return mNext;
- }
- mEventType = mDelegate.getEventType();
- mNext = mDelegate.next();
- if (mEventType == START_TAG) {
- int count = mDelegate.getAttributeCount();
- mAttributes = count > 0 ? new ArrayList<Attribute>(count) :
- Collections.<Attribute>emptyList();
- for (int i = 0; i < count; i++) {
- mAttributes.add(new Attribute(mDelegate.getAttributeNamespace(i),
- mDelegate.getAttributeName(i), mDelegate.getAttributeValue(i)));
- }
- }
- mDepth = mDelegate.getDepth();
- mText = mDelegate.getText();
- mName = mDelegate.getName();
- mPeeked = true;
- return mNext;
- }
-
- private void reset() {
- mAttributes = null;
- mText = null;
- mName = null;
- mPeeked = false;
- }
-
- @Override
- public int next() throws XmlPullParserException, IOException {
- int returnValue;
- int depth;
- if (mPeeked) {
- returnValue = mNext;
- depth = mDepth;
- reset();
- } else if (mEndNow) {
- return END_DOCUMENT;
- } else {
- returnValue = mDelegate.next();
- depth = getDepth();
- }
- if (returnValue == END_TAG && depth <= mFinalDepth) {
- mEndNow = true;
- }
- return returnValue;
- }
-
- @Override
- public int getEventType() throws XmlPullParserException {
- return mPeeked ? mEventType : mDelegate.getEventType();
- }
-
- @Override
- public int getDepth() {
- return mPeeked ? mDepth : mDelegate.getDepth();
- }
-
- @Override
- public String getName() {
- return mPeeked ? mName : mDelegate.getName();
- }
-
- @Override
- public String getText() {
- return mPeeked ? mText : mDelegate.getText();
- }
-
- @Override
- public String getAttributeValue(@Nullable String namespace, String name) {
- String returnValue = null;
- if (mPeeked) {
- if (mAttributes == null) {
- if (mEventType != START_TAG) {
- throw new IndexOutOfBoundsException("getAttributeValue() called when not at START_TAG.");
- } else {
- return null;
- }
- } else {
- for (Attribute attribute : mAttributes) {
- //noinspection StringEquality for nullness check.
- if (attribute.name.equals(name) && (attribute.namespace == namespace ||
- attribute.namespace != null && attribute.namespace.equals(namespace))) {
- returnValue = attribute.value;
- break;
- }
- }
- }
- } else {
- returnValue = mDelegate.getAttributeValue(namespace, name);
- }
- // Check if the value is bound via data-binding, if yes get the default value.
- if (returnValue != null && mFinalDepth >= 0 && returnValue.startsWith("@{")) {
- // TODO: Improve the detection of default keyword.
- int i = returnValue.lastIndexOf(DEFAULT);
- return i > 0 ? returnValue.substring(i + DEFAULT.length(), returnValue.length() - 1)
- : null;
- }
- return returnValue;
- }
-
- private static class Attribute {
- @Nullable
- public final String namespace;
- public final String name;
- public final String value;
-
- public Attribute(@Nullable String namespace, String name, String value) {
- this.namespace = namespace;
- this.name = name;
- this.value = value;
- }
- }
-
- // Not affected by peeking.
-
- @Override
- public void setFeature(String s, boolean b) throws XmlPullParserException {
- mDelegate.setFeature(s, b);
- }
-
- @Override
- public void setProperty(String s, Object o) throws XmlPullParserException {
- mDelegate.setProperty(s, o);
- }
-
- @Override
- public void setInput(InputStream inputStream, String s) throws XmlPullParserException {
- mDelegate.setInput(inputStream, s);
- }
-
- @Override
- public void setInput(Reader reader) throws XmlPullParserException {
- mDelegate.setInput(reader);
- }
-
- @Override
- public String getInputEncoding() {
- return mDelegate.getInputEncoding();
- }
-
- @Override
- public String getNamespace(String s) {
- return mDelegate.getNamespace(s);
- }
-
- @Override
- public String getPositionDescription() {
- return mDelegate.getPositionDescription();
- }
-
- @Override
- public int getLineNumber() {
- return mDelegate.getLineNumber();
- }
-
- @Override
- public String getNamespace() {
- return mDelegate.getNamespace();
- }
-
- @Override
- public int getColumnNumber() {
- return mDelegate.getColumnNumber();
- }
-
- // -- We don't care much about the methods that follow.
-
- @Override
- public void require(int i, String s, String s1) throws XmlPullParserException, IOException {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public boolean getFeature(String s) {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public void defineEntityReplacementText(String s, String s1) throws XmlPullParserException {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public Object getProperty(String s) {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public int nextToken() throws XmlPullParserException, IOException {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public int getNamespaceCount(int i) throws XmlPullParserException {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public String getNamespacePrefix(int i) throws XmlPullParserException {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public String getNamespaceUri(int i) throws XmlPullParserException {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public boolean isWhitespace() throws XmlPullParserException {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public char[] getTextCharacters(int[] ints) {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public String getPrefix() {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public boolean isEmptyElementTag() throws XmlPullParserException {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public int getAttributeCount() {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public String getAttributeNamespace(int i) {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public String getAttributeName(int i) {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public String getAttributePrefix(int i) {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public String getAttributeType(int i) {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public boolean isAttributeDefault(int i) {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public String getAttributeValue(int i) {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public String nextText() throws XmlPullParserException, IOException {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-
- @Override
- public int nextTag() throws XmlPullParserException, IOException {
- throw new UnsupportedOperationException("Only few parser methods are supported.");
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
deleted file mode 100644
index 1ae9cb6..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2011 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.layoutlib.bridge.impl;
-
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * A factory for {@link XmlPullParser}.
- *
- */
-public class ParserFactory {
-
- public final static boolean LOG_PARSER = false;
-
- // Used to get a new XmlPullParser from the client.
- @Nullable
- private static com.android.ide.common.rendering.api.ParserFactory sParserFactory;
-
- public static void setParserFactory(
- @Nullable com.android.ide.common.rendering.api.ParserFactory parserFactory) {
- sParserFactory = parserFactory;
- }
-
- @NonNull
- public static XmlPullParser create(@NonNull File f)
- throws XmlPullParserException, FileNotFoundException {
- return create(f, false);
- }
-
- public static XmlPullParser create(@NonNull File f, boolean isLayout)
- throws XmlPullParserException, FileNotFoundException {
- InputStream stream = new FileInputStream(f);
- return create(stream, f.getName(), f.length(), isLayout);
- }
- @NonNull
- public static XmlPullParser create(@NonNull InputStream stream, @Nullable String name)
- throws XmlPullParserException {
- return create(stream, name, -1, false);
- }
-
- @NonNull
- private static XmlPullParser create(@NonNull InputStream stream, @Nullable String name,
- long size, boolean isLayout) throws XmlPullParserException {
- XmlPullParser parser = instantiateParser(name);
-
- stream = readAndClose(stream, name, size);
-
- parser.setInput(stream, null);
- if (isLayout) {
- try {
- return new LayoutParserWrapper(parser).peekTillLayoutStart();
- } catch (IOException e) {
- throw new XmlPullParserException(null, parser, e);
- }
- }
- return parser;
- }
-
- @NonNull
- public static XmlPullParser instantiateParser(@Nullable String name)
- throws XmlPullParserException {
- if (sParserFactory == null) {
- throw new XmlPullParserException("ParserFactory not initialized.");
- }
- XmlPullParser parser = sParserFactory.createParser(name);
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
- return parser;
- }
-
- @NonNull
- private static InputStream readAndClose(@NonNull InputStream stream, @Nullable String name,
- long size) throws XmlPullParserException {
- // just a sanity check. It's doubtful we'll have such big files!
- if (size > Integer.MAX_VALUE) {
- throw new XmlPullParserException("File " + name + " is too big to be parsed");
- }
- int intSize = (int) size;
-
- // create a buffered reader to facilitate reading.
- BufferedInputStream bufferedStream = new BufferedInputStream(stream);
- try {
- int avail;
- if (intSize != -1) {
- avail = intSize;
- } else {
- // get the size to read.
- avail = bufferedStream.available();
- }
-
- // create the initial buffer and read it.
- byte[] buffer = new byte[avail];
- int read = stream.read(buffer);
-
- // this is the easy case.
- if (read == intSize) {
- return new ByteArrayInputStream(buffer);
- }
-
- // check if there is more to read (read() does not necessarily read all that
- // available() returned!)
- while ((avail = bufferedStream.available()) > 0) {
- if (read + avail > buffer.length) {
- // just allocate what is needed. We're mostly reading small files
- // so it shouldn't be too problematic.
- byte[] moreBuffer = new byte[read + avail];
- System.arraycopy(buffer, 0, moreBuffer, 0, read);
- buffer = moreBuffer;
- }
-
- read += stream.read(buffer, read, avail);
- }
-
- // return a new stream encapsulating this buffer.
- return new ByteArrayInputStream(buffer);
-
- } catch (IOException e) {
- throw new XmlPullParserException("Failed to read " + name, null, e);
- } finally {
- try {
- bufferedStream.close();
- } catch (IOException ignored) {
- }
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PlayAnimationThread.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PlayAnimationThread.java
deleted file mode 100644
index 7b70180..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PlayAnimationThread.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2010 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.layoutlib.bridge.impl;
-
-import com.android.ide.common.rendering.api.IAnimationListener;
-import com.android.ide.common.rendering.api.Result;
-import com.android.ide.common.rendering.api.Result.Status;
-
-import android.animation.AnimationThread;
-import android.animation.Animator;
-
-public class PlayAnimationThread extends AnimationThread {
-
- private final Animator mAnimator;
-
- public PlayAnimationThread(Animator animator, RenderSessionImpl scene, String animName,
- IAnimationListener listener) {
- super(scene, animName, listener);
- mAnimator = animator;
- }
-
- @Override
- public Result preAnimation() {
- // start the animation. This will send a message to the handler right away, so
- // the queue is filled when this method returns.
- mAnimator.start();
-
- return Status.SUCCESS.createResult();
- }
-
- @Override
- public void postAnimation() {
- // nothing to be done.
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
deleted file mode 100644
index 70e2eb1..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2014 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.layoutlib.bridge.impl;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-
-import android.graphics.BlendComposite;
-import android.graphics.BlendComposite.BlendingMode;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffColorFilter_Delegate;
-
-import java.awt.AlphaComposite;
-import java.awt.Composite;
-
-/**
- * Provides various utility methods for {@link PorterDuffColorFilter_Delegate}.
- */
-public final class PorterDuffUtility {
-
- private static final int MODES_COUNT = Mode.values().length;
-
- // Make the class non-instantiable.
- private PorterDuffUtility() {
- }
-
- /**
- * Convert the porterDuffMode from the framework to its corresponding enum. This defaults to
- * {@link Mode#SRC_OVER} for invalid modes.
- */
- public static Mode getPorterDuffMode(int porterDuffMode) {
- if (porterDuffMode >= 0 && porterDuffMode < MODES_COUNT) {
- return PorterDuff.intToMode(porterDuffMode);
- }
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- String.format("Unknown PorterDuff.Mode: %1$d", porterDuffMode), null);
- assert false;
- return Mode.SRC_OVER;
- }
-
- /**
- * A utility method to get the {@link Composite} that represents the filter for the given
- * PorterDuff mode and the alpha. Defaults to {@link Mode#SRC_OVER} for invalid modes.
- */
- public static Composite getComposite(Mode mode, int alpha255) {
- float alpha1 = alpha255 != 0xFF ? alpha255 / 255.f : 1.f;
- switch (mode) {
- case CLEAR:
- return AlphaComposite.getInstance(AlphaComposite.CLEAR, alpha1);
- case SRC:
- return AlphaComposite.getInstance(AlphaComposite.SRC, alpha1);
- case DST:
- return AlphaComposite.getInstance(AlphaComposite.DST, alpha1);
- case SRC_OVER:
- return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha1);
- case DST_OVER:
- return AlphaComposite.getInstance(AlphaComposite.DST_OVER, alpha1);
- case SRC_IN:
- return AlphaComposite.getInstance(AlphaComposite.SRC_IN, alpha1);
- case DST_IN:
- return AlphaComposite.getInstance(AlphaComposite.DST_IN, alpha1);
- case SRC_OUT:
- return AlphaComposite.getInstance(AlphaComposite.SRC_OUT, alpha1);
- case DST_OUT:
- return AlphaComposite.getInstance(AlphaComposite.DST_OUT, alpha1);
- case SRC_ATOP:
- return AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha1);
- case DST_ATOP:
- return AlphaComposite.getInstance(AlphaComposite.DST_ATOP, alpha1);
- case XOR:
- return AlphaComposite.getInstance(AlphaComposite.XOR, alpha1);
- case DARKEN:
- return BlendComposite.getInstance(BlendingMode.DARKEN, alpha1);
- case LIGHTEN:
- return BlendComposite.getInstance(BlendingMode.LIGHTEN, alpha1);
- case MULTIPLY:
- return BlendComposite.getInstance(BlendingMode.MULTIPLY, alpha1);
- case SCREEN:
- return BlendComposite.getInstance(BlendingMode.SCREEN, alpha1);
- case ADD:
- return BlendComposite.getInstance(BlendingMode.ADD, alpha1);
- case OVERLAY:
- return BlendComposite.getInstance(BlendingMode.OVERLAY, alpha1);
- default:
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
- String.format("Unsupported PorterDuff Mode: %1$s", mode.name()),
- null, null /*data*/);
-
- return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha1);
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
deleted file mode 100644
index 0c53753..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
+++ /dev/null
@@ -1,410 +0,0 @@
-/*
- * Copyright (C) 2010 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.layoutlib.bridge.impl;
-
-import com.android.ide.common.rendering.api.HardwareConfig;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.RenderParams;
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.RenderResources.FrameworkResourceIdProvider;
-import com.android.ide.common.rendering.api.Result;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.resources.Density;
-import com.android.resources.ResourceType;
-import com.android.resources.ScreenOrientation;
-import com.android.resources.ScreenRound;
-import com.android.resources.ScreenSize;
-
-import android.content.res.Configuration;
-import android.os.HandlerThread_Delegate;
-import android.util.DisplayMetrics;
-import android.view.ViewConfiguration_Accessor;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodManager_Accessor;
-
-import java.util.Locale;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.ReentrantLock;
-
-import static com.android.ide.common.rendering.api.Result.Status.ERROR_LOCK_INTERRUPTED;
-import static com.android.ide.common.rendering.api.Result.Status.ERROR_TIMEOUT;
-import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
-
-/**
- * Base class for rendering action.
- *
- * It provides life-cycle methods to init and stop the rendering.
- * The most important methods are:
- * {@link #init(long)} and {@link #acquire(long)} to start a rendering and {@link #release()}
- * after the rendering.
- *
- *
- * @param <T> the {@link RenderParams} implementation
- *
- */
-public abstract class RenderAction<T extends RenderParams> extends FrameworkResourceIdProvider {
-
- /**
- * The current context being rendered. This is set through {@link #acquire(long)} and
- * {@link #init(long)}, and unset in {@link #release()}.
- */
- private static BridgeContext sCurrentContext = null;
-
- private final T mParams;
-
- private BridgeContext mContext;
-
- /**
- * Creates a renderAction.
- * <p>
- * This <b>must</b> be followed by a call to {@link RenderAction#init(long)}, which act as a
- * call to {@link RenderAction#acquire(long)}
- *
- * @param params the RenderParams. This must be a copy that the action can keep
- *
- */
- protected RenderAction(T params) {
- mParams = params;
- }
-
- /**
- * Initializes and acquires the scene, creating various Android objects such as context,
- * inflater, and parser.
- *
- * @param timeout the time to wait if another rendering is happening.
- *
- * @return whether the scene was prepared
- *
- * @see #acquire(long)
- * @see #release()
- */
- public Result init(long timeout) {
- // acquire the lock. if the result is null, lock was just acquired, otherwise, return
- // the result.
- Result result = acquireLock(timeout);
- if (result != null) {
- return result;
- }
-
- HardwareConfig hardwareConfig = mParams.getHardwareConfig();
-
- // setup the display Metrics.
- DisplayMetrics metrics = new DisplayMetrics();
- metrics.densityDpi = metrics.noncompatDensityDpi =
- hardwareConfig.getDensity().getDpiValue();
-
- metrics.density = metrics.noncompatDensity =
- metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT;
-
- metrics.scaledDensity = metrics.noncompatScaledDensity = metrics.density;
-
- metrics.widthPixels = metrics.noncompatWidthPixels = hardwareConfig.getScreenWidth();
- metrics.heightPixels = metrics.noncompatHeightPixels = hardwareConfig.getScreenHeight();
- metrics.xdpi = metrics.noncompatXdpi = hardwareConfig.getXdpi();
- metrics.ydpi = metrics.noncompatYdpi = hardwareConfig.getYdpi();
-
- RenderResources resources = mParams.getResources();
-
- // build the context
- mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources,
- mParams.getAssets(), mParams.getLayoutlibCallback(), getConfiguration(mParams),
- mParams.getTargetSdkVersion(), mParams.isRtlSupported());
-
- setUp();
-
- return SUCCESS.createResult();
- }
-
- /**
- * Prepares the scene for action.
- * <p>
- * This call is blocking if another rendering/inflating is currently happening, and will return
- * whether the preparation worked.
- *
- * The preparation can fail if another rendering took too long and the timeout was elapsed.
- *
- * More than one call to this from the same thread will have no effect and will return
- * {@link Result.Status#SUCCESS}.
- *
- * After scene actions have taken place, only one call to {@link #release()} must be
- * done.
- *
- * @param timeout the time to wait if another rendering is happening.
- *
- * @return whether the scene was prepared
- *
- * @see #release()
- *
- * @throws IllegalStateException if {@link #init(long)} was never called.
- */
- public Result acquire(long timeout) {
- if (mContext == null) {
- throw new IllegalStateException("After scene creation, #init() must be called");
- }
-
- // acquire the lock. if the result is null, lock was just acquired, otherwise, return
- // the result.
- Result result = acquireLock(timeout);
- if (result != null) {
- return result;
- }
-
- setUp();
-
- return SUCCESS.createResult();
- }
-
- /**
- * Acquire the lock so that the scene can be acted upon.
- * <p>
- * This returns null if the lock was just acquired, otherwise it returns
- * {@link Result.Status#SUCCESS} if the lock already belonged to that thread, or another
- * instance (see {@link Result#getStatus()}) if an error occurred.
- *
- * @param timeout the time to wait if another rendering is happening.
- * @return null if the lock was just acquire or another result depending on the state.
- *
- * @throws IllegalStateException if the current context is different than the one owned by
- * the scene.
- */
- private Result acquireLock(long timeout) {
- ReentrantLock lock = Bridge.getLock();
- if (!lock.isHeldByCurrentThread()) {
- try {
- boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
-
- if (!acquired) {
- return ERROR_TIMEOUT.createResult();
- }
- } catch (InterruptedException e) {
- return ERROR_LOCK_INTERRUPTED.createResult();
- }
- } else {
- // This thread holds the lock already. Checks that this wasn't for a different context.
- // If this is called by init, mContext will be null and so should sCurrentContext
- // anyway
- if (mContext != sCurrentContext) {
- throw new IllegalStateException("Acquiring different scenes from same thread without releases");
- }
- return SUCCESS.createResult();
- }
-
- return null;
- }
-
- /**
- * Cleans up the scene after an action.
- */
- public void release() {
- ReentrantLock lock = Bridge.getLock();
-
- // with the use of finally blocks, it is possible to find ourself calling this
- // without a successful call to prepareScene. This test makes sure that unlock() will
- // not throw IllegalMonitorStateException.
- if (lock.isHeldByCurrentThread()) {
- tearDown();
- lock.unlock();
- }
- }
-
- /**
- * Sets up the session for rendering.
- * <p/>
- * The counterpart is {@link #tearDown()}.
- */
- private void setUp() {
- // setup the ParserFactory
- ParserFactory.setParserFactory(mParams.getLayoutlibCallback().getParserFactory());
-
- // make sure the Resources object references the context (and other objects) for this
- // scene
- mContext.initResources();
- sCurrentContext = mContext;
-
- // create an InputMethodManager
- InputMethodManager.getInstance();
-
- LayoutLog currentLog = mParams.getLog();
- Bridge.setLog(currentLog);
- mContext.getRenderResources().setFrameworkResourceIdProvider(this);
- mContext.getRenderResources().setLogger(currentLog);
- }
-
- /**
- * Tear down the session after rendering.
- * <p/>
- * The counterpart is {@link #setUp()}.
- */
- private void tearDown() {
- // The context may be null, if there was an error during init().
- if (mContext != null) {
- // Make sure to remove static references, otherwise we could not unload the lib
- mContext.disposeResources();
- }
-
- if (sCurrentContext != null) {
- // quit HandlerThread created during this session.
- HandlerThread_Delegate.cleanUp(sCurrentContext);
- }
-
- // clear the stored ViewConfiguration since the map is per density and not per context.
- ViewConfiguration_Accessor.clearConfigurations();
-
- // remove the InputMethodManager
- InputMethodManager_Accessor.resetInstance();
-
- sCurrentContext = null;
-
- Bridge.setLog(null);
- if (mContext != null) {
- mContext.getRenderResources().setFrameworkResourceIdProvider(null);
- mContext.getRenderResources().setLogger(null);
- }
- ParserFactory.setParserFactory(null);
- }
-
- public static BridgeContext getCurrentContext() {
- return sCurrentContext;
- }
-
- protected T getParams() {
- return mParams;
- }
-
- protected BridgeContext getContext() {
- return mContext;
- }
-
- /**
- * Returns the log associated with the session.
- * @return the log or null if there are none.
- */
- public LayoutLog getLog() {
- if (mParams != null) {
- return mParams.getLog();
- }
-
- return null;
- }
-
- /**
- * Checks that the lock is owned by the current thread and that the current context is the one
- * from this scene.
- *
- * @throws IllegalStateException if the current context is different than the one owned by
- * the scene, or if {@link #acquire(long)} was not called.
- */
- protected void checkLock() {
- ReentrantLock lock = Bridge.getLock();
- if (!lock.isHeldByCurrentThread()) {
- throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
- }
- if (sCurrentContext != mContext) {
- throw new IllegalStateException("Thread acquired a scene but is rendering a different one");
- }
- }
-
- // VisibleForTesting
- public static Configuration getConfiguration(RenderParams params) {
- Configuration config = new Configuration();
-
- HardwareConfig hardwareConfig = params.getHardwareConfig();
-
- ScreenSize screenSize = hardwareConfig.getScreenSize();
- if (screenSize != null) {
- switch (screenSize) {
- case SMALL:
- config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_SMALL;
- break;
- case NORMAL:
- config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_NORMAL;
- break;
- case LARGE:
- config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_LARGE;
- break;
- case XLARGE:
- config.screenLayout |= Configuration.SCREENLAYOUT_SIZE_XLARGE;
- break;
- }
- }
-
- Density density = hardwareConfig.getDensity();
- if (density == null) {
- density = Density.MEDIUM;
- }
-
- config.screenWidthDp = hardwareConfig.getScreenWidth() / density.getDpiValue();
- config.screenHeightDp = hardwareConfig.getScreenHeight() / density.getDpiValue();
- if (config.screenHeightDp < config.screenWidthDp) {
- //noinspection SuspiciousNameCombination
- config.smallestScreenWidthDp = config.screenHeightDp;
- } else {
- config.smallestScreenWidthDp = config.screenWidthDp;
- }
- config.densityDpi = density.getDpiValue();
-
- // never run in compat mode:
- config.compatScreenWidthDp = config.screenWidthDp;
- config.compatScreenHeightDp = config.screenHeightDp;
-
- ScreenOrientation orientation = hardwareConfig.getOrientation();
- if (orientation != null) {
- switch (orientation) {
- case PORTRAIT:
- config.orientation = Configuration.ORIENTATION_PORTRAIT;
- break;
- case LANDSCAPE:
- config.orientation = Configuration.ORIENTATION_LANDSCAPE;
- break;
- case SQUARE:
- //noinspection deprecation
- config.orientation = Configuration.ORIENTATION_SQUARE;
- break;
- }
- } else {
- config.orientation = Configuration.ORIENTATION_UNDEFINED;
- }
-
- ScreenRound roundness = hardwareConfig.getScreenRoundness();
- if (roundness != null) {
- switch (roundness) {
- case ROUND:
- config.screenLayout |= Configuration.SCREENLAYOUT_ROUND_YES;
- break;
- case NOTROUND:
- config.screenLayout |= Configuration.SCREENLAYOUT_ROUND_NO;
- }
- } else {
- config.screenLayout |= Configuration.SCREENLAYOUT_ROUND_UNDEFINED;
- }
- String locale = params.getLocale();
- if (locale != null && !locale.isEmpty()) config.locale = new Locale(locale);
-
- // TODO: fill in more config info.
-
- return config;
- }
-
-
- // --- FrameworkResourceIdProvider methods
-
- @Override
- public Integer getId(ResourceType resType, String resName) {
- return Bridge.getResourceId(resType, resName);
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
deleted file mode 100644
index d797eec..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2011 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.layoutlib.bridge.impl;
-
-import com.android.ide.common.rendering.api.DrawableParams;
-import com.android.ide.common.rendering.api.HardwareConfig;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.rendering.api.Result;
-import com.android.ide.common.rendering.api.Result.Status;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.android.RenderParamsFlags;
-import com.android.resources.ResourceType;
-
-import android.graphics.Bitmap;
-import android.graphics.Bitmap_Delegate;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.StateListDrawable;
-import android.view.AttachInfo_Accessor;
-import android.view.View.MeasureSpec;
-import android.widget.FrameLayout;
-
-import java.awt.AlphaComposite;
-import java.awt.Color;
-import java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Action to render a given Drawable provided through {@link DrawableParams#getDrawable()}.
- *
- * The class only provides a simple {@link #render()} method, but the full life-cycle of the
- * action must be respected.
- *
- * @see RenderAction
- *
- */
-public class RenderDrawable extends RenderAction<DrawableParams> {
-
- public RenderDrawable(DrawableParams params) {
- super(new DrawableParams(params));
- }
-
- public Result render() {
- checkLock();
- // get the drawable resource value
- DrawableParams params = getParams();
- HardwareConfig hardwareConfig = params.getHardwareConfig();
- ResourceValue drawableResource = params.getDrawable();
-
- // resolve it
- BridgeContext context = getContext();
- drawableResource = context.getRenderResources().resolveResValue(drawableResource);
-
- if (drawableResource == null) {
- return Status.ERROR_NOT_A_DRAWABLE.createResult();
- }
-
- ResourceType resourceType = drawableResource.getResourceType();
- if (resourceType != ResourceType.DRAWABLE && resourceType != ResourceType.MIPMAP) {
- return Status.ERROR_NOT_A_DRAWABLE.createResult();
- }
-
- Drawable d = ResourceHelper.getDrawable(drawableResource, context);
-
- final Boolean allStates =
- params.getFlag(RenderParamsFlags.FLAG_KEY_RENDER_ALL_DRAWABLE_STATES);
- if (allStates == Boolean.TRUE) {
- final List<BufferedImage> result;
-
- if (d instanceof StateListDrawable) {
- result = new ArrayList<BufferedImage>();
- final StateListDrawable stateList = (StateListDrawable) d;
- for (int i = 0; i < stateList.getStateCount(); i++) {
- final Drawable stateDrawable = stateList.getStateDrawable(i);
- result.add(renderImage(hardwareConfig, stateDrawable, context));
- }
- } else {
- result = Collections.singletonList(renderImage(hardwareConfig, d, context));
- }
-
- return Status.SUCCESS.createResult(result);
- } else {
- BufferedImage image = renderImage(hardwareConfig, d, context);
- return Status.SUCCESS.createResult(image);
- }
- }
-
- private BufferedImage renderImage(HardwareConfig hardwareConfig, Drawable d,
- BridgeContext context) {
- // create a simple FrameLayout
- FrameLayout content = new FrameLayout(context);
-
- // get the actual Drawable object to draw
- content.setBackground(d);
-
- // set the AttachInfo on the root view.
- AttachInfo_Accessor.setAttachInfo(content);
-
-
- // measure
- int w = d.getIntrinsicWidth();
- int h = d.getIntrinsicHeight();
-
- final int screenWidth = hardwareConfig.getScreenWidth();
- final int screenHeight = hardwareConfig.getScreenHeight();
-
- if (w == -1 || h == -1) {
- // Use screen size when either intrinsic width or height isn't available
- w = screenWidth;
- h = screenHeight;
- } else if (w > screenWidth || h > screenHeight) {
- // If image wouldn't fit to the screen, resize it to avoid cropping.
-
- // We need to find scale such that scale * w <= screenWidth, scale * h <= screenHeight
- double scale = Math.min((double) screenWidth / w, (double) screenHeight / h);
-
- // scale * w / scale * h = w / h, so, proportions are preserved.
- w = (int) Math.floor(scale * w);
- h = (int) Math.floor(scale * h);
- }
-
- int w_spec = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY);
- int h_spec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);
- content.measure(w_spec, h_spec);
-
- // now do the layout.
- content.layout(0, 0, w, h);
-
- // preDraw setup
- AttachInfo_Accessor.dispatchOnPreDraw(content);
-
- // draw into a new image
- BufferedImage image = getImage(w, h);
-
- // create an Android bitmap around the BufferedImage
- Bitmap bitmap = Bitmap_Delegate.createBitmap(image,
- true /*isMutable*/, hardwareConfig.getDensity());
-
- // create a Canvas around the Android bitmap
- Canvas canvas = new Canvas(bitmap);
- canvas.setDensity(hardwareConfig.getDensity().getDpiValue());
-
- // and draw
- content.draw(canvas);
- return image;
- }
-
- protected BufferedImage getImage(int w, int h) {
- BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
- Graphics2D gc = image.createGraphics();
- gc.setComposite(AlphaComposite.Src);
-
- gc.setColor(new Color(0x00000000, true));
- gc.fillRect(0, 0, w, h);
-
- // done
- gc.dispose();
-
- return image;
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
deleted file mode 100644
index d21955e..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ /dev/null
@@ -1,1557 +0,0 @@
-/*
- * Copyright (C) 2010 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.layoutlib.bridge.impl;
-
-import com.android.ide.common.rendering.api.AdapterBinding;
-import com.android.ide.common.rendering.api.HardwareConfig;
-import com.android.ide.common.rendering.api.IAnimationListener;
-import com.android.ide.common.rendering.api.ILayoutPullParser;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.LayoutlibCallback;
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.RenderSession;
-import com.android.ide.common.rendering.api.ResourceReference;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.rendering.api.Result;
-import com.android.ide.common.rendering.api.Result.Status;
-import com.android.ide.common.rendering.api.SessionParams;
-import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
-import com.android.ide.common.rendering.api.ViewInfo;
-import com.android.ide.common.rendering.api.ViewType;
-import com.android.internal.view.menu.ActionMenuItemView;
-import com.android.internal.view.menu.BridgeMenuItemImpl;
-import com.android.internal.view.menu.IconMenuItemView;
-import com.android.internal.view.menu.ListMenuItemView;
-import com.android.internal.view.menu.MenuItemImpl;
-import com.android.internal.view.menu.MenuView;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes;
-import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
-import com.android.layoutlib.bridge.android.RenderParamsFlags;
-import com.android.layoutlib.bridge.android.graphics.NopCanvas;
-import com.android.layoutlib.bridge.android.support.DesignLibUtil;
-import com.android.layoutlib.bridge.android.support.SupportPreferencesUtil;
-import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
-import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
-import com.android.resources.ResourceType;
-import com.android.tools.layoutlib.java.System_Delegate;
-import com.android.util.Pair;
-import com.android.util.PropertiesMap;
-
-import android.animation.AnimationThread;
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
-import android.animation.LayoutTransition;
-import android.animation.LayoutTransition.TransitionListener;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Fragment_Delegate;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap_Delegate;
-import android.graphics.Canvas;
-import android.os.Looper;
-import android.preference.Preference_Delegate;
-import android.view.AttachInfo_Accessor;
-import android.view.BridgeInflater;
-import android.view.Choreographer_Delegate;
-import android.view.IWindowManager;
-import android.view.IWindowManagerImpl;
-import android.view.Surface;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.view.ViewGroup.MarginLayoutParams;
-import android.view.ViewParent;
-import android.view.WindowManagerGlobal_Delegate;
-import android.widget.AbsListView;
-import android.widget.AbsSpinner;
-import android.widget.ActionMenuView;
-import android.widget.AdapterView;
-import android.widget.ExpandableListView;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.QuickContactBadge;
-import android.widget.TabHost;
-import android.widget.TabHost.TabSpec;
-import android.widget.TabWidget;
-
-import java.awt.AlphaComposite;
-import java.awt.Color;
-import java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import static com.android.ide.common.rendering.api.Result.Status.ERROR_ANIM_NOT_FOUND;
-import static com.android.ide.common.rendering.api.Result.Status.ERROR_INFLATION;
-import static com.android.ide.common.rendering.api.Result.Status.ERROR_NOT_INFLATED;
-import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
-import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN;
-import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
-import static com.android.layoutlib.bridge.util.ReflectionUtils.isInstanceOf;
-
-/**
- * Class implementing the render session.
- * <p/>
- * A session is a stateful representation of a layout file. It is initialized with data coming
- * through the {@link Bridge} API to inflate the layout. Further actions and rendering can then
- * be done on the layout.
- */
-public class RenderSessionImpl extends RenderAction<SessionParams> {
-
- private static final Canvas NOP_CANVAS = new NopCanvas();
-
- // scene state
- private RenderSession mScene;
- private BridgeXmlBlockParser mBlockParser;
- private BridgeInflater mInflater;
- private ViewGroup mViewRoot;
- private FrameLayout mContentRoot;
- private Canvas mCanvas;
- private int mMeasuredScreenWidth = -1;
- private int mMeasuredScreenHeight = -1;
- private boolean mIsAlphaChannelImage;
- /** If >= 0, a frame will be executed */
- private long mElapsedFrameTimeNanos = -1;
- /** True if one frame has been already executed to start the animations */
- private boolean mFirstFrameExecuted = false;
-
- // information being returned through the API
- private BufferedImage mImage;
- private List<ViewInfo> mViewInfoList;
- private List<ViewInfo> mSystemViewInfoList;
- private Layout.Builder mLayoutBuilder;
- private boolean mNewRenderSize;
-
- private static final class PostInflateException extends Exception {
- private static final long serialVersionUID = 1L;
-
- private PostInflateException(String message) {
- super(message);
- }
- }
-
- /**
- * Creates a layout scene with all the information coming from the layout bridge API.
- * <p>
- * This <b>must</b> be followed by a call to {@link RenderSessionImpl#init(long)},
- * which act as a
- * call to {@link RenderSessionImpl#acquire(long)}
- *
- * @see Bridge#createSession(SessionParams)
- */
- public RenderSessionImpl(SessionParams params) {
- super(new SessionParams(params));
- }
-
- /**
- * Initializes and acquires the scene, creating various Android objects such as context,
- * inflater, and parser.
- *
- * @param timeout the time to wait if another rendering is happening.
- *
- * @return whether the scene was prepared
- *
- * @see #acquire(long)
- * @see #release()
- */
- @Override
- public Result init(long timeout) {
- Result result = super.init(timeout);
- if (!result.isSuccess()) {
- return result;
- }
-
- SessionParams params = getParams();
- BridgeContext context = getContext();
-
- // use default of true in case it's not found to use alpha by default
- mIsAlphaChannelImage = ResourceHelper.getBooleanThemeValue(params.getResources(),
- "windowIsFloating", true, true);
-
- mLayoutBuilder = new Layout.Builder(params, context);
-
- // FIXME: find those out, and possibly add them to the render params
- boolean hasNavigationBar = true;
- //noinspection ConstantConditions
- IWindowManager iwm = new IWindowManagerImpl(getContext().getConfiguration(),
- context.getMetrics(), Surface.ROTATION_0, hasNavigationBar);
- WindowManagerGlobal_Delegate.setWindowManagerService(iwm);
-
- // build the inflater and parser.
- mInflater = new BridgeInflater(context, params.getLayoutlibCallback());
- context.setBridgeInflater(mInflater);
-
- mBlockParser = new BridgeXmlBlockParser(params.getLayoutDescription(), context, false);
-
- return SUCCESS.createResult();
- }
-
- /**
- * Measures the the current layout if needed (see {@link #invalidateRenderingSize}).
- */
- private void measureLayout(@NonNull SessionParams params) {
- // only do the screen measure when needed.
- if (mMeasuredScreenWidth != -1) {
- return;
- }
-
- RenderingMode renderingMode = params.getRenderingMode();
- HardwareConfig hardwareConfig = params.getHardwareConfig();
-
- mNewRenderSize = true;
- mMeasuredScreenWidth = hardwareConfig.getScreenWidth();
- mMeasuredScreenHeight = hardwareConfig.getScreenHeight();
-
- if (renderingMode != RenderingMode.NORMAL) {
- int widthMeasureSpecMode = renderingMode.isHorizExpand() ?
- MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
- : MeasureSpec.EXACTLY;
- int heightMeasureSpecMode = renderingMode.isVertExpand() ?
- MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
- : MeasureSpec.EXACTLY;
-
- // We used to compare the measured size of the content to the screen size but
- // this does not work anymore due to the 2 following issues:
- // - If the content is in a decor (system bar, title/action bar), the root view
- // will not resize even with the UNSPECIFIED because of the embedded layout.
- // - If there is no decor, but a dialog frame, then the dialog padding prevents
- // comparing the size of the content to the screen frame (as it would not
- // take into account the dialog padding).
-
- // The solution is to first get the content size in a normal rendering, inside
- // the decor or the dialog padding.
- // Then measure only the content with UNSPECIFIED to see the size difference
- // and apply this to the screen size.
-
- View measuredView = mContentRoot.getChildAt(0);
-
- // first measure the full layout, with EXACTLY to get the size of the
- // content as it is inside the decor/dialog
- @SuppressWarnings("deprecation")
- Pair<Integer, Integer> exactMeasure = measureView(
- mViewRoot, measuredView,
- mMeasuredScreenWidth, MeasureSpec.EXACTLY,
- mMeasuredScreenHeight, MeasureSpec.EXACTLY);
-
- // now measure the content only using UNSPECIFIED (where applicable, based on
- // the rendering mode). This will give us the size the content needs.
- @SuppressWarnings("deprecation")
- Pair<Integer, Integer> result = measureView(
- mContentRoot, mContentRoot.getChildAt(0),
- mMeasuredScreenWidth, widthMeasureSpecMode,
- mMeasuredScreenHeight, heightMeasureSpecMode);
-
- // If measuredView is not null, exactMeasure nor result will be null.
- assert exactMeasure != null;
- assert result != null;
-
- // now look at the difference and add what is needed.
- if (renderingMode.isHorizExpand()) {
- int measuredWidth = exactMeasure.getFirst();
- int neededWidth = result.getFirst();
- if (neededWidth > measuredWidth) {
- mMeasuredScreenWidth += neededWidth - measuredWidth;
- }
- if (mMeasuredScreenWidth < measuredWidth) {
- // If the screen width is less than the exact measured width,
- // expand to match.
- mMeasuredScreenWidth = measuredWidth;
- }
- }
-
- if (renderingMode.isVertExpand()) {
- int measuredHeight = exactMeasure.getSecond();
- int neededHeight = result.getSecond();
- if (neededHeight > measuredHeight) {
- mMeasuredScreenHeight += neededHeight - measuredHeight;
- }
- if (mMeasuredScreenHeight < measuredHeight) {
- // If the screen height is less than the exact measured height,
- // expand to match.
- mMeasuredScreenHeight = measuredHeight;
- }
- }
- }
- }
-
- /**
- * Inflates the layout.
- * <p>
- * {@link #acquire(long)} must have been called before this.
- *
- * @throws IllegalStateException if the current context is different than the one owned by
- * the scene, or if {@link #init(long)} was not called.
- */
- public Result inflate() {
- checkLock();
-
- try {
- mViewRoot = new Layout(mLayoutBuilder);
- mLayoutBuilder = null; // Done with the builder.
- mContentRoot = ((Layout) mViewRoot).getContentRoot();
- SessionParams params = getParams();
- BridgeContext context = getContext();
-
- if (Bridge.isLocaleRtl(params.getLocale())) {
- if (!params.isRtlSupported()) {
- Bridge.getLog().warning(LayoutLog.TAG_RTL_NOT_ENABLED,
- "You are using a right-to-left " +
- "(RTL) locale but RTL is not enabled", null);
- } else if (params.getSimulatedPlatformVersion() < 17) {
- // This will render ok because we are using the latest layoutlib but at least
- // warn the user that this might fail in a real device.
- Bridge.getLog().warning(LayoutLog.TAG_RTL_NOT_SUPPORTED, "You are using a " +
- "right-to-left " +
- "(RTL) locale but RTL is not supported for API level < 17", null);
- }
- }
-
- // Sets the project callback (custom view loader) to the fragment delegate so that
- // it can instantiate the custom Fragment.
- Fragment_Delegate.setLayoutlibCallback(params.getLayoutlibCallback());
-
- String rootTag = params.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG);
- boolean isPreference = "PreferenceScreen".equals(rootTag);
- View view;
- if (isPreference) {
- // First try to use the support library inflater. If something fails, fallback
- // to the system preference inflater.
- view = SupportPreferencesUtil.inflatePreference(getContext(), mBlockParser,
- mContentRoot);
- if (view == null) {
- view = Preference_Delegate.inflatePreference(getContext(), mBlockParser,
- mContentRoot);
- }
- } else {
- view = mInflater.inflate(mBlockParser, mContentRoot);
- }
-
- // done with the parser, pop it.
- context.popParser();
-
- Fragment_Delegate.setLayoutlibCallback(null);
-
- // set the AttachInfo on the root view.
- AttachInfo_Accessor.setAttachInfo(mViewRoot);
-
- // post-inflate process. For now this supports TabHost/TabWidget
- postInflateProcess(view, params.getLayoutlibCallback(), isPreference ? view : null);
- mInflater.onDoneInflation();
-
- setActiveToolbar(view, context, params);
-
- measureLayout(params);
- measureView(mViewRoot, null /*measuredView*/,
- mMeasuredScreenWidth, MeasureSpec.EXACTLY,
- mMeasuredScreenHeight, MeasureSpec.EXACTLY);
- mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
- mSystemViewInfoList =
- visitAllChildren(mViewRoot, 0, 0, params.getExtendedViewInfoMode(),
- false);
-
- return SUCCESS.createResult();
- } catch (PostInflateException e) {
- return ERROR_INFLATION.createResult(e.getMessage(), e);
- } catch (Throwable e) {
- // get the real cause of the exception.
- Throwable t = e;
- while (t.getCause() != null) {
- t = t.getCause();
- }
-
- return ERROR_INFLATION.createResult(t.getMessage(), t);
- }
- }
-
- /**
- * Sets the time for which the next frame will be selected. The time is the elapsed time from
- * the current system nanos time. You
- */
- public void setElapsedFrameTimeNanos(long nanos) {
- mElapsedFrameTimeNanos = nanos;
- }
-
- /**
- * Runs a layout pass for the given view root
- */
- private static void doLayout(@NonNull BridgeContext context, @NonNull ViewGroup viewRoot,
- int width, int height) {
- // measure again with the size we need
- // This must always be done before the call to layout
- measureView(viewRoot, null /*measuredView*/,
- width, MeasureSpec.EXACTLY,
- height, MeasureSpec.EXACTLY);
-
- // now do the layout.
- viewRoot.layout(0, 0, width, height);
- handleScrolling(context, viewRoot);
- }
-
- /**
- * Renders the given view hierarchy to the passed canvas and returns the result of the render
- * operation.
- * @param canvas an optional canvas to render the views to. If null, only the measure and
- * layout steps will be executed.
- */
- private static Result renderAndBuildResult(@NonNull ViewGroup viewRoot, @Nullable Canvas canvas) {
- if (canvas == null) {
- return SUCCESS.createResult();
- }
-
- AttachInfo_Accessor.dispatchOnPreDraw(viewRoot);
- viewRoot.draw(canvas);
-
- return SUCCESS.createResult();
- }
-
- /**
- * Renders the scene.
- * <p>
- * {@link #acquire(long)} must have been called before this.
- *
- * @param freshRender whether the render is a new one and should erase the existing bitmap (in
- * the case where bitmaps are reused). This is typically needed when not playing
- * animations.)
- *
- * @throws IllegalStateException if the current context is different than the one owned by
- * the scene, or if {@link #acquire(long)} was not called.
- *
- * @see SessionParams#getRenderingMode()
- * @see RenderSession#render(long)
- */
- public Result render(boolean freshRender) {
- return renderAndBuildResult(freshRender, false);
- }
-
- /**
- * Measures the layout
- * <p>
- * {@link #acquire(long)} must have been called before this.
- *
- * @throws IllegalStateException if the current context is different than the one owned by
- * the scene, or if {@link #acquire(long)} was not called.
- *
- * @see SessionParams#getRenderingMode()
- * @see RenderSession#render(long)
- */
- public Result measure() {
- return renderAndBuildResult(false, true);
- }
-
- /**
- * Renders the scene.
- * <p>
- * {@link #acquire(long)} must have been called before this.
- *
- * @param freshRender whether the render is a new one and should erase the existing bitmap (in
- * the case where bitmaps are reused). This is typically needed when not playing
- * animations.)
- *
- * @throws IllegalStateException if the current context is different than the one owned by
- * the scene, or if {@link #acquire(long)} was not called.
- *
- * @see SessionParams#getRenderingMode()
- * @see RenderSession#render(long)
- */
- private Result renderAndBuildResult(boolean freshRender, boolean onlyMeasure) {
- checkLock();
-
- SessionParams params = getParams();
-
- try {
- if (mViewRoot == null) {
- return ERROR_NOT_INFLATED.createResult();
- }
-
- measureLayout(params);
-
- HardwareConfig hardwareConfig = params.getHardwareConfig();
- Result renderResult = SUCCESS.createResult();
- if (onlyMeasure) {
- // delete the canvas and image to reset them on the next full rendering
- mImage = null;
- mCanvas = null;
- doLayout(getContext(), mViewRoot, mMeasuredScreenWidth, mMeasuredScreenHeight);
- } else {
- // draw the views
- // create the BufferedImage into which the layout will be rendered.
- boolean newImage = false;
-
- // When disableBitmapCaching is true, we do not reuse mImage and
- // we create a new one in every render.
- // This is useful when mImage is just a wrapper of Graphics2D so
- // it doesn't get cached.
- boolean disableBitmapCaching = Boolean.TRUE.equals(params.getFlag(
- RenderParamsFlags.FLAG_KEY_DISABLE_BITMAP_CACHING));
- if (mNewRenderSize || mCanvas == null || disableBitmapCaching) {
- mNewRenderSize = false;
- if (params.getImageFactory() != null) {
- mImage = params.getImageFactory().getImage(
- mMeasuredScreenWidth,
- mMeasuredScreenHeight);
- } else {
- mImage = new BufferedImage(
- mMeasuredScreenWidth,
- mMeasuredScreenHeight,
- BufferedImage.TYPE_INT_ARGB);
- newImage = true;
- }
-
- if (params.isBgColorOverridden()) {
- // since we override the content, it's the same as if it was a new image.
- newImage = true;
- Graphics2D gc = mImage.createGraphics();
- gc.setColor(new Color(params.getOverrideBgColor(), true));
- gc.setComposite(AlphaComposite.Src);
- gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
- gc.dispose();
- }
-
- // create an Android bitmap around the BufferedImage
- Bitmap bitmap = Bitmap_Delegate.createBitmap(mImage,
- true /*isMutable*/, hardwareConfig.getDensity());
-
- if (mCanvas == null) {
- // create a Canvas around the Android bitmap
- mCanvas = new Canvas(bitmap);
- } else {
- mCanvas.setBitmap(bitmap);
- }
- mCanvas.setDensity(hardwareConfig.getDensity().getDpiValue());
- }
-
- if (freshRender && !newImage) {
- Graphics2D gc = mImage.createGraphics();
- gc.setComposite(AlphaComposite.Src);
-
- gc.setColor(new Color(0x00000000, true));
- gc.fillRect(0, 0,
- mMeasuredScreenWidth, mMeasuredScreenHeight);
-
- // done
- gc.dispose();
- }
-
- doLayout(getContext(), mViewRoot, mMeasuredScreenWidth, mMeasuredScreenHeight);
- if (mElapsedFrameTimeNanos >= 0) {
- long initialTime = System_Delegate.nanoTime();
- if (!mFirstFrameExecuted) {
- // We need to run an initial draw call to initialize the animations
- renderAndBuildResult(mViewRoot, NOP_CANVAS);
-
- // The first frame will initialize the animations
- Choreographer_Delegate.doFrame(initialTime);
- mFirstFrameExecuted = true;
- }
- // Second frame will move the animations
- Choreographer_Delegate.doFrame(initialTime + mElapsedFrameTimeNanos);
- }
- renderResult = renderAndBuildResult(mViewRoot, mCanvas);
- }
-
- mSystemViewInfoList =
- visitAllChildren(mViewRoot, 0, 0, params.getExtendedViewInfoMode(),
- false);
-
- // success!
- return renderResult;
- } catch (Throwable e) {
- // get the real cause of the exception.
- Throwable t = e;
- while (t.getCause() != null) {
- t = t.getCause();
- }
-
- return ERROR_UNKNOWN.createResult(t.getMessage(), t);
- }
- }
-
- /**
- * Executes {@link View#measure(int, int)} on a given view with the given parameters (used
- * to create measure specs with {@link MeasureSpec#makeMeasureSpec(int, int)}.
- *
- * if <var>measuredView</var> is non null, the method returns a {@link Pair} of (width, height)
- * for the view (using {@link View#getMeasuredWidth()} and {@link View#getMeasuredHeight()}).
- *
- * @param viewToMeasure the view on which to execute measure().
- * @param measuredView if non null, the view to query for its measured width/height.
- * @param width the width to use in the MeasureSpec.
- * @param widthMode the MeasureSpec mode to use for the width.
- * @param height the height to use in the MeasureSpec.
- * @param heightMode the MeasureSpec mode to use for the height.
- * @return the measured width/height if measuredView is non-null, null otherwise.
- */
- @SuppressWarnings("deprecation") // For the use of Pair
- private static Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView,
- int width, int widthMode, int height, int heightMode) {
- int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode);
- int h_spec = MeasureSpec.makeMeasureSpec(height, heightMode);
- viewToMeasure.measure(w_spec, h_spec);
-
- if (measuredView != null) {
- return Pair.of(measuredView.getMeasuredWidth(), measuredView.getMeasuredHeight());
- }
-
- return null;
- }
-
- /**
- * Animate an object
- * <p>
- * {@link #acquire(long)} must have been called before this.
- *
- * @throws IllegalStateException if the current context is different than the one owned by
- * the scene, or if {@link #acquire(long)} was not called.
- *
- * @see RenderSession#animate(Object, String, boolean, IAnimationListener)
- */
- public Result animate(Object targetObject, String animationName,
- boolean isFrameworkAnimation, IAnimationListener listener) {
- checkLock();
-
- BridgeContext context = getContext();
-
- // find the animation file.
- ResourceValue animationResource;
- int animationId = 0;
- if (isFrameworkAnimation) {
- animationResource = context.getRenderResources().getFrameworkResource(
- ResourceType.ANIMATOR, animationName);
- if (animationResource != null) {
- animationId = Bridge.getResourceId(ResourceType.ANIMATOR, animationName);
- }
- } else {
- animationResource = context.getRenderResources().getProjectResource(
- ResourceType.ANIMATOR, animationName);
- if (animationResource != null) {
- animationId = context.getLayoutlibCallback().getResourceId(
- ResourceType.ANIMATOR, animationName);
- }
- }
-
- if (animationResource != null) {
- try {
- Animator anim = AnimatorInflater.loadAnimator(context, animationId);
- if (anim != null) {
- anim.setTarget(targetObject);
-
- new PlayAnimationThread(anim, this, animationName, listener).start();
-
- return SUCCESS.createResult();
- }
- } catch (Exception e) {
- // get the real cause of the exception.
- Throwable t = e;
- while (t.getCause() != null) {
- t = t.getCause();
- }
-
- return ERROR_UNKNOWN.createResult(t.getMessage(), t);
- }
- }
-
- return ERROR_ANIM_NOT_FOUND.createResult();
- }
-
- /**
- * Insert a new child into an existing parent.
- * <p>
- * {@link #acquire(long)} must have been called before this.
- *
- * @throws IllegalStateException if the current context is different than the one owned by
- * the scene, or if {@link #acquire(long)} was not called.
- *
- * @see RenderSession#insertChild(Object, ILayoutPullParser, int, IAnimationListener)
- */
- public Result insertChild(final ViewGroup parentView, ILayoutPullParser childXml,
- final int index, IAnimationListener listener) {
- checkLock();
-
- BridgeContext context = getContext();
-
- // create a block parser for the XML
- BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
- childXml, context, false /* platformResourceFlag */);
-
- // inflate the child without adding it to the root since we want to control where it'll
- // get added. We do pass the parentView however to ensure that the layoutParams will
- // be created correctly.
- final View child = mInflater.inflate(blockParser, parentView, false /*attachToRoot*/);
- blockParser.ensurePopped();
-
- invalidateRenderingSize();
-
- if (listener != null) {
- new AnimationThread(this, "insertChild", listener) {
-
- @Override
- public Result preAnimation() {
- parentView.setLayoutTransition(new LayoutTransition());
- return addView(parentView, child, index);
- }
-
- @Override
- public void postAnimation() {
- parentView.setLayoutTransition(null);
- }
- }.start();
-
- // always return success since the real status will come through the listener.
- return SUCCESS.createResult(child);
- }
-
- // add it to the parentView in the correct location
- Result result = addView(parentView, child, index);
- if (!result.isSuccess()) {
- return result;
- }
-
- result = render(false /*freshRender*/);
- if (result.isSuccess()) {
- result = result.getCopyWithData(child);
- }
-
- return result;
- }
-
- /**
- * Adds a given view to a given parent at a given index.
- *
- * @param parent the parent to receive the view
- * @param view the view to add to the parent
- * @param index the index where to do the add.
- *
- * @return a Result with {@link Status#SUCCESS} or
- * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
- * adding views.
- */
- private Result addView(ViewGroup parent, View view, int index) {
- try {
- parent.addView(view, index);
- return SUCCESS.createResult();
- } catch (UnsupportedOperationException e) {
- // looks like this is a view class that doesn't support children manipulation!
- return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
- }
- }
-
- /**
- * Moves a view to a new parent at a given location
- * <p>
- * {@link #acquire(long)} must have been called before this.
- *
- * @throws IllegalStateException if the current context is different than the one owned by
- * the scene, or if {@link #acquire(long)} was not called.
- *
- * @see RenderSession#moveChild(Object, Object, int, Map, IAnimationListener)
- */
- public Result moveChild(final ViewGroup newParentView, final View childView, final int index,
- Map<String, String> layoutParamsMap, final IAnimationListener listener) {
- checkLock();
-
- invalidateRenderingSize();
-
- LayoutParams layoutParams = null;
- if (layoutParamsMap != null) {
- // need to create a new LayoutParams object for the new parent.
- layoutParams = newParentView.generateLayoutParams(
- new BridgeLayoutParamsMapAttributes(layoutParamsMap));
- }
-
- // get the current parent of the view that needs to be moved.
- final ViewGroup previousParent = (ViewGroup) childView.getParent();
-
- if (listener != null) {
- final LayoutParams params = layoutParams;
-
- // there is no support for animating views across layouts, so in case the new and old
- // parent views are different we fake the animation through a no animation thread.
- if (previousParent != newParentView) {
- new Thread("not animated moveChild") {
- @Override
- public void run() {
- Result result = moveView(previousParent, newParentView, childView, index,
- params);
- if (!result.isSuccess()) {
- listener.done(result);
- }
-
- // ready to do the work, acquire the scene.
- result = acquire(250);
- if (!result.isSuccess()) {
- listener.done(result);
- return;
- }
-
- try {
- result = render(false /*freshRender*/);
- if (result.isSuccess()) {
- listener.onNewFrame(RenderSessionImpl.this.getSession());
- }
- } finally {
- release();
- }
-
- listener.done(result);
- }
- }.start();
- } else {
- new AnimationThread(this, "moveChild", listener) {
-
- @Override
- public Result preAnimation() {
- // set up the transition for the parent.
- LayoutTransition transition = new LayoutTransition();
- previousParent.setLayoutTransition(transition);
-
- // tweak the animation durations and start delays (to match the duration of
- // animation playing just before).
- // Note: Cannot user Animation.setDuration() directly. Have to set it
- // on the LayoutTransition.
- transition.setDuration(LayoutTransition.DISAPPEARING, 100);
- // CHANGE_DISAPPEARING plays after DISAPPEARING
- transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 100);
-
- transition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 100);
-
- transition.setDuration(LayoutTransition.CHANGE_APPEARING, 100);
- // CHANGE_APPEARING plays after CHANGE_APPEARING
- transition.setStartDelay(LayoutTransition.APPEARING, 100);
-
- transition.setDuration(LayoutTransition.APPEARING, 100);
-
- return moveView(previousParent, newParentView, childView, index, params);
- }
-
- @Override
- public void postAnimation() {
- previousParent.setLayoutTransition(null);
- newParentView.setLayoutTransition(null);
- }
- }.start();
- }
-
- // always return success since the real status will come through the listener.
- return SUCCESS.createResult(layoutParams);
- }
-
- Result result = moveView(previousParent, newParentView, childView, index, layoutParams);
- if (!result.isSuccess()) {
- return result;
- }
-
- result = render(false /*freshRender*/);
- if (layoutParams != null && result.isSuccess()) {
- result = result.getCopyWithData(layoutParams);
- }
-
- return result;
- }
-
- /**
- * Moves a View from its current parent to a new given parent at a new given location, with
- * an optional new {@link LayoutParams} instance
- *
- * @param previousParent the previous parent, still owning the child at the time of the call.
- * @param newParent the new parent
- * @param movedView the view to move
- * @param index the new location in the new parent
- * @param params an option (can be null) {@link LayoutParams} instance.
- *
- * @return a Result with {@link Status#SUCCESS} or
- * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
- * adding views.
- */
- private Result moveView(ViewGroup previousParent, final ViewGroup newParent,
- final View movedView, final int index, final LayoutParams params) {
- try {
- // check if there is a transition on the previousParent.
- LayoutTransition previousTransition = previousParent.getLayoutTransition();
- if (previousTransition != null) {
- // in this case there is an animation. This means we have to wait for the child's
- // parent reference to be null'ed out so that we can add it to the new parent.
- // It is technically removed right before the DISAPPEARING animation is done (if
- // the animation of this type is not null, otherwise it's after which is impossible
- // to handle).
- // Because there is no move animation, if the new parent is the same as the old
- // parent, we need to wait until the CHANGE_DISAPPEARING animation is done before
- // adding the child or the child will appear in its new location before the
- // other children have made room for it.
-
- // add a listener to the transition to be notified of the actual removal.
- previousTransition.addTransitionListener(new TransitionListener() {
- private int mChangeDisappearingCount = 0;
-
- @Override
- public void startTransition(LayoutTransition transition, ViewGroup container,
- View view, int transitionType) {
- if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
- mChangeDisappearingCount++;
- }
- }
-
- @Override
- public void endTransition(LayoutTransition transition, ViewGroup container,
- View view, int transitionType) {
- if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
- mChangeDisappearingCount--;
- }
-
- if (transitionType == LayoutTransition.CHANGE_DISAPPEARING &&
- mChangeDisappearingCount == 0) {
- // add it to the parentView in the correct location
- if (params != null) {
- newParent.addView(movedView, index, params);
- } else {
- newParent.addView(movedView, index);
- }
- }
- }
- });
-
- // remove the view from the current parent.
- previousParent.removeView(movedView);
-
- // and return since adding the view to the new parent is done in the listener.
- return SUCCESS.createResult();
- } else {
- // standard code with no animation. pretty simple.
- previousParent.removeView(movedView);
-
- // add it to the parentView in the correct location
- if (params != null) {
- newParent.addView(movedView, index, params);
- } else {
- newParent.addView(movedView, index);
- }
-
- return SUCCESS.createResult();
- }
- } catch (UnsupportedOperationException e) {
- // looks like this is a view class that doesn't support children manipulation!
- return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
- }
- }
-
- /**
- * Removes a child from its current parent.
- * <p>
- * {@link #acquire(long)} must have been called before this.
- *
- * @throws IllegalStateException if the current context is different than the one owned by
- * the scene, or if {@link #acquire(long)} was not called.
- *
- * @see RenderSession#removeChild(Object, IAnimationListener)
- */
- public Result removeChild(final View childView, IAnimationListener listener) {
- checkLock();
-
- invalidateRenderingSize();
-
- final ViewGroup parent = (ViewGroup) childView.getParent();
-
- if (listener != null) {
- new AnimationThread(this, "moveChild", listener) {
-
- @Override
- public Result preAnimation() {
- parent.setLayoutTransition(new LayoutTransition());
- return removeView(parent, childView);
- }
-
- @Override
- public void postAnimation() {
- parent.setLayoutTransition(null);
- }
- }.start();
-
- // always return success since the real status will come through the listener.
- return SUCCESS.createResult();
- }
-
- Result result = removeView(parent, childView);
- if (!result.isSuccess()) {
- return result;
- }
-
- return render(false /*freshRender*/);
- }
-
- /**
- * Removes a given view from its current parent.
- *
- * @param view the view to remove from its parent
- *
- * @return a Result with {@link Status#SUCCESS} or
- * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
- * adding views.
- */
- private Result removeView(ViewGroup parent, View view) {
- try {
- parent.removeView(view);
- return SUCCESS.createResult();
- } catch (UnsupportedOperationException e) {
- // looks like this is a view class that doesn't support children manipulation!
- return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
- }
- }
-
- /**
- * Post process on a view hierarchy that was just inflated.
- * <p/>
- * At the moment this only supports TabHost: If {@link TabHost} is detected, look for the
- * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically
- * based on the content of the {@link FrameLayout}.
- * @param view the root view to process.
- * @param layoutlibCallback callback to the project.
- * @param skip the view and it's children are not processed.
- */
- @SuppressWarnings("deprecation") // For the use of Pair
- private void postInflateProcess(View view, LayoutlibCallback layoutlibCallback, View skip)
- throws PostInflateException {
- if (view == skip) {
- return;
- }
- if (view instanceof TabHost) {
- setupTabHost((TabHost) view, layoutlibCallback);
- } else if (view instanceof QuickContactBadge) {
- QuickContactBadge badge = (QuickContactBadge) view;
- badge.setImageToDefault();
- } else if (view instanceof AdapterView<?>) {
- // get the view ID.
- int id = view.getId();
-
- BridgeContext context = getContext();
-
- // get a ResourceReference from the integer ID.
- ResourceReference listRef = context.resolveId(id);
-
- if (listRef != null) {
- SessionParams params = getParams();
- AdapterBinding binding = params.getAdapterBindings().get(listRef);
-
- // if there was no adapter binding, trying to get it from the call back.
- if (binding == null) {
- binding = layoutlibCallback.getAdapterBinding(
- listRef, context.getViewKey(view), view);
- }
-
- if (binding != null) {
-
- if (view instanceof AbsListView) {
- if ((binding.getFooterCount() > 0 || binding.getHeaderCount() > 0) &&
- view instanceof ListView) {
- ListView list = (ListView) view;
-
- boolean skipCallbackParser = false;
-
- int count = binding.getHeaderCount();
- for (int i = 0; i < count; i++) {
- Pair<View, Boolean> pair = context.inflateView(
- binding.getHeaderAt(i),
- list, false, skipCallbackParser);
- if (pair.getFirst() != null) {
- list.addHeaderView(pair.getFirst());
- }
-
- skipCallbackParser |= pair.getSecond();
- }
-
- count = binding.getFooterCount();
- for (int i = 0; i < count; i++) {
- Pair<View, Boolean> pair = context.inflateView(
- binding.getFooterAt(i),
- list, false, skipCallbackParser);
- if (pair.getFirst() != null) {
- list.addFooterView(pair.getFirst());
- }
-
- skipCallbackParser |= pair.getSecond();
- }
- }
-
- if (view instanceof ExpandableListView) {
- ((ExpandableListView) view).setAdapter(
- new FakeExpandableAdapter(listRef, binding, layoutlibCallback));
- } else {
- ((AbsListView) view).setAdapter(
- new FakeAdapter(listRef, binding, layoutlibCallback));
- }
- } else if (view instanceof AbsSpinner) {
- ((AbsSpinner) view).setAdapter(
- new FakeAdapter(listRef, binding, layoutlibCallback));
- }
- }
- }
- } else if (view instanceof ViewGroup) {
- mInflater.postInflateProcess(view);
- ViewGroup group = (ViewGroup) view;
- final int count = group.getChildCount();
- for (int c = 0; c < count; c++) {
- View child = group.getChildAt(c);
- postInflateProcess(child, layoutlibCallback, skip);
- }
- }
- }
-
- /**
- * If the root layout is a CoordinatorLayout with an AppBar:
- * Set the title of the AppBar to the title of the activity context.
- */
- private void setActiveToolbar(View view, BridgeContext context, SessionParams params) {
- View coordinatorLayout = findChildView(view, DesignLibUtil.CN_COORDINATOR_LAYOUT);
- if (coordinatorLayout == null) {
- return;
- }
- View appBar = findChildView(coordinatorLayout, DesignLibUtil.CN_APPBAR_LAYOUT);
- if (appBar == null) {
- return;
- }
- ViewGroup collapsingToolbar =
- (ViewGroup) findChildView(appBar, DesignLibUtil.CN_COLLAPSING_TOOLBAR_LAYOUT);
- if (collapsingToolbar == null) {
- return;
- }
- if (!hasToolbar(collapsingToolbar)) {
- return;
- }
- RenderResources res = context.getRenderResources();
- String title = params.getAppLabel();
- ResourceValue titleValue = res.findResValue(title, false);
- if (titleValue != null && titleValue.getValue() != null) {
- title = titleValue.getValue();
- }
- DesignLibUtil.setTitle(collapsingToolbar, title);
- }
-
- private View findChildView(View view, String className) {
- if (!(view instanceof ViewGroup)) {
- return null;
- }
- ViewGroup group = (ViewGroup) view;
- for (int i = 0; i < group.getChildCount(); i++) {
- if (isInstanceOf(group.getChildAt(i), className)) {
- return group.getChildAt(i);
- }
- }
- return null;
- }
-
- private boolean hasToolbar(View collapsingToolbar) {
- if (!(collapsingToolbar instanceof ViewGroup)) {
- return false;
- }
- ViewGroup group = (ViewGroup) collapsingToolbar;
- for (int i = 0; i < group.getChildCount(); i++) {
- if (isInstanceOf(group.getChildAt(i), DesignLibUtil.CN_TOOLBAR)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Set the scroll position on all the components with the "scrollX" and "scrollY" attribute. If
- * the component supports nested scrolling attempt that first, then use the unconsumed scroll
- * part to scroll the content in the component.
- */
- private static void handleScrolling(BridgeContext context, View view) {
- int scrollPosX = context.getScrollXPos(view);
- int scrollPosY = context.getScrollYPos(view);
- if (scrollPosX != 0 || scrollPosY != 0) {
- if (view.isNestedScrollingEnabled()) {
- int[] consumed = new int[2];
- int axis = scrollPosX != 0 ? View.SCROLL_AXIS_HORIZONTAL : 0;
- axis |= scrollPosY != 0 ? View.SCROLL_AXIS_VERTICAL : 0;
- if (view.startNestedScroll(axis)) {
- view.dispatchNestedPreScroll(scrollPosX, scrollPosY, consumed, null);
- view.dispatchNestedScroll(consumed[0], consumed[1], scrollPosX, scrollPosY,
- null);
- view.stopNestedScroll();
- scrollPosX -= consumed[0];
- scrollPosY -= consumed[1];
- }
- }
- if (scrollPosX != 0 || scrollPosY != 0) {
- view.scrollTo(scrollPosX, scrollPosY);
- }
- }
-
- if (!(view instanceof ViewGroup)) {
- return;
- }
- ViewGroup group = (ViewGroup) view;
- for (int i = 0; i < group.getChildCount(); i++) {
- View child = group.getChildAt(i);
- handleScrolling(context, child);
- }
- }
-
- /**
- * Sets up a {@link TabHost} object.
- * @param tabHost the TabHost to setup.
- * @param layoutlibCallback The project callback object to access the project R class.
- * @throws PostInflateException if TabHost is missing the required ids for TabHost
- */
- private void setupTabHost(TabHost tabHost, LayoutlibCallback layoutlibCallback)
- throws PostInflateException {
- // look for the TabWidget, and the FrameLayout. They have their own specific names
- View v = tabHost.findViewById(android.R.id.tabs);
-
- if (v == null) {
- throw new PostInflateException(
- "TabHost requires a TabWidget with id \"android:id/tabs\".\n");
- }
-
- if (!(v instanceof TabWidget)) {
- throw new PostInflateException(String.format(
- "TabHost requires a TabWidget with id \"android:id/tabs\".\n" +
- "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName()));
- }
-
- v = tabHost.findViewById(android.R.id.tabcontent);
-
- if (v == null) {
- // TODO: see if we can fake tabs even without the FrameLayout (same below when the frameLayout is empty)
- //noinspection SpellCheckingInspection
- throw new PostInflateException(
- "TabHost requires a FrameLayout with id \"android:id/tabcontent\".");
- }
-
- if (!(v instanceof FrameLayout)) {
- //noinspection SpellCheckingInspection
- throw new PostInflateException(String.format(
- "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" +
- "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName()));
- }
-
- FrameLayout content = (FrameLayout)v;
-
- // now process the content of the frameLayout and dynamically create tabs for it.
- final int count = content.getChildCount();
-
- // this must be called before addTab() so that the TabHost searches its TabWidget
- // and FrameLayout.
- tabHost.setup();
-
- if (count == 0) {
- // Create a dummy child to get a single tab
- TabSpec spec = tabHost.newTabSpec("tag")
- .setIndicator("Tab Label", tabHost.getResources()
- .getDrawable(android.R.drawable.ic_menu_info_details, null))
- .setContent(tag -> new LinearLayout(getContext()));
- tabHost.addTab(spec);
- } else {
- // for each child of the frameLayout, add a new TabSpec
- for (int i = 0 ; i < count ; i++) {
- View child = content.getChildAt(i);
- String tabSpec = String.format("tab_spec%d", i+1);
- @SuppressWarnings("ConstantConditions") // child cannot be null.
- int id = child.getId();
- @SuppressWarnings("deprecation")
- Pair<ResourceType, String> resource = layoutlibCallback.resolveResourceId(id);
- String name;
- if (resource != null) {
- name = resource.getSecond();
- } else {
- name = String.format("Tab %d", i+1); // default name if id is unresolved.
- }
- tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id));
- }
- }
- }
-
- /**
- * Visits a {@link View} and its children and generate a {@link ViewInfo} containing the
- * bounds of all the views.
- *
- * @param view the root View
- * @param hOffset horizontal offset for the view bounds.
- * @param vOffset vertical offset for the view bounds.
- * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
- * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
- * content frame.
- *
- * @return {@code ViewInfo} containing the bounds of the view and it children otherwise.
- */
- private ViewInfo visit(View view, int hOffset, int vOffset, boolean setExtendedInfo,
- boolean isContentFrame) {
- ViewInfo result = createViewInfo(view, hOffset, vOffset, setExtendedInfo, isContentFrame);
-
- if (view instanceof ViewGroup) {
- ViewGroup group = ((ViewGroup) view);
- result.setChildren(visitAllChildren(group, isContentFrame ? 0 : hOffset,
- isContentFrame ? 0 : vOffset,
- setExtendedInfo, isContentFrame));
- }
- return result;
- }
-
- /**
- * Visits all the children of a given ViewGroup and generates a list of {@link ViewInfo}
- * containing the bounds of all the views. It also initializes the {@link #mViewInfoList} with
- * the children of the {@code mContentRoot}.
- *
- * @param viewGroup the root View
- * @param hOffset horizontal offset from the top for the content view frame.
- * @param vOffset vertical offset from the top for the content view frame.
- * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
- * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
- * content frame. {@code false} if the {@code ViewInfo} to be created is
- * part of the system decor.
- */
- private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int hOffset, int vOffset,
- boolean setExtendedInfo, boolean isContentFrame) {
- if (viewGroup == null) {
- return null;
- }
-
- if (!isContentFrame) {
- vOffset += viewGroup.getTop();
- hOffset += viewGroup.getLeft();
- }
-
- int childCount = viewGroup.getChildCount();
- if (viewGroup == mContentRoot) {
- List<ViewInfo> childrenWithoutOffset = new ArrayList<>(childCount);
- List<ViewInfo> childrenWithOffset = new ArrayList<>(childCount);
- for (int i = 0; i < childCount; i++) {
- ViewInfo[] childViewInfo =
- visitContentRoot(viewGroup.getChildAt(i), hOffset, vOffset,
- setExtendedInfo);
- childrenWithoutOffset.add(childViewInfo[0]);
- childrenWithOffset.add(childViewInfo[1]);
- }
- mViewInfoList = childrenWithOffset;
- return childrenWithoutOffset;
- } else {
- List<ViewInfo> children = new ArrayList<>(childCount);
- for (int i = 0; i < childCount; i++) {
- children.add(visit(viewGroup.getChildAt(i), hOffset, vOffset, setExtendedInfo,
- isContentFrame));
- }
- return children;
- }
- }
-
- /**
- * Visits the children of {@link #mContentRoot} and generates {@link ViewInfo} containing the
- * bounds of all the views. It returns two {@code ViewInfo} objects with the same children,
- * one with the {@code offset} and other without the {@code offset}. The offset is needed to
- * get the right bounds if the {@code ViewInfo} hierarchy is accessed from
- * {@code mViewInfoList}. When the hierarchy is accessed via {@code mSystemViewInfoList}, the
- * offset is not needed.
- *
- * @return an array of length two, with ViewInfo at index 0 is without offset and ViewInfo at
- * index 1 is with the offset.
- */
- @NonNull
- private ViewInfo[] visitContentRoot(View view, int hOffset, int vOffset,
- boolean setExtendedInfo) {
- ViewInfo[] result = new ViewInfo[2];
- if (view == null) {
- return result;
- }
-
- result[0] = createViewInfo(view, 0, 0, setExtendedInfo, true);
- result[1] = createViewInfo(view, hOffset, vOffset, setExtendedInfo, true);
- if (view instanceof ViewGroup) {
- List<ViewInfo> children =
- visitAllChildren((ViewGroup) view, 0, 0, setExtendedInfo, true);
- result[0].setChildren(children);
- result[1].setChildren(children);
- }
- return result;
- }
-
- /**
- * Creates a {@link ViewInfo} for the view. The {@code ViewInfo} corresponding to the children
- * of the {@code view} are not created. Consequently, the children of {@code ViewInfo} is not
- * set.
- * @param hOffset horizontal offset for the view bounds. Used only if view is part of the
- * content frame.
- * @param vOffset vertial an offset for the view bounds. Used only if view is part of the
- * content frame.
- */
- private ViewInfo createViewInfo(View view, int hOffset, int vOffset, boolean setExtendedInfo,
- boolean isContentFrame) {
- if (view == null) {
- return null;
- }
-
- ViewParent parent = view.getParent();
- ViewInfo result;
- if (isContentFrame) {
- // Account for parent scroll values when calculating the bounding box
- int scrollX = parent != null ? ((View)parent).getScrollX() : 0;
- int scrollY = parent != null ? ((View)parent).getScrollY() : 0;
-
- // The view is part of the layout added by the user. Hence,
- // the ViewCookie may be obtained only through the Context.
- result = new ViewInfo(view.getClass().getName(),
- getContext().getViewKey(view), -scrollX + view.getLeft() + hOffset,
- -scrollY + view.getTop() + vOffset, -scrollX + view.getRight() + hOffset,
- -scrollY + view.getBottom() + vOffset,
- view, view.getLayoutParams());
- } else {
- // We are part of the system decor.
- SystemViewInfo r = new SystemViewInfo(view.getClass().getName(),
- getViewKey(view),
- view.getLeft(), view.getTop(), view.getRight(),
- view.getBottom(), view, view.getLayoutParams());
- result = r;
- // We currently mark three kinds of views:
- // 1. Menus in the Action Bar
- // 2. Menus in the Overflow popup.
- // 3. The overflow popup button.
- if (view instanceof ListMenuItemView) {
- // Mark 2.
- // All menus in the popup are of type ListMenuItemView.
- r.setViewType(ViewType.ACTION_BAR_OVERFLOW_MENU);
- } else {
- // Mark 3.
- ViewGroup.LayoutParams lp = view.getLayoutParams();
- if (lp instanceof ActionMenuView.LayoutParams &&
- ((ActionMenuView.LayoutParams) lp).isOverflowButton) {
- r.setViewType(ViewType.ACTION_BAR_OVERFLOW);
- } else {
- // Mark 1.
- // A view is a menu in the Action Bar is it is not the overflow button and of
- // its parent is of type ActionMenuView. We can also check if the view is
- // instanceof ActionMenuItemView but that will fail for menus using
- // actionProviderClass.
- while (parent != mViewRoot && parent instanceof ViewGroup) {
- if (parent instanceof ActionMenuView) {
- r.setViewType(ViewType.ACTION_BAR_MENU);
- break;
- }
- parent = parent.getParent();
- }
- }
- }
- }
-
- if (setExtendedInfo) {
- MarginLayoutParams marginParams = null;
- LayoutParams params = view.getLayoutParams();
- if (params instanceof MarginLayoutParams) {
- marginParams = (MarginLayoutParams) params;
- }
- result.setExtendedInfo(view.getBaseline(),
- marginParams != null ? marginParams.leftMargin : 0,
- marginParams != null ? marginParams.topMargin : 0,
- marginParams != null ? marginParams.rightMargin : 0,
- marginParams != null ? marginParams.bottomMargin : 0);
- }
-
- return result;
- }
-
- /* (non-Javadoc)
- * The cookie for menu items are stored in menu item and not in the map from View stored in
- * BridgeContext.
- */
- @Nullable
- private Object getViewKey(View view) {
- BridgeContext context = getContext();
- if (!(view instanceof MenuView.ItemView)) {
- return context.getViewKey(view);
- }
- MenuItemImpl menuItem;
- if (view instanceof ActionMenuItemView) {
- menuItem = ((ActionMenuItemView) view).getItemData();
- } else if (view instanceof ListMenuItemView) {
- menuItem = ((ListMenuItemView) view).getItemData();
- } else if (view instanceof IconMenuItemView) {
- menuItem = ((IconMenuItemView) view).getItemData();
- } else {
- menuItem = null;
- }
- if (menuItem instanceof BridgeMenuItemImpl) {
- return ((BridgeMenuItemImpl) menuItem).getViewCookie();
- }
-
- return null;
- }
-
- public void invalidateRenderingSize() {
- mMeasuredScreenWidth = mMeasuredScreenHeight = -1;
- }
-
- public BufferedImage getImage() {
- return mImage;
- }
-
- public boolean isAlphaChannelImage() {
- return mIsAlphaChannelImage;
- }
-
- public List<ViewInfo> getViewInfos() {
- return mViewInfoList;
- }
-
- public List<ViewInfo> getSystemViewInfos() {
- return mSystemViewInfoList;
- }
-
- public Map<Object, PropertiesMap> getDefaultProperties() {
- return getContext().getDefaultProperties();
- }
-
- public void setScene(RenderSession session) {
- mScene = session;
- }
-
- public RenderSession getSession() {
- return mScene;
- }
-
- public void dispose() {
- boolean createdLooper = false;
- if (Looper.myLooper() == null) {
- // Detaching the root view from the window will try to stop any running animations.
- // The stop method checks that it can run in the looper so, if there is no current
- // looper, we create a temporary one to complete the shutdown.
- Bridge.prepareThread();
- createdLooper = true;
- }
- AttachInfo_Accessor.detachFromWindow(mViewRoot);
- if (mCanvas != null) {
- mCanvas.release();
- mCanvas = null;
- }
- if (mViewInfoList != null) {
- mViewInfoList.clear();
- }
- if (mSystemViewInfoList != null) {
- mSystemViewInfoList.clear();
- }
- mImage = null;
- mViewRoot = null;
- mContentRoot = null;
-
- if (createdLooper) {
- Bridge.cleanupThread();
- Choreographer_Delegate.dispose();
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
deleted file mode 100644
index 75f9ec5..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
+++ /dev/null
@@ -1,706 +0,0 @@
-/*
- * Copyright (C) 2008 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.layoutlib.bridge.impl;
-
-import com.android.SdkConstants;
-import com.android.ide.common.rendering.api.DensityBasedResourceValue;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.internal.util.XmlUtils;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
-import com.android.layoutlib.bridge.android.RenderParamsFlags;
-import com.android.ninepatch.NinePatch;
-import com.android.ninepatch.NinePatchChunk;
-import com.android.resources.Density;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.res.ColorStateList;
-import android.content.res.ComplexColor;
-import android.content.res.ComplexColor_Accessor;
-import android.content.res.FontResourcesParser;
-import android.content.res.GradientColor;
-import android.content.res.Resources.Theme;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap_Delegate;
-import android.graphics.Color;
-import android.graphics.NinePatch_Delegate;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.Typeface_Accessor;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.NinePatchDrawable;
-import android.text.FontConfig;
-import android.util.TypedValue;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Helper class to provide various conversion method used in handling android resources.
- */
-public final class ResourceHelper {
-
- private final static Pattern sFloatPattern = Pattern.compile("(-?[0-9]+(?:\\.[0-9]+)?)(.*)");
- private final static float[] sFloatOut = new float[1];
-
- private final static TypedValue mValue = new TypedValue();
-
- /**
- * Returns the color value represented by the given string value
- * @param value the color value
- * @return the color as an int
- * @throws NumberFormatException if the conversion failed.
- */
- public static int getColor(String value) {
- if (value != null) {
- value = value.trim();
- if (!value.startsWith("#")) {
- if (value.startsWith(SdkConstants.PREFIX_THEME_REF)) {
- throw new NumberFormatException(String.format(
- "Attribute '%s' not found. Are you using the right theme?", value));
- }
- throw new NumberFormatException(
- String.format("Color value '%s' must start with #", value));
- }
-
- value = value.substring(1);
-
- // make sure it's not longer than 32bit
- if (value.length() > 8) {
- throw new NumberFormatException(String.format(
- "Color value '%s' is too long. Format is either" +
- "#AARRGGBB, #RRGGBB, #RGB, or #ARGB",
- value));
- }
-
- if (value.length() == 3) { // RGB format
- char[] color = new char[8];
- color[0] = color[1] = 'F';
- color[2] = color[3] = value.charAt(0);
- color[4] = color[5] = value.charAt(1);
- color[6] = color[7] = value.charAt(2);
- value = new String(color);
- } else if (value.length() == 4) { // ARGB format
- char[] color = new char[8];
- color[0] = color[1] = value.charAt(0);
- color[2] = color[3] = value.charAt(1);
- color[4] = color[5] = value.charAt(2);
- color[6] = color[7] = value.charAt(3);
- value = new String(color);
- } else if (value.length() == 6) {
- value = "FF" + value;
- }
-
- // this is a RRGGBB or AARRGGBB value
-
- // Integer.parseInt will fail to parse strings like "ff191919", so we use
- // a Long, but cast the result back into an int, since we know that we're only
- // dealing with 32 bit values.
- return (int)Long.parseLong(value, 16);
- }
-
- throw new NumberFormatException();
- }
-
- /**
- * Returns a {@link ComplexColor} from the given {@link ResourceValue}
- *
- * @param resValue the value containing a color value or a file path to a complex color
- * definition
- * @param context the current context
- * @param theme the theme to use when resolving the complex color
- * @param allowGradients when false, only {@link ColorStateList} will be returned. If a {@link
- * GradientColor} is found, null will be returned.
- */
- @Nullable
- private static ComplexColor getInternalComplexColor(@NonNull ResourceValue resValue,
- @NonNull BridgeContext context, @Nullable Theme theme, boolean allowGradients) {
- String value = resValue.getValue();
- if (value == null || RenderResources.REFERENCE_NULL.equals(value)) {
- return null;
- }
-
- XmlPullParser parser = null;
- // first check if the value is a file (xml most likely)
- Boolean psiParserSupport = context.getLayoutlibCallback().getFlag(
- RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT);
- if (psiParserSupport != null && psiParserSupport) {
- parser = context.getLayoutlibCallback().getXmlFileParser(value);
- }
- if (parser == null) {
- File f = new File(value);
- if (f.isFile()) {
- // let the framework inflate the color from the XML file, by
- // providing an XmlPullParser
- try {
- parser = ParserFactory.create(f);
- } catch (XmlPullParserException | FileNotFoundException e) {
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
- "Failed to parse file " + value, e, null /*data*/);
- }
- }
- }
-
- if (parser != null) {
- try {
- BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
- parser, context, resValue.isFramework());
- try {
- // Advance the parser to the first element so we can detect if it's a
- // color list or a gradient color
- int type;
- //noinspection StatementWithEmptyBody
- while ((type = blockParser.next()) != XmlPullParser.START_TAG
- && type != XmlPullParser.END_DOCUMENT) {
- // Seek parser to start tag.
- }
-
- if (type != XmlPullParser.START_TAG) {
- assert false : "No start tag found";
- return null;
- }
-
- final String name = blockParser.getName();
- if (allowGradients && "gradient".equals(name)) {
- return ComplexColor_Accessor.createGradientColorFromXmlInner(
- context.getResources(),
- blockParser, blockParser,
- theme);
- } else if ("selector".equals(name)) {
- return ComplexColor_Accessor.createColorStateListFromXmlInner(
- context.getResources(),
- blockParser, blockParser,
- theme);
- }
- } finally {
- blockParser.ensurePopped();
- }
- } catch (XmlPullParserException e) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Failed to configure parser for " + value, e, null /*data*/);
- // we'll return null below.
- } catch (Exception e) {
- // this is an error and not warning since the file existence is
- // checked before attempting to parse it.
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
- "Failed to parse file " + value, e, null /*data*/);
-
- return null;
- }
- } else {
- // try to load the color state list from an int
- try {
- int color = getColor(value);
- return ColorStateList.valueOf(color);
- } catch (NumberFormatException e) {
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
- "Failed to convert " + value + " into a ColorStateList", e,
- null /*data*/);
- }
- }
-
- return null;
- }
-
- /**
- * Returns a {@link ColorStateList} from the given {@link ResourceValue}
- *
- * @param resValue the value containing a color value or a file path to a complex color
- * definition
- * @param context the current context
- */
- @Nullable
- public static ColorStateList getColorStateList(@NonNull ResourceValue resValue,
- @NonNull BridgeContext context) {
- return (ColorStateList) getInternalComplexColor(resValue, context, context.getTheme(),
- false);
- }
-
- /**
- * Returns a {@link ComplexColor} from the given {@link ResourceValue}
- *
- * @param resValue the value containing a color value or a file path to a complex color
- * definition
- * @param context the current context
- */
- @Nullable
- public static ComplexColor getComplexColor(@NonNull ResourceValue resValue,
- @NonNull BridgeContext context) {
- return getInternalComplexColor(resValue, context, context.getTheme(), true);
- }
-
- /**
- * Returns a drawable from the given value.
- * @param value The value that contains a path to a 9 patch, a bitmap or a xml based drawable,
- * or an hexadecimal color
- * @param context the current context
- */
- public static Drawable getDrawable(ResourceValue value, BridgeContext context) {
- return getDrawable(value, context, null);
- }
-
- /**
- * Returns a drawable from the given value.
- * @param value The value that contains a path to a 9 patch, a bitmap or a xml based drawable,
- * or an hexadecimal color
- * @param context the current context
- * @param theme the theme to be used to inflate the drawable.
- */
- public static Drawable getDrawable(ResourceValue value, BridgeContext context, Theme theme) {
- if (value == null) {
- return null;
- }
- String stringValue = value.getValue();
- if (RenderResources.REFERENCE_NULL.equals(stringValue)) {
- return null;
- }
-
- String lowerCaseValue = stringValue.toLowerCase();
-
- Density density = Density.MEDIUM;
- if (value instanceof DensityBasedResourceValue) {
- density = ((DensityBasedResourceValue) value).getResourceDensity();
- }
-
- if (lowerCaseValue.endsWith(NinePatch.EXTENSION_9PATCH)) {
- File file = new File(stringValue);
- if (file.isFile()) {
- try {
- return getNinePatchDrawable(new FileInputStream(file), density,
- value.isFramework(), stringValue, context);
- } catch (IOException e) {
- // failed to read the file, we'll return null below.
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
- "Failed lot load " + file.getAbsolutePath(), e, null /*data*/);
- }
- }
-
- return null;
- } else if (lowerCaseValue.endsWith(".xml") || stringValue.startsWith("@aapt:_aapt/")) {
- // create a block parser for the file
- try {
- XmlPullParser parser = context.getLayoutlibCallback().getParser(value);
- if (parser == null) {
- File drawableFile = new File(stringValue);
- if (drawableFile.isFile()) {
- parser = ParserFactory.create(drawableFile);
- }
- }
-
- BridgeXmlBlockParser blockParser =
- new BridgeXmlBlockParser(parser, context, value.isFramework());
- try {
- return Drawable.createFromXml(context.getResources(), blockParser, theme);
- } finally {
- blockParser.ensurePopped();
- }
- } catch (Exception e) {
- // this is an error and not warning since the file existence is checked before
- // attempting to parse it.
- Bridge.getLog().error(null, "Failed to parse file " + stringValue, e,
- null /*data*/);
- }
-
- return null;
- } else {
- File bmpFile = new File(stringValue);
- if (bmpFile.isFile()) {
- try {
- Bitmap bitmap = Bridge.getCachedBitmap(stringValue,
- value.isFramework() ? null : context.getProjectKey());
-
- if (bitmap == null) {
- bitmap =
- Bitmap_Delegate.createBitmap(bmpFile, false /*isMutable*/, density);
- Bridge.setCachedBitmap(stringValue, bitmap,
- value.isFramework() ? null : context.getProjectKey());
- }
-
- return new BitmapDrawable(context.getResources(), bitmap);
- } catch (IOException e) {
- // we'll return null below
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
- "Failed lot load " + bmpFile.getAbsolutePath(), e, null /*data*/);
- }
- } else {
- // attempt to get a color from the value
- try {
- int color = getColor(stringValue);
- return new ColorDrawable(color);
- } catch (NumberFormatException e) {
- // we'll return null below.
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
- "Failed to convert " + stringValue + " into a drawable", e,
- null /*data*/);
- }
- }
- }
-
- return null;
- }
-
- /**
- * Returns a {@link Typeface} given a font name. The font name, can be a system font family
- * (like sans-serif) or a full path if the font is to be loaded from resources.
- */
- public static Typeface getFont(String fontName, BridgeContext context, Theme theme, boolean
- isFramework) {
- if (fontName == null) {
- return null;
- }
-
- if (Typeface_Accessor.isSystemFont(fontName)) {
- // Shortcut for the case where we are asking for a system font name. Those are not
- // loaded using external resources.
- return null;
- }
-
- // Check if this is an asset that we've already loaded dynamically
- Typeface typeface = Typeface.findFromCache(context.getAssets(), fontName);
- if (typeface != null) {
- return typeface;
- }
-
- String lowerCaseValue = fontName.toLowerCase();
- if (lowerCaseValue.endsWith(".xml")) {
- // create a block parser for the file
- Boolean psiParserSupport = context.getLayoutlibCallback().getFlag(
- RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT);
- XmlPullParser parser = null;
- if (psiParserSupport != null && psiParserSupport) {
- parser = context.getLayoutlibCallback().getXmlFileParser(fontName);
- }
- else {
- File f = new File(fontName);
- if (f.isFile()) {
- try {
- parser = ParserFactory.create(f);
- } catch (XmlPullParserException | FileNotFoundException e) {
- // this is an error and not warning since the file existence is checked before
- // attempting to parse it.
- Bridge.getLog().error(null, "Failed to parse file " + fontName,
- e, null /*data*/);
- }
- }
- }
-
- if (parser != null) {
- BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
- parser, context, isFramework);
- try {
- FontResourcesParser.FamilyResourceEntry entry =
- FontResourcesParser.parse(blockParser, context.getResources());
- typeface = Typeface.createFromResources(entry, context.getAssets(),
- fontName);
- } catch (XmlPullParserException | IOException e) {
- Bridge.getLog().error(null, "Failed to parse file " + fontName,
- e, null /*data*/);
- } finally {
- blockParser.ensurePopped();
- }
- } else {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- String.format("File %s does not exist (or is not a file)", fontName),
- null /*data*/);
- }
- } else {
- typeface = Typeface.createFromResources(context.getAssets(), fontName, 0);
- }
-
- return typeface;
- }
-
- /**
- * Returns a {@link Typeface} given a font name. The font name, can be a system font family
- * (like sans-serif) or a full path if the font is to be loaded from resources.
- */
- public static Typeface getFont(ResourceValue value, BridgeContext context, Theme theme) {
- if (value == null) {
- return null;
- }
-
- return getFont(value.getValue(), context, theme, value.isFramework());
- }
-
- private static Drawable getNinePatchDrawable(InputStream inputStream, Density density,
- boolean isFramework, String cacheKey, BridgeContext context) throws IOException {
- // see if we still have both the chunk and the bitmap in the caches
- NinePatchChunk chunk = Bridge.getCached9Patch(cacheKey,
- isFramework ? null : context.getProjectKey());
- Bitmap bitmap = Bridge.getCachedBitmap(cacheKey,
- isFramework ? null : context.getProjectKey());
-
- // if either chunk or bitmap is null, then we reload the 9-patch file.
- if (chunk == null || bitmap == null) {
- try {
- NinePatch ninePatch = NinePatch.load(inputStream, true /*is9Patch*/,
- false /* convert */);
- if (ninePatch != null) {
- if (chunk == null) {
- chunk = ninePatch.getChunk();
-
- Bridge.setCached9Patch(cacheKey, chunk,
- isFramework ? null : context.getProjectKey());
- }
-
- if (bitmap == null) {
- bitmap = Bitmap_Delegate.createBitmap(ninePatch.getImage(),
- false /*isMutable*/,
- density);
-
- Bridge.setCachedBitmap(cacheKey, bitmap,
- isFramework ? null : context.getProjectKey());
- }
- }
- } catch (MalformedURLException e) {
- // URL is wrong, we'll return null below
- }
- }
-
- if (chunk != null && bitmap != null) {
- int[] padding = chunk.getPadding();
- Rect paddingRect = new Rect(padding[0], padding[1], padding[2], padding[3]);
-
- return new NinePatchDrawable(context.getResources(), bitmap,
- NinePatch_Delegate.serialize(chunk),
- paddingRect, null);
- }
-
- return null;
- }
-
- /**
- * Looks for an attribute in the current theme.
- *
- * @param resources the render resources
- * @param name the name of the attribute
- * @param defaultValue the default value.
- * @param isFrameworkAttr if the attribute is in android namespace
- * @return the value of the attribute or the default one if not found.
- */
- public static boolean getBooleanThemeValue(@NonNull RenderResources resources, String name,
- boolean isFrameworkAttr, boolean defaultValue) {
- ResourceValue value = resources.findItemInTheme(name, isFrameworkAttr);
- value = resources.resolveResValue(value);
- if (value == null) {
- return defaultValue;
- }
- return XmlUtils.convertValueToBoolean(value.getValue(), defaultValue);
- }
-
- // ------- TypedValue stuff
- // This is taken from //device/libs/utils/ResourceTypes.cpp
-
- private static final class UnitEntry {
- String name;
- int type;
- int unit;
- float scale;
-
- UnitEntry(String name, int type, int unit, float scale) {
- this.name = name;
- this.type = type;
- this.unit = unit;
- this.scale = scale;
- }
- }
-
- private final static UnitEntry[] sUnitNames = new UnitEntry[] {
- new UnitEntry("px", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PX, 1.0f),
- new UnitEntry("dip", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
- new UnitEntry("dp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
- new UnitEntry("sp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_SP, 1.0f),
- new UnitEntry("pt", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PT, 1.0f),
- new UnitEntry("in", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_IN, 1.0f),
- new UnitEntry("mm", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_MM, 1.0f),
- new UnitEntry("%", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION, 1.0f/100),
- new UnitEntry("%p", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION_PARENT, 1.0f/100),
- };
-
- /**
- * Returns the raw value from the given attribute float-type value string.
- * This object is only valid until the next call on to {@link ResourceHelper}.
- */
- public static TypedValue getValue(String attribute, String value, boolean requireUnit) {
- if (parseFloatAttribute(attribute, value, mValue, requireUnit)) {
- return mValue;
- }
-
- return null;
- }
-
- /**
- * Parse a float attribute and return the parsed value into a given TypedValue.
- * @param attribute the name of the attribute. Can be null if <var>requireUnit</var> is false.
- * @param value the string value of the attribute
- * @param outValue the TypedValue to receive the parsed value
- * @param requireUnit whether the value is expected to contain a unit.
- * @return true if success.
- */
- public static boolean parseFloatAttribute(String attribute, @NonNull String value,
- TypedValue outValue, boolean requireUnit) {
- assert !requireUnit || attribute != null;
-
- // remove the space before and after
- value = value.trim();
- int len = value.length();
-
- if (len <= 0) {
- return false;
- }
-
- // check that there's no non ascii characters.
- char[] buf = value.toCharArray();
- for (int i = 0 ; i < len ; i++) {
- if (buf[i] > 255) {
- return false;
- }
- }
-
- // check the first character
- if ((buf[0] < '0' || buf[0] > '9') && buf[0] != '.' && buf[0] != '-' && buf[0] != '+') {
- return false;
- }
-
- // now look for the string that is after the float...
- Matcher m = sFloatPattern.matcher(value);
- if (m.matches()) {
- String f_str = m.group(1);
- String end = m.group(2);
-
- float f;
- try {
- f = Float.parseFloat(f_str);
- } catch (NumberFormatException e) {
- // this shouldn't happen with the regexp above.
- return false;
- }
-
- if (end.length() > 0 && end.charAt(0) != ' ') {
- // Might be a unit...
- if (parseUnit(end, outValue, sFloatOut)) {
- computeTypedValue(outValue, f, sFloatOut[0]);
- return true;
- }
- return false;
- }
-
- // make sure it's only spaces at the end.
- end = end.trim();
-
- if (end.length() == 0) {
- if (outValue != null) {
- if (!requireUnit) {
- outValue.type = TypedValue.TYPE_FLOAT;
- outValue.data = Float.floatToIntBits(f);
- } else {
- // no unit when required? Use dp and out an error.
- applyUnit(sUnitNames[1], outValue, sFloatOut);
- computeTypedValue(outValue, f, sFloatOut[0]);
-
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
- String.format(
- "Dimension \"%1$s\" in attribute \"%2$s\" is missing unit!",
- value, attribute),
- null);
- }
- return true;
- }
- }
- }
-
- return false;
- }
-
- private static void computeTypedValue(TypedValue outValue, float value, float scale) {
- value *= scale;
- boolean neg = value < 0;
- if (neg) {
- value = -value;
- }
- long bits = (long)(value*(1<<23)+.5f);
- int radix;
- int shift;
- if ((bits&0x7fffff) == 0) {
- // Always use 23p0 if there is no fraction, just to make
- // things easier to read.
- radix = TypedValue.COMPLEX_RADIX_23p0;
- shift = 23;
- } else if ((bits&0xffffffffff800000L) == 0) {
- // Magnitude is zero -- can fit in 0 bits of precision.
- radix = TypedValue.COMPLEX_RADIX_0p23;
- shift = 0;
- } else if ((bits&0xffffffff80000000L) == 0) {
- // Magnitude can fit in 8 bits of precision.
- radix = TypedValue.COMPLEX_RADIX_8p15;
- shift = 8;
- } else if ((bits&0xffffff8000000000L) == 0) {
- // Magnitude can fit in 16 bits of precision.
- radix = TypedValue.COMPLEX_RADIX_16p7;
- shift = 16;
- } else {
- // Magnitude needs entire range, so no fractional part.
- radix = TypedValue.COMPLEX_RADIX_23p0;
- shift = 23;
- }
- int mantissa = (int)(
- (bits>>shift) & TypedValue.COMPLEX_MANTISSA_MASK);
- if (neg) {
- mantissa = (-mantissa) & TypedValue.COMPLEX_MANTISSA_MASK;
- }
- outValue.data |=
- (radix<<TypedValue.COMPLEX_RADIX_SHIFT)
- | (mantissa<<TypedValue.COMPLEX_MANTISSA_SHIFT);
- }
-
- private static boolean parseUnit(String str, TypedValue outValue, float[] outScale) {
- str = str.trim();
-
- for (UnitEntry unit : sUnitNames) {
- if (unit.name.equals(str)) {
- applyUnit(unit, outValue, outScale);
- return true;
- }
- }
-
- return false;
- }
-
- private static void applyUnit(UnitEntry unit, TypedValue outValue, float[] outScale) {
- outValue.type = unit.type;
- // COMPLEX_UNIT_SHIFT is 0 and hence intelliJ complains about it. Suppress the warning.
- //noinspection PointlessBitwiseExpression
- outValue.data = unit.unit << TypedValue.COMPLEX_UNIT_SHIFT;
- outScale[0] = unit.scale;
- }
-}
-
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Stack.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Stack.java
deleted file mode 100644
index 9bd0015..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Stack.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2010 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.layoutlib.bridge.impl;
-
-import java.util.ArrayList;
-
-/**
- * Custom Stack implementation on top of an {@link ArrayList} instead of
- * using {@link java.util.Stack} which is on top of a vector.
- *
- * @param <T>
- */
-public class Stack<T> extends ArrayList<T> {
-
- private static final long serialVersionUID = 1L;
-
- public Stack() {
- super();
- }
-
- public Stack(int size) {
- super(size);
- }
-
- /**
- * Pushes the given object to the stack
- * @param object the object to push
- */
- public void push(T object) {
- add(object);
- }
-
- /**
- * Remove the object at the top of the stack and returns it.
- * @return the removed object or null if the stack was empty.
- */
- public T pop() {
- if (size() > 0) {
- return remove(size() - 1);
- }
-
- return null;
- }
-
- /**
- * Returns the object at the top of the stack.
- * @return the object at the top or null if the stack is empty.
- */
- public T peek() {
- if (size() > 0) {
- return get(size() - 1);
- }
-
- return null;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java
deleted file mode 100644
index 9fea167..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2014 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.layoutlib.bridge.impl;
-
-import com.android.ide.common.rendering.api.ViewInfo;
-import com.android.ide.common.rendering.api.ViewType;
-
-/**
- * ViewInfo for views added by the platform.
- */
-public class SystemViewInfo extends ViewInfo {
-
- private ViewType mViewType;
-
- public SystemViewInfo(String name, Object cookie, int left, int top,
- int right, int bottom) {
- super(name, cookie, left, top, right, bottom);
- }
-
- public SystemViewInfo(String name, Object cookie, int left, int top,
- int right, int bottom, Object viewObject, Object layoutParamsObject) {
- super(name, cookie, left, top, right, bottom, viewObject,
- layoutParamsObject);
- }
-
- @Override
- public ViewType getViewType() {
- if (mViewType != null) {
- return mViewType;
- }
- return ViewType.SYSTEM_UNKNOWN;
- }
-
- public void setViewType(ViewType type) {
- mViewType = type;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
deleted file mode 100644
index c0ca1d5..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2013 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.layoutlib.bridge.impl.binding;
-
-import com.android.ide.common.rendering.api.DataBindingItem;
-import com.android.ide.common.rendering.api.LayoutlibCallback;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.ResourceReference;
-import com.android.ide.common.rendering.api.IProjectCallback.ViewAttribute;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.impl.RenderAction;
-import com.android.util.Pair;
-
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.Checkable;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-/**
- * A Helper class to do fake data binding in {@link AdapterView} objects.
- */
-public class AdapterHelper {
-
- @SuppressWarnings("deprecation")
- static Pair<View, Boolean> getView(AdapterItem item, AdapterItem parentItem, ViewGroup parent,
- LayoutlibCallback callback, ResourceReference adapterRef, boolean skipCallbackParser) {
- // we don't care about recycling here because we never scroll.
- DataBindingItem dataBindingItem = item.getDataBindingItem();
-
- BridgeContext context = RenderAction.getCurrentContext();
-
- Pair<View, Boolean> pair = context.inflateView(dataBindingItem.getViewReference(),
- parent, false /*attachToRoot*/, skipCallbackParser);
-
- View view = pair.getFirst();
- skipCallbackParser |= pair.getSecond();
-
- if (view != null) {
- fillView(context, view, item, parentItem, callback, adapterRef);
- } else {
- // create a text view to display an error.
- TextView tv = new TextView(context);
- tv.setText("Unable to find layout: " + dataBindingItem.getViewReference().getName());
- view = tv;
- }
-
- return Pair.of(view, skipCallbackParser);
- }
-
- private static void fillView(BridgeContext context, View view, AdapterItem item,
- AdapterItem parentItem, LayoutlibCallback callback, ResourceReference adapterRef) {
- if (view instanceof ViewGroup) {
- ViewGroup group = (ViewGroup) view;
- final int count = group.getChildCount();
- for (int i = 0 ; i < count ; i++) {
- fillView(context, group.getChildAt(i), item, parentItem, callback, adapterRef);
- }
- } else {
- int id = view.getId();
- if (id != 0) {
- ResourceReference resolvedRef = context.resolveId(id);
- if (resolvedRef != null) {
- int fullPosition = item.getFullPosition();
- int positionPerType = item.getPositionPerType();
- int fullParentPosition = parentItem != null ? parentItem.getFullPosition() : 0;
- int parentPositionPerType = parentItem != null ?
- parentItem.getPositionPerType() : 0;
-
- if (view instanceof TextView) {
- TextView tv = (TextView) view;
- Object value = callback.getAdapterItemValue(
- adapterRef, context.getViewKey(view),
- item.getDataBindingItem().getViewReference(),
- fullPosition, positionPerType,
- fullParentPosition, parentPositionPerType,
- resolvedRef, ViewAttribute.TEXT, tv.getText().toString());
- if (value != null) {
- if (value.getClass() != ViewAttribute.TEXT.getAttributeClass()) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format(
- "Wrong Adapter Item value class for TEXT. Expected String, got %s",
- value.getClass().getName()), null);
- } else {
- tv.setText((String) value);
- }
- }
- }
-
- if (view instanceof Checkable) {
- Checkable cb = (Checkable) view;
-
- Object value = callback.getAdapterItemValue(
- adapterRef, context.getViewKey(view),
- item.getDataBindingItem().getViewReference(),
- fullPosition, positionPerType,
- fullParentPosition, parentPositionPerType,
- resolvedRef, ViewAttribute.IS_CHECKED, cb.isChecked());
- if (value != null) {
- if (value.getClass() != ViewAttribute.IS_CHECKED.getAttributeClass()) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format(
- "Wrong Adapter Item value class for IS_CHECKED. Expected Boolean, got %s",
- value.getClass().getName()), null);
- } else {
- cb.setChecked((Boolean) value);
- }
- }
- }
-
- if (view instanceof ImageView) {
- ImageView iv = (ImageView) view;
-
- Object value = callback.getAdapterItemValue(
- adapterRef, context.getViewKey(view),
- item.getDataBindingItem().getViewReference(),
- fullPosition, positionPerType,
- fullParentPosition, parentPositionPerType,
- resolvedRef, ViewAttribute.SRC, iv.getDrawable());
- if (value != null) {
- if (value.getClass() != ViewAttribute.SRC.getAttributeClass()) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format(
- "Wrong Adapter Item value class for SRC. Expected Boolean, got %s",
- value.getClass().getName()), null);
- } else {
- // FIXME
- }
- }
- }
- }
- }
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterItem.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterItem.java
deleted file mode 100644
index 8e28dba..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterItem.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2013 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.layoutlib.bridge.impl.binding;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import com.android.ide.common.rendering.api.DataBindingItem;
-
-/**
- * This is the items provided by the adapter. They are dynamically generated.
- */
-final class AdapterItem {
- private final DataBindingItem mItem;
- private final int mType;
- private final int mFullPosition;
- private final int mPositionPerType;
- private List<AdapterItem> mChildren;
-
- protected AdapterItem(DataBindingItem item, int type, int fullPosition,
- int positionPerType) {
- mItem = item;
- mType = type;
- mFullPosition = fullPosition;
- mPositionPerType = positionPerType;
- }
-
- void addChild(AdapterItem child) {
- if (mChildren == null) {
- mChildren = new ArrayList<AdapterItem>();
- }
-
- mChildren.add(child);
- }
-
- List<AdapterItem> getChildren() {
- if (mChildren != null) {
- return mChildren;
- }
-
- return Collections.emptyList();
- }
-
- int getType() {
- return mType;
- }
-
- int getFullPosition() {
- return mFullPosition;
- }
-
- int getPositionPerType() {
- return mPositionPerType;
- }
-
- DataBindingItem getDataBindingItem() {
- return mItem;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java
deleted file mode 100644
index 142eac1..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2011 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.layoutlib.bridge.impl.binding;
-
-import com.android.ide.common.rendering.api.AdapterBinding;
-import com.android.ide.common.rendering.api.DataBindingItem;
-import com.android.ide.common.rendering.api.LayoutlibCallback;
-import com.android.ide.common.rendering.api.ResourceReference;
-import com.android.util.Pair;
-
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.ListAdapter;
-import android.widget.SpinnerAdapter;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Fake adapter to do fake data binding in {@link AdapterView} objects for {@link ListAdapter}
- * and {@link SpinnerAdapter}.
- *
- */
-public class FakeAdapter extends BaseAdapter {
-
- // don't use a set because the order is important.
- private final List<ResourceReference> mTypes = new ArrayList<ResourceReference>();
- private final LayoutlibCallback mCallback;
- private final ResourceReference mAdapterRef;
- private final List<AdapterItem> mItems = new ArrayList<AdapterItem>();
- private boolean mSkipCallbackParser = false;
-
- public FakeAdapter(ResourceReference adapterRef, AdapterBinding binding,
- LayoutlibCallback callback) {
- mAdapterRef = adapterRef;
- mCallback = callback;
-
- final int repeatCount = binding.getRepeatCount();
- final int itemCount = binding.getItemCount();
-
- // Need an array to count for each type.
- // This is likely too big, but is the max it can be.
- int[] typeCount = new int[itemCount];
-
- // We put several repeating sets.
- for (int r = 0 ; r < repeatCount ; r++) {
- // loop on the type of list items, and add however many for each type.
- for (DataBindingItem dataBindingItem : binding) {
- ResourceReference viewRef = dataBindingItem.getViewReference();
- int typeIndex = mTypes.indexOf(viewRef);
- if (typeIndex == -1) {
- typeIndex = mTypes.size();
- mTypes.add(viewRef);
- }
-
- int count = dataBindingItem.getCount();
-
- int index = typeCount[typeIndex];
- typeCount[typeIndex] += count;
-
- for (int k = 0 ; k < count ; k++) {
- mItems.add(new AdapterItem(dataBindingItem, typeIndex, mItems.size(), index++));
- }
- }
- }
- }
-
- @Override
- public boolean isEnabled(int position) {
- return true;
- }
-
- @Override
- public int getCount() {
- return mItems.size();
- }
-
- @Override
- public Object getItem(int position) {
- return mItems.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public int getItemViewType(int position) {
- return mItems.get(position).getType();
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- // we don't care about recycling here because we never scroll.
- AdapterItem item = mItems.get(position);
- @SuppressWarnings("deprecation")
- Pair<View, Boolean> pair = AdapterHelper.getView(item, null, parent, mCallback,
- mAdapterRef, mSkipCallbackParser);
- mSkipCallbackParser = pair.getSecond();
- return pair.getFirst();
- }
-
- @Override
- public int getViewTypeCount() {
- return mTypes.size();
- }
-
- // ---- SpinnerAdapter
-
- @Override
- public View getDropDownView(int position, View convertView, ViewGroup parent) {
- // pass
- return null;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java
deleted file mode 100644
index 344b17e..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2011 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.layoutlib.bridge.impl.binding;
-
-import com.android.ide.common.rendering.api.AdapterBinding;
-import com.android.ide.common.rendering.api.DataBindingItem;
-import com.android.ide.common.rendering.api.LayoutlibCallback;
-import com.android.ide.common.rendering.api.ResourceReference;
-import com.android.util.Pair;
-
-import android.database.DataSetObserver;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ExpandableListAdapter;
-import android.widget.HeterogeneousExpandableList;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@SuppressWarnings("deprecation")
-public class FakeExpandableAdapter implements ExpandableListAdapter, HeterogeneousExpandableList {
-
- private final LayoutlibCallback mCallback;
- private final ResourceReference mAdapterRef;
- private boolean mSkipCallbackParser = false;
-
- protected final List<AdapterItem> mItems = new ArrayList<AdapterItem>();
-
- // don't use a set because the order is important.
- private final List<ResourceReference> mGroupTypes = new ArrayList<ResourceReference>();
- private final List<ResourceReference> mChildrenTypes = new ArrayList<ResourceReference>();
-
- public FakeExpandableAdapter(ResourceReference adapterRef, AdapterBinding binding,
- LayoutlibCallback callback) {
- mAdapterRef = adapterRef;
- mCallback = callback;
-
- createItems(binding, binding.getItemCount(), binding.getRepeatCount(), mGroupTypes, 1);
- }
-
- private void createItems(Iterable<DataBindingItem> iterable, final int itemCount,
- final int repeatCount, List<ResourceReference> types, int depth) {
- // Need an array to count for each type.
- // This is likely too big, but is the max it can be.
- int[] typeCount = new int[itemCount];
-
- // we put several repeating sets.
- for (int r = 0 ; r < repeatCount ; r++) {
- // loop on the type of list items, and add however many for each type.
- for (DataBindingItem dataBindingItem : iterable) {
- ResourceReference viewRef = dataBindingItem.getViewReference();
- int typeIndex = types.indexOf(viewRef);
- if (typeIndex == -1) {
- typeIndex = types.size();
- types.add(viewRef);
- }
-
- List<DataBindingItem> children = dataBindingItem.getChildren();
- int count = dataBindingItem.getCount();
-
- // if there are children, we use the count as a repeat count for the children.
- if (children.size() > 0) {
- count = 1;
- }
-
- int index = typeCount[typeIndex];
- typeCount[typeIndex] += count;
-
- for (int k = 0 ; k < count ; k++) {
- AdapterItem item = new AdapterItem(dataBindingItem, typeIndex, mItems.size(),
- index++);
- mItems.add(item);
-
- if (children.size() > 0) {
- createItems(dataBindingItem, depth + 1);
- }
- }
- }
- }
- }
-
- private void createItems(DataBindingItem item, int depth) {
- if (depth == 2) {
- createItems(item, item.getChildren().size(), item.getCount(), mChildrenTypes, depth);
- }
- }
-
- private AdapterItem getChildItem(int groupPosition, int childPosition) {
- AdapterItem item = mItems.get(groupPosition);
-
- List<AdapterItem> children = item.getChildren();
- return children.get(childPosition);
- }
-
- // ---- ExpandableListAdapter
-
- @Override
- public int getGroupCount() {
- return mItems.size();
- }
-
- @Override
- public int getChildrenCount(int groupPosition) {
- AdapterItem item = mItems.get(groupPosition);
- return item.getChildren().size();
- }
-
- @Override
- public Object getGroup(int groupPosition) {
- return mItems.get(groupPosition);
- }
-
- @Override
- public Object getChild(int groupPosition, int childPosition) {
- return getChildItem(groupPosition, childPosition);
- }
-
- @Override
- public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
- ViewGroup parent) {
- // we don't care about recycling here because we never scroll.
- AdapterItem item = mItems.get(groupPosition);
- Pair<View, Boolean> pair = AdapterHelper.getView(item, null /*parentItem*/, parent,
- mCallback, mAdapterRef, mSkipCallbackParser);
- mSkipCallbackParser = pair.getSecond();
- return pair.getFirst();
- }
-
- @Override
- public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
- View convertView, ViewGroup parent) {
- // we don't care about recycling here because we never scroll.
- AdapterItem parentItem = mItems.get(groupPosition);
- AdapterItem item = getChildItem(groupPosition, childPosition);
- Pair<View, Boolean> pair = AdapterHelper.getView(item, parentItem, parent, mCallback,
- mAdapterRef, mSkipCallbackParser);
- mSkipCallbackParser = pair.getSecond();
- return pair.getFirst();
- }
-
- @Override
- public long getGroupId(int groupPosition) {
- return groupPosition;
- }
-
- @Override
- public long getChildId(int groupPosition, int childPosition) {
- return childPosition;
- }
-
- @Override
- public long getCombinedGroupId(long groupId) {
- return groupId << 16 | 0x0000FFFF;
- }
-
- @Override
- public long getCombinedChildId(long groupId, long childId) {
- return groupId << 16 | childId;
- }
-
- @Override
- public boolean isChildSelectable(int groupPosition, int childPosition) {
- return true;
- }
-
- @Override
- public void onGroupCollapsed(int groupPosition) {
- // pass
- }
-
- @Override
- public void onGroupExpanded(int groupPosition) {
- // pass
- }
-
- @Override
- public void registerDataSetObserver(DataSetObserver observer) {
- // pass
- }
-
- @Override
- public void unregisterDataSetObserver(DataSetObserver observer) {
- // pass
- }
-
- @Override
- public boolean hasStableIds() {
- return true;
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return true;
- }
-
- @Override
- public boolean isEmpty() {
- return mItems.isEmpty();
- }
-
- // ---- HeterogeneousExpandableList
-
- @Override
- public int getChildType(int groupPosition, int childPosition) {
- return getChildItem(groupPosition, childPosition).getType();
- }
-
- @Override
- public int getChildTypeCount() {
- return mChildrenTypes.size();
- }
-
- @Override
- public int getGroupType(int groupPosition) {
- return mItems.get(groupPosition).getType();
- }
-
- @Override
- public int getGroupTypeCount() {
- return mGroupTypes.size();
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/libcore/io/BridgeBufferIterator.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/libcore/io/BridgeBufferIterator.java
deleted file mode 100644
index 96cba78..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/libcore/io/BridgeBufferIterator.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2014 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.layoutlib.bridge.libcore.io;
-
-import java.nio.ByteBuffer;
-
-import libcore.io.BufferIterator;
-
-/**
- * Provides an implementation of {@link BufferIterator} over a {@link ByteBuffer}.
- */
-public class BridgeBufferIterator extends BufferIterator {
-
- private final long mSize;
- private final ByteBuffer mByteBuffer;
-
- public BridgeBufferIterator(long size, ByteBuffer buffer) {
- mSize = size;
- mByteBuffer = buffer;
- }
-
- @Override
- public void seek(int offset) {
- assert offset <= mSize;
- mByteBuffer.position(offset);
- }
-
- @Override
- public int pos() {
- return mByteBuffer.position();
- }
-
- @Override
- public void skip(int byteCount) {
- int newPosition = mByteBuffer.position() + byteCount;
- assert newPosition <= mSize;
- mByteBuffer.position(newPosition);
- }
-
- @Override
- public void readByteArray(byte[] dst, int dstOffset, int byteCount) {
- assert dst.length >= dstOffset + byteCount;
- mByteBuffer.get(dst, dstOffset, byteCount);
- }
-
- @Override
- public byte readByte() {
- return mByteBuffer.get();
- }
-
- @Override
- public int readInt() {
- return mByteBuffer.getInt();
- }
-
- @Override
- public void readIntArray(int[] dst, int dstOffset, int intCount) {
- while (--intCount >= 0) {
- dst[dstOffset++] = mByteBuffer.getInt();
- }
- }
-
- @Override
- public short readShort() {
- return mByteBuffer.getShort();
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/shadowutil/ShadowBuffer.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/shadowutil/ShadowBuffer.java
deleted file mode 100644
index ae33e1d..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/shadowutil/ShadowBuffer.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.shadowutil;
-
-import android.annotation.NonNull;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-
-public class ShadowBuffer {
-
- private int mWidth;
- private int mHeight;
- private Bitmap mBitmap;
- private int[] mData;
-
- public ShadowBuffer(int width, int height) {
- mWidth = width;
- mHeight = height;
- mBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
- mData = new int[mBitmap.getWidth() * mBitmap.getHeight()];
- mBitmap.getPixels(mData, 0, mBitmap.getWidth(), 0, 0, mBitmap.getWidth(),
- mBitmap.getHeight());
- }
-
- public void generateTriangles(@NonNull float[] strip, float scale) {
- for (int i = 0; i < strip.length - 8; i += 3) {
- float fx3 = strip[i];
- float fy3 = strip[i + 1];
- float fz3 = scale * strip[i + 2];
-
- float fx2 = strip[i + 3];
- float fy2 = strip[i + 4];
- float fz2 = scale * strip[i + 5];
-
- float fx1 = strip[i + 6];
- float fy1 = strip[i + 7];
- float fz1 = scale * strip[i + 8];
-
- if (fx1 * (fy2 - fy3) + fx2 * (fy3 - fy1) + fx3 * (fy1 - fy2) == 0) {
- continue;
- }
-
- triangleZBuffMin(mData, mWidth, mHeight, fx3, fy3, fz3, fx2, fy2, fz2, fx1, fy1, fz1);
- triangleZBuffMin(mData, mWidth, mHeight, fx1, fy1, fz1, fx2, fy2, fz2, fx3, fy3, fz3);
- }
- mBitmap.setPixels(mData, 0, mBitmap.getWidth(), 0, 0, mBitmap.getWidth(),
- mBitmap.getHeight());
- }
-
- private void triangleZBuffMin(@NonNull int[] buff, int w, int h, float fx3, float fy3,
- float fz3, float fx2, float fy2, float fz2, float fx1, float fy1, float fz1) {
- if (((fx1 - fx2) * (fy3 - fy2) - (fy1 - fy2) * (fx3 - fx2)) < 0) {
- float tmpX = fx1;
- float tmpY = fy1;
- float tmpZ = fz1;
- fx1 = fx2;
- fy1 = fy2;
- fz1 = fz2;
- fx2 = tmpX;
- fy2 = tmpY;
- fz2 = tmpZ;
- }
- double d = (fx1 * (fy3 - fy2) - fx2 * fy3 + fx3 * fy2 + (fx2 - fx3) * fy1);
-
- if (d == 0) {
- return;
- }
- float dx = (float) (-(fy1 * (fz3 - fz2) - fy2 * fz3 + fy3 * fz2 + (fy2 - fy3) * fz1) / d);
- float dy = (float) ((fx1 * (fz3 - fz2) - fx2 * fz3 + fx3 * fz2 + (fx2 - fx3) * fz1) / d);
- float zOff = (float) ((fx1 * (fy3 * fz2 - fy2 * fz3) + fy1 * (fx2 * fz3 - fx3 * fz2) +
- (fx3 * fy2 - fx2 * fy3) * fz1) / d);
-
- int Y1 = (int) (16.0f * fy1 + .5f);
- int Y2 = (int) (16.0f * fy2 + .5f);
- int Y3 = (int) (16.0f * fy3 + .5f);
-
- int X1 = (int) (16.0f * fx1 + .5f);
- int X2 = (int) (16.0f * fx2 + .5f);
- int X3 = (int) (16.0f * fx3 + .5f);
-
- int DX12 = X1 - X2;
- int DX23 = X2 - X3;
- int DX31 = X3 - X1;
-
- int DY12 = Y1 - Y2;
- int DY23 = Y2 - Y3;
- int DY31 = Y3 - Y1;
-
- int FDX12 = DX12 << 4;
- int FDX23 = DX23 << 4;
- int FDX31 = DX31 << 4;
-
- int FDY12 = DY12 << 4;
- int FDY23 = DY23 << 4;
- int FDY31 = DY31 << 4;
-
- int minX = (min(X1, X2, X3) + 0xF) >> 4;
- int maxX = (max(X1, X2, X3) + 0xF) >> 4;
- int minY = (min(Y1, Y2, Y3) + 0xF) >> 4;
- int maxY = (max(Y1, Y2, Y3) + 0xF) >> 4;
-
- if (minY < 0) {
- minY = 0;
- }
- if (minX < 0) {
- minX = 0;
- }
- if (maxX > w) {
- maxX = w;
- }
- if (maxY > h) {
- maxY = h;
- }
- int off = minY * w;
-
- int C1 = DY12 * X1 - DX12 * Y1;
- int C2 = DY23 * X2 - DX23 * Y2;
- int C3 = DY31 * X3 - DX31 * Y3;
-
- if (DY12 < 0 || (DY12 == 0 && DX12 > 0)) {
- C1++;
- }
- if (DY23 < 0 || (DY23 == 0 && DX23 > 0)) {
- C2++;
- }
- if (DY31 < 0 || (DY31 == 0 && DX31 > 0)) {
- C3++;
- }
- int CY1 = C1 + DX12 * (minY << 4) - DY12 * (minX << 4);
- int CY2 = C2 + DX23 * (minY << 4) - DY23 * (minX << 4);
- int CY3 = C3 + DX31 * (minY << 4) - DY31 * (minX << 4);
-
- for (int y = minY; y < maxY; y++) {
- int CX1 = CY1;
- int CX2 = CY2;
- int CX3 = CY3;
- float p = zOff + dy * y;
- for (int x = minX; x < maxX; x++) {
- if (CX1 > 0 && CX2 > 0 && CX3 > 0) {
- int point = x + off;
- float zVal = p + dx * x;
- buff[point] |= ((int) (zVal * 255)) << 24;
- }
- CX1 -= FDY12;
- CX2 -= FDY23;
- CX3 -= FDY31;
- }
- CY1 += FDX12;
- CY2 += FDX23;
- CY3 += FDX31;
- off += w;
- }
- }
-
- private int min(int x1, int x2, int x3) {
- return (x1 > x2) ? ((x2 > x3) ? x3 : x2) : ((x1 > x3) ? x3 : x1);
- }
-
- private int max(int x1, int x2, int x3) {
- return (x1 < x2) ? ((x2 < x3) ? x3 : x2) : ((x1 < x3) ? x3 : x1);
- }
-
- public void draw(@NonNull Canvas c) {
- c.drawBitmap(mBitmap, 0, 0, null);
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/shadowutil/SpotShadow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/shadowutil/SpotShadow.java
deleted file mode 100644
index 33375ff..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/shadowutil/SpotShadow.java
+++ /dev/null
@@ -1,630 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.shadowutil;
-
-import android.annotation.NonNull;
-
-public class SpotShadow {
-
- private static float rayIntersectPoly(@NonNull float[] poly, int polyLength, float px, float py,
- float dx, float dy) {
- int p1 = polyLength - 1;
- for (int p2 = 0; p2 < polyLength; p2++) {
- float p1x = poly[p1 * 2 + 0];
- float p1y = poly[p1 * 2 + 1];
- float p2x = poly[p2 * 2 + 0];
- float p2y = poly[p2 * 2 + 1];
- float div = (dx * (p1y - p2y) + dy * p2x - dy * p1x);
- if (div != 0) {
- float t = (dx * (p1y - py) + dy * px - dy * p1x) / (div);
- if (t >= 0 && t <= 1) {
- float t2 = (p1x * (py - p2y) + p2x * (p1y - py) + px * (p2y - p1y)) / div;
- if (t2 > 0) {
- return t2;
- }
- }
- }
- p1 = p2;
- }
- return Float.NaN;
- }
-
- private static void centroid2d(@NonNull float[] poly, int len, @NonNull float[] ret) {
- float sumX = 0;
- float sumY = 0;
- int p1 = len - 1;
- float area = 0;
- for (int p2 = 0; p2 < len; p2++) {
- float x1 = poly[p1 * 2 + 0];
- float y1 = poly[p1 * 2 + 1];
- float x2 = poly[p2 * 2 + 0];
- float y2 = poly[p2 * 2 + 1];
- float a = (x1 * y2 - x2 * y1);
- sumX += (x1 + x2) * a;
- sumY += (y1 + y2) * a;
- area += a;
- p1 = p2;
- }
-
- float centroidX = sumX / (3 * area);
- float centroidY = sumY / (3 * area);
- ret[0] = centroidX;
- ret[1] = centroidY;
- }
-
- /**
- * calculates the Centroid of a 3d polygon
- * @param poly The flatten 3d vertices coordinates of polygon, the format is like
- * [x0, y0, z0, x1, y1, z1, x2, ...]
- * @param len The number of polygon vertices. So the length of poly should be len * 3.
- * @param ret The array used to sotre the result. The length should be 3.
- */
- private static void centroid3d(@NonNull float[] poly, int len, @NonNull float[] ret) {
- int n = len - 1;
- double area = 0;
- double cx = 0, cy = 0, cz = 0;
- for (int i = 1; i < n; i++) {
- int k = i + 1;
- float a0 = poly[i * 3 + 0] - poly[0 * 3 + 0];
- float a1 = poly[i * 3 + 1] - poly[0 * 3 + 1];
- float a2 = poly[i * 3 + 2] - poly[0 * 3 + 2];
- float b0 = poly[k * 3 + 0] - poly[0 * 3 + 0];
- float b1 = poly[k * 3 + 1] - poly[0 * 3 + 1];
- float b2 = poly[k * 3 + 2] - poly[0 * 3 + 2];
- float c0 = a1 * b2 - b1 * a2;
- float c1 = a2 * b0 - b2 * a0;
- float c2 = a0 * b1 - b0 * a1;
- double areaOfTriangle = Math.sqrt(c0 * c0 + c1 * c1 + c2 * c2);
- area += areaOfTriangle;
- cx += areaOfTriangle * (poly[i * 3 + 0] + poly[k * 3 + 0] + poly[0 * 3 + 0]);
- cy += areaOfTriangle * (poly[i * 3 + 1] + poly[k * 3 + 1] + poly[0 * 3 + 1]);
- cz += areaOfTriangle * (poly[i * 3 + 2] + poly[k * 3 + 2] + poly[0 * 3 + 2]);
- }
- ret[0] = (float) (cx / (3 * area));
- ret[1] = (float) (cy / (3 * area));
- ret[2] = (float) (cz / (3 * area));
- }
-
- /**
- * Extracts the convex hull of a polygon.
- * @param points The vertices coordinates of polygon
- * @param pointsLength The number of polygon vertices. So the length of poly should be len * 3.
- * @param retPoly retPoly is at most the size of the input polygon
- * @return The number of points in the retPolygon
- */
- private static int hull(@NonNull float[] points, int pointsLength, @NonNull float[] retPoly) {
- quicksortX(points, 0, pointsLength - 1);
- int n = pointsLength;
- float[] lUpper = new float[n * 2];
- lUpper[0] = points[0];
- lUpper[1] = points[1];
- lUpper[2] = points[2];
- lUpper[3] = points[3];
-
- int lUpperSize = 2;
-
- for (int i = 2; i < n; i++) {
- lUpper[lUpperSize * 2 + 0] = points[i * 2 + 0];
- lUpper[lUpperSize * 2 + 1] = points[i * 2 + 1];
- lUpperSize++;
-
- while (lUpperSize > 2 &&
- !rightTurn(lUpper[(lUpperSize - 3) * 2], lUpper[(lUpperSize - 3) * 2 + 1],
- lUpper[(lUpperSize - 2) * 2], lUpper[(lUpperSize - 2) * 2 + 1],
- lUpper[(lUpperSize - 1) * 2], lUpper[(lUpperSize - 1) * 2 + 1])) {
- // Remove the middle point of the three last
- lUpper[(lUpperSize - 2) * 2 + 0] = lUpper[(lUpperSize - 1) * 2 + 0];
- lUpper[(lUpperSize - 2) * 2 + 1] = lUpper[(lUpperSize - 1) * 2 + 1];
- lUpperSize--;
- }
- }
-
- float[] lLower = new float[n * 2];
- lLower[0] = points[(n - 1) * 2 + 0];
- lLower[1] = points[(n - 1) * 2 + 1];
- lLower[2] = points[(n - 2) * 2 + 0];
- lLower[3] = points[(n - 2) * 2 + 1];
-
- int lLowerSize = 2;
-
- for (int i = n - 3; i >= 0; i--) {
- lLower[lLowerSize * 2 + 0] = points[i * 2 + 0];
- lLower[lLowerSize * 2 + 1] = points[i * 2 + 1];
- lLowerSize++;
-
- while (lLowerSize > 2 &&
- !rightTurn(lLower[(lLowerSize - 3) * 2], lLower[(lLowerSize - 3) * 2 + 1],
- lLower[(lLowerSize - 2) * 2], lLower[(lLowerSize - 2) * 2 + 1],
- lLower[(lLowerSize - 1) * 2], lLower[(lLowerSize - 1) * 2 + 1])) {
- // Remove the middle point of the three last
- lLower[(lLowerSize - 2) * 2 + 0] = lLower[(lLowerSize - 1) * 2 + 0];
- lLower[(lLowerSize - 2) * 2 + 1] = lLower[(lLowerSize - 1) * 2 + 1];
- lLowerSize--;
- }
- }
-
- int count = 0;
- for (int i = 0; i < lUpperSize; i++) {
- retPoly[count * 2 + 0] = lUpper[i * 2 + 0];
- retPoly[count * 2 + 1] = lUpper[i * 2 + 1];
- count++;
- }
- for (int i = 1; i < lLowerSize - 1; i++) {
- retPoly[count * 2 + 0] = lLower[i * 2 + 0];
- retPoly[count * 2 + 1] = lLower[i * 2 + 1];
- count++;
- }
- return count;
- }
-
- private static boolean rightTurn(float ax, float ay, float bx, float by, float cx, float cy) {
- return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax) > 0.00001;
- }
-
- /**
- * calculates the intersection of poly1 with poly2 and put in poly2
- * @param poly1 The flatten 2d coordinates of polygon
- * @param poly1length The vertices number of poly1
- * @param poly2 The flatten 2d coordinates of polygon
- * @param poly2length The vertices number of poly2
- * @return number of vertices in poly2
- */
- private static int intersection(@NonNull float[] poly1, int poly1length, @NonNull float[] poly2,
- int poly2length) {
- makeClockwise(poly1, poly1length);
- makeClockwise(poly2, poly2length);
- float[] poly = new float[(poly1length * poly2length + 2) * 2];
- int count = 0;
- int pCount = 0;
- for (int i = 0; i < poly1length; i++) {
- if (pointInsidePolygon(poly1[i * 2 + 0], poly1[i * 2 + 1], poly2, poly2length)) {
- poly[count * 2 + 0] = poly1[i * 2 + 0];
- poly[count * 2 + 1] = poly1[i * 2 + 1];
- count++;
- pCount++;
- }
- }
- int fromP1 = pCount;
- for (int i = 0; i < poly2length; i++) {
- if (pointInsidePolygon(poly2[i * 2 + 0], poly2[i * 2 + 1], poly1, poly1length)) {
- poly[count * 2 + 0] = poly2[i * 2 + 0];
- poly[count * 2 + 1] = poly2[i * 2 + 1];
- count++;
- }
- }
- int fromP2 = count - fromP1;
- if (fromP1 == poly1length) { // use p1
- for (int i = 0; i < poly1length; i++) {
- poly2[i * 2 + 0] = poly1[i * 2 + 0];
- poly2[i * 2 + 1] = poly1[i * 2 + 1];
- }
- return poly1length;
- }
- if (fromP2 == poly2length) { // use p2
- return poly2length;
- }
- float[] intersection = new float[2];
- for (int i = 0; i < poly2length; i++) {
- for (int j = 0; j < poly1length; j++) {
- int i1_by_2 = i * 2;
- int i2_by_2 = ((i + 1) % poly2length) * 2;
- int j1_by_2 = j * 2;
- int j2_by_2 = ((j + 1) % poly1length) * 2;
- boolean found =
- lineIntersection(poly2[i1_by_2 + 0], poly2[i1_by_2 + 1], poly2[i2_by_2 + 0],
- poly2[i2_by_2 + 1], poly1[j1_by_2 + 0], poly1[j1_by_2 + 1],
- poly1[j2_by_2 + 0], poly1[j2_by_2 + 1], intersection);
- if (found) {
- poly[count * 2 + 0] = intersection[0];
- poly[count * 2 + 1] = intersection[1];
- count++;
- } else {
- float dx = poly2[i * 2 + 0] - poly1[j * 2 + 0];
- float dy = poly2[i * 2 + 1] - poly1[j * 2 + 1];
-
- if (dx * dx + dy * dy < 0.01) {
- poly[count * 2 + 0] = poly2[i * 2 + 0];
- poly[count * 2 + 1] = poly2[i * 2 + 1];
- count++;
- }
- }
- }
- }
- if (count == 0) {
- return 0;
- }
- float avgX = 0;
- float avgY = 0;
- for (int i = 0; i < count; i++) {
- avgX += poly[i * 2 + 0];
- avgY += poly[i * 2 + 1];
- }
- avgX /= count;
- avgY /= count;
-
- float[] ctr = new float[]{avgX, avgY};
- sort(poly, count, ctr);
- int size = count;
-
- poly2[0] = poly[0];
- poly2[1] = poly[1];
-
- count = 1;
- for (int i = 1; i < size; i++) {
- float dx = poly[i * 2 + 0] - poly[(i - 1) * 2 + 0];
- float dy = poly[i * 2 + 1] - poly[(i - 1) * 2 + 1];
- if (dx * dx + dy * dy >= 0.01) {
- poly2[count * 2 + 0] = poly[i * 2 + 0];
- poly2[count * 2 + 1] = poly[i * 2 + 1];
- count++;
- }
- }
- return count;
- }
-
- public static void sort(@NonNull float[] poly, int polyLength, @NonNull float[] ctr) {
- quicksortCircle(poly, 0, polyLength - 1, ctr);
- }
-
- public static float angle(float x1, float y1, @NonNull float[] ctr) {
- return -(float) Math.atan2(x1 - ctr[0], y1 - ctr[1]);
- }
-
- private static void swapPair(@NonNull float[] points, int i, int j) {
- float x = points[i * 2 + 0];
- float y = points[i * 2 + 1];
- points[i * 2 + 0] = points[j * 2 + 0];
- points[i * 2 + 1] = points[j * 2 + 1];
- points[j * 2 + 0] = x;
- points[j * 2 + 1] = y;
- }
-
- private static void quicksortCircle(@NonNull float[] points, int low, int high,
- @NonNull float[] ctr) {
- int i = low, j = high;
- int p = low + (high - low) / 2;
- float pivot = angle(points[p * 2], points[p * 2 + 1], ctr);
- while (i <= j) {
- while (angle(points[i * 2 + 0], points[i * 2 + 1], ctr) < pivot) {
- i++;
- }
- while (angle(points[j * 2 + 0], points[j * 2 + 1], ctr) > pivot) {
- j--;
- }
- if (i <= j) {
- swapPair(points, i, j);
- i++;
- j--;
- }
- }
- if (low < j) {
- quicksortCircle(points, low, j, ctr);
- }
- if (i < high) {
- quicksortCircle(points, i, high, ctr);
- }
- }
-
- /**
- * This function do Quick Sort by comparing X axis only.<br>
- * Note that the input values of points are paired coordinates, e.g. {@code [x0, y0, x1, y1, x2,
- * y2, ...]).}
- * @param points The input point pairs. Every {@code (2 * i, 2 * i + 1)} points are pairs.
- * @param low lowest index used to do quick sort sort
- * @param high highest index used to do quick sort
- */
- private static void quicksortX(@NonNull float[] points, int low, int high) {
- int i = low, j = high;
- int p = low + (high - low) / 2;
- float pivot = points[p * 2];
- while (i <= j) {
- while (points[i * 2 + 0] < pivot) {
- i++;
- }
- while (points[j * 2 + 0] > pivot) {
- j--;
- }
-
- if (i <= j) {
- swapPair(points, i, j);
- i++;
- j--;
- }
- }
- if (low < j) {
- quicksortX(points, low, j);
- }
- if (i < high) {
- quicksortX(points, i, high);
- }
- }
-
- private static boolean pointInsidePolygon(float x, float y, @NonNull float[] poly, int len) {
- boolean c = false;
- float testX = x;
- float testY = y;
- for (int i = 0, j = len - 1; i < len; j = i++) {
- if (((poly[i * 2 + 1] > testY) != (poly[j * 2 + 1] > testY)) && (testX <
- (poly[j * 2 + 0] - poly[i * 2 + 0]) * (testY - poly[i * 2 + 1]) /
- (poly[j * 2 + 1] - poly[i * 2 + 1]) + poly[i * 2 + 0])) {
- c = !c;
- }
- }
- return c;
- }
-
- private static void makeClockwise(@NonNull float[] polygon, int len) {
- if (polygon == null || len == 0) {
- return;
- }
- if (!isClockwise(polygon, len)) {
- reverse(polygon, len);
- }
- }
-
- private static boolean isClockwise(@NonNull float[] polygon, int len) {
- float sum = 0;
- float p1x = polygon[(len - 1) * 2 + 0];
- float p1y = polygon[(len - 1) * 2 + 1];
- for (int i = 0; i < len; i++) {
- float p2x = polygon[i * 2 + 0];
- float p2y = polygon[i * 2 + 1];
- sum += p1x * p2y - p2x * p1y;
- p1x = p2x;
- p1y = p2y;
- }
- return sum < 0;
- }
-
- private static void reverse(@NonNull float[] polygon, int len) {
- int n = len / 2;
- for (int i = 0; i < n; i++) {
- float tmp0 = polygon[i * 2 + 0];
- float tmp1 = polygon[i * 2 + 1];
- int k = len - 1 - i;
- polygon[i * 2 + 0] = polygon[k * 2 + 0];
- polygon[i * 2 + 1] = polygon[k * 2 + 1];
- polygon[k * 2 + 0] = tmp0;
- polygon[k * 2 + 1] = tmp1;
- }
- }
-
- /**
- * Intersects two lines in parametric form.
- */
- private static final boolean lineIntersection(float x1, float y1, float x2, float y2, float x3,
- float y3, float x4, float y4, @NonNull float[] ret) {
- float d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
- if (d == 0.000f) {
- return false;
- }
-
- float dx = (x1 * y2 - y1 * x2);
- float dy = (x3 * y4 - y3 * x4);
- float x = (dx * (x3 - x4) - (x1 - x2) * dy) / d;
- float y = (dx * (y3 - y4) - (y1 - y2) * dy) / d;
-
- if (((x - x1) * (x - x2) > 0.0000001) || ((x - x3) * (x - x4) > 0.0000001) ||
- ((y - y1) * (y - y2) > 0.0000001) || ((y - y3) * (y - y4) > 0.0000001)) {
- return false;
- }
- ret[0] = x;
- ret[1] = y;
- return true;
- }
-
- @NonNull
- public static float[] calculateLight(float size, int points, float x, float y, float height) {
- float[] ret = new float[points * 3];
- for (int i = 0; i < points; i++) {
- double angle = 2 * i * Math.PI / points;
- ret[i * 3 + 0] = (float) Math.cos(angle) * size + x;
- ret[i * 3 + 1] = (float) Math.sin(angle) * size + y;
- ret[i * 3 + 2] = (height);
- }
- return ret;
- }
-
- /**
- layers indicates the number of circular strips to generate.
- Strength is how dark a shadow to generate.
-
- /**
- * This calculates a collection of triangles that represent the shadow cast by a polygonal
- * light source (lightPoly) hitting a convex polygon (poly).
- * @param lightPoly The flatten 3d coordinates of light
- * @param lightPolyLength The vertices number of light polygon.
- * @param poly The flatten 3d coordinates of item
- * @param polyLength The vertices number of light polygon.
- * @param rays defines the number of points around the perimeter of the shadow to generate
- * @param layers The number of shadow's fading.
- * @param strength The factor of the black color of shadow.
- * @param retStrips Used to store the calculated shadow strength
- * @return true if the params is able to calculate a shadow, else false.
- */
- public static boolean calcShadow(@NonNull float[] lightPoly, int lightPolyLength,
- @NonNull float[] poly, int polyLength, int rays, int layers, float strength,
- @NonNull float[] retStrips) {
- float[] shadowRegion = new float[lightPolyLength * polyLength * 2];
- float[] outline = new float[polyLength * 2];
- float[] umbra = new float[polyLength * lightPolyLength * 2];
- int umbraLength = 0;
-
- int k = 0;
- for (int j = 0; j < lightPolyLength; j++) {
- int m = 0;
- for (int i = 0; i < polyLength; i++) {
- float t = lightPoly[j * 3 + 2] - poly[i * 3 + 2];
- if (t == 0) {
- return false;
- }
- t = lightPoly[j * 3 + 2] / t;
- float x = lightPoly[j * 3 + 0] - t * (lightPoly[j * 3 + 0] - poly[i * 3 + 0]);
- float y = lightPoly[j * 3 + 1] - t * (lightPoly[j * 3 + 1] - poly[i * 3 + 1]);
-
- shadowRegion[k * 2 + 0] = x;
- shadowRegion[k * 2 + 1] = y;
- outline[m * 2 + 0] = x;
- outline[m * 2 + 1] = y;
-
- k++;
- m++;
- }
- if (umbraLength == 0) {
- System.arraycopy(outline, 0, umbra, 0, polyLength);
- umbraLength = polyLength;
- } else {
- umbraLength = intersection(outline, polyLength, umbra, umbraLength);
- if (umbraLength == 0) {
- break;
- }
- }
- }
- int shadowRegionLength = k;
-
- float[] penumbra = new float[k * 2];
- int penumbraLength = hull(shadowRegion, shadowRegionLength, penumbra);
- if (umbraLength < 3) {// no real umbra make a fake one
- float[] p = new float[3];
- centroid3d(lightPoly, lightPolyLength, p);
- float[] centShadow = new float[polyLength * 2];
- for (int i = 0; i < polyLength; i++) {
- float t = p[2] - poly[i * 3 + 2];
- if (t == 0) {
- return false;
- }
- t = p[2] / t;
- float x = p[0] - t * (p[0] - poly[i * 3 + 0]);
- float y = p[1] - t * (p[1] - poly[i * 3 + 1]);
-
- centShadow[i * 2 + 0] = x;
- centShadow[i * 2 + 1] = y;
- }
- float[] c = new float[2];
- centroid2d(centShadow, polyLength, c);
- for (int i = 0; i < polyLength; i++) {
- centShadow[i * 2 + 0] = (c[0] * 9 + centShadow[i * 2 + 0]) / 10;
- centShadow[i * 2 + 1] = (c[1] * 9 + centShadow[i * 2 + 1]) / 10;
- }
- umbra = centShadow; // fake umbra
- umbraLength = polyLength; // same size as the original polygon
- }
-
- triangulateConcentricPolygon(penumbra, penumbraLength, umbra, umbraLength, rays, layers,
- strength, retStrips);
- return true;
- }
-
- /**
- * triangulate concentric circles.
- * This takes the inner and outer polygons of the umbra and penumbra and triangulates it.
- * @param penumbra The 2d flatten vertices of penumbra polygons.
- * @param penumbraLength The number of vertices in penumbra.
- * @param umbra The 2d flatten vertices of umbra polygons.
- * @param umbraLength The number of vertices in umbra.
- * @param rays defines the number of points around the perimeter of the shadow to generate
- * @param layers The number of shadow's fading.
- * @param strength The factor of the black color of shadow.
- * @param retStrips Used to store the calculated shadow strength.
- */
- private static void triangulateConcentricPolygon(@NonNull float[] penumbra, int penumbraLength,
- @NonNull float[] umbra, int umbraLength, int rays, int layers, float strength,
- @NonNull float[] retStrips) {
- int rings = layers + 1;
- double step = Math.PI * 2 / rays;
- float[] retXY = new float[2];
- centroid2d(umbra, umbraLength, retXY);
- float cx = retXY[0];
- float cy = retXY[1];
-
- float[] t1 = new float[rays];
- float[] t2 = new float[rays];
-
- for (int i = 0; i < rays; i++) {
- float dx = (float) Math.cos(Math.PI / 4 + step * i);
- float dy = (float) Math.sin(Math.PI / 4 + step * i);
- t2[i] = rayIntersectPoly(umbra, umbraLength, cx, cy, dx, dy);
- t1[i] = rayIntersectPoly(penumbra, penumbraLength, cx, cy, dx, dy);
- }
-
- int p = 0;
- // Calculate the vertex
- for (int r = 0; r < layers; r++) {
- int startP = p;
- for (int i = 0; i < rays; i++) {
- float dx = (float) Math.cos(Math.PI / 4 + step * i);
- float dy = (float) Math.sin(Math.PI / 4 + step * i);
-
- for (int j = r; j < (r + 2); j++) {
- float jf = j / (float) (rings - 1);
- float t = t1[i] + jf * (t2[i] - t1[i]);
- float op = (jf + 1 - 1 / (1 + (t - t1[i]) * (t - t1[i]))) / 2;
- retStrips[p * 3 + 0] = dx * t + cx;
- retStrips[p * 3 + 1] = dy * t + cy;
- retStrips[p * 3 + 2] = jf * op * strength;
- p++;
-
- }
- }
- retStrips[p * 3 + 0] = retStrips[startP * 3 + 0];
- retStrips[p * 3 + 1] = retStrips[startP * 3 + 1];
- retStrips[p * 3 + 2] = retStrips[startP * 3 + 2];
- p++;
- startP++;
- retStrips[p * 3 + 0] = retStrips[startP * 3 + 0];
- retStrips[p * 3 + 1] = retStrips[startP * 3 + 1];
- retStrips[p * 3 + 2] = retStrips[startP * 3 + 2];
- p++;
- }
- int oldP = p - 1;
- retStrips[p * 3 + 0] = retStrips[oldP * 3 + 0];
- retStrips[p * 3 + 1] = retStrips[oldP * 3 + 1];
- retStrips[p * 3 + 2] = retStrips[oldP * 3 + 2];
- p++;
-
- // Skip the first point here, then make it same as last point later.
- oldP = p;
- p++;
- for (int k = 0; k < rays; k++) {
- int i = k / 2;
- if ((k & 1) == 1) { // traverse the inside in a zig zag pattern
- // for strips
- i = rays - i - 1;
- }
- float dx = (float) Math.cos(Math.PI / 4 + step * i);
- float dy = (float) Math.sin(Math.PI / 4 + step * i);
-
- float jf = 1;
-
- float t = t1[i] + jf * (t2[i] - t1[i]);
- float op = (jf + 1 - 1 / (1 + (t - t1[i]) * (t - t1[i]))) / 2;
-
- retStrips[p * 3 + 0] = dx * t + cx;
- retStrips[p * 3 + 1] = dy * t + cy;
- retStrips[p * 3 + 2] = jf * op * strength;
- p++;
- }
- p = oldP;
- retStrips[p * 3 + 0] = retStrips[oldP * 3 + 0];
- retStrips[p * 3 + 1] = retStrips[oldP * 3 + 1];
- retStrips[p * 3 + 2] = retStrips[oldP * 3 + 2];
- }
-
- public static int getStripSize(int rays, int layers) {
- return (2 + rays + ((layers) * 2 * (rays + 1)));
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/CachedPathIteratorFactory.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/CachedPathIteratorFactory.java
deleted file mode 100644
index 0a9b9ec..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/CachedPathIteratorFactory.java
+++ /dev/null
@@ -1,485 +0,0 @@
-/*
- * Copyright (C) 2015 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.layoutlib.bridge.util;
-
-import android.annotation.NonNull;
-
-import java.awt.geom.CubicCurve2D;
-import java.awt.geom.PathIterator;
-import java.awt.geom.Point2D;
-import java.awt.geom.QuadCurve2D;
-import java.util.ArrayList;
-
-import com.google.android.collect.Lists;
-
-/**
- * Class that returns iterators for a given path. These iterators are lightweight and can be reused
- * multiple times to iterate over the path.
- */
-public class CachedPathIteratorFactory {
- /*
- * A few conventions used in the code:
- * Coordinates or coords arrays store segment coordinates. They use the same format as
- * PathIterator#currentSegment coordinates array.
- * float arrays store always points where the first element is X and the second is Y.
- */
-
- // This governs how accurate the approximation of the Path is.
- private static final float PRECISION = 0.002f;
-
- private final int mWindingRule;
- private final int[] mTypes;
- private final float[][] mCoordinates;
- private final float[] mSegmentsLength;
- private final float mTotalLength;
-
- public CachedPathIteratorFactory(@NonNull PathIterator iterator) {
- mWindingRule = iterator.getWindingRule();
-
- ArrayList<Integer> typesArray = Lists.newArrayList();
- ArrayList<float[]> pointsArray = Lists.newArrayList();
- float[] points = new float[6];
- while (!iterator.isDone()) {
- int type = iterator.currentSegment(points);
- int nPoints = getNumberOfPoints(type) * 2; // 2 coordinates per point
-
- typesArray.add(type);
- float[] itemPoints = new float[nPoints];
- System.arraycopy(points, 0, itemPoints, 0, nPoints);
- pointsArray.add(itemPoints);
- iterator.next();
- }
-
- mTypes = new int[typesArray.size()];
- mCoordinates = new float[mTypes.length][];
- for (int i = 0; i < typesArray.size(); i++) {
- mTypes[i] = typesArray.get(i);
- mCoordinates[i] = pointsArray.get(i);
- }
-
- // Do measurement
- mSegmentsLength = new float[mTypes.length];
-
- // Curves that we can reuse to estimate segments length
- CubicCurve2D.Float cubicCurve = new CubicCurve2D.Float();
- QuadCurve2D.Float quadCurve = new QuadCurve2D.Float();
- float lastX = 0;
- float lastY = 0;
- float totalLength = 0;
- for (int i = 0; i < mTypes.length; i++) {
- switch (mTypes[i]) {
- case PathIterator.SEG_CUBICTO:
- cubicCurve.setCurve(lastX, lastY,
- mCoordinates[i][0], mCoordinates[i][1], mCoordinates[i][2],
- mCoordinates[i][3], lastX = mCoordinates[i][4],
- lastY = mCoordinates[i][5]);
- mSegmentsLength[i] =
- getFlatPathLength(cubicCurve.getPathIterator(null, PRECISION));
- break;
- case PathIterator.SEG_QUADTO:
- quadCurve.setCurve(lastX, lastY, mCoordinates[i][0], mCoordinates[i][1],
- lastX = mCoordinates[i][2], lastY = mCoordinates[i][3]);
- mSegmentsLength[i] =
- getFlatPathLength(quadCurve.getPathIterator(null, PRECISION));
- break;
- case PathIterator.SEG_CLOSE:
- mSegmentsLength[i] = (float) Point2D.distance(lastX, lastY,
- lastX = mCoordinates[0][0],
- lastY = mCoordinates[0][1]);
- mCoordinates[i] = new float[2];
- // We convert a SEG_CLOSE segment to a SEG_LINETO so we do not have to worry
- // about this special case in the rest of the code.
- mTypes[i] = PathIterator.SEG_LINETO;
- mCoordinates[i][0] = mCoordinates[0][0];
- mCoordinates[i][1] = mCoordinates[0][1];
- break;
- case PathIterator.SEG_MOVETO:
- mSegmentsLength[i] = 0;
- lastX = mCoordinates[i][0];
- lastY = mCoordinates[i][1];
- break;
- case PathIterator.SEG_LINETO:
- mSegmentsLength[i] = (float) Point2D.distance(lastX, lastY, mCoordinates[i][0],
- mCoordinates[i][1]);
- lastX = mCoordinates[i][0];
- lastY = mCoordinates[i][1];
- default:
- }
- totalLength += mSegmentsLength[i];
- }
-
- mTotalLength = totalLength;
- }
-
- private static void quadCurveSegment(float[] coords, float t0, float t1) {
- // Calculate X and Y at 0.5 (We'll use this to reconstruct the control point later)
- float mt = t0 + (t1 - t0) / 2;
- float mu = 1 - mt;
- float mx = mu * mu * coords[0] + 2 * mu * mt * coords[2] + mt * mt * coords[4];
- float my = mu * mu * coords[1] + 2 * mu * mt * coords[3] + mt * mt * coords[5];
-
- float u0 = 1 - t0;
- float u1 = 1 - t1;
-
- // coords at t0
- coords[0] = coords[0] * u0 * u0 + coords[2] * 2 * t0 * u0 + coords[4] * t0 * t0;
- coords[1] = coords[1] * u0 * u0 + coords[3] * 2 * t0 * u0 + coords[5] * t0 * t0;
-
- // coords at t1
- coords[4] = coords[0] * u1 * u1 + coords[2] * 2 * t1 * u1 + coords[4] * t1 * t1;
- coords[5] = coords[1] * u1 * u1 + coords[3] * 2 * t1 * u1 + coords[5] * t1 * t1;
-
- // estimated control point at t'=0.5
- coords[2] = 2 * mx - coords[0] / 2 - coords[4] / 2;
- coords[3] = 2 * my - coords[1] / 2 - coords[5] / 2;
- }
-
- private static void cubicCurveSegment(float[] coords, float t0, float t1) {
- // http://stackoverflow.com/questions/11703283/cubic-bezier-curve-segment
- float u0 = 1 - t0;
- float u1 = 1 - t1;
-
- // Calculate the points at t0 and t1 for the quadratic curves formed by (P0, P1, P2) and
- // (P1, P2, P3)
- float qxa = coords[0] * u0 * u0 + coords[2] * 2 * t0 * u0 + coords[4] * t0 * t0;
- float qxb = coords[0] * u1 * u1 + coords[2] * 2 * t1 * u1 + coords[4] * t1 * t1;
- float qxc = coords[2] * u0 * u0 + coords[4] * 2 * t0 * u0 + coords[6] * t0 * t0;
- float qxd = coords[2] * u1 * u1 + coords[4] * 2 * t1 * u1 + coords[6] * t1 * t1;
-
- float qya = coords[1] * u0 * u0 + coords[3] * 2 * t0 * u0 + coords[5] * t0 * t0;
- float qyb = coords[1] * u1 * u1 + coords[3] * 2 * t1 * u1 + coords[5] * t1 * t1;
- float qyc = coords[3] * u0 * u0 + coords[5] * 2 * t0 * u0 + coords[7] * t0 * t0;
- float qyd = coords[3] * u1 * u1 + coords[5] * 2 * t1 * u1 + coords[7] * t1 * t1;
-
- // Linear interpolation
- coords[0] = qxa * u0 + qxc * t0;
- coords[1] = qya * u0 + qyc * t0;
-
- coords[2] = qxa * u1 + qxc * t1;
- coords[3] = qya * u1 + qyc * t1;
-
- coords[4] = qxb * u0 + qxd * t0;
- coords[5] = qyb * u0 + qyd * t0;
-
- coords[6] = qxb * u1 + qxd * t1;
- coords[7] = qyb * u1 + qyd * t1;
- }
-
- /**
- * Returns the end point of a given segment
- *
- * @param type the segment type
- * @param coords the segment coordinates array
- * @param point the return array where the point will be stored
- */
- private static void getShapeEndPoint(int type, @NonNull float[] coords, @NonNull float[]
- point) {
- // start index of the end point for the segment type
- int pointIndex = (getNumberOfPoints(type) - 1) * 2;
- point[0] = coords[pointIndex];
- point[1] = coords[pointIndex + 1];
- }
-
- /**
- * Returns the number of points stored in a coordinates array for the given segment type.
- */
- private static int getNumberOfPoints(int segmentType) {
- switch (segmentType) {
- case PathIterator.SEG_QUADTO:
- return 2;
- case PathIterator.SEG_CUBICTO:
- return 3;
- case PathIterator.SEG_CLOSE:
- return 0;
- default:
- return 1;
- }
- }
-
- /**
- * Returns the estimated length of a flat path. If the passed path is not flat (i.e. contains a
- * segment that is not {@link PathIterator#SEG_CLOSE}, {@link PathIterator#SEG_MOVETO} or {@link
- * PathIterator#SEG_LINETO} this method will fail.
- */
- private static float getFlatPathLength(@NonNull PathIterator iterator) {
- float segment[] = new float[6];
- float totalLength = 0;
- float[] previousPoint = new float[2];
- boolean isFirstPoint = true;
-
- while (!iterator.isDone()) {
- int type = iterator.currentSegment(segment);
- assert type == PathIterator.SEG_LINETO || type == PathIterator.SEG_CLOSE || type ==
- PathIterator.SEG_MOVETO;
-
- // MoveTo shouldn't affect the length
- if (!isFirstPoint && type != PathIterator.SEG_MOVETO) {
- totalLength += Point2D.distance(previousPoint[0], previousPoint[1], segment[0],
- segment[1]);
- } else {
- isFirstPoint = false;
- }
- previousPoint[0] = segment[0];
- previousPoint[1] = segment[1];
- iterator.next();
- }
-
- return totalLength;
- }
-
- /**
- * Returns the estimated position along a path of the given length.
- */
- private void getPointAtLength(int type, @NonNull float[] coords, float lastX, float
- lastY, float t, @NonNull float[] point) {
- if (type == PathIterator.SEG_LINETO) {
- point[0] = lastX + (coords[0] - lastX) * t;
- point[1] = lastY + (coords[1] - lastY) * t;
- // Return here, since we do not need a shape to estimate
- return;
- }
-
- float[] curve = new float[8];
- int lastPointIndex = (getNumberOfPoints(type) - 1) * 2;
-
- System.arraycopy(coords, 0, curve, 2, coords.length);
- curve[0] = lastX;
- curve[1] = lastY;
- if (type == PathIterator.SEG_CUBICTO) {
- cubicCurveSegment(curve, 0f, t);
- } else {
- quadCurveSegment(curve, 0f, t);
- }
-
- point[0] = curve[2 + lastPointIndex];
- point[1] = curve[2 + lastPointIndex + 1];
- }
-
- public CachedPathIterator iterator() {
- return new CachedPathIterator();
- }
-
- /**
- * Class that allows us to iterate over a path multiple times
- */
- public class CachedPathIterator implements PathIterator {
- private int mNextIndex;
-
- /**
- * Current segment type.
- *
- * @see PathIterator
- */
- private int mCurrentType;
-
- /**
- * Stores the coordinates array of the current segment. The number of points stored depends
- * on the segment type.
- *
- * @see PathIterator
- */
- private float[] mCurrentCoords = new float[6];
- private float mCurrentSegmentLength;
-
- /**
- * Current segment length offset. When asking for the length of the current segment, the
- * length will be reduced by this amount. This is useful when we are only using portions of
- * the segment.
- *
- * @see #jumpToSegment(float)
- */
- private float mOffsetLength;
-
- /** Point where the current segment started */
- private float[] mLastPoint = new float[2];
- private boolean isIteratorDone;
-
- private CachedPathIterator() {
- next();
- }
-
- public float getCurrentSegmentLength() {
- return mCurrentSegmentLength;
- }
-
- @Override
- public int getWindingRule() {
- return mWindingRule;
- }
-
- @Override
- public boolean isDone() {
- return isIteratorDone;
- }
-
- @Override
- public void next() {
- if (mNextIndex >= mTypes.length) {
- isIteratorDone = true;
- return;
- }
-
- if (mNextIndex >= 1) {
- // We've already called next() once so there is a previous segment in this path.
- // We want to get the coordinates where the path ends.
- getShapeEndPoint(mCurrentType, mCurrentCoords, mLastPoint);
- } else {
- // This is the first segment, no previous point so initialize to 0, 0
- mLastPoint[0] = mLastPoint[1] = 0f;
- }
- mCurrentType = mTypes[mNextIndex];
- mCurrentSegmentLength = mSegmentsLength[mNextIndex] - mOffsetLength;
-
- if (mOffsetLength > 0f && (mCurrentType == SEG_CUBICTO || mCurrentType == SEG_QUADTO)) {
- // We need to skip part of the start of the current segment (because
- // mOffsetLength > 0)
- float[] points = new float[8];
-
- if (mNextIndex < 1) {
- points[0] = points[1] = 0f;
- } else {
- getShapeEndPoint(mTypes[mNextIndex - 1], mCoordinates[mNextIndex - 1], points);
- }
-
- System.arraycopy(mCoordinates[mNextIndex], 0, points, 2,
- mCoordinates[mNextIndex].length);
- float t0 = (mSegmentsLength[mNextIndex] - mCurrentSegmentLength) /
- mSegmentsLength[mNextIndex];
- if (mCurrentType == SEG_CUBICTO) {
- cubicCurveSegment(points, t0, 1f);
- } else {
- quadCurveSegment(points, t0, 1f);
- }
- System.arraycopy(points, 2, mCurrentCoords, 0, mCoordinates[mNextIndex].length);
- } else {
- System.arraycopy(mCoordinates[mNextIndex], 0, mCurrentCoords, 0,
- mCoordinates[mNextIndex].length);
- }
-
- mOffsetLength = 0f;
- mNextIndex++;
- }
-
- @Override
- public int currentSegment(float[] coords) {
- System.arraycopy(mCurrentCoords, 0, coords, 0, getNumberOfPoints(mCurrentType) * 2);
- return mCurrentType;
- }
-
- @Override
- public int currentSegment(double[] coords) {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Returns the point where the current segment ends
- */
- public void getCurrentSegmentEnd(float[] point) {
- point[0] = mLastPoint[0];
- point[1] = mLastPoint[1];
- }
-
- /**
- * Restarts the iterator and jumps all the segments of this path up to the length value.
- */
- public void jumpToSegment(float length) {
- isIteratorDone = false;
- if (length <= 0f) {
- mNextIndex = 0;
- return;
- }
-
- float accLength = 0;
- float lastPoint[] = new float[2];
- for (mNextIndex = 0; mNextIndex < mTypes.length; mNextIndex++) {
- float segmentLength = mSegmentsLength[mNextIndex];
- if (accLength + segmentLength >= length && mTypes[mNextIndex] != SEG_MOVETO) {
- float[] estimatedPoint = new float[2];
- getPointAtLength(mTypes[mNextIndex],
- mCoordinates[mNextIndex], lastPoint[0], lastPoint[1],
- (length - accLength) / segmentLength,
- estimatedPoint);
-
- // This segment makes us go further than length so we go back one step,
- // set a moveto and offset the length of the next segment by the length
- // of this segment that we've already used.
- mCurrentType = PathIterator.SEG_MOVETO;
- mCurrentCoords[0] = estimatedPoint[0];
- mCurrentCoords[1] = estimatedPoint[1];
- mCurrentSegmentLength = 0;
-
- // We need to offset next path length to account for the segment we've just
- // skipped.
- mOffsetLength = length - accLength;
- return;
- }
- accLength += segmentLength;
- getShapeEndPoint(mTypes[mNextIndex], mCoordinates[mNextIndex], lastPoint);
- }
- }
-
- /**
- * Returns the current segment up to certain length. If the current segment is shorter than
- * length, then the whole segment is returned. The segment coordinates are copied into the
- * coords array.
- *
- * @return the segment type
- */
- public int currentSegment(@NonNull float[] coords, float length) {
- int type = currentSegment(coords);
- // If the length is greater than the current segment length, no need to find
- // the cut point. Same if this is a SEG_MOVETO.
- if (mCurrentSegmentLength <= length || type == SEG_MOVETO) {
- return type;
- }
-
- float t = length / getCurrentSegmentLength();
-
- // We find at which offset the end point is located within the coords array and set
- // a new end point to cut the segment short
- switch (type) {
- case SEG_CUBICTO:
- case SEG_QUADTO:
- float[] curve = new float[8];
- curve[0] = mLastPoint[0];
- curve[1] = mLastPoint[1];
- System.arraycopy(coords, 0, curve, 2, coords.length);
- if (type == SEG_CUBICTO) {
- cubicCurveSegment(curve, 0f, t);
- } else {
- quadCurveSegment(curve, 0f, t);
- }
- System.arraycopy(curve, 2, coords, 0, coords.length);
- break;
- default:
- float[] point = new float[2];
- getPointAtLength(type, coords, mLastPoint[0], mLastPoint[1], t, point);
- coords[0] = point[0];
- coords[1] = point[1];
- }
-
- return type;
- }
-
- /**
- * Returns the total length of the path
- */
- public float getTotalLength() {
- return mTotalLength;
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/Debug.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/Debug.java
deleted file mode 100644
index 82eab85..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/Debug.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2011 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.layoutlib.bridge.util;
-
-public class Debug {
-
- public final static boolean DEBUG = false;
-
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
deleted file mode 100644
index 161bf41..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/DynamicIdMap.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2012 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.layoutlib.bridge.util;
-
-import com.android.resources.ResourceType;
-import com.android.util.Pair;
-
-import android.annotation.NonNull;
-import android.util.SparseArray;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class DynamicIdMap {
-
- private final Map<Pair<ResourceType, String>, Integer> mDynamicIds = new HashMap<>();
- private final SparseArray<Pair<ResourceType, String>> mRevDynamicIds = new SparseArray<>();
- private int mDynamicSeed;
-
- public DynamicIdMap(int seed) {
- mDynamicSeed = seed;
- }
-
- public void reset(int seed) {
- mDynamicIds.clear();
- mRevDynamicIds.clear();
- mDynamicSeed = seed;
- }
-
- /**
- * Returns a dynamic integer for the given resource type/name, creating it if it doesn't
- * already exist.
- *
- * @param type the type of the resource
- * @param name the name of the resource
- * @return an integer.
- */
- @NonNull
- public Integer getId(ResourceType type, String name) {
- return getId(Pair.of(type, name));
- }
-
- /**
- * Returns a dynamic integer for the given resource type/name, creating it if it doesn't
- * already exist.
- *
- * @param resource the type/name of the resource
- * @return an integer.
- */
- @NonNull
- public Integer getId(Pair<ResourceType, String> resource) {
- Integer value = mDynamicIds.get(resource);
- if (value == null) {
- value = ++mDynamicSeed;
- mDynamicIds.put(resource, value);
- mRevDynamicIds.put(value, resource);
- }
-
- return value;
- }
-
- public Pair<ResourceType, String> resolveId(int id) {
- return mRevDynamicIds.get(id);
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java
deleted file mode 100644
index f149b6c..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.util;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-
-/**
- * Simpler wrapper around FileInputStream. This is used when the input stream represent
- * not a normal bitmap but a nine patch.
- * This is useful when the InputStream is created in a method but used in another that needs
- * to know whether this is 9-patch or not, such as BitmapFactory.
- */
-public class NinePatchInputStream extends FileInputStream {
- private boolean mFakeMarkSupport = true;
- public NinePatchInputStream(File file) throws FileNotFoundException {
- super(file);
- }
-
- @Override
- public boolean markSupported() {
- // this is needed so that BitmapFactory doesn't wrap this in a BufferedInputStream.
- return mFakeMarkSupport || super.markSupported();
-
- }
-
- public void disableFakeMarkSupport() {
- // disable fake mark support so that in case codec actually try to use them
- // we don't lie to them.
- mFakeMarkSupport = false;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
deleted file mode 100644
index b89718f..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2015 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.layoutlib.bridge.util;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-/**
- * Utility to convert checked Reflection exceptions to unchecked exceptions.
- */
-public class ReflectionUtils {
-
- @NonNull
- public static Method getMethod(@NonNull Class<?> clazz, @NonNull String name,
- @Nullable Class<?>... params) throws ReflectionException {
- try {
- return clazz.getMethod(name, params);
- } catch (NoSuchMethodException e) {
- throw new ReflectionException(e);
- }
- }
-
- @NonNull
- public static Method getAccessibleMethod(@NonNull Class<?> clazz, @NonNull String name,
- @Nullable Class<?>... params) throws ReflectionException {
- Method method = getMethod(clazz, name, params);
- method.setAccessible(true);
-
- return method;
- }
-
- @Nullable
- public static Object invoke(@NonNull Method method, @Nullable Object object,
- @Nullable Object... args) throws ReflectionException {
- Exception ex;
- try {
- return method.invoke(object, args);
- } catch (IllegalAccessException | InvocationTargetException e) {
- ex = e;
- }
- throw new ReflectionException(ex);
- }
-
- /**
- * Check if the object is an instance of a class named {@code className}. This doesn't work
- * for interfaces.
- */
- public static boolean isInstanceOf(Object object, String className) {
- Class superClass = object.getClass();
- while (superClass != null) {
- String name = superClass.getName();
- if (name.equals(className)) {
- return true;
- }
- superClass = superClass.getSuperclass();
- }
- return false;
- }
-
- @NonNull
- public static Throwable getCause(@NonNull Throwable throwable) {
- Throwable cause = throwable.getCause();
- return cause == null ? throwable : cause;
- }
-
- /**
- * Looks through the class hierarchy of {@code object} at runtime and returns the class matching
- * the name {@code className}.
- * <p>
- * This is used when we cannot use Class.forName() since the class we want was loaded from a
- * different ClassLoader.
- */
- @NonNull
- public static Class<?> getClassInstance(@NonNull Object object, @NonNull String className) {
- Class<?> superClass = object.getClass();
- while (superClass != null) {
- if (className.equals(superClass.getName())) {
- return superClass;
- }
- superClass = superClass.getSuperclass();
- }
- throw new RuntimeException("invalid object/classname combination.");
- }
-
- /**
- * Wraps all reflection related exceptions. Created since ReflectiveOperationException was
- * introduced in 1.7 and we are still on 1.6
- */
- public static class ReflectionException extends Exception {
- public ReflectionException() {
- super();
- }
-
- public ReflectionException(String message) {
- super(message);
- }
-
- public ReflectionException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public ReflectionException(Throwable cause) {
- super(cause);
- }
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java
deleted file mode 100644
index a2a8aa9..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2011 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.layoutlib.bridge.util;
-
-
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.GrowingArrayUtils;
-
-import android.util.SparseArray;
-
-import java.lang.ref.WeakReference;
-
-/**
- * This is a custom {@link SparseArray} that uses {@link WeakReference} around the objects added
- * to it. When the array is compacted, not only deleted indices but also empty references
- * are removed, making the array efficient at removing references that were reclaimed.
- *
- * The code is taken from {@link SparseArray} directly and adapted to use weak references.
- *
- * Because our usage means that we never actually call {@link #remove(long)} or
- * {@link #delete(long)}, we must manually check if there are reclaimed references to
- * trigger an internal compact step (which is normally only triggered when an item is manually
- * removed).
- *
- * SparseArrays map integral values to Objects. Unlike a normal array of Objects,
- * there can be gaps in the indices. It is intended to be more efficient
- * than using a HashMap to map Integers (or Longs) to Objects.
- */
-@SuppressWarnings("unchecked")
-public class SparseWeakArray<E> {
-
- private static final Object DELETED_REF = new Object();
- private static final WeakReference<?> DELETED = new WeakReference(DELETED_REF);
- private boolean mGarbage = false;
-
- /**
- * Creates a new SparseArray containing no mappings.
- */
- public SparseWeakArray() {
- this(10);
- }
-
- /**
- * Creates a new SparseArray containing no mappings that will not
- * require any additional memory allocation to store the specified
- * number of mappings.
- */
- public SparseWeakArray(int initialCapacity) {
- mKeys = ArrayUtils.newUnpaddedLongArray(initialCapacity);
- mValues = new WeakReference[mKeys.length];
- mSize = 0;
- }
-
- /**
- * Gets the Object mapped from the specified key, or <code>null</code>
- * if no such mapping has been made.
- */
- public E get(long key) {
- return get(key, null);
- }
-
- /**
- * Gets the Object mapped from the specified key, or the specified Object
- * if no such mapping has been made.
- */
- public E get(long key, E valueIfKeyNotFound) {
- int i = binarySearch(mKeys, 0, mSize, key);
-
- if (i < 0 || mValues[i] == DELETED || mValues[i].get() == null) {
- return valueIfKeyNotFound;
- } else {
- return (E) mValues[i].get();
- }
- }
-
- /**
- * Removes the mapping from the specified key, if there was any.
- */
- public void delete(long key) {
- int i = binarySearch(mKeys, 0, mSize, key);
-
- if (i >= 0) {
- if (mValues[i] != DELETED) {
- mValues[i] = DELETED;
- mGarbage = true;
- }
- }
- }
-
- /**
- * Alias for {@link #delete(long)}.
- */
- public void remove(long key) {
- delete(key);
- }
-
- /**
- * Removes the mapping at the specified index.
- */
- public void removeAt(int index) {
- if (mValues[index] != DELETED) {
- mValues[index] = DELETED;
- mGarbage = true;
- }
- }
-
- private void gc() {
- int n = mSize;
- int o = 0;
- long[] keys = mKeys;
- WeakReference<?>[] values = mValues;
-
- for (int i = 0; i < n; i++) {
- WeakReference<?> val = values[i];
-
- // Don't keep any non DELETED values, but only the one that still have a valid
- // reference.
- if (val != DELETED && val.get() != null) {
- if (i != o) {
- keys[o] = keys[i];
- values[o] = val;
- }
-
- o++;
- }
- }
-
- mGarbage = false;
- mSize = o;
- }
-
- /**
- * Adds a mapping from the specified key to the specified value,
- * replacing the previous mapping from the specified key if there
- * was one.
- */
- public void put(long key, E value) {
- int i = binarySearch(mKeys, 0, mSize, key);
-
- if (i >= 0) {
- mValues[i] = new WeakReference(value);
- } else {
- i = ~i;
-
- if (i < mSize && (mValues[i] == DELETED || mValues[i].get() == null)) {
- mKeys[i] = key;
- mValues[i] = new WeakReference(value);
- return;
- }
-
- if (mSize >= mKeys.length && (mGarbage || hasReclaimedRefs())) {
- gc();
-
- // Search again because indices may have changed.
- i = ~binarySearch(mKeys, 0, mSize, key);
- }
-
- mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
- mValues = GrowingArrayUtils.insert(mValues, mSize, i, new WeakReference(value));
- mSize++;
- }
- }
-
- /**
- * Returns the number of key-value mappings that this SparseArray
- * currently stores.
- */
- public int size() {
- if (mGarbage) {
- gc();
- }
-
- return mSize;
- }
-
- /**
- * Given an index in the range <code>0...size()-1</code>, returns
- * the key from the <code>index</code>th key-value mapping that this
- * SparseArray stores.
- */
- public long keyAt(int index) {
- if (mGarbage) {
- gc();
- }
-
- return mKeys[index];
- }
-
- /**
- * Given an index in the range <code>0...size()-1</code>, returns
- * the value from the <code>index</code>th key-value mapping that this
- * SparseArray stores.
- */
- public E valueAt(int index) {
- if (mGarbage) {
- gc();
- }
-
- return (E) mValues[index].get();
- }
-
- /**
- * Given an index in the range <code>0...size()-1</code>, sets a new
- * value for the <code>index</code>th key-value mapping that this
- * SparseArray stores.
- */
- public void setValueAt(int index, E value) {
- if (mGarbage) {
- gc();
- }
-
- mValues[index] = new WeakReference(value);
- }
-
- /**
- * Returns the index for which {@link #keyAt} would return the
- * specified key, or a negative number if the specified
- * key is not mapped.
- */
- public int indexOfKey(long key) {
- if (mGarbage) {
- gc();
- }
-
- return binarySearch(mKeys, 0, mSize, key);
- }
-
- /**
- * Returns an index for which {@link #valueAt} would return the
- * specified key, or a negative number if no keys map to the
- * specified value.
- * Beware that this is a linear search, unlike lookups by key,
- * and that multiple keys can map to the same value and this will
- * find only one of them.
- */
- public int indexOfValue(E value) {
- if (mGarbage) {
- gc();
- }
-
- for (int i = 0; i < mSize; i++)
- if (mValues[i].get() == value)
- return i;
-
- return -1;
- }
-
- /**
- * Removes all key-value mappings from this SparseArray.
- */
- public void clear() {
- int n = mSize;
- WeakReference<?>[] values = mValues;
-
- for (int i = 0; i < n; i++) {
- values[i] = null;
- }
-
- mSize = 0;
- mGarbage = false;
- }
-
- /**
- * Puts a key/value pair into the array, optimizing for the case where
- * the key is greater than all existing keys in the array.
- */
- public void append(long key, E value) {
- if (mSize != 0 && key <= mKeys[mSize - 1]) {
- put(key, value);
- return;
- }
-
- if (mSize >= mKeys.length && (mGarbage || hasReclaimedRefs())) {
- gc();
- }
-
- mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
- mValues = GrowingArrayUtils.append(mValues, mSize, new WeakReference(value));
- mSize++;
- }
-
- private boolean hasReclaimedRefs() {
- for (int i = 0 ; i < mSize ; i++) {
- if (mValues[i].get() == null) { // DELETED.get() never returns null.
- return true;
- }
- }
-
- return false;
- }
-
- private static int binarySearch(long[] a, int start, int len, long key) {
- int high = start + len, low = start - 1, guess;
-
- while (high - low > 1) {
- guess = (high + low) / 2;
-
- if (a[guess] < key)
- low = guess;
- else
- high = guess;
- }
-
- if (high == start + len)
- return ~(start + len);
- else if (a[high] == key)
- return high;
- else
- return ~high;
- }
-
- private long[] mKeys;
- private WeakReference<?>[] mValues;
- private int mSize;
-}
diff --git a/tools/layoutlib/bridge/src/com/google/android/maps/MapView.java b/tools/layoutlib/bridge/src/com/google/android/maps/MapView.java
deleted file mode 100644
index 6d013bb..0000000
--- a/tools/layoutlib/bridge/src/com/google/android/maps/MapView.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2008 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.google.android.maps;
-
-import com.android.layoutlib.bridge.MockView;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.view.View;
-
-/**
- * Mock version of the MapView.
- * Only non override public methods from the real MapView have been added in there.
- * Methods that take an unknown class as parameter or as return object, have been removed for now.
- *
- * TODO: generate automatically.
- *
- */
-public class MapView extends MockView {
-
- /**
- * Construct a new WebView with a Context object.
- * @param context A Context object used to access application assets.
- */
- public MapView(Context context) {
- this(context, null);
- }
-
- /**
- * Construct a new WebView with layout parameters.
- * @param context A Context object used to access application assets.
- * @param attrs An AttributeSet passed to our parent.
- */
- public MapView(Context context, AttributeSet attrs) {
- this(context, attrs, com.android.internal.R.attr.mapViewStyle);
- }
-
- /**
- * Construct a new WebView with layout parameters and a default style.
- * @param context A Context object used to access application assets.
- * @param attrs An AttributeSet passed to our parent.
- * @param defStyle The default style resource ID.
- */
- public MapView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- // START FAKE PUBLIC METHODS
-
- public void displayZoomControls(boolean takeFocus) {
- }
-
- public boolean canCoverCenter() {
- return false;
- }
-
- public void preLoad() {
- }
-
- public int getZoomLevel() {
- return 0;
- }
-
- public void setSatellite(boolean on) {
- }
-
- public boolean isSatellite() {
- return false;
- }
-
- public void setTraffic(boolean on) {
- }
-
- public boolean isTraffic() {
- return false;
- }
-
- public void setStreetView(boolean on) {
- }
-
- public boolean isStreetView() {
- return false;
- }
-
- public int getLatitudeSpan() {
- return 0;
- }
-
- public int getLongitudeSpan() {
- return 0;
- }
-
- public int getMaxZoomLevel() {
- return 0;
- }
-
- public void onSaveInstanceState(Bundle state) {
- }
-
- public void onRestoreInstanceState(Bundle state) {
- }
-
- public View getZoomControls() {
- return null;
- }
-}
diff --git a/tools/layoutlib/bridge/src/dalvik/system/VMRuntime_Delegate.java b/tools/layoutlib/bridge/src/dalvik/system/VMRuntime_Delegate.java
deleted file mode 100644
index 36efc3a..0000000
--- a/tools/layoutlib/bridge/src/dalvik/system/VMRuntime_Delegate.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2014 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 dalvik.system;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-/**
- * Delegate used to provide implementation of a select few native methods of {@link VMRuntime}
- * <p/>
- * Through the layoutlib_create tool, the original native methods of VMRuntime have been replaced
- * by calls to methods of the same name in this delegate class.
- */
-public class VMRuntime_Delegate {
-
- // Copied from libcore/libdvm/src/main/java/dalvik/system/VMRuntime
- @LayoutlibDelegate
- /*package*/ static Object newUnpaddedArray(VMRuntime runtime, Class<?> componentType,
- int minLength) {
- // Dalvik has 32bit pointers, the array header is 16bytes plus 4bytes for dlmalloc,
- // allocations are 8byte aligned so having 4bytes of array data avoids padding.
- if (!componentType.isPrimitive()) {
- int size = ((minLength & 1) == 0) ? minLength + 1 : minLength;
- return java.lang.reflect.Array.newInstance(componentType, size);
- } else if (componentType == char.class) {
- int bytes = 20 + (2 * minLength);
- int alignedUpBytes = (bytes + 7) & -8;
- int dataBytes = alignedUpBytes - 20;
- int size = dataBytes / 2;
- return new char[size];
- } else if (componentType == int.class) {
- int size = ((minLength & 1) == 0) ? minLength + 1 : minLength;
- return new int[size];
- } else if (componentType == byte.class) {
- int bytes = 20 + minLength;
- int alignedUpBytes = (bytes + 7) & -8;
- int dataBytes = alignedUpBytes - 20;
- int size = dataBytes;
- return new byte[size];
- } else if (componentType == boolean.class) {
- int bytes = 20 + minLength;
- int alignedUpBytes = (bytes + 7) & -8;
- int dataBytes = alignedUpBytes - 20;
- int size = dataBytes;
- return new boolean[size];
- } else if (componentType == short.class) {
- int bytes = 20 + (2 * minLength);
- int alignedUpBytes = (bytes + 7) & -8;
- int dataBytes = alignedUpBytes - 20;
- int size = dataBytes / 2;
- return new short[size];
- } else if (componentType == float.class) {
- int size = ((minLength & 1) == 0) ? minLength + 1 : minLength;
- return new float[size];
- } else if (componentType == long.class) {
- return new long[minLength];
- } else if (componentType == double.class) {
- return new double[minLength];
- } else {
- assert componentType == void.class;
- throw new IllegalArgumentException("Can't allocate an array of void");
- }
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
deleted file mode 100644
index 9c58010..0000000
--- a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Copyright (C) 2011 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 libcore.icu;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.icu.text.DateTimePatternGenerator;
-import android.icu.util.Currency;
-import android.icu.util.ULocale;
-import android.icu.util.VersionInfo;
-
-import java.util.Locale;
-
-/**
- * Delegate implementing the native methods of libcore.icu.ICU
- *
- * Through the layoutlib_create tool, the original native methods of ICU have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class ICU_Delegate {
-
- // --- Java delegates
-
- @LayoutlibDelegate
- /*package*/ static String toLowerCase(String s, String localeName) {
- return s.toLowerCase();
- }
-
- @LayoutlibDelegate
- /*package*/ static String toUpperCase(String s, String localeName) {
- return s.toUpperCase();
- }
-
- // --- Native methods accessing ICU's database.
-
- @LayoutlibDelegate
- /*package*/ static String getBestDateTimePatternNative(String skeleton, String localeName) {
- return DateTimePatternGenerator.getInstance(new ULocale(localeName))
- .getBestPattern(skeleton);
- }
-
- @LayoutlibDelegate
- @SuppressWarnings("deprecation")
- /*package*/ static String getCldrVersion() {
- return VersionInfo.ICU_DATA_VERSION.toString();
- }
-
- @LayoutlibDelegate
- /*package*/ static String getIcuVersion() {
- return VersionInfo.ICU_VERSION.toString();
- }
-
- @LayoutlibDelegate
- /*package*/ static String getUnicodeVersion() {
- return VersionInfo.UNICODE_7_0.toString();
- }
-
- @LayoutlibDelegate
- /*package*/ static String[] getAvailableBreakIteratorLocalesNative() {
- return new String[0];
- }
-
- @LayoutlibDelegate
- /*package*/ static String[] getAvailableCalendarLocalesNative() {
- return new String[0];
- }
-
- @LayoutlibDelegate
- /*package*/ static String[] getAvailableCollatorLocalesNative() {
- return new String[0];
- }
-
- @LayoutlibDelegate
- /*package*/ static String[] getAvailableDateFormatLocalesNative() {
- return new String[0];
- }
-
- @LayoutlibDelegate
- /*package*/ static String[] getAvailableLocalesNative() {
- return new String[0];
- }
-
- @LayoutlibDelegate
- /*package*/ static String[] getAvailableNumberFormatLocalesNative() {
- return new String[0];
- }
-
- @LayoutlibDelegate
- /*package*/ static String[] getAvailableCurrencyCodes() {
- return new String[0];
- }
-
- @LayoutlibDelegate
- /*package*/ static String getCurrencyCode(String locale) {
- return "";
- }
-
- @LayoutlibDelegate
- /*package*/ static String getCurrencyDisplayName(String locale, String currencyCode) {
- return "";
- }
-
- @LayoutlibDelegate
- /*package*/ static int getCurrencyFractionDigits(String currencyCode) {
- return 0;
- }
-
- @LayoutlibDelegate
- /*package*/ static int getCurrencyNumericCode(String currencyCode) {
- return Currency.getInstance(currencyCode).getNumericCode();
- }
-
- @LayoutlibDelegate
- /*package*/ static String getCurrencySymbol(String locale, String currencyCode) {
- return "";
- }
-
- @LayoutlibDelegate
- /*package*/ static String getDisplayCountryNative(String countryCode, String locale) {
- return "";
- }
-
- @LayoutlibDelegate
- /*package*/ static String getDisplayLanguageNative(String languageCode, String locale) {
- return "";
- }
-
- @LayoutlibDelegate
- /*package*/ static String getDisplayVariantNative(String variantCode, String locale) {
- return "";
- }
-
- @LayoutlibDelegate
- /*package*/ static String getDisplayScriptNative(String variantCode, String locale) {
- return "";
- }
-
- @LayoutlibDelegate
- /*package*/ static String getISO3Country(String locale) {
- return "";
- }
-
- @LayoutlibDelegate
- /*package*/ static String getISO3Language(String locale) {
- return "";
- }
-
- @LayoutlibDelegate
- /*package*/ static String addLikelySubtags(String locale) {
- return "";
- }
-
- @LayoutlibDelegate
- /*package*/ static String getScript(String locale) {
- return "";
- }
-
- @LayoutlibDelegate
- /*package*/ static String[] getISOLanguagesNative() {
- return Locale.getISOLanguages();
- }
-
- @LayoutlibDelegate
- /*package*/ static String[] getISOCountriesNative() {
- return Locale.getISOCountries();
- }
-
- @LayoutlibDelegate
- /*package*/ static boolean initLocaleDataNative(String locale, LocaleData result) {
-
- // Used by Calendar.
- result.firstDayOfWeek = 1;
- result.minimalDaysInFirstWeek = 1;
-
- // Used by DateFormatSymbols.
- result.amPm = new String[] { "AM", "PM" };
- result.eras = new String[] { "BC", "AD" };
-
- result.longMonthNames = new String[] { "January", "February", "March", "April", "May",
- "June", "July", "August", "September", "October", "November", "December" };
- result.shortMonthNames = new String[] { "Jan", "Feb", "Mar", "Apr", "May",
- "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
- result.longStandAloneMonthNames = result.longMonthNames;
- result.shortStandAloneMonthNames = result.shortMonthNames;
-
- // The platform code expects this to begin at index 1, rather than 0. It maps it directly to
- // the constants from java.util.Calendar.<weekday>
- result.longWeekdayNames = new String[] {
- "", "Sunday", "Monday" ,"Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
- result.shortWeekdayNames = new String[] {
- "", "Sun", "Mon" ,"Tue", "Wed", "Thu", "Fri", "Sat" };
- result.tinyWeekdayNames = new String[] {
- "", "S", "M", "T", "W", "T", "F", "S" };
-
- result.longStandAloneWeekdayNames = result.longWeekdayNames;
- result.shortStandAloneWeekdayNames = result.shortWeekdayNames;
- result.tinyStandAloneWeekdayNames = result.tinyWeekdayNames;
-
- result.fullTimeFormat = "";
- result.longTimeFormat = "";
- result.mediumTimeFormat = "";
- result.shortTimeFormat = "";
-
- result.fullDateFormat = "";
- result.longDateFormat = "";
- result.mediumDateFormat = "";
- result.shortDateFormat = "";
-
- // Used by DecimalFormatSymbols.
- result.zeroDigit = '0';
- result.decimalSeparator = '.';
- result.groupingSeparator = ',';
- result.patternSeparator = ' ';
- result.percent = "%";
- result.perMill = '\u2030';
- result.monetarySeparator = ' ';
- result.minusSign = "-";
- result.exponentSeparator = "e";
- result.infinity = "\u221E";
- result.NaN = "NaN";
- // Also used by Currency.
- result.currencySymbol = "$";
- result.internationalCurrencySymbol = "USD";
-
- // Used by DecimalFormat and NumberFormat.
- result.numberPattern = "%f";
- result.integerPattern = "%d";
- result.currencyPattern = "%s";
- result.percentPattern = "%f";
-
- return true;
- }
-
- @LayoutlibDelegate
- /*package*/ static void setDefaultLocale(String locale) {
- ICU.setDefaultLocale(locale);
- }
-
- @LayoutlibDelegate
- /*package*/ static String getDefaultLocale() {
- return ICU.getDefaultLocale();
- }
-
- @LayoutlibDelegate
- /*package*/ static String getTZDataVersion() {
- return ICU.getTZDataVersion();
- }
-}
diff --git a/tools/layoutlib/bridge/src/libcore/io/MemoryMappedFile_Delegate.java b/tools/layoutlib/bridge/src/libcore/io/MemoryMappedFile_Delegate.java
deleted file mode 100644
index 723d5c4..0000000
--- a/tools/layoutlib/bridge/src/libcore/io/MemoryMappedFile_Delegate.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2014 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 libcore.io;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.layoutlib.bridge.libcore.io.BridgeBufferIterator;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.system.ErrnoException;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.ByteOrder;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel.MapMode;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Delegate used to provide alternate implementation of select methods of {@link MemoryMappedFile}.
- */
-public class MemoryMappedFile_Delegate {
-
- private static final DelegateManager<MemoryMappedFile_Delegate> sManager = new
- DelegateManager<MemoryMappedFile_Delegate>(MemoryMappedFile_Delegate.class);
-
- private static final Map<MemoryMappedFile, Long> sMemoryMappedFileMap =
- new HashMap<MemoryMappedFile, Long>();
-
- private final MappedByteBuffer mMappedByteBuffer;
- private final long mSize;
-
- /** Path on the target device where the data file is available. */
- private static final String TARGET_PATH = System.getenv("ANDROID_ROOT") + "/usr/share/zoneinfo";
- /** Path on the host (inside the SDK) where the data files are available. */
- private static File sRootPath;
-
- @LayoutlibDelegate
- static MemoryMappedFile mmapRO(String path) throws ErrnoException {
- if (!path.startsWith(TARGET_PATH)) {
- throw new ErrnoException("Custom timezone data files are not supported.", 1);
- }
- if (sRootPath == null) {
- throw new ErrnoException("Bridge has not been initialized properly.", 1);
- }
- path = path.substring(TARGET_PATH.length());
- try {
- File f = new File(sRootPath, path);
- if (!f.exists()) {
- throw new ErrnoException("File not found: " + f.getPath(), 1);
- }
- RandomAccessFile file = new RandomAccessFile(f, "r");
- try {
- long size = file.length();
- MemoryMappedFile_Delegate newDelegate = new MemoryMappedFile_Delegate(file);
- long filePointer = file.getFilePointer();
- MemoryMappedFile mmFile = new MemoryMappedFile(filePointer, size);
- long delegateIndex = sManager.addNewDelegate(newDelegate);
- sMemoryMappedFileMap.put(mmFile, delegateIndex);
- return mmFile;
- } finally {
- file.close();
- }
- } catch (IOException e) {
- throw new ErrnoException("mmapRO", 1, e);
- }
- }
-
- @LayoutlibDelegate
- static void close(MemoryMappedFile thisFile) throws ErrnoException {
- Long index = sMemoryMappedFileMap.get(thisFile);
- if (index != null) {
- sMemoryMappedFileMap.remove(thisFile);
- sManager.removeJavaReferenceFor(index);
- }
- }
-
- @LayoutlibDelegate
- static BufferIterator bigEndianIterator(MemoryMappedFile file) {
- MemoryMappedFile_Delegate delegate = getDelegate(file);
- return new BridgeBufferIterator(delegate.mSize, delegate.mMappedByteBuffer.duplicate());
- }
-
- // TODO: implement littleEndianIterator()
-
- public MemoryMappedFile_Delegate(RandomAccessFile file) throws IOException {
- mSize = file.length();
- // It's weird that map() takes size as long, but returns MappedByteBuffer which uses an int
- // to store the marker to the position.
- mMappedByteBuffer = file.getChannel().map(MapMode.READ_ONLY, 0, mSize);
- assert mMappedByteBuffer.order() == ByteOrder.BIG_ENDIAN;
- }
-
- public static void setDataDir(File path) {
- sRootPath = path;
- }
-
- private static MemoryMappedFile_Delegate getDelegate(MemoryMappedFile file) {
- Long index = sMemoryMappedFileMap.get(file);
- return index == null ? null : sManager.getDelegate(index);
- }
-
-}
diff --git a/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java b/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java
deleted file mode 100644
index 04fabc2..0000000
--- a/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.util;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-/**
- * Delegate implementing the native methods of {@link NativeAllocationRegistry}
- *
- * Through the layoutlib_create tool, the original native methods of NativeAllocationRegistry have
- * been replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original NativeAllocationRegistry class.
- *
- * @see DelegateManager
- */
-public class NativeAllocationRegistry_Delegate {
-
- // ---- delegate manager ----
- private static final DelegateManager<NativeAllocationRegistry_Delegate> sManager =
- new DelegateManager<>(NativeAllocationRegistry_Delegate.class);
-
- private final FreeFunction mFinalizer;
-
- private NativeAllocationRegistry_Delegate(FreeFunction finalizer) {
- mFinalizer = finalizer;
- }
-
- /**
- * The result of this method should be cached by the class and reused.
- */
- public static long createFinalizer(FreeFunction finalizer) {
- return sManager.addNewDelegate(new NativeAllocationRegistry_Delegate(finalizer));
- }
-
- @LayoutlibDelegate
- /*package*/ static void applyFreeFunction(long freeFunction, long nativePtr) {
- // This method MIGHT run in the context of the finalizer thread. If the delegate method
- // crashes, it could bring down the VM. That's why we catch all the exceptions and ignore
- // them.
- try {
- NativeAllocationRegistry_Delegate delegate = sManager.getDelegate(freeFunction);
- if (delegate != null) {
- delegate.mFinalizer.free(nativePtr);
- }
- } catch (Throwable ignore) {
- }
- }
-
- public interface FreeFunction {
- void free(long nativePtr);
- }
-}
diff --git a/tools/layoutlib/bridge/tests/.classpath b/tools/layoutlib/bridge/tests/.classpath
deleted file mode 100644
index 2b32e09..0000000
--- a/tools/layoutlib/bridge/tests/.classpath
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
- <classpathentry kind="src" path="src"/>
- <classpathentry kind="src" path="res"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
- <classpathentry combineaccessrules="false" kind="src" path="/layoutlib_bridge"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_PLAT_SRC/dalvik/libcore/xml/src/main/java"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar" sourcepath="/ANDROID_PLAT_SRC/frameworks/base"/>
- <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
- <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/tools/layoutlib/bridge/tests/.project b/tools/layoutlib/bridge/tests/.project
deleted file mode 100644
index 2325eed..0000000
--- a/tools/layoutlib/bridge/tests/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>layoutlib_bridge-tests</name>
- <comment></comment>
- <projects>
- </projects>
- <buildSpec>
- <buildCommand>
- <name>org.eclipse.jdt.core.javabuilder</name>
- <arguments>
- </arguments>
- </buildCommand>
- </buildSpec>
- <natures>
- <nature>org.eclipse.jdt.core.javanature</nature>
- </natures>
-</projectDescription>
diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk
deleted file mode 100644
index 1b65eee..0000000
--- a/tools/layoutlib/bridge/tests/Android.mk
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright (C) 2011 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)
-
-# Only compile source java files in this lib.
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src) \
- $(call all-java-files-under, res/testApp/MyApplication/src/main/myapplication.widgets)
-LOCAL_JAVA_RESOURCE_DIRS := res
-
-LOCAL_MODULE := layoutlib-tests
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_JAVA_LIBRARIES := layoutlib \
- kxml2-2.3.0 \
- layoutlib_api-prebuilt \
- tools-common-prebuilt \
- sdk-common \
- junit-host \
- guavalib \
- mockito-host
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-# Copy the jar to DIST_DIR for sdk builds
-$(call dist-for-goals, sdk win_sdk, $(LOCAL_INSTALLED_MODULE))
-
-# Build all sub-directories
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/layoutlib/bridge/tests/res/com/android/layoutlib/testdata/layout1.xml b/tools/layoutlib/bridge/tests/res/com/android/layoutlib/testdata/layout1.xml
deleted file mode 100644
index b8fc947..0000000
--- a/tools/layoutlib/bridge/tests/res/com/android/layoutlib/testdata/layout1.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
- Copyright (C) 2008 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
->
- <Button
- android:id="@+id/bouton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:text="My Button Text"
- >
- </Button>
- <View
- android:id="@+id/surface"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="2"
- />
- <TextView
- android:id="@+id/status"
- android:paddingLeft="2dip"
- android:layout_weight="0"
- android:background="@drawable/black"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:lines="1"
- android:gravity="center_vertical|center_horizontal"
- android:text="My TextView Text"
- />
-</LinearLayout>
diff --git a/tools/layoutlib/bridge/tests/res/empty.xml b/tools/layoutlib/bridge/tests/res/empty.xml
deleted file mode 100644
index e69de29..0000000
--- a/tools/layoutlib/bridge/tests/res/empty.xml
+++ /dev/null
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/.gitignore b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/.gitignore
deleted file mode 100644
index a2ce0dc..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/.gitignore
+++ /dev/null
@@ -1,14 +0,0 @@
-.gradle
-local.properties
-.idea
-.DS_Store
-*.iml
-# We need the built .class files to load custom views and R class.
-# The only way to negate an exclusion is by including every single parent
-# and excluding all children of those parents.
-
-/build/*
-!/build/intermediates/
-
-/build/intermediates/*
-!/build/intermediates/classes/
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle
deleted file mode 100644
index 4781660..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle
+++ /dev/null
@@ -1,48 +0,0 @@
-buildscript {
- repositories {
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:1.2.3'
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-allprojects {
- repositories {
- jcenter()
- }
-}
-
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 25
- buildToolsVersion '25.0.0'
- defaultConfig {
- applicationId 'com.android.layoutlib.test.myapplication'
- minSdkVersion 21
- targetSdkVersion 25
- versionCode 1
- versionName '1.0'
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
- lintOptions {
- abortOnError false
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_6
- targetCompatibility JavaVersion.VERSION_1_6
- }
-}
-
-dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
-}
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/androidTest/debug/com/android/layoutlib/test/myapplication/test/BuildConfig.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/androidTest/debug/com/android/layoutlib/test/myapplication/test/BuildConfig.class
deleted file mode 100644
index 1ca7e01..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/androidTest/debug/com/android/layoutlib/test/myapplication/test/BuildConfig.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class
deleted file mode 100644
index f73528a..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/BuildConfig.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/BuildConfig.class
deleted file mode 100644
index ceb56bf..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/BuildConfig.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class
deleted file mode 100644
index 5bb04fc..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$array.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$array.class
deleted file mode 100644
index b87f193..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$array.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class
deleted file mode 100644
index e2968d40..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$color.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$color.class
deleted file mode 100644
index ff699d1..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$color.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class
deleted file mode 100644
index a3931b8..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class
deleted file mode 100644
index e293677..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class
deleted file mode 100644
index d6268bf..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class
deleted file mode 100644
index 08b98fb..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class
deleted file mode 100644
index f9be1ca..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class
deleted file mode 100644
index 6874b49..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class
deleted file mode 100644
index a4205a8..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class
deleted file mode 100644
index 4fb3b61..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class
deleted file mode 100644
index dba67fd..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png
deleted file mode 100644
index affc31e..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon.png
deleted file mode 100644
index 7014ddb..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
deleted file mode 100644
index f15d669..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
deleted file mode 100644
index 88fa9dd..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png
deleted file mode 100644
index e33f92d..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png
deleted file mode 100644
index 915868c..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/array_check.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/array_check.png
deleted file mode 100644
index ee72a6f..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/array_check.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/expand_horz_layout.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/expand_horz_layout.png
deleted file mode 100644
index 781d617..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/expand_horz_layout.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/expand_vert_layout.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/expand_vert_layout.png
deleted file mode 100644
index 5b64d33..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/expand_vert_layout.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
deleted file mode 100644
index 736b287..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners.png
deleted file mode 100644
index a8567b3..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent.png
deleted file mode 100644
index 5ae95ea..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent_land.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent_land.png
deleted file mode 100644
index b2b6a97..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent_land.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/scrolled.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/scrolled.png
deleted file mode 100644
index cf1a769..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/scrolled.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png
deleted file mode 100644
index 67355b8..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity-old-theme.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity-old-theme.png
deleted file mode 100644
index e0abcf4..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity-old-theme.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity.png
deleted file mode 100644
index 3d0fbd6..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png
deleted file mode 100644
index 86b00415..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png
deleted file mode 100644
index 7bbae09..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_91383.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_91383.png
deleted file mode 100644
index e9dca692..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_91383.png
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle.properties b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle.properties
deleted file mode 100644
index 5d08ba7..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle.properties
+++ /dev/null
@@ -1,18 +0,0 @@
-# Project-wide Gradle settings.
-
-# IDE (e.g. Android Studio) users:
-# Settings specified in this file will override any Gradle settings
-# configured through the IDE.
-
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-# Default value: -Xmx10248m -XX:MaxPermSize=256m
-# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
-
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.jar b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 8c0fb64..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.jar
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 3b51ffe..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Tue Mar 17 15:13:06 PDT 2015
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew
deleted file mode 100755
index 91a7e26..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew
+++ /dev/null
@@ -1,164 +0,0 @@
-#!/usr/bin/env bash
-
-##############################################################################
-##
-## Gradle start up script for UN*X
-##
-##############################################################################
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
- echo "$*"
-}
-
-die ( ) {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
-esac
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched.
-if $cygwin ; then
- [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
-fi
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >&-
-APP_HOME="`pwd -P`"
-cd "$SAVED" >&-
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- if [ ! -x "$JAVACMD" ] ; then
- die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
- fi
- i=$((i+1))
- done
- case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
-fi
-
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
-}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
-
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew.bat b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew.bat
deleted file mode 100644
index aec9973..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew.bat
+++ /dev/null
@@ -1,90 +0,0 @@
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windowz variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/proguard-rules.pro b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/proguard-rules.pro
deleted file mode 100644
index b0fcd2d..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/proguard-rules.pro
+++ /dev/null
@@ -1,17 +0,0 @@
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in /usr/local/google/home/deepanshu/ssd/sdk_out/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/AndroidManifest.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/AndroidManifest.xml
deleted file mode 100644
index 2067474..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.layoutlib.test.myapplication" >
-<!-- If changing package here, update LayoutLibCallBack in tests. -->
- <application
- android:allowBackup="true"
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name"
- android:theme="@style/AppTheme" >
- <activity
- android:name="com.android.layoutlib.test.myapplication.MyActivity"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
-</manifest>
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ArraysCheckWidget.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ArraysCheckWidget.java
deleted file mode 100644
index e7f22bf..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ArraysCheckWidget.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.test.myapplication;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.util.AttributeSet;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-/**
- * A widget to test obtaining arrays from resources.
- */
-public class ArraysCheckWidget extends LinearLayout {
- public ArraysCheckWidget(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ArraysCheckWidget(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public ArraysCheckWidget(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- Resources resources = context.getResources();
- for (CharSequence chars : resources.getTextArray(R.array.array)) {
- addTextView(context, chars);
- }
- for (int i : resources.getIntArray(R.array.int_array)) {
- addTextView(context, String.valueOf(i));
- }
- for (String string : resources.getStringArray(R.array.string_array)) {
- addTextView(context, string);
- }
- }
-
- private void addTextView(Context context, CharSequence string) {
- TextView textView = new TextView(context);
- textView.setText(string);
- textView.setTextSize(30);
- addView(textView);
- }
-}
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/MyActivity.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/MyActivity.java
deleted file mode 100644
index 59de457..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/MyActivity.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.android.layoutlib.test.myapplication;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.Menu;
-import android.view.MenuItem;
-
-public class MyActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity);
- }
-
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.my, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // Handle action bar item clicks here. The action bar will
- // automatically handle clicks on the Home/Up button, so long
- // as you specify a parent activity in AndroidManifest.xml.
- int id = item.getItemId();
- if (id == R.id.action_settings) {
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-}
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CustomCalendar.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CustomCalendar.java
deleted file mode 100644
index 3b819e5..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CustomCalendar.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.test.myapplication.widgets;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.CalendarView;
-
-public class CustomCalendar extends CalendarView {
- public CustomCalendar(Context context) {
- super(context);
- init();
- }
-
- public CustomCalendar(Context context, AttributeSet attrs) {
- super(context, attrs);
- init();
- }
-
- public CustomCalendar(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- init();
- }
-
- public CustomCalendar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- init();
- }
-
- private void init() {
- setDate(871732800000L, false, true);
- }
-}
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CustomDate.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CustomDate.java
deleted file mode 100644
index f3877f1..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CustomDate.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.test.myapplication.widgets;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.DatePicker;
-
-public class CustomDate extends DatePicker {
- public CustomDate(Context context) {
- super(context);
- init();
- }
-
- public CustomDate(Context context, AttributeSet attrs) {
- super(context, attrs);
- init();
- }
-
- public CustomDate(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- init();
- }
-
- public CustomDate(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- init();
- }
-
- private void init() {
- init(2015, 0, 20, null);
- }
-}
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/InsetsWidget.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/InsetsWidget.java
deleted file mode 100644
index 36e5c26..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/InsetsWidget.java
+++ /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.
- */
-
-package com.android.layoutlib.test.myapplication.widgets;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.WindowInsets;
-import android.widget.TextView;
-
-public class InsetsWidget extends TextView {
- public static boolean sApplyInsetsCalled = false;
-
- public InsetsWidget(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- requestApplyInsets();
- }
-
- @Override
- public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- sApplyInsetsCalled = true;
- return super.onApplyWindowInsets(insets);
- }
-}
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/package-info.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/package-info.java
deleted file mode 100644
index 58ad46d..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/package-info.java
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * This package contains custom widgets used to set a specific time for the DayTimePicker during
- * testing.
- * The classes here are both used from the Android project and from the Bridge test project.
- */
-package com.android.layoutlib.test.myapplication.widgets;
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/color/gradient.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/color/gradient.xml
deleted file mode 100644
index fc0afa6..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/color/gradient.xml
+++ /dev/null
@@ -1,24 +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.
- -->
-
-
-<gradient xmlns:android="http://schemas.android.com/apk/res/android"
- android:startX="10"
- android:startY="10"
- android:endX="50"
- android:endY="50"
- android:startColor="#ffff0000"
- android:endColor="#ff00ff00" />
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml
deleted file mode 100644
index 8f862c8..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
- <background android:drawable="@android:color/red" />
- <foreground android:drawable="@drawable/headset" />
-</adaptive-icon>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/android.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/android.xml
deleted file mode 100644
index 42e3beb..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/android.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ 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.
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:viewportWidth="1102"
- android:viewportHeight="642"
- android:width="1102px"
- android:height="642px">
-
- <group
- android:translateX="-126.347"
- android:translateY="6.7928655e-4">
-
-
- <path
- android:fillColor="#94c147"
- android:pathData="
- m 552.777,147.57
- c -14.147,0 -25.622,11.652 -25.622,26.02
- v 101.68
- c 0,14.372 11.475,26.019 25.622,26.019 14.147,0 25.61,-11.646 25.61,-26.019
- V 173.59
- c 0.001,-14.368 -11.462,-26.02 -25.61,-26.02
- z
-
- m -309.011,0
- c -14.155,0 -25.618,11.652 -25.618,26.02
- v 101.68
- c 0,14.372 11.462,26.019 25.618,26.019 14.153,0 25.623,-11.646 25.623,-26.019
- V 173.59
- c -0.008,-14.368 -11.475,-26.02 -25.623,-26.02
- z" />
-
-
- <path
- android:fillColor="#94c147"
- android:pathData="m 284.799,148.364 v 185.768 c 0,11.035 8.948,19.98 19.983,19.98 h 22.815 v 56.567 c 0,14.37 11.47,26.016 25.623,26.016 14.148,0 25.623,-11.646 25.623,-26.016 v -56.567 h 39.878 v 56.567 c 0,14.37 11.463,26.016 25.61,26.016 14.147,0 25.622,-11.646 25.622,-26.016 v -56.567 h 22.828 c 11.022,0 19.971,-8.953 19.971,-19.98 V 148.364 H 284.799 l 0,0 z" />
-
- <group
- android:name="head"
- android:pivotX="400"
- android:pivotY="131.105">
-
- <path
- android:fillColor="#94c147"
- android:pathData="m 452.302,52.105 21.057,-30.572 c 1.245,-1.819 0.939,-4.199 -0.695,-5.329 -1.637,-1.123 -3.968,-0.568 -5.225,1.251 l -21.875,31.75 c -14.404,-5.682 -30.418,-8.844 -47.293,-8.844 -16.87,0 -32.893,3.162 -47.297,8.844 l -21.875,-31.75 c -1.257,-1.819 -3.589,-2.375 -5.225,-1.251 -1.636,1.124 -1.946,3.509 -0.696,5.329 l 21.057,30.572 c -33.464,15.57 -56.951,45.166 -59.941,79.706 H 512.25 C 509.259,97.271 485.772,67.676 452.302,52.105 z M 350.187,100.28 c -6.965,0 -12.617,-5.646 -12.617,-12.616 0,-6.958 5.647,-12.61 12.617,-12.61 6.97,0 12.603,5.652 12.603,12.61 0,6.965 -5.64,12.616 -12.603,12.616 z m 97.744,0 c -6.97,0 -12.609,-5.646 -12.609,-12.616 0,-6.958 5.64,-12.61 12.609,-12.61 6.971,0 12.61,5.652 12.61,12.61 0,6.965 -5.64,12.616 -12.61,12.616 z" />
- </group>
-
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/headset.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/headset.xml
deleted file mode 100644
index 897c411..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/headset.xml
+++ /dev/null
@@ -1,25 +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.
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="150dp"
- android:height="150dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="m12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 3.13,-7 7,-7s7,3.13 7,7v2h-4v8h4v1h-7v2h6c1.66,0 3,-1.34 3,-3V10c0,-4.97 -4.03,-9 -9,-9z"/>
-</vector>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/ic_launcher.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/ic_launcher.xml
deleted file mode 100644
index 67481d4..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/ic_launcher.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="#ff0000" />
- <size
- android:width="20dp"
- android:height="20dp" />
-</shape>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml
deleted file mode 100644
index 0998b25..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml
+++ /dev/null
@@ -1,88 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="76dp"
- android:width="76dp"
- android:viewportHeight="48"
- android:viewportWidth="48"
- android:alpha="0.6">
-
- <group
- android:name="root"
- android:translateX="24.0"
- android:translateY="24.0">
- <!--
- This is the same as the material indeterminate progressbar which involves drawing
- several cubic segments
- -->
- <path
- android:pathData="M0, 0 m 0, -19 a 19,19 0 1,1 0,38 a 19,19 0 1,1 0,-38"
- android:strokeColor="#00FF00"
- android:strokeLineCap="square"
- android:strokeLineJoin="miter"
- android:strokeWidth="1"
- android:trimPathEnd="0.8"
- android:trimPathStart="0.3" />
- <!-- Same figure with reversed end and start -->
- <path
- android:pathData="M0, 0 m 0, -12 a 19,19 0 1,1 0,38 a 19,19 0 1,1 0,-38"
- android:strokeColor="#FFFF00"
- android:strokeLineCap="square"
- android:strokeLineJoin="miter"
- android:strokeWidth="1"
- android:trimPathEnd="0.3"
- android:trimPathStart="0.8" />
-
- <!--
- Draw a few partial quadratic segments
- -->
- <path
- android:strokeWidth="2"
- android:strokeColor="#FFFF00"
- android:pathData="M2,2 Q 5 30 15 0"
- android:trimPathStart="0.1"
- android:trimPathEnd="0.9"
- />
-
- <!--
- Draw a line
- -->
- <path
- android:strokeWidth="3"
- android:strokeColor="#00FFFF"
- android:pathData="M-10,-10 L 10, 10"
- android:trimPathStart="0.2"
- android:trimPathEnd="0.8"
- />
-
- <!--
- Draw a line with gradient stroke color
- -->
- <path
- android:strokeWidth="1"
- android:strokeColor="#FF00FF"
- android:fillColor="@color/gradient"
- android:pathData="M-20,-20 l0, 10 l10, 0 l0, -10 l-10,0 "
- />
-
- <!--
- Draw squares with different fill types
- -->
- <path
- android:fillType="evenOdd"
- android:strokeWidth="1"
- android:strokeColor="#AABBCC"
- android:fillColor="#AAEFCC"
- android:pathData="M-20,-40 l0, 10 l10, 0 l0, -10 l-10,0 m5,0 l0, 10 l10, 0 l0, -10 l-10,0"
- />
-
- <path
- android:fillType="nonZero"
- android:strokeWidth="1"
- android:strokeColor="#AABBCC"
- android:fillColor="#40AAEFCC"
- android:pathData="M0,-40 l0, 10 l10, 0 l0, -10 l-10,0 m5,0 l0, 10 l10, 0 l0, -10 l-10,0"
- />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfamily.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfamily.xml
deleted file mode 100644
index b1e9206..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/font/testfamily.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<font-family xmlns:android="http://schemas.android.com/apk/res/android">
- <font android:fontStyle="normal" android:fontWeight="400" android:font="@font/testfont" />
- <font android:fontStyle="italic" android:fontWeight="400" android:font="@font/testfont2" />
-</font-family>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/activity.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/activity.xml
deleted file mode 100644
index 97d1983..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/activity.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- android:paddingBottom="@dimen/activity_vertical_margin"
- tools:context=".MyActivity">
-
- <TextView
- android:text="@string/hello_world"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@+id/text1"/>
-
- <include layout="@layout/layout"
- android:layout_below="@+id/text1"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content" />
-</RelativeLayout>
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/adaptive_icon.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/adaptive_icon.xml
deleted file mode 100644
index ca9fa55..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/adaptive_icon.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:padding="16dp"
- android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <ImageView
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/adaptive" />
-
-</LinearLayout>
-
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml
deleted file mode 100644
index 456b5f6..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml
+++ /dev/null
@@ -1,403 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:ignore="HardcodedText,LabelFor,TextFields,ContentDescription,RtlHardcoded">
-
- <FrameLayout
- android:id="@id/frameLayout"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_alignParentTop="true"
- android:layout_marginEnd="311dp">
-
- <TextView
- android:id="@id/textView"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="left|top"
- android:text="New Text" />
- </FrameLayout>
-
- <TextView
- android:id="@id/textView2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_below="@id/frameLayout"
- android:text="Large Text"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:pointerIcon="hand" />
-
- <TextView
- android:id="@id/textView3"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_toEndOf="@id/textView2"
- android:text="Medium Text"
- android:textAppearance="?android:attr/textAppearanceMedium" />
-
- <TextView
- android:id="@id/textView4"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignTop="@id/textView2"
- android:layout_toEndOf="@id/textView2"
- android:text="Small Text"
- android:textAppearance="?android:attr/textAppearanceSmall" />
-
- <Button
- android:id="@id/button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/textView3"
- android:layout_toEndOf="@id/textView4"
- android:text="New Button" />
-
- <Button
- android:id="@id/button2"
- style="?android:attr/buttonStyleSmall"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_toEndOf="@id/button"
- android:text="New Button" />
-
- <CheckBox
- android:id="@id/checkBox"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignEnd="@id/button"
- android:layout_below="@id/button"
- android:text="New CheckBox" />
-
- <Switch
- android:id="@id/switch1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_below="@id/textView2"
- android:text="New Switch" />
-
- <ImageButton
- android:id="@id/imageButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/button"
- android:layout_toEndOf="@id/switch1" />
-
- <ImageView
- android:id="@id/imageView"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/ic_launcher"
- android:layout_below="@id/button"
- android:layout_toEndOf="@id/imageButton" />
-
- <GridLayout
- android:id="@id/gridLayout"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_below="@id/imageButton"
- android:columnCount="2"
- android:rowCount="2">
-
- <ProgressBar
- android:id="@id/progressBar"
- style="?android:attr/progressBarStyleLarge"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_column="0"
- android:layout_row="0" />
-
- <ProgressBar
- android:id="@id/progressBar2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_column="1"
- android:layout_row="0" />
-
- <ProgressBar
- android:id="@id/progressBar3"
- style="?android:attr/progressBarStyleSmall"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_column="0"
- android:layout_row="1" />
-
- <ProgressBar
- android:id="@id/progressBar4"
- style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_column="1"
- android:layout_row="1" />
- </GridLayout>
-
- <SeekBar
- android:id="@id/seekBar"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignTop="@id/gridLayout"
- android:layout_toEndOf="@id/gridLayout" />
-
- <RatingBar
- android:id="@id/ratingBar"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/switch2"
- android:layout_toEndOf="@id/gridLayout" />
-
- <Switch
- android:id="@id/switch2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/seekBar"
- android:layout_toEndOf="@id/switch1"
- android:checked="true" />
-
- <EditText
- android:id="@id/editText"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignBottom="@id/ratingBar"
- android:layout_alignParentStart="true"
- android:text="plain text" />
-
- <EditText
- android:id="@id/editText2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_below="@id/ratingBar"
- android:ems="3"
- android:inputType="textPersonName"
- android:text="Name" />
-
- <EditText
- android:id="@id/editText3"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:layout_toEndOf="@id/editText2"
- android:ems="2"
- android:inputType="textPassword"
- android:text="password" />
-
- <EditText
- android:id="@id/editText4"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignTop="@id/editText3"
- android:layout_toEndOf="@id/editText3"
- android:ems="3"
- android:inputType="numberPassword"
- android:text="numeric password" />
-
- <ToggleButton
- android:id="@+id/toggleButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignTop="@id/editText4"
- android:layout_toEndOf="@id/editText4"
- android:text="New ToggleButton" />
-
- <EditText
- android:id="@id/editText5"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/editText3"
- android:layout_toStartOf="@id/editText6"
- android:ems="7"
- android:inputType="textEmailAddress"
- android:text="email@domain.com" />
-
- <EditText
- android:id="@id/editText6"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_below="@id/editText4"
- android:ems="7"
- android:inputType="phone"
- android:text="+11235554344" />
-
- <EditText
- android:id="@id/editText7"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/editText"
- android:layout_toEndOf="@id/editText4"
- android:ems="10"
- android:inputType="textPostalAddress"
- android:text="1600 Amphitheatre" />
-
- <EditText
- android:id="@id/editText9"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/editText5"
- android:layout_alignParentStart="true"
- android:ems="3"
- android:inputType="time"
- android:text="12:12" />
-
- <RadioGroup
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/editText5"
- android:layout_toEndOf="@id/editText9"
- android:orientation="horizontal">
-
- <RadioButton
- android:id="@id/radioButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="New RadioButton" />
-
- <RadioButton
- android:id="@id/radioButton2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="New RadioButton"
- android:checked="true" />
-
- </RadioGroup>
-
- <CheckedTextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="CheckedTextView"
- android:id="@id/checkedTextView"
- android:layout_below="@id/button2"
- android:layout_alignParentEnd="true"
- android:layout_alignStart="@id/button2" />
-
- <DialerFilter
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/checkBox"
- android:layout_toStartOf="@id/quickContactBadge"
- android:id="@id/dialerFilter"
- android:layout_above="@id/ratingBar"
- android:layout_toEndOf="@id/seekBar">
-
- <EditText
- android:id="@android:id/hint"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Hint" />
-
- <EditText
- android:id="@android:id/primary"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@android:id/hint"
- android:text="Primary" />
- </DialerFilter>
-
- <QuickContactBadge
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@id/quickContactBadge"
- android:layout_below="@id/checkedTextView"
- android:layout_alignParentEnd="true" />
-
- <android.inputmethodservice.ExtractEditText
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="ExtractEditText"
- android:id="@id/extractEditText"
- android:layout_below="@id/editText9"
- android:layout_alignParentEnd="true"
- android:layout_alignStart="@id/checkedTextView" />
-
- <ZoomControls
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@id/zoomControls"
- android:layout_below="@id/editText9"
- android:layout_alignParentStart="true" />
-
- <TextureView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@id/textureView"
- android:layout_below="@id/zoomControls"
- android:layout_alignParentStart="true"
- android:layout_alignBottom="@id/extractEditText"
- android:layout_toStartOf="@id/editText3" />
-
- <ListView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@id/listView"
- android:layout_below="@id/textureView"
- android:layout_alignParentStart="true"
- android:layout_alignEnd="@id/textureView" />
-
- <GridView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@id/gridView"
- android:layout_below="@id/extractEditText"
- android:layout_alignParentEnd="true"
- android:layout_alignStart="@id/extractEditText" />
-
- <ScrollView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@id/scrollView"
- android:layout_below="@id/zoomControls"
- android:layout_toRightOf="@id/listView"
- android:layout_toLeftOf="@id/extractEditText">
-
- <TabHost
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:id="@id/tabHost">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <TabWidget
- android:id="@android:id/tabs"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
-
- <FrameLayout
- android:id="@android:id/tabcontent"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <LinearLayout
- android:id="@id/linearLayout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"/>
-
- <LinearLayout
- android:id="@id/linearLayout2"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"/>
-
- </FrameLayout>
- </LinearLayout>
- </TabHost>
-</ScrollView>
-
- <SearchView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@id/searchView"
- android:layout_alignBottom="@id/zoomControls"
- android:layout_toEndOf="@id/seekBar" />
-
-</RelativeLayout>
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/array_check.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/array_check.xml
deleted file mode 100644
index e9aa9e1..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/array_check.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<com.android.layoutlib.test.myapplication.ArraysCheckWidget
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical" android:layout_width="match_parent"
- android:layout_height="match_parent">
-
-</com.android.layoutlib.test.myapplication.ArraysCheckWidget>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/expand_horz_layout.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/expand_horz_layout.xml
deleted file mode 100644
index 2c66b7f..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/expand_horz_layout.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:padding="16dp"
- android:orientation="horizontal"
- android:background="#AAAAAA"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
-
- <include layout="@layout/expand_layout"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content" />
-
-</LinearLayout>
-
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/expand_layout.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/expand_layout.xml
deleted file mode 100644
index a255da7..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/expand_layout.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <TextView
- android:background="#FF0000"
- android:textSize="200sp"
- android:layout_width="200dp"
- android:layout_height="200dp" />
- <TextView
- android:background="#00FF00"
- android:textSize="200sp"
- android:layout_width="200dp"
- android:layout_height="200dp" />
- <TextView
- android:background="#0000FF"
- android:textSize="200sp"
- android:layout_width="200dp"
- android:layout_height="200dp" />
- <TextView
- android:background="#FF00FF"
- android:textSize="200sp"
- android:layout_width="200dp"
- android:layout_height="200dp" />
- <TextView
- android:background="#00FFFF"
- android:textSize="200sp"
- android:layout_width="200dp"
- android:layout_height="200dp" />
-</merge>
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/expand_vert_layout.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/expand_vert_layout.xml
deleted file mode 100644
index 5319654..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/expand_vert_layout.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:padding="16dp"
- android:orientation="vertical"
- android:background="#AAAAAA"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
-
- <include layout="@layout/expand_layout"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content" />
-
-</LinearLayout>
-
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/fonts_test.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/fonts_test.xml
deleted file mode 100644
index c63b211..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/fonts_test.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical" android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="CONDENSED"
- android:textSize="50sp"
- android:fontFamily="sans-serif-condensed"
- />
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="CONDENSED ITALIC"
- android:textSize="30sp"
- android:fontFamily="sans-serif-condensed"
- android:textStyle="italic"
- />
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="MONOSPACE"
- android:textSize="50sp"
- android:fontFamily="monospace"/>
-
- <Space
- android:layout_width="wrap_content"
- android:layout_height="30dp" />
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Custom"
- android:textSize="20sp"
- android:fontFamily="@font/testfont"/>
-
- <Space
- android:layout_width="wrap_content"
- android:layout_height="30dp" />
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Custom family"
- android:textSize="20sp"
- android:fontFamily="@font/testfamily"/>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Custom family"
- android:textSize="20sp"
- android:fontFamily="@font/testfamily"
- android:textStyle="italic"/>
-
-</LinearLayout>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/four_corners.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/four_corners.xml
deleted file mode 100644
index c42284a..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/four_corners.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:layout_centerHorizontal="true"
- android:textSize="40sp"
- android:text="Bottom Text" />
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_centerHorizontal="true"
- android:textSize="40sp"
- android:text="Top text" />
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_centerVertical="true"
- android:textSize="40sp"
- android:text="Start text" />
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_centerVertical="true"
- android:textSize="40sp"
- android:text="End text" />
-</RelativeLayout>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml
deleted file mode 100644
index 70d7396..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:padding="16dp"
- android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
-
- <ProgressBar
- android:layout_height="fill_parent"
- android:layout_width="fill_parent" />
-
-</LinearLayout>
-
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/insets.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/insets.xml
deleted file mode 100644
index ff06d79..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/insets.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:padding="16dp"
- android:orientation="horizontal"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
-
- <com.android.layoutlib.test.myapplication.widgets.InsetsWidget
- android:text="Hello world"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@+id/text1"/>
-</LinearLayout>
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml
deleted file mode 100644
index ff14ce0..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:columnCount="2"
- android:layout_height="match_parent">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Some text"/>
- <Switch
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:checked="true"
- android:layout_gravity="center"
- />
- <com.android.layoutlib.test.myapplication.widgets.CustomDate
- android:layout_width="100dp"
- android:layout_height="wrap_content"/>
- <com.android.layoutlib.test.myapplication.widgets.CustomCalendar
- android:layout_width="200dp"
- android:layout_gravity="center_horizontal"
- android:layout_height="200dp"/>
-</GridLayout>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/scrolled.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/scrolled.xml
deleted file mode 100644
index a07498c..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/scrolled.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:scrollX="30px"
- android:scrollY="90px">
- <LinearLayout
- android:layout_width="60dp"
- android:layout_height="60dp"
- android:background="#FF0000" />
- <LinearLayout
- android:layout_width="60dp"
- android:layout_height="30dp"
- android:background="#00FF00" />
- <LinearLayout
- android:layout_width="60dp"
- android:layout_height="60dp"
- android:background="#0000FF" />
- <LinearLayout
- android:layout_width="60dp"
- android:layout_height="30dp"
- android:background="#FF00FF" />
- <LinearLayout
- android:layout_width="60dp"
- android:layout_height="60dp"
- android:background="#00FFFF" />
-
- <LinearLayout
- android:layout_width="200dp"
- android:layout_height="400dp"
- android:orientation="vertical"
- android:scrollX="-90px"
- android:scrollY="450px">
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="60dp"
- android:background="#FF0000" />
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="30dp"
- android:background="#00FF00" />
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="60dp"
- android:background="#0000FF" />
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="30dp"
- android:background="#FF00FF" />
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="60dp"
- android:background="#00FFFF" />
- </LinearLayout>
-
-
-</LinearLayout>
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_test.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_test.xml
deleted file mode 100644
index b1d6a60..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_test.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2017 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1">
-
- <Button
- android:layout_marginLeft="40dp"
- android:layout_width="48dp"
- android:layout_height="40dp"
- android:layout_alignParentLeft="true"
- android:layout_centerVertical="true"
- android:elevation="48dp"
- android:stateListAnimator="@null"/>
-
- <Button
- android:layout_marginRight="40dp"
- android:layout_width="48dp"
- android:layout_height="40dp"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true"
- android:elevation="48dp"
- android:stateListAnimator="@null"/>
-
- </RelativeLayout>
-
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1">
-
- <Button
- android:layout_marginLeft="40dp"
- android:layout_width="48dp"
- android:layout_height="40dp"
- android:layout_alignParentLeft="true"
- android:layout_centerVertical="true"
- android:elevation="0dp"
- android:stateListAnimator="@null"/>
-
- <Button
- android:layout_marginRight="40dp"
- android:layout_width="48dp"
- android:layout_height="40dp"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true"
- android:elevation="108dp"
- android:stateListAnimator="@null"/>
-
- </RelativeLayout>
-
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1">
-
- <Button
- android:layout_marginLeft="40dp"
- android:layout_width="48dp"
- android:layout_height="40dp"
- android:layout_alignParentLeft="true"
- android:layout_centerVertical="true"
- android:elevation="12dp"
- android:stateListAnimator="@null"/>
-
- <Button
- android:layout_marginRight="40dp"
- android:layout_width="48dp"
- android:layout_height="40dp"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true"
- android:elevation="108dp"
- android:stateListAnimator="@null"/>
-
- </RelativeLayout>
-</LinearLayout>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/simple_activity.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/simple_activity.xml
deleted file mode 100644
index 14b93f3..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/simple_activity.xml
+++ /dev/null
@@ -1,32 +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.
- -->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- android:paddingBottom="@dimen/activity_vertical_margin">
-
- <TextView
- android:text="@string/hello_world"
- android:layout_width="wrap_content"
- android:layout_height="200dp"
- android:background="#FF0000"
- android:id="@+id/text1"/>
-
-</RelativeLayout>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/vector_drawable.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/vector_drawable.xml
deleted file mode 100644
index 2ce4f4c..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/vector_drawable.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
- ~ Copyright (C) 2015 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:padding="16dp"
- android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <ImageView
- android:layout_height="fill_parent"
- android:layout_width="fill_parent"
- android:src="@drawable/multi_path" />
-
-</LinearLayout>
-
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/vector_drawable_android.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/vector_drawable_android.xml
deleted file mode 100644
index 3b01ea0..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/vector_drawable_android.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
- ~ 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.
- -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:padding="16dp"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <ImageView
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/android"/>
- <ImageView
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/headset"/>
-
-</LinearLayout>
-
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/menu/my.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/menu/my.xml
deleted file mode 100644
index bea58cc..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/menu/my.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- tools:context=".MyActivity" >
- <item android:id="@+id/action_settings"
- android:title="@string/action_settings"
- android:orderInCategory="100"
- android:showAsAction="never" />
-</menu>
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml
deleted file mode 100644
index 5f58d39..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <array name="array">
- <item>first</item>
- <item>second</item>
- </array>
- <integer-array name="int_array">
- <item>1</item>
- <item>0xaB</item> <!-- hex entry (decimal 171) -->
- <item>010</item> <!-- octal entry -->
- <item>@integer/ten</item> <!-- value reference -->
- <item>?attr/myattr</item> <!-- theme attr reference -->
- </integer-array>
- <string-array name="string_array">
- <item>mystring</item>
- <item>@string/hello_world</item> <!-- string ref in appNs -->
- <!-- theme ref in android NS. value = @string/candidates_style = <u>candidates</u> -->
- <item>?android:attr/candidatesTextStyleSpans</item>
- <item>@android:string/unknownName</item> <!-- value = Unknown -->
- <item>?EC</item>
- </string-array>
-
- <!-- resources that the above array can refer to -->
- <integer name="ten">10</integer>
- <integer name="twelve">12</integer>
-</resources>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/attrs.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/attrs.xml
deleted file mode 100644
index 894e18c..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/attrs.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <attr name="myattr" format="integer" />
-</resources>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/dimens.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/dimens.xml
deleted file mode 100644
index 47c8224..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/dimens.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<resources>
- <!-- Default screen margins, per the Android Design guidelines. -->
- <dimen name="activity_horizontal_margin">16dp</dimen>
- <dimen name="activity_vertical_margin">16dp</dimen>
-</resources>
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/ids.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/ids.xml
deleted file mode 100644
index 1dc2fa0..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/ids.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <item type="id" name="button" />
- <item type="id" name="button2" />
- <item type="id" name="checkBox" />
- <item type="id" name="checkedTextView" />
- <item type="id" name="dialerFilter" />
- <item type="id" name="editText" />
- <item type="id" name="editText2" />
- <item type="id" name="editText3" />
- <item type="id" name="editText4" />
- <item type="id" name="editText5" />
- <item type="id" name="editText6" />
- <item type="id" name="editText7" />
- <item type="id" name="editText8" />
- <item type="id" name="editText9" />
- <item type="id" name="extractEditText" />
- <item type="id" name="frameLayout" />
- <item type="id" name="gridLayout" />
- <item type="id" name="gridView" />
- <item type="id" name="imageButton" />
- <item type="id" name="imageView" />
- <item type="id" name="linearLayout" />
- <item type="id" name="linearLayout2" />
- <item type="id" name="listView" />
- <item type="id" name="progressBar" />
- <item type="id" name="progressBar2" />
- <item type="id" name="progressBar3" />
- <item type="id" name="progressBar4" />
- <item type="id" name="quickContactBadge" />
- <item type="id" name="radioButton" />
- <item type="id" name="radioButton2" />
- <item type="id" name="ratingBar" />
- <item type="id" name="scrollView" />
- <item type="id" name="searchView" />
- <item type="id" name="seekBar" />
- <item type="id" name="spinner" />
- <item type="id" name="switch1" />
- <item type="id" name="switch2" />
- <item type="id" name="tabHost" />
- <item type="id" name="textView" />
- <item type="id" name="textView2" />
- <item type="id" name="textView3" />
- <item type="id" name="textView4" />
- <item type="id" name="textureView" />
- <item type="id" name="zoomControls" />
-</resources>
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/strings.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/strings.xml
deleted file mode 100644
index 2b7083b..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-
- <string name="app_name">My Application</string>
- <string name="hello_world">Hello world!</string>
- <string name="action_settings">Settings</string>
-
-</resources>
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
deleted file mode 100644
index debe33b..0000000
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<resources>
-
- <!-- Base application theme. -->
- <style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
- <item name="myattr">@integer/ten</item>
- </style>
-
-</resources>
diff --git a/tools/layoutlib/bridge/tests/src/android/app/SystemServiceRegistry_AccessorTest.java b/tools/layoutlib/bridge/tests/src/android/app/SystemServiceRegistry_AccessorTest.java
deleted file mode 100644
index 4f1c16c..0000000
--- a/tools/layoutlib/bridge/tests/src/android/app/SystemServiceRegistry_AccessorTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app;
-
-import org.junit.Test;
-
-import android.content.Context;
-import android.hardware.input.InputManager;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-
-import static org.junit.Assert.*;
-
-public class SystemServiceRegistry_AccessorTest {
- @Test
- public void testRegistry() {
- // Just check a few services to make sure we don't break the accessor
- assertEquals(Context.ACCESSIBILITY_SERVICE,
- SystemServiceRegistry_Accessor.getSystemServiceName(AccessibilityManager.class));
- assertEquals(Context.INPUT_SERVICE,
- SystemServiceRegistry_Accessor.getSystemServiceName(InputManager.class));
- assertEquals(Context.WINDOW_SERVICE,
- SystemServiceRegistry_Accessor.getSystemServiceName(WindowManager.class));
- }
-}
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java b/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java
deleted file mode 100644
index d20fb14..0000000
--- a/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2008 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.graphics;
-
-import junit.framework.TestCase;
-
-/**
- *
- */
-public class Matrix_DelegateTest extends TestCase {
-
- public void testIdentity() {
- Matrix m1 = new Matrix();
-
- assertTrue(m1.isIdentity());
-
- m1.setValues(new float[] { 1,0,0, 0,1,0, 0,0,1 });
- assertTrue(m1.isIdentity());
- }
-
- public void testCopyConstructor() {
- Matrix m1 = new Matrix();
- Matrix m2 = new Matrix(m1);
-
- float[] v1 = new float[9];
- float[] v2 = new float[9];
- m1.getValues(v1);
- m2.getValues(v2);
-
- for (int i = 0 ; i < 9; i++) {
- assertEquals(v1[i], v2[i]);
- }
- }
-}
diff --git a/tools/layoutlib/bridge/tests/src/android/util/BridgeXmlPullAttributesTest.java b/tools/layoutlib/bridge/tests/src/android/util/BridgeXmlPullAttributesTest.java
deleted file mode 100644
index 2fcec8e..0000000
--- a/tools/layoutlib/bridge/tests/src/android/util/BridgeXmlPullAttributesTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.util;
-
-import com.android.ide.common.rendering.api.RenderResources;
-import com.android.layoutlib.bridge.BridgeConstants;
-import com.android.layoutlib.bridge.android.BridgeContext;
-
-import org.junit.Test;
-import org.xmlpull.v1.XmlPullParser;
-
-import com.google.common.collect.ImmutableMap;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class BridgeXmlPullAttributesTest {
- @Test
- public void testGetAttributeIntValueForEnums() {
- RenderResources renderResources = new RenderResources();
-
- XmlPullParser parser = mock(XmlPullParser.class);
- when(parser.getAttributeValue(BridgeConstants.NS_RESOURCES, "layout_width"))
- .thenReturn("match_parent");
- when(parser.getAttributeName(0)).thenReturn("layout_width");
- when(parser.getAttributeNamespace(0)).thenReturn(BridgeConstants.NS_RESOURCES);
- // Return every value twice since there is one test using name and other using index
- when(parser.getAttributeValue("http://custom", "my_custom_attr"))
- .thenReturn("a", "a", "b", "b", "invalid", "invalid");
- when(parser.getAttributeName(1)).thenReturn("my_custom_attr");
- when(parser.getAttributeNamespace(1)).thenReturn("http://custom");
-
- BridgeContext context = mock(BridgeContext.class);
- when(context.getRenderResources()).thenReturn(renderResources);
-
- BridgeXmlPullAttributes attributes = new BridgeXmlPullAttributes(
- parser,
- context,
- false,
- attrName -> {
- if ("layout_width".equals(attrName)) {
- return ImmutableMap.of(
- "match_parent", 123);
- }
- return ImmutableMap.of();
- },
- attrName -> {
- if ("my_custom_attr".equals(attrName)) {
- return ImmutableMap.of(
- "a", 1,
- "b", 2
- );
- }
- return ImmutableMap.of();
- });
-
- // Test a framework defined enum attribute
- assertEquals(123, attributes.getAttributeIntValue(BridgeConstants.NS_RESOURCES,
- "layout_width", 500));
- assertEquals(123, attributes.getAttributeIntValue(0, 500));
- // Test non existing attribute (it should return the default value)
- assertEquals(500, attributes.getAttributeIntValue(BridgeConstants.NS_RESOURCES,
- "layout_height", 500));
- assertEquals(500, attributes.getAttributeIntValue(2, 500));
-
- // Test project defined enum attribute
- assertEquals(1, attributes.getAttributeIntValue("http://custom",
- "my_custom_attr", 500));
- assertEquals(1, attributes.getAttributeIntValue(1, 500));
- assertEquals(2, attributes.getAttributeIntValue("http://custom",
- "my_custom_attr", 500));
- assertEquals(2, attributes.getAttributeIntValue(1, 500));
- // Test an invalid enum
- boolean exception = false;
- try {
- attributes.getAttributeIntValue("http://custom", "my_custom_attr", 500);
- } catch(NumberFormatException e) {
- exception = true;
- }
- assertTrue(exception);
- exception = false;
- try {
- attributes.getAttributeIntValue(1, 500);
- } catch(NumberFormatException e) {
- exception = true;
- }
- assertTrue(exception);
-
- // Test non existing project attribute
- assertEquals(500, attributes.getAttributeIntValue("http://custom",
- "my_other_attr", 500));
- }
-
-}
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/BridgeRenderSessionTest.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/BridgeRenderSessionTest.java
deleted file mode 100644
index 63b9b43..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/BridgeRenderSessionTest.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge;
-
-import com.android.ide.common.rendering.api.Result;
-import com.android.ide.common.rendering.api.Result.Status;
-
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-public class BridgeRenderSessionTest {
- @Test
- public void testNullSession() {
- BridgeRenderSession renderSession = new BridgeRenderSession(null, Status.ERROR_UNKNOWN
- .createResult("Test result"));
-
- assertNotNull(renderSession.getImage());
- assertNotNull(renderSession.getRootViews());
- assertNotNull(renderSession.getSystemRootViews());
- assertNotNull(renderSession.getDefaultProperties());
- }
-}
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java
deleted file mode 100644
index d8937f4..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2010 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.layoutlib.bridge;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-import com.android.tools.layoutlib.create.CreateInfo;
-
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.List;
-
-import junit.framework.TestCase;
-
-/**
- * Tests that native delegate classes implement all the required methods.
- *
- * This looks at {@link CreateInfo#DELEGATE_CLASS_NATIVES} to get the list of classes that
- * have their native methods reimplemented through a delegate.
- *
- * Since the reimplemented methods are not native anymore, we look for the annotation
- * {@link LayoutlibDelegate}, and look for a matching method in the delegate (named the same
- * as the modified class with _Delegate added as a suffix).
- * If the original native method is not static, then we make sure the delegate method also
- * include the original class as first parameter (to access "this").
- *
- */
-public class TestDelegates extends TestCase {
-
- private List<String> mErrors = new ArrayList<String>();
-
- public void testNativeDelegates() {
-
- final String[] classes = CreateInfo.DELEGATE_CLASS_NATIVES;
- mErrors.clear();
- for (String clazz : classes) {
- loadAndCompareClasses(clazz, clazz + "_Delegate");
- }
- assertTrue(getErrors(), mErrors.isEmpty());
- }
-
- public void testMethodDelegates() {
- final String[] methods = CreateInfo.DELEGATE_METHODS;
- mErrors.clear();
- for (String methodName : methods) {
- // extract the class name
- String className = methodName.substring(0, methodName.indexOf('#'));
- String targetClassName = className.replace('$', '_') + "_Delegate";
-
- loadAndCompareClasses(className, targetClassName);
- }
- assertTrue(getErrors(), mErrors.isEmpty());
- }
-
- private void loadAndCompareClasses(String originalClassName, String delegateClassName) {
- // load the classes
- try {
- ClassLoader classLoader = TestDelegates.class.getClassLoader();
- Class<?> originalClass = classLoader.loadClass(originalClassName);
- Class<?> delegateClass = classLoader.loadClass(delegateClassName);
-
- compare(originalClass, delegateClass);
- } catch (ClassNotFoundException e) {
- mErrors.add("Failed to load class: " + e.getMessage());
- } catch (SecurityException e) {
- mErrors.add("Failed to load class: " + e.getMessage());
- }
- }
-
- private void compare(Class<?> originalClass, Class<?> delegateClass) throws SecurityException {
- List<Method> checkedDelegateMethods = new ArrayList<Method>();
-
- // loop on the methods of the original class, and for the ones that are annotated
- // with @LayoutlibDelegate, look for a matching method in the delegate class.
- // The annotation is automatically added by layoutlib_create when it replace a method
- // by a call to a delegate
- Method[] originalMethods = originalClass.getDeclaredMethods();
- for (Method originalMethod : originalMethods) {
- // look for methods that are delegated: they have the LayoutlibDelegate annotation
- if (originalMethod.getAnnotation(LayoutlibDelegate.class) == null) {
- continue;
- }
-
- // get the signature.
- Class<?>[] parameters = originalMethod.getParameterTypes();
-
- // if the method is not static, then the class is added as the first parameter
- // (for "this")
- if ((originalMethod.getModifiers() & Modifier.STATIC) == 0) {
-
- Class<?>[] newParameters = new Class<?>[parameters.length + 1];
- newParameters[0] = originalClass;
- System.arraycopy(parameters, 0, newParameters, 1, parameters.length);
- parameters = newParameters;
- }
-
- // if the original class is an inner class that's not static, then
- // we add this on the enclosing class at the beginning
- if (originalClass.getEnclosingClass() != null &&
- (originalClass.getModifiers() & Modifier.STATIC) == 0) {
- Class<?>[] newParameters = new Class<?>[parameters.length + 1];
- newParameters[0] = originalClass.getEnclosingClass();
- System.arraycopy(parameters, 0, newParameters, 1, parameters.length);
- parameters = newParameters;
- }
-
- try {
- // try to load the method with the given parameter types.
- Method delegateMethod = delegateClass.getDeclaredMethod(originalMethod.getName(),
- parameters);
-
- // check the return type of the methods match.
- if (delegateMethod.getReturnType() != originalMethod.getReturnType()) {
- mErrors.add(
- String.format("Delegate method %1$s.%2$s does not match the " +
- "corresponding framework method which returns %3$s",
- delegateClass.getName(),
- getMethodName(delegateMethod),
- originalMethod.getReturnType().getName()));
- }
-
- // check that the method has the annotation
- if (delegateMethod.getAnnotation(LayoutlibDelegate.class) == null) {
- mErrors.add(
- String.format("Delegate method %1$s for class %2$s does not have the " +
- "@LayoutlibDelegate annotation",
- delegateMethod.getName(),
- originalClass.getName()));
- }
-
- // check that the method is static
- if ((delegateMethod.getModifiers() & Modifier.STATIC) != Modifier.STATIC) {
- mErrors.add(
- String.format(
- "Delegate method %1$s for class %2$s is not static",
- delegateMethod.getName(),
- originalClass.getName())
- );
- }
-
- // add the method as checked.
- checkedDelegateMethods.add(delegateMethod);
- } catch (NoSuchMethodException e) {
- String name = getMethodName(originalMethod, parameters);
- mErrors.add(String.format("Missing %1$s.%2$s", delegateClass.getName(), name));
- }
- }
-
- // look for dead (delegate) code.
- // This looks for all methods in the delegate class, and if they have the
- // @LayoutlibDelegate annotation, make sure they have been previously found as a
- // match for a method in the original class.
- // If not, this means the method is a delegate for a method that either doesn't exist
- // anymore or is not delegated anymore.
- Method[] delegateMethods = delegateClass.getDeclaredMethods();
- for (Method delegateMethod : delegateMethods) {
- // look for methods that are delegates: they have the LayoutlibDelegate annotation
- if (delegateMethod.getAnnotation(LayoutlibDelegate.class) == null) {
- continue;
- }
-
- if (!checkedDelegateMethods.contains(delegateMethod)) {
- mErrors.add(String.format(
- "Delegate method %1$s.%2$s is not used anymore and must be removed",
- delegateClass.getName(),
- getMethodName(delegateMethod)));
- }
- }
-
- }
-
- private String getMethodName(Method method) {
- return getMethodName(method, method.getParameterTypes());
- }
-
- private String getMethodName(Method method, Class<?>[] parameters) {
- // compute a full class name that's long but not too long.
- StringBuilder sb = new StringBuilder(method.getName() + "(");
- for (int j = 0; j < parameters.length; j++) {
- Class<?> theClass = parameters[j];
- int dimensions = 0;
- while (theClass.isArray()) {
- dimensions++;
- theClass = theClass.getComponentType();
- }
- sb.append(theClass.getName());
- for (int i = 0; i < dimensions; i++) {
- sb.append("[]");
- }
- if (j < (parameters.length - 1)) {
- sb.append(",");
- }
- }
- sb.append(")");
-
- return sb.toString();
- }
-
- private String getErrors() {
- StringBuilder s = new StringBuilder();
- for (String error : mErrors) {
- s.append(error).append('\n');
- }
- return s.toString();
- }
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java
deleted file mode 100644
index 77c997b..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2008 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.layoutlib.bridge.android;
-
-import com.android.layoutlib.bridge.impl.ParserFactory;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.kxml2.io.KXmlParser;
-import org.w3c.dom.Node;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.annotation.NonNull;
-
-import static org.junit.Assert.assertEquals;
-
-public class BridgeXmlBlockParserTest {
-
- @BeforeClass
- public static void setUp() {
- ParserFactory.setParserFactory(new ParserFactoryImpl());
- }
-
- @Test
- public void testXmlBlockParser() throws Exception {
-
- XmlPullParser parser = ParserFactory.create(
- getClass().getResourceAsStream("/com/android/layoutlib/testdata/layout1.xml"),
- "layout1.xml");
-
- parser = new BridgeXmlBlockParser(parser, null, false /* platformResourceFlag */);
-
- assertEquals(XmlPullParser.START_DOCUMENT, parser.next());
-
- assertEquals(XmlPullParser.START_TAG, parser.next());
- assertEquals("LinearLayout", parser.getName());
-
- assertEquals(XmlPullParser.TEXT, parser.next());
-
- assertEquals(XmlPullParser.START_TAG, parser.next());
- assertEquals("Button", parser.getName());
- assertEquals(XmlPullParser.TEXT, parser.next());
- assertEquals(XmlPullParser.END_TAG, parser.next());
-
- assertEquals(XmlPullParser.TEXT, parser.next());
-
- assertEquals(XmlPullParser.START_TAG, parser.next());
- assertEquals("View", parser.getName());
- assertEquals(XmlPullParser.END_TAG, parser.next());
-
- assertEquals(XmlPullParser.TEXT, parser.next());
-
- assertEquals(XmlPullParser.START_TAG, parser.next());
- assertEquals("TextView", parser.getName());
- assertEquals(XmlPullParser.END_TAG, parser.next());
-
- assertEquals(XmlPullParser.TEXT, parser.next());
-
- assertEquals(XmlPullParser.END_TAG, parser.next());
- assertEquals(XmlPullParser.END_DOCUMENT, parser.next());
- }
-
- //------------
-
- /**
- * Quick 'n' dirty debug helper that dumps an XML structure to stdout.
- */
- @SuppressWarnings("unused")
- private void dump(Node node, String prefix) {
- Node n;
-
- String[] types = {
- "unknown",
- "ELEMENT_NODE",
- "ATTRIBUTE_NODE",
- "TEXT_NODE",
- "CDATA_SECTION_NODE",
- "ENTITY_REFERENCE_NODE",
- "ENTITY_NODE",
- "PROCESSING_INSTRUCTION_NODE",
- "COMMENT_NODE",
- "DOCUMENT_NODE",
- "DOCUMENT_TYPE_NODE",
- "DOCUMENT_FRAGMENT_NODE",
- "NOTATION_NODE"
- };
-
- String s = String.format("%s<%s> %s %s",
- prefix,
- types[node.getNodeType()],
- node.getNodeName(),
- node.getNodeValue() == null ? "" : node.getNodeValue().trim());
-
- System.out.println(s);
-
- n = node.getFirstChild();
- if (n != null) {
- dump(n, prefix + "- ");
- }
-
- n = node.getNextSibling();
- if (n != null) {
- dump(n, prefix);
- }
- }
-
- @AfterClass
- public static void tearDown() {
- ParserFactory.setParserFactory(null);
- }
-
- private static class ParserFactoryImpl
- extends com.android.ide.common.rendering.api.ParserFactory {
-
- @NonNull
- @Override
- public XmlPullParser createParser(String displayName) throws XmlPullParserException {
- return new KXmlParser();
- }
- }
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/impl/LayoutParserWrapperTest.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/impl/LayoutParserWrapperTest.java
deleted file mode 100644
index 2c33862..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/impl/LayoutParserWrapperTest.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2015 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.layoutlib.bridge.impl;
-
-import org.junit.Test;
-import org.kxml2.io.KXmlParser;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.StringReader;
-
-import static com.android.SdkConstants.NS_RESOURCES;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotSame;
-import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
-import static org.xmlpull.v1.XmlPullParser.END_TAG;
-import static org.xmlpull.v1.XmlPullParser.START_TAG;
-
-
-public class LayoutParserWrapperTest {
- @Test
- @SuppressWarnings("StatementWithEmptyBody") // some for loops need to be empty statements.
- public void testDataBindingLayout() throws Exception {
- LayoutParserWrapper parser = getParserFromString(sDataBindingLayout);
- parser.peekTillLayoutStart();
- assertEquals("Expected START_TAG", START_TAG, parser.next());
- assertEquals("RelativeLayout", parser.getName());
- for (int next = parser.next(); next != START_TAG && next != END_DOCUMENT;
- next = parser.next());
- assertEquals("Expected START_TAG", START_TAG, parser.getEventType());
- assertEquals("TextView", parser.getName());
- assertEquals("layout_width incorrect for first text view.", "wrap_content",
- parser.getAttributeValue(NS_RESOURCES, "layout_width"));
- // Ensure that data-binding part is stripped.
- assertEquals("Bound attribute android:text incorrect", "World",
- parser.getAttributeValue(NS_RESOURCES, "text"));
- assertEquals("resource attribute 'id' for first text view incorrect.", "@+id/first",
- parser.getAttributeValue(NS_RESOURCES, "id"));
- for (int next = parser.next();
- (next != END_TAG || !"RelativeLayout".equals(parser.getName())) && next != END_DOCUMENT;
- next = parser.next());
- assertNotSame("Unexpected end of document", END_DOCUMENT, parser.getEventType());
- assertEquals("Document didn't end when expected.", END_DOCUMENT, parser.next());
- }
-
- @Test
- @SuppressWarnings("StatementWithEmptyBody")
- public void testNonDataBindingLayout() throws Exception {
- LayoutParserWrapper parser = getParserFromString(sNonDataBindingLayout);
- parser.peekTillLayoutStart();
- assertEquals("Expected START_TAG", START_TAG, parser.next());
- assertEquals("RelativeLayout", parser.getName());
- for (int next = parser.next(); next != START_TAG && next != END_DOCUMENT;
- next = parser.next());
- assertEquals("Expected START_TAG", START_TAG, parser.getEventType());
- assertEquals("TextView", parser.getName());
- assertEquals("layout_width incorrect for first text view.", "wrap_content",
- parser.getAttributeValue(NS_RESOURCES, "layout_width"));
- // Ensure that value isn't modified.
- assertEquals("Bound attribute android:text incorrect", "@{user.firstName,default=World}",
- parser.getAttributeValue(NS_RESOURCES, "text"));
- assertEquals("resource attribute 'id' for first text view incorrect.", "@+id/first",
- parser.getAttributeValue(NS_RESOURCES, "id"));
- for (int next = parser.next();
- (next != END_TAG || !"RelativeLayout".equals(parser.getName())) && next != END_DOCUMENT;
- next = parser.next());
- assertNotSame("Unexpected end of document", END_DOCUMENT, parser.getEventType());
- assertEquals("Document didn't end when expected.", END_DOCUMENT, parser.next());
- }
-
- private static LayoutParserWrapper getParserFromString(String layoutContent) throws
- XmlPullParserException {
- XmlPullParser parser = new KXmlParser();
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
- parser.setInput(new StringReader(layoutContent));
- return new LayoutParserWrapper(parser);
- }
-
- private static final String sDataBindingLayout =
- //language=XML
- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
- "<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
- " xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n" +
- " xmlns:tools=\"http://schemas.android.com/tools\"\n" +
- " tools:context=\".MainActivity\"\n" +
- " tools:showIn=\"@layout/activity_main\">\n" +
- "\n" +
- " <data>\n" +
- "\n" +
- " <variable\n" +
- " name=\"user\"\n" +
- " type=\"com.example.User\" />\n" +
- " <variable\n" +
- " name=\"activity\"\n" +
- " type=\"com.example.MainActivity\" />\n" +
- " </data>\n" +
- "\n" +
- " <RelativeLayout\n" +
- " android:layout_width=\"match_parent\"\n" +
- " android:layout_height=\"match_parent\"\n" +
- " android:paddingBottom=\"@dimen/activity_vertical_margin\"\n" +
- " android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n" +
- " android:paddingRight=\"@dimen/activity_horizontal_margin\"\n" +
- " android:paddingTop=\"@dimen/activity_vertical_margin\"\n" +
- " app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n" +
- " >\n" +
- "\n" +
- " <TextView\n" +
- " android:id=\"@+id/first\"\n" +
- " android:layout_width=\"wrap_content\"\n" +
- " android:layout_alignParentStart=\"true\"\n" +
- " android:layout_alignParentLeft=\"true\"\n" +
- " android:layout_height=\"wrap_content\"\n" +
- " android:text=\"@{user.firstName,default=World}\" />\n" +
- "\n" +
- " <TextView\n" +
- " android:id=\"@+id/last\"\n" +
- " android:layout_width=\"wrap_content\"\n" +
- " android:layout_height=\"wrap_content\"\n" +
- " android:layout_toEndOf=\"@id/first\"\n" +
- " android:layout_toRightOf=\"@id/first\"\n" +
- " android:text=\"@{user.lastName,default=Hello}\" />\n" +
- "\n" +
- " <Button\n" +
- " android:layout_width=\"wrap_content\"\n" +
- " android:layout_height=\"wrap_content\"\n" +
- " android:layout_below=\"@id/last\"\n" +
- " android:text=\"Submit\"\n" +
- " android:onClick=\"@{activity.onClick}\"/>\n" +
- " </RelativeLayout>\n" +
- "</layout>";
-
- private static final String sNonDataBindingLayout =
- //language=XML
- "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
- " xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n" +
- " android:layout_width=\"match_parent\"\n" +
- " android:layout_height=\"match_parent\"\n" +
- " android:paddingBottom=\"@dimen/activity_vertical_margin\"\n" +
- " android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n" +
- " android:paddingRight=\"@dimen/activity_horizontal_margin\"\n" +
- " android:paddingTop=\"@dimen/activity_vertical_margin\"\n" +
- " app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n" +
- ">\n" +
- "\n" +
- " <TextView\n" +
- " android:id=\"@+id/first\"\n" +
- " android:layout_width=\"wrap_content\"\n" +
- " android:layout_alignParentStart=\"true\"\n" +
- " android:layout_alignParentLeft=\"true\"\n" +
- " android:layout_height=\"wrap_content\"\n" +
- " android:text=\"@{user.firstName,default=World}\" />\n" +
- "\n" +
- " <TextView\n" +
- " android:id=\"@+id/last\"\n" +
- " android:layout_width=\"wrap_content\"\n" +
- " android:layout_height=\"wrap_content\"\n" +
- " android:layout_toEndOf=\"@id/first\"\n" +
- " android:layout_toRightOf=\"@id/first\"\n" +
- " android:text=\"@{user.lastName,default=Hello}\" />\n" +
- "\n" +
- " <Button\n" +
- " android:layout_width=\"wrap_content\"\n" +
- " android:layout_height=\"wrap_content\"\n" +
- " android:layout_below=\"@id/last\"\n" +
- " android:text=\"Submit\"\n" +
- " android:onClick=\"@{activity.onClick}\"/>\n" +
- "</RelativeLayout>";
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
deleted file mode 100644
index eb264d65..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.intensive;
-
-import com.android.layoutlib.bridge.BridgeRenderSessionTest;
-import com.android.layoutlib.bridge.TestDelegates;
-import com.android.layoutlib.bridge.android.BridgeXmlBlockParserTest;
-import com.android.layoutlib.bridge.impl.LayoutParserWrapperTest;
-
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
-import org.junit.runners.Suite.SuiteClasses;
-
-import android.graphics.Matrix_DelegateTest;
-import android.util.BridgeXmlPullAttributesTest;
-
-/**
- * Suite used by the layoutlib build system
- */
-@RunWith(Suite.class)
-@SuiteClasses({
- RenderTests.class, LayoutParserWrapperTest.class,
- BridgeXmlBlockParserTest.class, BridgeXmlPullAttributesTest.class,
- Matrix_DelegateTest.class, TestDelegates.class, PerformanceTests.class,
- BridgeRenderSessionTest.class
-})
-public class Main {
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/PerformanceTests.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/PerformanceTests.java
deleted file mode 100644
index c90c26a..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/PerformanceTests.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.intensive;
-
-import com.android.ide.common.rendering.api.SessionParams;
-import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
-import com.android.layoutlib.bridge.intensive.util.perf.PerformanceRunner;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import android.annotation.NonNull;
-
-/**
- * Set of render tests
- */
-@RunWith(PerformanceRunner.class)
-public class PerformanceTests extends RenderTestBase {
-
- @Before
- public void setUp() {
- ignoreAllLogging();
- }
-
-
- private void render(@NonNull String layoutFileName) throws ClassNotFoundException {
- SessionParams params = createSessionParams(layoutFileName, ConfigGenerator.NEXUS_5);
- render(params, 250);
- }
-
- @Test
- public void testActivity() throws ClassNotFoundException {
- render("activity.xml");
- }
-
- @Test
- public void testAllWidgets() throws ClassNotFoundException {
- render("allwidgets.xml");
- }
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java
deleted file mode 100644
index 087478f..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.intensive;
-
-import com.android.ide.common.rendering.api.RenderSession;
-import com.android.ide.common.rendering.api.Result;
-import com.android.ide.common.rendering.api.ViewInfo;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.awt.image.BufferedImage;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-class RenderResult {
- private final List<ViewInfo> mRootViews;
- private final List<ViewInfo> mSystemViews;
- private final Result mRenderResult;
- private BufferedImage mImage;
-
- private RenderResult(@Nullable Result result, @Nullable List<ViewInfo> systemViewInfoList,
- @Nullable List<ViewInfo> rootViewInfoList, @Nullable BufferedImage image) {
- mSystemViews = systemViewInfoList == null ? Collections.emptyList() : systemViewInfoList;
- mRootViews = rootViewInfoList == null ? Collections.emptyList() : rootViewInfoList;
- mRenderResult = result;
- mImage = image;
- }
-
- @NonNull
- static RenderResult getFromSession(@NonNull RenderSession session) {
- return new RenderResult(session.getResult(),
- new ArrayList<>(session.getSystemRootViews()),
- new ArrayList<>(session.getRootViews()),
- session.getImage());
- }
-
- @Nullable
- Result getResult() {
- return mRenderResult;
- }
-
- @NonNull
- public List<ViewInfo> getRootViews() {
- return mRootViews;
- }
-
- @NonNull
- public List<ViewInfo> getSystemViews() {
- return mSystemViews;
- }
-
- @Nullable
- public BufferedImage getImage() {
- return mImage;
- }
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
deleted file mode 100644
index 8739b7f..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
+++ /dev/null
@@ -1,545 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.intensive;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.RenderSession;
-import com.android.ide.common.rendering.api.Result;
-import com.android.ide.common.rendering.api.SessionParams;
-import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
-import com.android.ide.common.resources.FrameworkResources;
-import com.android.ide.common.resources.ResourceItem;
-import com.android.ide.common.resources.ResourceRepository;
-import com.android.ide.common.resources.ResourceResolver;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
-import com.android.io.FolderWrapper;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.android.RenderParamsFlags;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
-import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback;
-import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
-import com.android.layoutlib.bridge.intensive.util.ImageUtils;
-import com.android.layoutlib.bridge.intensive.util.ModuleClassLoader;
-import com.android.layoutlib.bridge.intensive.util.TestAssetRepository;
-import com.android.layoutlib.bridge.intensive.util.TestUtils;
-import com.android.tools.layoutlib.java.System_Delegate;
-import com.android.utils.ILogger;
-
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
-
-import com.google.android.collect.Lists;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-
-/**
- * Base class for render tests. The render tests load all the framework resources and a project
- * checked in this test's resources. The main dependencies
- * are:
- * 1. Fonts directory.
- * 2. Framework Resources.
- * 3. App resources.
- * 4. build.prop file
- * <p>
- * These are configured by two variables set in the system properties.
- * <p>
- * 1. platform.dir: This is the directory for the current platform in the built SDK
- * (.../sdk/platforms/android-<version>).
- * <p>
- * The fonts are platform.dir/data/fonts.
- * The Framework resources are platform.dir/data/res.
- * build.prop is at platform.dir/build.prop.
- * <p>
- * 2. test_res.dir: This is the directory for the resources of the test. If not specified, this
- * falls back to getClass().getProtectionDomain().getCodeSource().getLocation()
- * <p>
- * The app resources are at: test_res.dir/testApp/MyApplication/app/src/main/res
- */
-public class RenderTestBase {
-
- private static final String PLATFORM_DIR_PROPERTY = "platform.dir";
- private static final String RESOURCE_DIR_PROPERTY = "test_res.dir";
-
- private static final String PLATFORM_DIR;
- private static final String TEST_RES_DIR;
- /** Location of the app to test inside {@link #TEST_RES_DIR} */
- private static final String APP_TEST_DIR = "testApp/MyApplication";
- /** Location of the app's res dir inside {@link #TEST_RES_DIR} */
- private static final String APP_TEST_RES = APP_TEST_DIR + "/src/main/res";
- private static final String APP_CLASSES_LOCATION =
- APP_TEST_DIR + "/build/intermediates/classes/debug/";
- protected static Bridge sBridge;
- /** List of log messages generated by a render call. It can be used to find specific errors */
- protected static ArrayList<String> sRenderMessages = Lists.newArrayList();
- private static LayoutLog sLayoutLibLog;
- private static FrameworkResources sFrameworkRepo;
- private static ResourceRepository sProjectResources;
- private static ILogger sLogger;
-
- static {
- // Test that System Properties are properly set.
- PLATFORM_DIR = getPlatformDir();
- if (PLATFORM_DIR == null) {
- fail(String.format("System Property %1$s not properly set. The value is %2$s",
- PLATFORM_DIR_PROPERTY, System.getProperty(PLATFORM_DIR_PROPERTY)));
- }
-
- TEST_RES_DIR = getTestResDir();
- if (TEST_RES_DIR == null) {
- fail(String.format("System property %1$s.dir not properly set. The value is %2$s",
- RESOURCE_DIR_PROPERTY, System.getProperty(RESOURCE_DIR_PROPERTY)));
- }
- }
-
- @Rule
- public TestWatcher sRenderMessageWatcher = new TestWatcher() {
- @Override
- protected void succeeded(Description description) {
- // We only check error messages if the rest of the test case was successful.
- if (!sRenderMessages.isEmpty()) {
- fail(description.getMethodName() + " render error message: " +
- sRenderMessages.get(0));
- }
- }
- };
- // Default class loader with access to the app classes
- protected ClassLoader mDefaultClassLoader =
- new ModuleClassLoader(APP_CLASSES_LOCATION, getClass().getClassLoader());
-
- private static String getPlatformDir() {
- String platformDir = System.getProperty(PLATFORM_DIR_PROPERTY);
- if (platformDir != null && !platformDir.isEmpty() && new File(platformDir).isDirectory()) {
- return platformDir;
- }
- // System Property not set. Try to find the directory in the build directory.
- String androidHostOut = System.getenv("ANDROID_HOST_OUT");
- if (androidHostOut != null) {
- platformDir = getPlatformDirFromHostOut(new File(androidHostOut));
- if (platformDir != null) {
- return platformDir;
- }
- }
- String workingDirString = System.getProperty("user.dir");
- File workingDir = new File(workingDirString);
- // Test if workingDir is android checkout root.
- platformDir = getPlatformDirFromRoot(workingDir);
- if (platformDir != null) {
- return platformDir;
- }
-
- // Test if workingDir is platform/frameworks/base/tools/layoutlib/bridge.
- File currentDir = workingDir;
- if (currentDir.getName().equalsIgnoreCase("bridge")) {
- currentDir = currentDir.getParentFile();
- }
- // Test if currentDir is platform/frameworks/base/tools/layoutlib. That is, root should be
- // workingDir/../../../../ (4 levels up)
- for (int i = 0; i < 4; i++) {
- if (currentDir != null) {
- currentDir = currentDir.getParentFile();
- }
- }
- return currentDir == null ? null : getPlatformDirFromRoot(currentDir);
- }
-
- private static String getPlatformDirFromRoot(File root) {
- if (!root.isDirectory()) {
- return null;
- }
- File out = new File(root, "out");
- if (!out.isDirectory()) {
- return null;
- }
- File host = new File(out, "host");
- if (!host.isDirectory()) {
- return null;
- }
- File[] hosts = host.listFiles(path -> path.isDirectory() &&
- (path.getName().startsWith("linux-") || path.getName().startsWith("darwin-")));
- assert hosts != null;
- for (File hostOut : hosts) {
- String platformDir = getPlatformDirFromHostOut(hostOut);
- if (platformDir != null) {
- return platformDir;
- }
- }
- return null;
- }
-
- private static String getPlatformDirFromHostOut(File out) {
- if (!out.isDirectory()) {
- return null;
- }
- File sdkDir = new File(out, "sdk");
- if (!sdkDir.isDirectory()) {
- return null;
- }
- File[] sdkDirs = sdkDir.listFiles(path -> {
- // We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7)
- return path.isDirectory() && path.getName().startsWith("sdk");
- });
- assert sdkDirs != null;
- for (File dir : sdkDirs) {
- String platformDir = getPlatformDirFromHostOutSdkSdk(dir);
- if (platformDir != null) {
- return platformDir;
- }
- }
- return null;
- }
-
- private static String getPlatformDirFromHostOutSdkSdk(File sdkDir) {
- File[] possibleSdks = sdkDir.listFiles(
- path -> path.isDirectory() && path.getName().contains("android-sdk"));
- assert possibleSdks != null;
- for (File possibleSdk : possibleSdks) {
- File platformsDir = new File(possibleSdk, "platforms");
- File[] platforms = platformsDir.listFiles(
- path -> path.isDirectory() && path.getName().startsWith("android-"));
- if (platforms == null || platforms.length == 0) {
- continue;
- }
- Arrays.sort(platforms, (o1, o2) -> {
- final int MAX_VALUE = 1000;
- String suffix1 = o1.getName().substring("android-".length());
- String suffix2 = o2.getName().substring("android-".length());
- int suff1, suff2;
- try {
- suff1 = Integer.parseInt(suffix1);
- } catch (NumberFormatException e) {
- suff1 = MAX_VALUE;
- }
- try {
- suff2 = Integer.parseInt(suffix2);
- } catch (NumberFormatException e) {
- suff2 = MAX_VALUE;
- }
- if (suff1 != MAX_VALUE || suff2 != MAX_VALUE) {
- return suff2 - suff1;
- }
- return suffix2.compareTo(suffix1);
- });
- return platforms[0].getAbsolutePath();
- }
- return null;
- }
-
- private static String getTestResDir() {
- String resourceDir = System.getProperty(RESOURCE_DIR_PROPERTY);
- if (resourceDir != null && !resourceDir.isEmpty() && new File(resourceDir).isDirectory()) {
- return resourceDir;
- }
- // TEST_RES_DIR not explicitly set. Fallback to the class's source location.
- try {
- URL location = RenderTestBase.class.getProtectionDomain().getCodeSource().getLocation();
- return new File(location.getPath()).exists() ? location.getPath() : null;
- } catch (NullPointerException e) {
- // Prevent a lot of null checks by just catching the exception.
- return null;
- }
- }
-
- /**
- * Initialize the bridge and the resource maps.
- */
- @BeforeClass
- public static void beforeClass() {
- File data_dir = new File(PLATFORM_DIR, "data");
- File res = new File(data_dir, "res");
- sFrameworkRepo = new FrameworkResources(new FolderWrapper(res));
- sFrameworkRepo.loadResources();
- sFrameworkRepo.loadPublicResources(getLogger());
-
- sProjectResources =
- new ResourceRepository(new FolderWrapper(TEST_RES_DIR + "/" + APP_TEST_RES),
- false) {
- @NonNull
- @Override
- protected ResourceItem createResourceItem(@NonNull String name) {
- return new ResourceItem(name);
- }
- };
- sProjectResources.loadResources();
-
- File fontLocation = new File(data_dir, "fonts");
- File buildProp = new File(PLATFORM_DIR, "build.prop");
- File attrs = new File(res, "values" + File.separator + "attrs.xml");
- sBridge = new Bridge();
- sBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation,
- ConfigGenerator.getEnumMap(attrs), getLayoutLog());
- Bridge.getLock().lock();
- try {
- Bridge.setLog(getLayoutLog());
- } finally {
- Bridge.getLock().unlock();
- }
- }
-
- @AfterClass
- public static void tearDown() {
- sLayoutLibLog = null;
- sFrameworkRepo = null;
- sProjectResources = null;
- sLogger = null;
- sBridge = null;
-
- TestUtils.gc();
-
- System.out.println("Objects still linked from the DelegateManager:");
- DelegateManager.dump(System.out);
- }
-
- @NonNull
- protected static RenderResult render(SessionParams params, long frameTimeNanos) {
- // TODO: Set up action bar handler properly to test menu rendering.
- // Create session params.
- System_Delegate.setBootTimeNanos(TimeUnit.MILLISECONDS.toNanos(871732800000L));
- System_Delegate.setNanosTime(TimeUnit.MILLISECONDS.toNanos(871732800000L));
- RenderSession session = sBridge.createSession(params);
-
- try {
- if (frameTimeNanos != -1) {
- session.setElapsedFrameTimeNanos(frameTimeNanos);
- }
-
- if (!session.getResult().isSuccess()) {
- getLogger().error(session.getResult().getException(),
- session.getResult().getErrorMessage());
- }
- else {
- // Render the session with a timeout of 50s.
- Result renderResult = session.render(50000);
- if (!renderResult.isSuccess()) {
- getLogger().error(session.getResult().getException(),
- session.getResult().getErrorMessage());
- }
- }
-
- return RenderResult.getFromSession(session);
- } finally {
- session.dispose();
- }
- }
-
- /**
- * Create a new rendering session and test that rendering the given layout doesn't throw any
- * exceptions and matches the provided image.
- * <p>
- * If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time indicates
- * how far in the future is.
- */
- @Nullable
- protected static RenderResult renderAndVerify(SessionParams params, String goldenFileName,
- long frameTimeNanos) throws ClassNotFoundException {
- RenderResult result = RenderTestBase.render(params, frameTimeNanos);
- try {
- String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenFileName;
- assertNotNull(result.getImage());
- ImageUtils.requireSimilar(goldenImagePath, result.getImage());
- } catch (IOException e) {
- getLogger().error(e, e.getMessage());
- }
-
- return result;
- }
-
- /**
- * Create a new rendering session and test that rendering the given layout doesn't throw any
- * exceptions and matches the provided image.
- */
- @Nullable
- protected static RenderResult renderAndVerify(SessionParams params, String goldenFileName)
- throws ClassNotFoundException {
- return RenderTestBase.renderAndVerify(params, goldenFileName, -1);
- }
-
- private static LayoutLog getLayoutLog() {
- if (sLayoutLibLog == null) {
- sLayoutLibLog = new LayoutLog() {
- @Override
- public void warning(String tag, String message, Object data) {
- System.out.println("Warning " + tag + ": " + message);
- failWithMsg(message);
- }
-
- @Override
- public void fidelityWarning(@Nullable String tag, String message,
- Throwable throwable, Object data) {
-
- System.out.println("FidelityWarning " + tag + ": " + message);
- if (throwable != null) {
- throwable.printStackTrace();
- }
- failWithMsg(message == null ? "" : message);
- }
-
- @Override
- public void error(String tag, String message, Object data) {
- System.out.println("Error " + tag + ": " + message);
- failWithMsg(message);
- }
-
- @Override
- public void error(String tag, String message, Throwable throwable, Object data) {
- System.out.println("Error " + tag + ": " + message);
- if (throwable != null) {
- throwable.printStackTrace();
- }
- failWithMsg(message);
- }
- };
- }
- return sLayoutLibLog;
- }
-
- protected static void ignoreAllLogging() {
- sLayoutLibLog = new LayoutLog();
- sLogger = new ILogger() {
- @Override
- public void error(Throwable t, String msgFormat, Object... args) {
- }
-
- @Override
- public void warning(String msgFormat, Object... args) {
- }
-
- @Override
- public void info(String msgFormat, Object... args) {
- }
-
- @Override
- public void verbose(String msgFormat, Object... args) {
- }
- };
- }
-
- protected static ILogger getLogger() {
- if (sLogger == null) {
- sLogger = new ILogger() {
- @Override
- public void error(Throwable t, @Nullable String msgFormat, Object... args) {
- if (t != null) {
- t.printStackTrace();
- }
- failWithMsg(msgFormat == null ? "" : msgFormat, args);
- }
-
- @Override
- public void warning(@NonNull String msgFormat, Object... args) {
- failWithMsg(msgFormat, args);
- }
-
- @Override
- public void info(@NonNull String msgFormat, Object... args) {
- // pass.
- }
-
- @Override
- public void verbose(@NonNull String msgFormat, Object... args) {
- // pass.
- }
- };
- }
- return sLogger;
- }
-
- private static void failWithMsg(@NonNull String msgFormat, Object... args) {
- sRenderMessages.add(args == null ? msgFormat : String.format(msgFormat, args));
- }
-
- @Before
- public void beforeTestCase() {
- sRenderMessages.clear();
- }
-
- @NonNull
- protected LayoutPullParser createLayoutPullParser(String layoutPath) {
- return new LayoutPullParser(APP_TEST_RES + "/layout/" + layoutPath);
- }
-
- /**
- * Create a new rendering session and test that rendering the given layout on nexus 5
- * doesn't throw any exceptions and matches the provided image.
- */
- @Nullable
- protected RenderResult renderAndVerify(String layoutFileName, String goldenFileName)
- throws ClassNotFoundException {
- return renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5);
- }
-
- /**
- * Create a new rendering session and test that rendering the given layout on given device
- * doesn't throw any exceptions and matches the provided image.
- */
- @Nullable
- protected RenderResult renderAndVerify(String layoutFileName, String goldenFileName,
- ConfigGenerator deviceConfig) throws ClassNotFoundException {
- SessionParams params = createSessionParams(layoutFileName, deviceConfig);
- return renderAndVerify(params, goldenFileName);
- }
-
- protected SessionParams createSessionParams(String layoutFileName, ConfigGenerator deviceConfig)
- throws ClassNotFoundException {
- // Create the layout pull parser.
- LayoutPullParser parser = createLayoutPullParser(layoutFileName);
- // Create LayoutLibCallback.
- LayoutLibTestCallback layoutLibCallback =
- new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
- layoutLibCallback.initResources();
- // TODO: Set up action bar handler properly to test menu rendering.
- // Create session params.
- return getSessionParams(parser, deviceConfig, layoutLibCallback, "AppTheme", true,
- RenderingMode.NORMAL, 22);
- }
-
- /**
- * Uses Theme.Material and Target sdk version as 22.
- */
- protected SessionParams getSessionParams(LayoutPullParser layoutParser,
- ConfigGenerator configGenerator, LayoutLibTestCallback layoutLibCallback,
- String themeName, boolean isProjectTheme, RenderingMode renderingMode,
- @SuppressWarnings("SameParameterValue") int targetSdk) {
- FolderConfiguration config = configGenerator.getFolderConfig();
- ResourceResolver resourceResolver =
- ResourceResolver.create(sProjectResources.getConfiguredResources(config),
- sFrameworkRepo.getConfiguredResources(config), themeName, isProjectTheme);
-
- SessionParams sessionParams =
- new SessionParams(layoutParser, renderingMode, null /*used for caching*/,
- configGenerator.getHardwareConfig(), resourceResolver, layoutLibCallback, 0,
- targetSdk, getLayoutLog());
- sessionParams.setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true);
- sessionParams.setAssetRepository(new TestAssetRepository());
- return sessionParams;
- }
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
deleted file mode 100644
index 7b565b1..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
+++ /dev/null
@@ -1,449 +0,0 @@
-/*
- * Copyright (C) 2014 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.layoutlib.bridge.intensive;
-
-import com.android.ide.common.rendering.api.RenderSession;
-import com.android.ide.common.rendering.api.SessionParams;
-import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
-import com.android.ide.common.rendering.api.ViewInfo;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.android.RenderParamsFlags;
-import com.android.layoutlib.bridge.impl.RenderAction;
-import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
-import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback;
-import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
-import com.android.resources.Density;
-import com.android.resources.Navigation;
-import com.android.resources.ResourceType;
-
-import org.junit.Test;
-
-import android.content.res.AssetManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
-
-import java.lang.reflect.Field;
-import java.util.concurrent.TimeUnit;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Set of render tests
- */
-public class RenderTests extends RenderTestBase {
-
- @Test
- public void testActivity() throws ClassNotFoundException {
- renderAndVerify("activity.xml", "activity.png");
- }
-
- @Test
- public void testActivityOnOldTheme() throws ClassNotFoundException {
- LayoutLibTestCallback layoutLibCallback =
- new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
- layoutLibCallback.initResources();
-
- LayoutPullParser parser = createLayoutPullParser("simple_activity.xml");
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.NoTitleBar", false,
- RenderingMode.NORMAL, 22);
-
- renderAndVerify(params, "simple_activity-old-theme.png");
- }
-
- @Test
- public void testTranslucentBars() throws ClassNotFoundException {
- LayoutLibTestCallback layoutLibCallback =
- new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
- layoutLibCallback.initResources();
-
- LayoutPullParser parser = createLayoutPullParser("four_corners.xml");
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
- RenderingMode.NORMAL, 22);
- renderAndVerify(params, "four_corners_translucent.png");
-
- parser = createLayoutPullParser("four_corners.xml");
- params = getSessionParams(parser, ConfigGenerator.NEXUS_5_LAND,
- layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
- RenderingMode.NORMAL, 22);
- renderAndVerify(params, "four_corners_translucent_land.png");
-
- parser = createLayoutPullParser("four_corners.xml");
- params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
- RenderingMode.NORMAL, 22);
- renderAndVerify(params, "four_corners.png");
- }
-
- @Test
- public void testAllWidgets() throws ClassNotFoundException {
- renderAndVerify("allwidgets.xml", "allwidgets.png");
-
- // We expect fidelity warnings for Path.isConvex. Fail for anything else.
- sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
- }
-
- @Test
- public void testArrayCheck() throws ClassNotFoundException {
- renderAndVerify("array_check.xml", "array_check.png");
- }
-
- @Test
- public void testAllWidgetsTablet() throws ClassNotFoundException {
- renderAndVerify("allwidgets.xml", "allwidgets_tab.png", ConfigGenerator.NEXUS_7_2012);
-
- // We expect fidelity warnings for Path.isConvex. Fail for anything else.
- sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
- }
-
- @Test
- public void testActivityActionBar() throws ClassNotFoundException {
- LayoutPullParser parser = createLayoutPullParser("simple_activity.xml");
- LayoutLibTestCallback layoutLibCallback =
- new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
- layoutLibCallback.initResources();
-
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
- RenderingMode.V_SCROLL, 22);
-
- renderAndVerify(params, "simple_activity_noactionbar.png");
-
- parser = createLayoutPullParser("simple_activity.xml");
- params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.Light", false,
- RenderingMode.V_SCROLL, 22);
-
- renderAndVerify(params, "simple_activity.png");
-
- // This also tests that a theme with "NoActionBar" DOES HAVE an action bar when we are
- // displaying menus.
- parser = createLayoutPullParser("simple_activity.xml");
- params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
- RenderingMode.V_SCROLL, 22);
- params.setFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG, "menu");
- renderAndVerify(params, "simple_activity.png");
- }
-
- @Test
- public void testOnApplyInsetsCall()
- throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
- // We get the widget via reflection to avoid IntelliJ complaining about the class being
- // located in the wrong package. (From the Bridge tests point of view, it is)
- Class insetsWidgetClass = Class.forName("com.android.layoutlib.test.myapplication.widgets" +
- ".InsetsWidget");
- Field field = insetsWidgetClass.getDeclaredField("sApplyInsetsCalled");
- assertFalse((Boolean)field.get(null));
-
- LayoutPullParser parser = createLayoutPullParser("insets.xml");
- LayoutLibTestCallback layoutLibCallback =
- new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
- layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
- RenderingMode.NORMAL, 22);
-
- render(params, -1);
-
- assertTrue((Boolean)field.get(null));
- field.set(null, false);
- }
-
- /** Test expand_layout.xml */
- @Test
- public void testExpand() throws ClassNotFoundException {
- // Create the layout pull parser.
- LayoutPullParser parser = createLayoutPullParser("expand_vert_layout.xml");
- // Create LayoutLibCallback.
- LayoutLibTestCallback layoutLibCallback =
- new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
- layoutLibCallback.initResources();
-
- ConfigGenerator customConfigGenerator = new ConfigGenerator()
- .setScreenWidth(300)
- .setScreenHeight(20)
- .setDensity(Density.XHIGH)
- .setNavigation(Navigation.NONAV);
-
- SessionParams params = getSessionParams(parser, customConfigGenerator,
- layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
-
- renderAndVerify(params, "expand_vert_layout.png");
-
- customConfigGenerator = new ConfigGenerator()
- .setScreenWidth(20)
- .setScreenHeight(300)
- .setDensity(Density.XHIGH)
- .setNavigation(Navigation.NONAV);
- parser = createLayoutPullParser("expand_horz_layout.xml");
- params = getSessionParams(parser, customConfigGenerator,
- layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
- RenderingMode.H_SCROLL, 22);
-
- renderAndVerify(params, "expand_horz_layout.png");
- }
-
- /** Test indeterminate_progressbar.xml */
- @Test
- public void testVectorAnimation() throws ClassNotFoundException {
- // Create the layout pull parser.
- LayoutPullParser parser = createLayoutPullParser("indeterminate_progressbar.xml");
- // Create LayoutLibCallback.
- LayoutLibTestCallback layoutLibCallback =
- new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
- layoutLibCallback.initResources();
-
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
-
- renderAndVerify(params, "animated_vector.png", TimeUnit.SECONDS.toNanos(2));
-
- parser = createLayoutPullParser("indeterminate_progressbar.xml");
- params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
- renderAndVerify(params, "animated_vector_1.png", TimeUnit.SECONDS.toNanos(3));
- }
-
- /**
- * Test a vector drawable that uses trimStart and trimEnd. It also tests all the primitives
- * for vector drawables (lines, moves and cubic and quadratic curves).
- */
- @Test
- public void testVectorDrawable() throws ClassNotFoundException {
- // Create the layout pull parser.
- LayoutPullParser parser = createLayoutPullParser("vector_drawable.xml");
- // Create LayoutLibCallback.
- LayoutLibTestCallback layoutLibCallback =
- new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
- layoutLibCallback.initResources();
-
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
-
- renderAndVerify(params, "vector_drawable.png", TimeUnit.SECONDS.toNanos(2));
- }
-
- /**
- * Regression test for http://b.android.com/91383 and http://b.android.com/203797
- */
- @Test
- public void testVectorDrawable91383() throws ClassNotFoundException {
- // Create the layout pull parser.
- LayoutPullParser parser = createLayoutPullParser("vector_drawable_android.xml");
- // Create LayoutLibCallback.
- LayoutLibTestCallback layoutLibCallback =
- new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
- layoutLibCallback.initResources();
-
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
-
- renderAndVerify(params, "vector_drawable_91383.png", TimeUnit.SECONDS.toNanos(2));
- }
-
- /** Test activity.xml */
- @Test
- public void testScrollingAndMeasure() throws ClassNotFoundException {
- // Create the layout pull parser.
- LayoutPullParser parser = createLayoutPullParser("scrolled.xml");
- // Create LayoutLibCallback.
- LayoutLibTestCallback layoutLibCallback =
- new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
- layoutLibCallback.initResources();
-
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
- params.setForceNoDecor();
- params.setExtendedViewInfoMode(true);
-
- // Do an only-measure pass
- RenderSession session = sBridge.createSession(params);
- session.measure();
- RenderResult result = RenderResult.getFromSession(session);
- assertNotNull(result);
- assertNotNull(result.getResult());
- assertTrue(result.getResult().isSuccess());
-
- ViewInfo rootLayout = result.getRootViews().get(0);
- // Check the first box in the main LinearLayout
- assertEquals(-90, rootLayout.getChildren().get(0).getTop());
- assertEquals(-30, rootLayout.getChildren().get(0).getLeft());
- assertEquals(90, rootLayout.getChildren().get(0).getBottom());
- assertEquals(150, rootLayout.getChildren().get(0).getRight());
-
- // Check the first box within the nested LinearLayout
- assertEquals(-450, rootLayout.getChildren().get(5).getChildren().get(0).getTop());
- assertEquals(90, rootLayout.getChildren().get(5).getChildren().get(0).getLeft());
- assertEquals(-270, rootLayout.getChildren().get(5).getChildren().get(0).getBottom());
- assertEquals(690, rootLayout.getChildren().get(5).getChildren().get(0).getRight());
-
- // Do a full render pass
- parser = createLayoutPullParser("scrolled.xml");
-
- params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
- params.setForceNoDecor();
- params.setExtendedViewInfoMode(true);
-
- result = renderAndVerify(params, "scrolled.png");
- assertNotNull(result);
- assertNotNull(result.getResult());
- assertTrue(result.getResult().isSuccess());
- }
-
- @Test
- public void testGetResourceNameVariants() throws Exception {
- // Setup
- // Create the layout pull parser for our resources (empty.xml can not be part of the test
- // app as it won't compile).
- LayoutPullParser parser = new LayoutPullParser("/empty.xml");
- // Create LayoutLibCallback.
- LayoutLibTestCallback layoutLibCallback =
- new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
- layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
- layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
- AssetManager assetManager = AssetManager.getSystem();
- DisplayMetrics metrics = new DisplayMetrics();
- Configuration configuration = RenderAction.getConfiguration(params);
- //noinspection deprecation
- Resources resources = new Resources(assetManager, metrics, configuration);
- resources.mLayoutlibCallback = params.getLayoutlibCallback();
- resources.mContext =
- new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
- params.getAssets(), params.getLayoutlibCallback(), configuration,
- params.getTargetSdkVersion(), params.isRtlSupported());
- // Test
- assertEquals("android:style/ButtonBar",
- resources.getResourceName(android.R.style.ButtonBar));
- assertEquals("android", resources.getResourcePackageName(android.R.style.ButtonBar));
- assertEquals("ButtonBar", resources.getResourceEntryName(android.R.style.ButtonBar));
- assertEquals("style", resources.getResourceTypeName(android.R.style.ButtonBar));
- int id = resources.mLayoutlibCallback.getResourceId(ResourceType.STRING, "app_name");
- assertEquals("com.android.layoutlib.test.myapplication:string/app_name",
- resources.getResourceName(id));
- assertEquals("com.android.layoutlib.test.myapplication",
- resources.getResourcePackageName(id));
- assertEquals("string", resources.getResourceTypeName(id));
- assertEquals("app_name", resources.getResourceEntryName(id));
- }
-
- @Test
- public void testStringEscaping() throws Exception {
- // Setup
- // Create the layout pull parser for our resources (empty.xml can not be part of the test
- // app as it won't compile).
- LayoutPullParser parser = new LayoutPullParser("/empty.xml");
- // Create LayoutLibCallback.
- LayoutLibTestCallback layoutLibCallback =
- new LayoutLibTestCallback(RenderTestBase.getLogger(), mDefaultClassLoader);
- layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
- layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
- AssetManager assetManager = AssetManager.getSystem();
- DisplayMetrics metrics = new DisplayMetrics();
- Configuration configuration = RenderAction.getConfiguration(params);
- //noinspection deprecation
- Resources resources = new Resources(assetManager, metrics, configuration);
- resources.mLayoutlibCallback = params.getLayoutlibCallback();
- resources.mContext =
- new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
- params.getAssets(), params.getLayoutlibCallback(), configuration,
- params.getTargetSdkVersion(), params.isRtlSupported());
-
- int id = resources.mLayoutlibCallback.getResourceId(ResourceType.ARRAY, "string_array");
- String[] strings = resources.getStringArray(id);
- assertArrayEquals(
- new String[]{"mystring", "Hello world!", "candidates", "Unknown", "?EC"},
- strings);
- assertTrue(sRenderMessages.isEmpty());
- }
-
- //@Test
- // Temporarily disabled (b/37725933)
- public void testFonts() throws ClassNotFoundException {
- // TODO: styles seem to be broken in TextView
- renderAndVerify("fonts_test.xml", "font_test.png");
- }
-
- @Test
- public void testAdaptiveIcon() throws ClassNotFoundException {
- // Create the layout pull parser.
- LayoutPullParser parser = createLayoutPullParser("adaptive_icon.xml");
- // Create LayoutLibCallback.
- LayoutLibTestCallback layoutLibCallback =
- new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
- layoutLibCallback.initResources();
-
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
-
- renderAndVerify(params, "adaptive_icon.png");
- }
-
- @Test
- public void testColorTypedValue() throws Exception {
- // Setup
- // Create the layout pull parser for our resources (empty.xml can not be part of the test
- // app as it won't compile).
- LayoutPullParser parser = new LayoutPullParser("/empty.xml");
- // Create LayoutLibCallback.
- LayoutLibTestCallback layoutLibCallback =
- new LayoutLibTestCallback(RenderTestBase.getLogger(), mDefaultClassLoader);
- layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
- layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
- AssetManager assetManager = AssetManager.getSystem();
- DisplayMetrics metrics = new DisplayMetrics();
- Configuration configuration = RenderAction.getConfiguration(params);
- //noinspection deprecation
- Resources resources = new Resources(assetManager, metrics, configuration);
- resources.mLayoutlibCallback = params.getLayoutlibCallback();
- resources.mContext =
- new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
- params.getAssets(), params.getLayoutlibCallback(), configuration,
- params.getTargetSdkVersion(), params.isRtlSupported());
-
- TypedValue outValue = new TypedValue();
- resources.mContext.resolveThemeAttribute(android.R.attr.colorPrimary, outValue, true);
- assertEquals(TypedValue.TYPE_INT_COLOR_ARGB8, outValue.type);
- assertNotEquals(0, outValue.data);
- assertTrue(sRenderMessages.isEmpty());
- }
-
- @Test
- public void testRectangleShadow() throws Exception {
- renderAndVerify("shadows_test.xml", "shadows_test.png");
- }
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java
deleted file mode 100644
index 34fc726..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * Copyright (C) 2014 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.layoutlib.bridge.intensive.setup;
-
-import com.android.ide.common.rendering.api.HardwareConfig;
-import com.android.ide.common.resources.configuration.CountryCodeQualifier;
-import com.android.ide.common.resources.configuration.DensityQualifier;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
-import com.android.ide.common.resources.configuration.KeyboardStateQualifier;
-import com.android.ide.common.resources.configuration.LayoutDirectionQualifier;
-import com.android.ide.common.resources.configuration.LocaleQualifier;
-import com.android.ide.common.resources.configuration.NavigationMethodQualifier;
-import com.android.ide.common.resources.configuration.NetworkCodeQualifier;
-import com.android.ide.common.resources.configuration.NightModeQualifier;
-import com.android.ide.common.resources.configuration.ScreenDimensionQualifier;
-import com.android.ide.common.resources.configuration.ScreenOrientationQualifier;
-import com.android.ide.common.resources.configuration.ScreenRatioQualifier;
-import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
-import com.android.ide.common.resources.configuration.TextInputMethodQualifier;
-import com.android.ide.common.resources.configuration.TouchScreenQualifier;
-import com.android.ide.common.resources.configuration.UiModeQualifier;
-import com.android.ide.common.resources.configuration.VersionQualifier;
-import com.android.resources.Density;
-import com.android.resources.Keyboard;
-import com.android.resources.KeyboardState;
-import com.android.resources.Navigation;
-import com.android.resources.NightMode;
-import com.android.resources.ScreenOrientation;
-import com.android.resources.ScreenRatio;
-import com.android.resources.ScreenSize;
-import com.android.resources.TouchScreen;
-import com.android.resources.UiMode;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlPullParserFactory;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.Map;
-import java.util.Properties;
-
-import com.google.android.collect.Maps;
-
-/**
- * Provides {@link FolderConfiguration} and {@link HardwareConfig} for various devices. Also
- * provides utility methods to parse build.prop and attrs.xml to generate the appropriate maps.
- */
-@SuppressWarnings("UnusedDeclaration") // For the pre-configured nexus generators.
-public class ConfigGenerator {
-
- public static final ConfigGenerator NEXUS_4 = new ConfigGenerator();
-
- public static final ConfigGenerator NEXUS_5 = new ConfigGenerator()
- .setScreenHeight(1920)
- .setScreenWidth(1080)
- .setXdpi(445)
- .setYdpi(445)
- .setOrientation(ScreenOrientation.PORTRAIT)
- .setDensity(Density.XXHIGH)
- .setRatio(ScreenRatio.NOTLONG)
- .setSize(ScreenSize.NORMAL)
- .setKeyboard(Keyboard.NOKEY)
- .setTouchScreen(TouchScreen.FINGER)
- .setKeyboardState(KeyboardState.SOFT)
- .setSoftButtons(true)
- .setNavigation(Navigation.NONAV);
-
- public static final ConfigGenerator NEXUS_7 = new ConfigGenerator()
- .setScreenHeight(1920)
- .setScreenWidth(1200)
- .setXdpi(323)
- .setYdpi(323)
- .setOrientation(ScreenOrientation.PORTRAIT)
- .setDensity(Density.XHIGH)
- .setRatio(ScreenRatio.NOTLONG)
- .setSize(ScreenSize.LARGE)
- .setKeyboard(Keyboard.NOKEY)
- .setTouchScreen(TouchScreen.FINGER)
- .setKeyboardState(KeyboardState.SOFT)
- .setSoftButtons(true)
- .setNavigation(Navigation.NONAV);
-
- public static final ConfigGenerator NEXUS_10 = new ConfigGenerator()
- .setScreenHeight(1600)
- .setScreenWidth(2560)
- .setXdpi(300)
- .setYdpi(300)
- .setOrientation(ScreenOrientation.LANDSCAPE)
- .setDensity(Density.XHIGH)
- .setRatio(ScreenRatio.NOTLONG)
- .setSize(ScreenSize.XLARGE)
- .setKeyboard(Keyboard.NOKEY)
- .setTouchScreen(TouchScreen.FINGER)
- .setKeyboardState(KeyboardState.SOFT)
- .setSoftButtons(true)
- .setNavigation(Navigation.NONAV);
-
- public static final ConfigGenerator NEXUS_5_LAND = new ConfigGenerator()
- .setScreenHeight(1080)
- .setScreenWidth(1920)
- .setXdpi(445)
- .setYdpi(445)
- .setOrientation(ScreenOrientation.LANDSCAPE)
- .setDensity(Density.XXHIGH)
- .setRatio(ScreenRatio.NOTLONG)
- .setSize(ScreenSize.NORMAL)
- .setKeyboard(Keyboard.NOKEY)
- .setTouchScreen(TouchScreen.FINGER)
- .setKeyboardState(KeyboardState.SOFT)
- .setSoftButtons(true)
- .setNavigation(Navigation.NONAV);
-
- public static final ConfigGenerator NEXUS_7_2012 = new ConfigGenerator()
- .setScreenHeight(1280)
- .setScreenWidth(800)
- .setXdpi(195)
- .setYdpi(200)
- .setOrientation(ScreenOrientation.PORTRAIT)
- .setDensity(Density.TV)
- .setRatio(ScreenRatio.NOTLONG)
- .setSize(ScreenSize.LARGE)
- .setKeyboard(Keyboard.NOKEY)
- .setTouchScreen(TouchScreen.FINGER)
- .setKeyboardState(KeyboardState.SOFT)
- .setSoftButtons(true)
- .setNavigation(Navigation.NONAV);
-
- private static final String TAG_ATTR = "attr";
- private static final String TAG_ENUM = "enum";
- private static final String TAG_FLAG = "flag";
- private static final String ATTR_NAME = "name";
- private static final String ATTR_VALUE = "value";
-
- // Device Configuration. Defaults are for a Nexus 4 device.
- private int mScreenHeight = 1280;
- private int mScreenWidth = 768;
- private int mXdpi = 320;
- private int mYdpi = 320;
- private ScreenOrientation mOrientation = ScreenOrientation.PORTRAIT;
- private Density mDensity = Density.XHIGH;
- private ScreenRatio mRatio = ScreenRatio.NOTLONG;
- private ScreenSize mSize = ScreenSize.NORMAL;
- private Keyboard mKeyboard = Keyboard.NOKEY;
- private TouchScreen mTouchScreen = TouchScreen.FINGER;
- private KeyboardState mKeyboardState = KeyboardState.SOFT;
- private boolean mSoftButtons = true;
- private Navigation mNavigation = Navigation.NONAV;
-
- public FolderConfiguration getFolderConfig() {
- FolderConfiguration config = new FolderConfiguration();
- config.createDefault();
- config.setDensityQualifier(new DensityQualifier(mDensity));
- config.setNavigationMethodQualifier(new NavigationMethodQualifier(mNavigation));
- if (mScreenWidth > mScreenHeight) {
- config.setScreenDimensionQualifier(new ScreenDimensionQualifier(mScreenWidth,
- mScreenHeight));
- } else {
- config.setScreenDimensionQualifier(new ScreenDimensionQualifier(mScreenHeight,
- mScreenWidth));
- }
- config.setScreenRatioQualifier(new ScreenRatioQualifier(mRatio));
- config.setScreenSizeQualifier(new ScreenSizeQualifier(mSize));
- config.setTextInputMethodQualifier(new TextInputMethodQualifier(mKeyboard));
- config.setTouchTypeQualifier(new TouchScreenQualifier(mTouchScreen));
- config.setKeyboardStateQualifier(new KeyboardStateQualifier(mKeyboardState));
- config.setScreenOrientationQualifier(new ScreenOrientationQualifier(mOrientation));
-
- config.updateScreenWidthAndHeight();
-
- // some default qualifiers.
- config.setUiModeQualifier(new UiModeQualifier(UiMode.NORMAL));
- config.setNightModeQualifier(new NightModeQualifier(NightMode.NOTNIGHT));
- config.setCountryCodeQualifier(new CountryCodeQualifier());
- config.setLayoutDirectionQualifier(new LayoutDirectionQualifier());
- config.setNetworkCodeQualifier(new NetworkCodeQualifier());
- config.setLocaleQualifier(new LocaleQualifier());
- config.setVersionQualifier(new VersionQualifier());
- return config;
- }
-
- public HardwareConfig getHardwareConfig() {
- return new HardwareConfig(mScreenWidth, mScreenHeight, mDensity, mXdpi, mYdpi, mSize,
- mOrientation, null, mSoftButtons);
- }
-
- public static Map<String, String> loadProperties(File path) {
- Properties p = new Properties();
- Map<String, String> map = Maps.newHashMap();
- try {
- p.load(new FileInputStream(path));
- for (String key : p.stringPropertyNames()) {
- map.put(key, p.getProperty(key));
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- return map;
- }
-
- public static Map<String, Map<String, Integer>> getEnumMap(File path) {
- Map<String, Map<String, Integer>> map = Maps.newHashMap();
- try {
- XmlPullParser xmlPullParser = XmlPullParserFactory.newInstance().newPullParser();
- xmlPullParser.setInput(new FileInputStream(path), null);
- int eventType = xmlPullParser.getEventType();
- String attr = null;
- while (eventType != XmlPullParser.END_DOCUMENT) {
- if (eventType == XmlPullParser.START_TAG) {
- if (TAG_ATTR.equals(xmlPullParser.getName())) {
- attr = xmlPullParser.getAttributeValue(null, ATTR_NAME);
- } else if (TAG_ENUM.equals(xmlPullParser.getName())
- || TAG_FLAG.equals(xmlPullParser.getName())) {
- String name = xmlPullParser.getAttributeValue(null, ATTR_NAME);
- String value = xmlPullParser.getAttributeValue(null, ATTR_VALUE);
- // Integer.decode cannot handle "ffffffff", see JDK issue 6624867
- int i = (int) (long) Long.decode(value);
- assert attr != null;
- Map<String, Integer> attributeMap = map.get(attr);
- if (attributeMap == null) {
- attributeMap = Maps.newHashMap();
- map.put(attr, attributeMap);
- }
- attributeMap.put(name, i);
- }
- } else if (eventType == XmlPullParser.END_TAG) {
- if (TAG_ATTR.equals(xmlPullParser.getName())) {
- attr = null;
- }
- }
- eventType = xmlPullParser.next();
- }
- } catch (XmlPullParserException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return map;
- }
-
- // Methods to set the configuration values.
-
- public ConfigGenerator setScreenHeight(int height) {
- mScreenHeight = height;
- return this;
- }
-
- public ConfigGenerator setScreenWidth(int width) {
- mScreenWidth = width;
- return this;
- }
-
- public ConfigGenerator setXdpi(int xdpi) {
- mXdpi = xdpi;
- return this;
- }
-
- public ConfigGenerator setYdpi(int ydpi) {
- mYdpi = ydpi;
- return this;
- }
-
- public ConfigGenerator setOrientation(ScreenOrientation orientation) {
- mOrientation = orientation;
- return this;
- }
-
- public ConfigGenerator setDensity(Density density) {
- mDensity = density;
- return this;
- }
-
- public ConfigGenerator setRatio(ScreenRatio ratio) {
- mRatio = ratio;
- return this;
- }
-
- public ConfigGenerator setSize(ScreenSize size) {
- mSize = size;
- return this;
- }
-
- public ConfigGenerator setKeyboard(Keyboard keyboard) {
- mKeyboard = keyboard;
- return this;
- }
-
- public ConfigGenerator setTouchScreen(TouchScreen touchScreen) {
- mTouchScreen = touchScreen;
- return this;
- }
-
- public ConfigGenerator setKeyboardState(KeyboardState state) {
- mKeyboardState = state;
- return this;
- }
-
- public ConfigGenerator setSoftButtons(boolean softButtons) {
- mSoftButtons = softButtons;
- return this;
- }
-
- public ConfigGenerator setNavigation(Navigation navigation) {
- mNavigation = navigation;
- return this;
- }
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java
deleted file mode 100644
index 8ebfc65..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2014 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.layoutlib.bridge.intensive.setup;
-
-import com.android.SdkConstants;
-import com.android.ide.common.rendering.api.ActionBarCallback;
-import com.android.ide.common.rendering.api.AdapterBinding;
-import com.android.ide.common.rendering.api.ILayoutPullParser;
-import com.android.ide.common.rendering.api.LayoutlibCallback;
-import com.android.ide.common.rendering.api.ParserFactory;
-import com.android.ide.common.rendering.api.ResourceReference;
-import com.android.ide.common.rendering.api.ResourceValue;
-import com.android.ide.common.rendering.api.SessionParams.Key;
-import com.android.ide.common.resources.IntArrayWrapper;
-import com.android.layoutlib.bridge.android.RenderParamsFlags;
-import com.android.resources.ResourceType;
-import com.android.util.Pair;
-import com.android.utils.ILogger;
-
-import org.kxml2.io.KXmlParser;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.io.File;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.Map;
-
-import com.google.android.collect.Maps;
-
-import static org.junit.Assert.fail;
-
-@SuppressWarnings("deprecation") // For Pair
-public class LayoutLibTestCallback extends LayoutlibCallback {
-
- private static final String PROJECT_CLASSES_LOCATION = "/testApp/MyApplication/build/intermediates/classes/debug/";
- private static final String PACKAGE_NAME = "com.android.layoutlib.test.myapplication";
-
- private final Map<Integer, Pair<ResourceType, String>> mProjectResources = Maps.newHashMap();
- private final Map<IntArrayWrapper, String> mStyleableValueToNameMap = Maps.newHashMap();
- private final Map<ResourceType, Map<String, Integer>> mResources = Maps.newHashMap();
- private final ILogger mLog;
- private final ActionBarCallback mActionBarCallback = new ActionBarCallback();
- private final ClassLoader mModuleClassLoader;
-
- public LayoutLibTestCallback(ILogger logger, ClassLoader classLoader) {
- mLog = logger;
- mModuleClassLoader = classLoader;
- }
-
- public void initResources() throws ClassNotFoundException {
- Class<?> rClass = mModuleClassLoader.loadClass(PACKAGE_NAME + ".R");
- Class<?>[] nestedClasses = rClass.getDeclaredClasses();
- for (Class<?> resClass : nestedClasses) {
- final ResourceType resType = ResourceType.getEnum(resClass.getSimpleName());
-
- if (resType != null) {
- final Map<String, Integer> resName2Id = Maps.newHashMap();
- mResources.put(resType, resName2Id);
-
- for (Field field : resClass.getDeclaredFields()) {
- final int modifiers = field.getModifiers();
- if (Modifier.isStatic(modifiers)) { // May not be final in library projects
- final Class<?> type = field.getType();
- try {
- if (type.isArray() && type.getComponentType() == int.class) {
- mStyleableValueToNameMap.put(
- new IntArrayWrapper((int[]) field.get(null)),
- field.getName());
- } else if (type == int.class) {
- final Integer value = (Integer) field.get(null);
- mProjectResources.put(value, Pair.of(resType, field.getName()));
- resName2Id.put(field.getName(), value);
- } else {
- mLog.error(null, "Unknown field type in R class: %1$s", type);
- }
- } catch (IllegalAccessException ignored) {
- mLog.error(ignored, "Malformed R class: %1$s", PACKAGE_NAME + ".R");
- }
- }
- }
- }
- }
- }
-
-
- @Override
- public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs)
- throws Exception {
- Class<?> viewClass = mModuleClassLoader.loadClass(name);
- Constructor<?> viewConstructor = viewClass.getConstructor(constructorSignature);
- viewConstructor.setAccessible(true);
- return viewConstructor.newInstance(constructorArgs);
- }
-
- @Override
- public String getNamespace() {
- return String.format(SdkConstants.NS_CUSTOM_RESOURCES_S,
- PACKAGE_NAME);
- }
-
- @Override
- public Pair<ResourceType, String> resolveResourceId(int id) {
- return mProjectResources.get(id);
- }
-
- @Override
- public String resolveResourceId(int[] id) {
- return mStyleableValueToNameMap.get(new IntArrayWrapper(id));
- }
-
- @Override
- public Integer getResourceId(ResourceType type, String name) {
- Map<String, Integer> resName2Id = mResources.get(type);
- if (resName2Id == null) {
- return null;
- }
- return resName2Id.get(name);
- }
-
- @Override
- public ILayoutPullParser getParser(String layoutName) {
- fail("This method shouldn't be called by this version of LayoutLib.");
- return null;
- }
-
- @Override
- public ILayoutPullParser getParser(ResourceValue layoutResource) {
- return new LayoutPullParser(new File(layoutResource.getValue()));
- }
-
- @Override
- public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie,
- ResourceReference itemRef, int fullPosition, int positionPerType,
- int fullParentPosition, int parentPositionPerType, ResourceReference viewRef,
- ViewAttribute viewAttribute, Object defaultValue) {
- return null;
- }
-
- @Override
- public AdapterBinding getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie,
- Object viewObject) {
- return null;
- }
-
- @Override
- public ActionBarCallback getActionBarCallback() {
- return mActionBarCallback;
- }
-
- @Override
- public boolean supports(int ideFeature) {
- return false;
- }
-
- @NonNull
- @Override
- public ParserFactory getParserFactory() {
- return new ParserFactory() {
- @NonNull
- @Override
- public XmlPullParser createParser(@Nullable String debugName)
- throws XmlPullParserException {
- return new KXmlParser();
- }
- };
- }
-
- @Override
- public <T> T getFlag(Key<T> key) {
- if (key.equals(RenderParamsFlags.FLAG_KEY_APPLICATION_PACKAGE)) {
- return (T) PACKAGE_NAME;
- }
- return null;
- }
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java
deleted file mode 100644
index bc8083f..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2014 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.layoutlib.bridge.intensive.setup;
-
-import com.android.ide.common.rendering.api.ILayoutPullParser;
-
-import org.kxml2.io.KXmlParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOError;
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
-
-import static com.android.SdkConstants.ATTR_IGNORE;
-import static com.android.SdkConstants.EXPANDABLE_LIST_VIEW;
-import static com.android.SdkConstants.GRID_VIEW;
-import static com.android.SdkConstants.LIST_VIEW;
-import static com.android.SdkConstants.SPINNER;
-import static com.android.SdkConstants.TOOLS_URI;
-
-public class LayoutPullParser extends KXmlParser implements ILayoutPullParser{
-
- /**
- * @param layoutPath Must start with '/' and be relative to test resources.
- */
- public LayoutPullParser(String layoutPath) {
- if (layoutPath.startsWith("/")) {
- layoutPath = layoutPath.substring(1);
- }
- try {
- init(getClass().getClassLoader().getResourceAsStream(layoutPath));
- } catch (XmlPullParserException e) {
- throw new IOError(e);
- }
- }
-
- /**
- * @param layoutFile Path of the layout xml file on disk.
- */
- public LayoutPullParser(File layoutFile) {
- try {
- init(new FileInputStream(layoutFile));
- } catch (XmlPullParserException | FileNotFoundException e) {
- throw new IOError(e);
- }
- }
-
- private void init(InputStream stream) throws XmlPullParserException {
- setFeature(FEATURE_PROCESS_NAMESPACES, true);
- setInput(stream, null);
- }
-
- @Override
- public Object getViewCookie() {
- // TODO: Implement this properly.
- String name = super.getName();
- if (name == null) {
- return null;
- }
-
- // Store tools attributes if this looks like a layout we'll need adapter view
- // bindings for in the LayoutlibCallback.
- if (LIST_VIEW.equals(name) || EXPANDABLE_LIST_VIEW.equals(name) || GRID_VIEW.equals(name) || SPINNER.equals(name)) {
- Map<String, String> map = null;
- int count = getAttributeCount();
- for (int i = 0; i < count; i++) {
- String namespace = getAttributeNamespace(i);
- if (namespace != null && namespace.equals(TOOLS_URI)) {
- String attribute = getAttributeName(i);
- if (attribute.equals(ATTR_IGNORE)) {
- continue;
- }
- if (map == null) {
- map = new HashMap<String, String>(4);
- }
- map.put(attribute, getAttributeValue(i));
- }
- }
-
- return map;
- }
-
- return null;
- }
-
- @Override
- @Deprecated
- public ILayoutPullParser getParser(String layoutName) {
- // Studio returns null.
- return null;
- }
-
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ImageUtils.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ImageUtils.java
deleted file mode 100644
index cdf5633..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ImageUtils.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.intensive.util;
-
-import android.annotation.NonNull;
-
-import java.awt.AlphaComposite;
-import java.awt.Color;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.imageio.ImageIO;
-
-import static java.awt.RenderingHints.*;
-import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
-import static java.io.File.separatorChar;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-
-// Adapted by taking the relevant pieces of code from the following classes:
-//
-// com.android.tools.idea.rendering.ImageUtils,
-// com.android.tools.idea.tests.gui.framework.fixture.layout.ImageFixture and
-// com.android.tools.idea.rendering.RenderTestBase
-/**
- * Utilities related to image processing.
- */
-public class ImageUtils {
- /**
- * Normally, this test will fail when there is a missing thumbnail. However, when
- * you create creating a new test, it's useful to be able to turn this off such that
- * you can generate all the missing thumbnails in one go, rather than having to run
- * the test repeatedly to get to each new render assertion generating its thumbnail.
- */
- private static final boolean FAIL_ON_MISSING_THUMBNAIL = true;
-
- private static final int THUMBNAIL_SIZE = 1000;
-
- private static final double MAX_PERCENT_DIFFERENCE = 0.3;
-
- public static void requireSimilar(@NonNull String relativePath, @NonNull BufferedImage image)
- throws IOException {
- int maxDimension = Math.max(image.getWidth(), image.getHeight());
- double scale = THUMBNAIL_SIZE / (double)maxDimension;
- BufferedImage thumbnail = scale(image, scale, scale);
-
- InputStream is = ImageUtils.class.getClassLoader().getResourceAsStream(relativePath);
- if (is == null) {
- String message = "Unable to load golden thumbnail: " + relativePath + "\n";
- message = saveImageAndAppendMessage(thumbnail, message, relativePath);
- if (FAIL_ON_MISSING_THUMBNAIL) {
- fail(message);
- } else {
- System.out.println(message);
- }
- }
- else {
- try {
- BufferedImage goldenImage = ImageIO.read(is);
- assertImageSimilar(relativePath, goldenImage, thumbnail, MAX_PERCENT_DIFFERENCE);
- } finally {
- is.close();
- }
- }
- }
-
- public static void assertImageSimilar(String relativePath, BufferedImage goldenImage,
- BufferedImage image, double maxPercentDifferent) throws IOException {
- assertEquals("Only TYPE_INT_ARGB image types are supported", TYPE_INT_ARGB, image.getType());
-
- if (goldenImage.getType() != TYPE_INT_ARGB) {
- BufferedImage temp = new BufferedImage(goldenImage.getWidth(), goldenImage.getHeight(),
- TYPE_INT_ARGB);
- temp.getGraphics().drawImage(goldenImage, 0, 0, null);
- goldenImage = temp;
- }
- assertEquals(TYPE_INT_ARGB, goldenImage.getType());
-
- int imageWidth = Math.min(goldenImage.getWidth(), image.getWidth());
- int imageHeight = Math.min(goldenImage.getHeight(), image.getHeight());
-
- // Blur the images to account for the scenarios where there are pixel
- // differences
- // in where a sharp edge occurs
- // goldenImage = blur(goldenImage, 6);
- // image = blur(image, 6);
-
- int width = 3 * imageWidth;
- @SuppressWarnings("UnnecessaryLocalVariable")
- int height = imageHeight; // makes code more readable
- BufferedImage deltaImage = new BufferedImage(width, height, TYPE_INT_ARGB);
- Graphics g = deltaImage.getGraphics();
-
- // Compute delta map
- long delta = 0;
- for (int y = 0; y < imageHeight; y++) {
- for (int x = 0; x < imageWidth; x++) {
- int goldenRgb = goldenImage.getRGB(x, y);
- int rgb = image.getRGB(x, y);
- if (goldenRgb == rgb) {
- deltaImage.setRGB(imageWidth + x, y, 0x00808080);
- continue;
- }
-
- // If the pixels have no opacity, don't delta colors at all
- if (((goldenRgb & 0xFF000000) == 0) && (rgb & 0xFF000000) == 0) {
- deltaImage.setRGB(imageWidth + x, y, 0x00808080);
- continue;
- }
-
- int deltaR = ((rgb & 0xFF0000) >>> 16) - ((goldenRgb & 0xFF0000) >>> 16);
- int newR = 128 + deltaR & 0xFF;
- int deltaG = ((rgb & 0x00FF00) >>> 8) - ((goldenRgb & 0x00FF00) >>> 8);
- int newG = 128 + deltaG & 0xFF;
- int deltaB = (rgb & 0x0000FF) - (goldenRgb & 0x0000FF);
- int newB = 128 + deltaB & 0xFF;
-
- int avgAlpha = ((((goldenRgb & 0xFF000000) >>> 24)
- + ((rgb & 0xFF000000) >>> 24)) / 2) << 24;
-
- int newRGB = avgAlpha | newR << 16 | newG << 8 | newB;
- deltaImage.setRGB(imageWidth + x, y, newRGB);
-
- delta += Math.abs(deltaR);
- delta += Math.abs(deltaG);
- delta += Math.abs(deltaB);
- }
- }
-
- // 3 different colors, 256 color levels
- long total = imageHeight * imageWidth * 3L * 256L;
- float percentDifference = (float) (delta * 100 / (double) total);
-
- String error = null;
- String imageName = getName(relativePath);
- if (percentDifference > maxPercentDifferent) {
- error = String.format("Images differ (by %.1f%%)", percentDifference);
- } else if (Math.abs(goldenImage.getWidth() - image.getWidth()) >= 2) {
- error = "Widths differ too much for " + imageName + ": " +
- goldenImage.getWidth() + "x" + goldenImage.getHeight() +
- "vs" + image.getWidth() + "x" + image.getHeight();
- } else if (Math.abs(goldenImage.getHeight() - image.getHeight()) >= 2) {
- error = "Heights differ too much for " + imageName + ": " +
- goldenImage.getWidth() + "x" + goldenImage.getHeight() +
- "vs" + image.getWidth() + "x" + image.getHeight();
- }
-
- assertEquals(TYPE_INT_ARGB, image.getType());
- if (error != null) {
- // Expected on the left
- // Golden on the right
- g.drawImage(goldenImage, 0, 0, null);
- g.drawImage(image, 2 * imageWidth, 0, null);
-
- // Labels
- if (imageWidth > 80) {
- g.setColor(Color.RED);
- g.drawString("Expected", 10, 20);
- g.drawString("Actual", 2 * imageWidth + 10, 20);
- }
-
- File output = new File(getFailureDir(), "delta-" + imageName);
- if (output.exists()) {
- boolean deleted = output.delete();
- assertTrue(deleted);
- }
- ImageIO.write(deltaImage, "PNG", output);
- error += " - see details in " + output.getPath() + "\n";
- error = saveImageAndAppendMessage(image, error, relativePath);
- System.out.println(error);
- fail(error);
- }
-
- g.dispose();
- }
-
- /**
- * Resize the given image
- *
- * @param source the image to be scaled
- * @param xScale x scale
- * @param yScale y scale
- * @return the scaled image
- */
- @NonNull
- public static BufferedImage scale(@NonNull BufferedImage source, double xScale, double yScale) {
-
- int sourceWidth = source.getWidth();
- int sourceHeight = source.getHeight();
- int destWidth = Math.max(1, (int) (xScale * sourceWidth));
- int destHeight = Math.max(1, (int) (yScale * sourceHeight));
- int imageType = source.getType();
- if (imageType == BufferedImage.TYPE_CUSTOM) {
- imageType = BufferedImage.TYPE_INT_ARGB;
- }
- if (xScale > 0.5 && yScale > 0.5) {
- BufferedImage scaled =
- new BufferedImage(destWidth, destHeight, imageType);
- Graphics2D g2 = scaled.createGraphics();
- g2.setComposite(AlphaComposite.Src);
- g2.setColor(new Color(0, true));
- g2.fillRect(0, 0, destWidth, destHeight);
- if (xScale == 1 && yScale == 1) {
- g2.drawImage(source, 0, 0, null);
- } else {
- setRenderingHints(g2);
- g2.drawImage(source, 0, 0, destWidth, destHeight, 0, 0, sourceWidth, sourceHeight,
- null);
- }
- g2.dispose();
- return scaled;
- } else {
- // When creating a thumbnail, using the above code doesn't work very well;
- // you get some visible artifacts, especially for text. Instead use the
- // technique of repeatedly scaling the image into half; this will cause
- // proper averaging of neighboring pixels, and will typically (for the kinds
- // of screen sizes used by this utility method in the layout editor) take
- // about 3-4 iterations to get the result since we are logarithmically reducing
- // the size. Besides, each successive pass in operating on much fewer pixels
- // (a reduction of 4 in each pass).
- //
- // However, we may not be resizing to a size that can be reached exactly by
- // successively diving in half. Therefore, once we're within a factor of 2 of
- // the final size, we can do a resize to the exact target size.
- // However, we can get even better results if we perform this final resize
- // up front. Let's say we're going from width 1000 to a destination width of 85.
- // The first approach would cause a resize from 1000 to 500 to 250 to 125, and
- // then a resize from 125 to 85. That last resize can distort/blur a lot.
- // Instead, we can start with the destination width, 85, and double it
- // successfully until we're close to the initial size: 85, then 170,
- // then 340, and finally 680. (The next one, 1360, is larger than 1000).
- // So, now we *start* the thumbnail operation by resizing from width 1000 to
- // width 680, which will preserve a lot of visual details such as text.
- // Then we can successively resize the image in half, 680 to 340 to 170 to 85.
- // We end up with the expected final size, but we've been doing an exact
- // divide-in-half resizing operation at the end so there is less distortion.
-
- int iterations = 0; // Number of halving operations to perform after the initial resize
- int nearestWidth = destWidth; // Width closest to source width that = 2^x, x is integer
- int nearestHeight = destHeight;
- while (nearestWidth < sourceWidth / 2) {
- nearestWidth *= 2;
- nearestHeight *= 2;
- iterations++;
- }
-
- BufferedImage scaled = new BufferedImage(nearestWidth, nearestHeight, imageType);
-
- Graphics2D g2 = scaled.createGraphics();
- setRenderingHints(g2);
- g2.drawImage(source, 0, 0, nearestWidth, nearestHeight, 0, 0, sourceWidth, sourceHeight,
- null);
- g2.dispose();
-
- sourceWidth = nearestWidth;
- sourceHeight = nearestHeight;
- source = scaled;
-
- for (int iteration = iterations - 1; iteration >= 0; iteration--) {
- int halfWidth = sourceWidth / 2;
- int halfHeight = sourceHeight / 2;
- scaled = new BufferedImage(halfWidth, halfHeight, imageType);
- g2 = scaled.createGraphics();
- setRenderingHints(g2);
- g2.drawImage(source, 0, 0, halfWidth, halfHeight, 0, 0, sourceWidth, sourceHeight,
- null);
- g2.dispose();
-
- sourceWidth = halfWidth;
- sourceHeight = halfHeight;
- source = scaled;
- iterations--;
- }
- return scaled;
- }
- }
-
- private static void setRenderingHints(@NonNull Graphics2D g2) {
- g2.setRenderingHint(KEY_INTERPOLATION,VALUE_INTERPOLATION_BILINEAR);
- g2.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY);
- g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
- }
-
- /**
- * Directory where to write the thumbnails and deltas.
- */
- @NonNull
- private static File getFailureDir() {
- String workingDirString = System.getProperty("user.dir");
- File failureDir = new File(workingDirString, "out/failures");
-
- //noinspection ResultOfMethodCallIgnored
- failureDir.mkdirs();
- return failureDir; //$NON-NLS-1$
- }
-
- /**
- * Saves the generated thumbnail image and appends the info message to an initial message
- */
- @NonNull
- private static String saveImageAndAppendMessage(@NonNull BufferedImage image,
- @NonNull String initialMessage, @NonNull String relativePath) throws IOException {
- File output = new File(getFailureDir(), getName(relativePath));
- if (output.exists()) {
- boolean deleted = output.delete();
- assertTrue(deleted);
- }
- ImageIO.write(image, "PNG", output);
- initialMessage += "Thumbnail for current rendering stored at " + output.getPath();
-// initialMessage += "\nRun the following command to accept the changes:\n";
-// initialMessage += String.format("mv %1$s %2$s", output.getPath(),
-// ImageUtils.class.getResource(relativePath).getPath());
- // The above has been commented out, since the destination path returned is in out dir
- // and it makes the tests pass without the code being actually checked in.
- return initialMessage;
- }
-
- private static String getName(@NonNull String relativePath) {
- return relativePath.substring(relativePath.lastIndexOf(separatorChar) + 1);
- }
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ModuleClassLoader.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ModuleClassLoader.java
deleted file mode 100644
index da360f3..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ModuleClassLoader.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.intensive.util;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-import libcore.io.Streams;
-
-/**
- * Module class loader that loads classes from the test project.
- */
-public class ModuleClassLoader extends ClassLoader {
- private final Map<String, Class<?>> mClasses = new HashMap<>();
- private String myModuleRoot;
-
- /**
- * @param moduleRoot The path to the module root
- * @param parent The parent class loader
- */
- public ModuleClassLoader(String moduleRoot, ClassLoader parent) {
- super(parent);
- myModuleRoot = moduleRoot + (moduleRoot.endsWith("/") ? "" : "/");
- }
-
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- try {
- return super.findClass(name);
- } catch (ClassNotFoundException ignored) {
- }
-
- Class<?> clazz = mClasses.get(name);
- if (clazz == null) {
- String path = name.replace('.', '/').concat(".class");
- try {
- byte[] b = Streams.readFully(getResourceAsStream(myModuleRoot + path));
- clazz = defineClass(name, b, 0, b.length);
- mClasses.put(name, clazz);
- } catch (IOException ignore) {
- throw new ClassNotFoundException(name + " not found");
- }
- }
-
- return clazz;
- }
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestAssetRepository.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestAssetRepository.java
deleted file mode 100644
index 0856ac9..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestAssetRepository.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.intensive.util;
-
-import com.android.ide.common.rendering.api.AssetRepository;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * {@link AssetRepository} used for render tests.
- */
-public class TestAssetRepository extends AssetRepository {
- private static InputStream open(String path) throws FileNotFoundException {
- File asset = new File(path);
- if (asset.isFile()) {
- return new FileInputStream(asset);
- }
-
- return null;
- }
-
- @Override
- public boolean isSupported() {
- return true;
- }
-
- @Override
- public InputStream openAsset(String path, int mode) throws IOException {
- return open(path);
- }
-
- @Override
- public InputStream openNonAsset(int cookie, String path, int mode) throws IOException {
- return open(path);
- }
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestUtils.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestUtils.java
deleted file mode 100644
index 1df8e79..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestUtils.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.intensive.util;
-
-import java.lang.ref.WeakReference;
-
-public class TestUtils {
- public static void gc() {
- // See RuntimeUtil#gc in jlibs (http://jlibs.in/)
- Object obj = new Object();
- WeakReference ref = new WeakReference<>(obj);
- //noinspection UnusedAssignment
- obj = null;
- while (ref.get() != null) {
- System.gc();
- System.runFinalization();
- }
-
- System.gc();
- System.runFinalization();
- }
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/LongStatsCollector.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/LongStatsCollector.java
deleted file mode 100644
index ee98b4b..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/LongStatsCollector.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.intensive.util.perf;
-
-import android.annotation.NonNull;
-import android.util.LongArray;
-
-import java.util.Arrays;
-import java.util.function.LongConsumer;
-
-/**
- * Class that collect a series of longs and produces the median, min and max values.
- */
-public class LongStatsCollector implements LongConsumer {
- private final LongArray mAllValues;
- private long mMin = Long.MAX_VALUE;
- private long mMax = Long.MIN_VALUE;
- public LongStatsCollector(int estimatedRuns) {
- mAllValues = new LongArray(estimatedRuns);
- }
-
- public int size() {
- return mAllValues.size();
- }
-
- @NonNull
- public Stats getStats() {
- if (mAllValues.size() == 0) {
- throw new IndexOutOfBoundsException("No data");
- }
-
- double median;
- int size = mAllValues.size();
- long[] buffer = new long[size];
- for (int i = 0; i < size; i++) {
- buffer[i] = mAllValues.get(i);
- }
-
- Arrays.sort(buffer);
-
- int midPoint = size / 2;
- median = (size % 2 == 0) ? (buffer[midPoint - 1] + buffer[midPoint]) / 2 : buffer[midPoint];
-
- return new Stats(mAllValues.size(), mMin, mMax, median);
- }
-
- @Override
- public void accept(long value) {
- mMin = Math.min(mMin, value);
- mMax = Math.max(mMax, value);
- mAllValues.add(value);
- }
-
- public static class Stats {
- private final int mSamples;
- private final long mMin;
- private final long mMax;
- private final double mMedian;
-
- private Stats(int samples, long min, long max, double median) {
- mSamples = samples;
- mMin = min;
- mMax = max;
- mMedian = median;
- }
-
- public int getSampleCount() {
- return mSamples;
- }
-
- public long getMin() {
- return mMin;
- }
-
- public long getMax() {
- return mMax;
- }
-
- public double getMedian() {
- return mMedian;
- }
- }
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/PerformanceRunner.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/PerformanceRunner.java
deleted file mode 100644
index 7225a10..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/PerformanceRunner.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.intensive.util.perf;
-
-import org.junit.runner.Runner;
-import org.junit.runner.notification.RunNotifier;
-import org.junit.runners.BlockJUnit4ClassRunner;
-import org.junit.runners.model.FrameworkMethod;
-import org.junit.runners.model.InitializationError;
-import org.junit.runners.model.Statement;
-
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * JUnit {@link Runner} that times the test execution and produces some stats.
- */
-public class PerformanceRunner extends BlockJUnit4ClassRunner {
- private static final int DEFAULT_WARMUP_ITERATIONS = 50;
- private static final int DEFAULT_RUNS = 100;
-
- private final int mWarmUpIterations;
- private final int mRuns;
-
- public PerformanceRunner(Class<?> testClass) throws InitializationError {
- super(testClass);
-
- Configuration classConfig = testClass.getAnnotation(Configuration.class);
- mWarmUpIterations = classConfig != null && classConfig.warmUpIterations() != -1 ?
- classConfig.warmUpIterations() :
- DEFAULT_WARMUP_ITERATIONS;
- mRuns = classConfig != null && classConfig.runs() != -1 ?
- classConfig.runs() :
- DEFAULT_RUNS;
- }
-
- @Override
- protected Statement methodInvoker(FrameworkMethod method, Object test) {
- int warmUpIterations;
- int runs;
-
- Configuration methodConfig = method.getAnnotation(Configuration.class);
- warmUpIterations = methodConfig != null && methodConfig.warmUpIterations() != -1 ?
- methodConfig.warmUpIterations() :
- mWarmUpIterations;
- runs = methodConfig != null && methodConfig.runs() != -1 ?
- methodConfig.runs() :
- mRuns;
- return new TimedStatement(super.methodInvoker(method, test), warmUpIterations, runs,
- (result) -> System.out.println(result.toString()));
- }
-
- @Override
- public void run(RunNotifier notifier) {
- super.run(notifier);
- }
-
- @Retention(RetentionPolicy.RUNTIME)
- @Inherited
- public @interface Configuration {
- int warmUpIterations() default -1;
-
- int runs() default -1;
- }
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatement.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatement.java
deleted file mode 100644
index 77a2b0e..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatement.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.intensive.util.perf;
-
-import com.android.layoutlib.bridge.intensive.util.TestUtils;
-
-import org.junit.runners.model.Statement;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Random;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
-
-import com.google.common.hash.HashCode;
-import com.google.common.hash.HashFunction;
-import com.google.common.hash.Hashing;
-
-/**
- * JUnit {@link Statement} used to measure some statistics about the test method.
- */
-public class TimedStatement extends Statement {
- private static final int CALIBRATION_WARMUP_ITERATIONS = 50;
- private static final int CALIBRATION_RUNS = 100;
-
- private static boolean sIsCalibrated;
- private static double sCalibrated;
-
- private final Statement mStatement;
- private final int mWarmUpIterations;
- private final int mRuns;
- private final Runtime mRuntime = Runtime.getRuntime();
- private final Consumer<TimedStatementResult> mCallback;
-
- TimedStatement(Statement statement, int warmUpIterations, int runs,
- Consumer<TimedStatementResult> finishedCallback) {
- mStatement = statement;
- mWarmUpIterations = warmUpIterations;
- mRuns = runs;
- mCallback = finishedCallback;
- }
-
- /**
- * The calibrate method tries to do some work that involves IO, memory allocations and some
- * operations on the randomly generated data to calibrate the speed of the machine with
- * something that resembles the execution of a test case.
- */
- private static void calibrateMethod() throws IOException {
- File tmpFile = File.createTempFile("test", "file");
- Random rnd = new Random();
- HashFunction hashFunction = Hashing.sha512();
- for (int i = 0; i < 5 + rnd.nextInt(5); i++) {
- FileOutputStream stream = new FileOutputStream(tmpFile);
- int bytes = 30000 + rnd.nextInt(60000);
- byte[] buffer = new byte[bytes];
-
- rnd.nextBytes(buffer);
- byte acc = 0;
- for (int j = 0; j < bytes; j++) {
- acc += buffer[i];
- }
- buffer[0] = acc;
- stream.write(buffer);
- System.gc();
- stream.close();
- FileInputStream input = new FileInputStream(tmpFile);
- byte[] readBuffer = new byte[bytes];
- //noinspection ResultOfMethodCallIgnored
- input.read(readBuffer);
- buffer = readBuffer;
- HashCode code1 = hashFunction.hashBytes(buffer);
- Arrays.sort(buffer);
- HashCode code2 = hashFunction.hashBytes(buffer);
- input.close();
-
- FileOutputStream hashStream = new FileOutputStream(tmpFile);
- hashStream.write(code1.asBytes());
- hashStream.write(code2.asBytes());
- hashStream.close();
- }
- }
-
- /**
- * Runs the calibration process and sets the calibration measure in {@link #sCalibrated}
- */
- private static void doCalibration() throws IOException {
- System.out.println("Calibrating ...");
- TestUtils.gc();
- for (int i = 0; i < CALIBRATION_WARMUP_ITERATIONS; i++) {
- calibrateMethod();
- }
-
- LongStatsCollector stats = new LongStatsCollector(CALIBRATION_RUNS);
- for (int i = 0; i < CALIBRATION_RUNS; i++) {
- TestUtils.gc();
- long start = System.currentTimeMillis();
- calibrateMethod();
- stats.accept(System.currentTimeMillis() - start);
- }
-
- sCalibrated = stats.getStats().getMedian();
- sIsCalibrated = true;
- System.out.printf(" DONE %fms\n", sCalibrated);
- }
-
- private long getUsedMemory() {
- return mRuntime.totalMemory() - mRuntime.freeMemory();
- }
-
-
- @Override
- public void evaluate() throws Throwable {
- if (!sIsCalibrated) {
- doCalibration();
- }
-
- for (int i = 0; i < mWarmUpIterations; i++) {
- mStatement.evaluate();
- }
-
- LongStatsCollector timeStats = new LongStatsCollector(mRuns);
- LongStatsCollector memoryUseStats = new LongStatsCollector(mRuns);
- AtomicBoolean collectSamples = new AtomicBoolean(false);
-
- ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
- TestUtils.gc();
- executorService.scheduleAtFixedRate(() -> {
- if (!collectSamples.get()) {
- return;
- }
- memoryUseStats.accept(getUsedMemory());
- }, 0, 200, TimeUnit.MILLISECONDS);
-
- try {
- for (int i = 0; i < mRuns; i++) {
- TestUtils.gc();
- collectSamples.set(true);
- long startTimeMs = System.currentTimeMillis();
- mStatement.evaluate();
- long stopTimeMs = System.currentTimeMillis();
- collectSamples.set(true);
- timeStats.accept(stopTimeMs - startTimeMs);
-
- }
- } finally {
- executorService.shutdownNow();
- }
-
- TimedStatementResult result = new TimedStatementResult(
- mWarmUpIterations,
- mRuns,
- sCalibrated,
- timeStats.getStats(),
- memoryUseStats.getStats());
- mCallback.accept(result);
- }
-
-}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatementResult.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatementResult.java
deleted file mode 100644
index 59f90d2..0000000
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatementResult.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.intensive.util.perf;
-
-import com.android.layoutlib.bridge.intensive.util.perf.LongStatsCollector.Stats;
-
-import java.text.DecimalFormat;
-
-/**
- * Result value of a {@link TimedStatement}
- */
-public class TimedStatementResult {
- private static final DecimalFormat UNITS_FORMAT = new DecimalFormat("#.##");
-
- private final int mWarmUpIterations;
- private final int mRuns;
- private final double mCalibrationTimeMs;
- private final Stats mTimeStats;
- private final Stats mMemoryStats;
-
- TimedStatementResult(int warmUpIterations, int runs,
- double calibrationTimeMs,
- Stats timeStats,
- Stats memoryStats) {
- mWarmUpIterations = warmUpIterations;
- mRuns = runs;
- mCalibrationTimeMs = calibrationTimeMs;
- mTimeStats = timeStats;
- mMemoryStats = memoryStats;
- }
-
- @Override
- public String toString() {
- return String.format(
- "Warm up %d. Runs %d\n" + "Time: %s ms (min: %s, max %s)\n" +
- "Calibration Time: %f ms\n" +
- "Calibrated Time: %s units (min: %s, max %s)\n" +
- "Sampled %d times\n" +
- " Memory used: %d bytes (max %d)\n\n",
- mWarmUpIterations, mRuns,
- mTimeStats.getMedian(), mTimeStats.getMin(), mTimeStats.getMax(),
- mCalibrationTimeMs,
- UNITS_FORMAT.format((mTimeStats.getMedian() / mCalibrationTimeMs) * 100000),
- UNITS_FORMAT.format((mTimeStats.getMin() / mCalibrationTimeMs) * 100000),
- UNITS_FORMAT.format((mTimeStats.getMax() / mCalibrationTimeMs) * 100000),
- mMemoryStats.getSampleCount(),
- (long)mMemoryStats.getMedian() - mMemoryStats.getMin(),
- mMemoryStats.getMax() - mMemoryStats.getMin());
- }
-}
diff --git a/tools/layoutlib/bridge/update_nav_icons.sh b/tools/layoutlib/bridge/update_nav_icons.sh
deleted file mode 100755
index 7030d19..0000000
--- a/tools/layoutlib/bridge/update_nav_icons.sh
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/bin/sh
-
-# copies the navigation bar icons from system ui code to layoutlib.
-# to run, simply execute the script. (if not using bash, cd to the dir
-# containing this script and then run by ./update_nav_icons.sh)
-
-# Try to get the location of this script.
-if [ -n $BASH ]; then
- # see http://stackoverflow.com/a/246128/1546000
- MY_LOCATION=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
- cd $MY_LOCATION
-else
- # Let's assume script was run from the same dir.
- MY_LOCATION=$(pwd)
-fi
-
-# Check mac or linux to get sed argument to enable extended regex.
-case $(uname -s) in
- Darwin)
- EXT_REGEX="-E"
- ;;
- *)
- EXT_REGEX="-r"
- ;;
-esac
-
-
-FB="frameworks/base"
-# frameworks/base relative to current location
-FB=$(echo $MY_LOCATION | sed $EXT_REGEX -e "s,.*$FB[^/]*/,," -e "s,[^/]+,..,g")
-CURRENT_API=21 # update only if icons change from this api version.
-DENSITIES="ldpi mdpi hdpi xhdpi xxhdpi"
-ICONS="ic_sysbar_back.png ic_sysbar_home.png ic_sysbar_recent.png"
-BARS="./resources/bars/"
-
-for icon in $ICONS
-do
- for density in $DENSITIES
- do
- destination="$BARS/v$CURRENT_API/$density/"
- mkdir -p "$destination" # create if not present.
- cp -v "$FB/packages/SystemUI/res/drawable-$density/$icon" "$destination"
- done
-
- for density in $DENSITIES
- do
- destination="$BARS/v$CURRENT_API/ldrtl-$density/"
- mkdir -p "$destination"
- cp -v "$FB/packages/SystemUI/res/drawable-ldrtl-$density/$icon" "$destination"
- done
-done
diff --git a/tools/layoutlib/create/.classpath b/tools/layoutlib/create/.classpath
deleted file mode 100644
index 25c3b3e..0000000
--- a/tools/layoutlib/create/.classpath
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
- <classpathentry kind="src" path="src"/>
- <classpathentry excluding="mock_data/" kind="src" path="tests"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
- <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/asm/asm-4.0.jar" sourcepath="/ANDROID_PLAT_SRC/prebuilts/misc/common/asm/src.zip"/>
- <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/tools/layoutlib/create/.project b/tools/layoutlib/create/.project
deleted file mode 100644
index e100d17..0000000
--- a/tools/layoutlib/create/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>layoutlib_create</name>
- <comment></comment>
- <projects>
- </projects>
- <buildSpec>
- <buildCommand>
- <name>org.eclipse.jdt.core.javabuilder</name>
- <arguments>
- </arguments>
- </buildCommand>
- </buildSpec>
- <natures>
- <nature>org.eclipse.jdt.core.javanature</nature>
- </natures>
-</projectDescription>
diff --git a/tools/layoutlib/create/.settings/README.txt b/tools/layoutlib/create/.settings/README.txt
deleted file mode 100644
index 9120b20..0000000
--- a/tools/layoutlib/create/.settings/README.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-Copy this in eclipse project as a .settings folder at the root.
-This ensure proper compilation compliance and warning/error levels.
\ No newline at end of file
diff --git a/tools/layoutlib/create/.settings/org.eclipse.jdt.core.prefs b/tools/layoutlib/create/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 5381a0e..0000000
--- a/tools/layoutlib/create/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,93 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.annotation.nonnull=com.android.annotations.NonNull
-org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=com.android.annotations.NonNullByDefault
-org.eclipse.jdt.core.compiler.annotation.nonnullisdefault=disabled
-org.eclipse.jdt.core.compiler.annotation.nullable=com.android.annotations.Nullable
-org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
-org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
-org.eclipse.jdt.core.compiler.problem.deadCode=warning
-org.eclipse.jdt.core.compiler.problem.deprecation=warning
-org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
-org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
-org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
-org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
-org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
-org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
-org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
-org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
-org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
-org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
-org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
-org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
-org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
-org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
-org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
-org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
-org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
-org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
-org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
-org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
-org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
-org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
-org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
-org.eclipse.jdt.core.compiler.problem.nullReference=error
-org.eclipse.jdt.core.compiler.problem.nullSpecInsufficientInfo=warning
-org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
-org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
-org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
-org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
-org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
-org.eclipse.jdt.core.compiler.problem.potentialNullSpecViolation=error
-org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning
-org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
-org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
-org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
-org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
-org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
-org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
-org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
-org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
-org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
-org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
-org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
-org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
-org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
-org.eclipse.jdt.core.compiler.problem.unclosedCloseable=error
-org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
-org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
-org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
-org.eclipse.jdt.core.compiler.problem.unusedImport=warning
-org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
-org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
-org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
-org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
-org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
-org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/tools/layoutlib/create/Android.mk b/tools/layoutlib/create/Android.mk
deleted file mode 100644
index 7611cde..0000000
--- a/tools/layoutlib/create/Android.mk
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# Copyright (C) 2008 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 := $(call all-java-files-under,src)
-
-LOCAL_JAR_MANIFEST := manifest.txt
-LOCAL_STATIC_JAVA_LIBRARIES := \
- asm-5.2
-
-LOCAL_MODULE := layoutlib_create
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-# Build all sub-directories
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt
deleted file mode 100644
index 727b194..0000000
--- a/tools/layoutlib/create/README.txt
+++ /dev/null
@@ -1,268 +0,0 @@
-# Copyright (C) 2008 The Android Open Source Project
-
-
-- Description -
----------------
-
-Layoutlib_create generates a JAR library used by the Eclipse graphical layout editor to perform
-layout.
-
-
-- Usage -
----------
-
- ./layoutlib_create destination.jar path/to/android1.jar path/to/android2.jar
-
-
-- Design Overview -
--------------------
-
-Layoutlib_create uses a few jars from the framework containing the Java code used by Android as
-generated by the Android build, right before the classes are converted to a DEX format.
-
-These jars can't be used directly in Eclipse as:
-- they contains references to native code (which we want to avoid in Eclipse),
-- some classes need to be overridden, for example all the drawing code that is replaced by Java 2D
- calls in Eclipse.
-- some of the classes that need to be changed are final and/or we need access to their private
- internal state.
-
-Consequently this tool:
-- parses the input JAR,
-- modifies some of the classes directly using some bytecode manipulation,
-- filters some packages and removes those we don't want in the output JAR,
-- injects some new classes,
-- generates a modified JAR file that is suitable for the Android plugin for Eclipse to perform
- rendering.
-
-The ASM library is used to do the bytecode modification using its visitor pattern API.
-
-The layoutlib_create is *NOT* generic. There is no configuration file. Instead all the configuration
-is done in the main() method and the CreateInfo structure is expected to change with the Android
-platform as new classes are added, changed or removed. Some configuration that may be platform
-dependent is also present elsewhere in code.
-
-The resulting JAR is used by layoutlib_bridge (a.k.a. "the bridge"), also part of the platform, that
-provides all the necessary missing implementation for rendering graphics in Eclipse.
-
-
-
-- Implementation Notes -
-------------------------
-
-The tool works in two phases:
-- first analyze the input jar (AsmAnalyzer class)
-- then generate the output jar (AsmGenerator class),
-
-
-- Analyzer
-----------
-
-The goal of the analyzer is to create a graph of all the classes from the input JAR with their
-dependencies and then only keep the ones we want.
-
-To do that, the analyzer is created with a list of base classes to keep -- everything that derives
-from these is kept. Currently the one such class is android.view.View: since we want to render
-layouts, anything that is sort of a view needs to be kept.
-
-The analyzer is also given a list of class names to keep in the output. This is done using
-shell-like glob patterns that filter on the fully-qualified class names, for example "android.*.R**"
-("*" does not matches dots whilst "**" does, and "." and "$" are interpreted as-is). In practice we
-almost but not quite request the inclusion of full packages.
-
-The analyzer is also given a list of classes to exclude. A fake implementation of these classes is
-injected by the Generator.
-
-With this information, the analyzer parses the input zip to find all the classes. All classes
-deriving from the requested bases classes are kept. All classes whose name match the glob pattern
-are kept. The analysis then finds all the dependencies of the classes that are to be kept using an
-ASM visitor on the class, the field types, the method types and annotations types. Classes that
-belong to the current JRE are excluded.
-
-The output of the analyzer is a set of ASM ClassReader instances which are then fed to the
-generator.
-
-
-- Generator
------------
-
-The generator is constructed from a CreateInfo struct that acts as a config file and lists:
-- the classes to inject in the output JAR -- these classes are directly implemented in
- layoutlib_create and will be used to interface with the renderer in Eclipse.
-- specific methods to override (see method stubs details below).
-- specific methods for which to delegate calls.
-- specific methods to remove based on their return type.
-- specific classes to rename.
-- specific classes to refactor.
-
-Each of these are specific strategies we use to be able to modify the Android code to fit within the
-Eclipse renderer. These strategies are explained below.
-
-The core method of the generator is transform(): it takes an input ASM ClassReader and modifies it
-to produce a byte array suitable for the final JAR file.
-
-The first step of the transformation is to implement the method delegates.
-
-The TransformClassAdapter is then used to process the potentially renamed class. All protected or
-private classes are market as public. All classes are made non-final. Interfaces are left as-is.
-
-If a method has a return type that must be erased, the whole method is skipped. Methods are also
-changed from protected/private to public. The code of the methods is then kept as-is, except for
-native methods which are replaced by a stub. Methods that are to be overridden are also replaced by
-a stub.
-
-Finally fields are also visited and changed from protected/private to public.
-
-The next step of the transformation is changing the name of the class in case we requested the class
-to be renamed. This uses the RenameClassAdapter to also rename all inner classes and references in
-methods and types. Note that other classes are not transformed and keep referencing the original
-name.
-
-The class is then fed to RefactorClassAdapter which is like RenameClassAdapter but updates the
-references in all classes. This is used to update the references of classes in the java package that
-were added in the Dalvik VM but are not a part of the Desktop VM. The existing classes are
-modified to update all references to these non-desktop classes. An alternate implementation of
-these (com.android.tools.layoutlib.java.*) is injected.
-
-RenameClassAdapter and RefactorClassAdapter both inherit from AbstractClassAdapter which changes the
-class version (version of the JDK used to compile the class) to 50 (corresponding to Java 6), if the
-class was originally compiled with Java 7 (version 51). This is because we don't currently generate
-the StackMapTable correctly and Java 7 VM enforces that classes with version greater than 51 have
-valid StackMapTable. As a side benefit of this, we can continue to support Java 6 because Java 7 on
-Mac has horrible font rendering support.
-
-ReplaceMethodCallsAdapter replaces calls to certain methods. This is different from the
-DelegateMethodAdapter since it doesn't preserve the original copy of the method and more importantly
-changes the calls to a method in each class instead of changing the implementation of the method.
-This is useful for methods in the Java namespace where we cannot add delegates. The configuration
-for this is not done through the CreateInfo class, but done in the ReplaceMethodAdapter.
-
-The ClassAdapters are chained together to achieve the desired output. (Look at section 2.2.7
-Transformation chains in the asm user guide, link in the References.) The order of execution of
-these is:
-ClassReader -> [DelegateClassAdapter] -> TransformClassAdapter -> [RenameClassAdapter] ->
-RefactorClassAdapter -> [ReplaceMethodCallsAdapter] -> ClassWriter
-
-- Method stubs
---------------
-
-As indicated above, all native and overridden methods are replaced by a stub. We don't have the
-code to replace with in layoutlib_create. Instead the StubMethodAdapter replaces the code of the
-method by a call to OverrideMethod.invokeX(). When using the final JAR, the bridge can register
-listeners from these overridden method calls based on the method signatures.
-
-The listeners are currently pretty basic: we only pass the signature of the method being called, its
-caller object and a flag indicating whether the method was native. We do not currently provide the
-parameters. The listener can however specify the return value of the overridden method.
-
-This strategy is now obsolete and replaced by the method delegates.
-
-
-- Strategies
-------------
-
-We currently have 6 strategies to deal with overriding the rendering code and make it run in
-Eclipse. Most of these strategies are implemented hand-in-hand by the bridge (which runs in Eclipse)
-and the generator.
-
-
-1- Class Injection
-
-This is the easiest: we currently inject the following classes:
-- OverrideMethod and its associated MethodListener and MethodAdapter are used to intercept calls to
- some specific methods that are stubbed out and change their return value.
-- CreateInfo class, which configured the generator. Not used yet, but could in theory help us track
- what the generator changed.
-- AutoCloseable and Objects are part of Java 7. To enable us to still run on Java 6, new classes are
- injected. The implementation for these classes has been taken from Android's libcore
- (platform/libcore/luni/src/main/java/java/...).
-- Charsets, IntegralToString and UnsafeByteSequence are not part of the Desktop VM. They are
- added to the Dalvik VM for performance reasons. An implementation that is very close to the
- original (which is at platform/libcore/luni/src/main/java/...) is injected. Since these classees
- were in part of the java package, where we can't inject classes, all references to these have been
- updated (See strategy 4- Refactoring Classes).
-
-
-2- Overriding methods
-
-As explained earlier, the creator doesn't have any replacement code for methods to override. Instead
-it removes the original code and replaces it by a call to a specific OveriddeMethod.invokeX(). The
-bridge then registers a listener on the method signature and can provide an implementation.
-
-This strategy is now obsolete and replaced by the method delegates (See strategy 6- Method
-Delegates).
-
-
-3- Renaming classes
-
-This simply changes the name of a class in its definition, as well as all its references in internal
-inner classes and methods. Calls from other classes are not modified -- they keep referencing the
-original class name. This allows the bridge to literally replace an implementation.
-
-An example will make this easier: android.graphics.Paint is the main drawing class that we need to
-replace. To do so, the generator renames Paint to _original_Paint. Later the bridge provides its own
-replacement version of Paint which will be used by the rest of the Android stack. The replacement
-version of Paint can still use (either by inheritance or delegation) all the original non-native
-code of _original_Paint if it so desires.
-
-Some of the Android classes are basically wrappers over native objects and since we don't have the
-native code in Eclipse, we need to provide a full alternate implementation. Sub-classing doesn't
-work as some native methods are static and we don't control object creation.
-
-This won't rename/replace the inner static methods of a given class.
-
-
-4- Refactoring classes
-
-This is very similar to the Renaming classes except that it also updates the reference in all
-classes. This is done for classes which are added to the Dalvik VM for performance reasons but are
-not present in the Desktop VM. An implementation for these classes is also injected.
-
-
-5- Method erasure based on return type
-
-This is mostly an implementation detail of the bridge: in the Paint class mentioned above, some
-inner static classes are used to pass around attributes (e.g. FontMetrics, or the Style enum) and
-all the original implementation is native.
-
-In this case we have a strategy that tells the generator that anything returning, for example, the
-inner class Paint$Style in the Paint class should be discarded and the bridge will provide its own
-implementation.
-
-
-6- Method Delegates
-
-This strategy is used to override method implementations. Given a method SomeClass.MethodName(), 1
-or 2 methods are generated:
-a- A copy of the original method named SomeClass.MethodName_Original(). The content is the original
-method as-is from the reader. This step is omitted if the method is native, since it has no Java
-implementation.
-b- A brand new implementation of SomeClass.MethodName() which calls to a non-existing static method
-named SomeClass_Delegate.MethodName(). The implementation of this 'delegate' method is done in
-layoutlib_bridge.
-
-The delegate method is a static method. If the original method is non-static, the delegate method
-receives the original 'this' as its first argument. If the original method is an inner non-static
-method, it also receives the inner 'this' as the second argument.
-
-
-
-- References -
---------------
-
-
-The JVM Specification 2nd edition:
- http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html
-
-Understanding bytecode:
- http://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/
-
-Bytecode opcode list:
- http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
-
-ASM user guide:
- http://download.forge.objectweb.org/asm/asm4-guide.pdf
-
-
---
-end
diff --git a/tools/layoutlib/create/create.iml b/tools/layoutlib/create/create.iml
deleted file mode 100644
index ac97502..0000000
--- a/tools/layoutlib/create/create.iml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
- <sourceFolder url="file://$MODULE_DIR$/tests/data" type="java-test-resource" />
- <sourceFolder url="file://$MODULE_DIR$/tests/mock_data" type="java-test-resource" />
- <excludeFolder url="file://$MODULE_DIR$/.settings" />
- </content>
- <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="module-library">
- <library name="asm-5.2">
- <CLASSES>
- <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/asm/asm-5.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES>
- <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/asm/src.zip!/" />
- </SOURCES>
- </library>
- </orderEntry>
- <orderEntry type="library" scope="TEST" name="junit" level="project" />
- </component>
-</module>
\ No newline at end of file
diff --git a/tools/layoutlib/create/manifest.txt b/tools/layoutlib/create/manifest.txt
deleted file mode 100644
index 238e7f9..0000000
--- a/tools/layoutlib/create/manifest.txt
+++ /dev/null
@@ -1 +0,0 @@
-Main-Class: com.android.tools.layoutlib.create.Main
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java
deleted file mode 100644
index 9a48ea6..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2010 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.tools.layoutlib.annotations;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Denotes a method that has been converted to a delegate by layoutlib_create.
- */
-@Retention(RetentionPolicy.RUNTIME)
-public @interface LayoutlibDelegate {
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/Nullable.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/Nullable.java
deleted file mode 100644
index 0689c92..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/Nullable.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2010 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.tools.layoutlib.annotations;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Denotes a parameter or field can be null.
- * <p/>
- * When decorating a method call parameter, this denotes the parameter can
- * legitimately be null and the method will gracefully deal with it. Typically used
- * on optional parameters.
- * <p/>
- * When decorating a method, this denotes the method might legitimately return null.
- * <p/>
- * This is a marker annotation and it has no specific attributes.
- */
-@Retention(RetentionPolicy.SOURCE)
-public @interface Nullable {
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/VisibleForTesting.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/VisibleForTesting.java
deleted file mode 100644
index e4e016b..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/VisibleForTesting.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2010 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.tools.layoutlib.annotations;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Denotes that the class, method or field has its visibility relaxed so
- * that unit tests can access it.
- * <p/>
- * The <code>visibility</code> argument can be used to specific what the original
- * visibility should have been if it had not been made public or package-private for testing.
- * The default is to consider the element private.
- */
-@Retention(RetentionPolicy.SOURCE)
-public @interface VisibleForTesting {
- /**
- * Intended visibility if the element had not been made public or package-private for
- * testing.
- */
- enum Visibility {
- /** The element should be considered protected. */
- PROTECTED,
- /** The element should be considered package-private. */
- PACKAGE,
- /** The element should be considered private. */
- PRIVATE
- }
-
- /**
- * Intended visibility if the element had not been made public or package-private for testing.
- * If not specified, one should assume the element originally intended to be private.
- */
- Visibility visibility() default Visibility.PRIVATE;
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
deleted file mode 100644
index 01c940a..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
+++ /dev/null
@@ -1,418 +0,0 @@
-/*
- * Copyright (C) 2013 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.tools.layoutlib.create;
-
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.signature.SignatureReader;
-import org.objectweb.asm.signature.SignatureVisitor;
-import org.objectweb.asm.signature.SignatureWriter;
-
-/**
- * Provides the common code for RenameClassAdapter and RefactorClassAdapter. It
- * goes through the complete class and finds references to other classes. It
- * then calls {@link #renameInternalType(String)} to convert the className to
- * the new value, if need be.
- */
-public abstract class AbstractClassAdapter extends ClassVisitor {
-
- /**
- * Returns the new FQCN for the class, if the reference to this class needs
- * to be updated. Else, it returns the same string.
- * @param name Old FQCN
- * @return New FQCN if it needs to be renamed, else the old FQCN
- */
- abstract String renameInternalType(String name);
-
- public AbstractClassAdapter(ClassVisitor cv) {
- super(Main.ASM_VERSION, cv);
- }
-
- /**
- * Renames a type descriptor, e.g. "Lcom.package.MyClass;"
- * If the type doesn't need to be renamed, returns the input string as-is.
- */
- String renameTypeDesc(String desc) {
- if (desc == null) {
- return null;
- }
-
- return renameType(Type.getType(desc));
- }
-
- /**
- * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an
- * object element, e.g. "[Lcom.package.MyClass;"
- * If the type doesn't need to be renamed, returns the internal name of the input type.
- */
- String renameType(Type type) {
- if (type == null) {
- return null;
- }
-
- if (type.getSort() == Type.OBJECT) {
- String in = type.getInternalName();
- return "L" + renameInternalType(in) + ";";
- } else if (type.getSort() == Type.ARRAY) {
- StringBuilder sb = new StringBuilder();
- for (int n = type.getDimensions(); n > 0; n--) {
- sb.append('[');
- }
- sb.append(renameType(type.getElementType()));
- return sb.toString();
- }
- return type.getDescriptor();
- }
-
- /**
- * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an
- * object element, e.g. "[Lcom.package.MyClass;".
- * This is like renameType() except that it returns a Type object.
- * If the type doesn't need to be renamed, returns the input type object.
- */
- Type renameTypeAsType(Type type) {
- if (type == null) {
- return null;
- }
-
- if (type.getSort() == Type.OBJECT) {
- String in = type.getInternalName();
- String newIn = renameInternalType(in);
- if (!newIn.equals(in)) {
- return Type.getType("L" + newIn + ";");
- }
- } else if (type.getSort() == Type.ARRAY) {
- StringBuilder sb = new StringBuilder();
- for (int n = type.getDimensions(); n > 0; n--) {
- sb.append('[');
- }
- sb.append(renameType(type.getElementType()));
- return Type.getType(sb.toString());
- }
- return type;
- }
-
- /**
- * Renames a method descriptor, i.e. applies renameType to all arguments and to the
- * return value.
- */
- String renameMethodDesc(String desc) {
- if (desc == null) {
- return null;
- }
-
- Type[] args = Type.getArgumentTypes(desc);
-
- StringBuilder sb = new StringBuilder("(");
- for (Type arg : args) {
- String name = renameType(arg);
- sb.append(name);
- }
- sb.append(')');
-
- Type ret = Type.getReturnType(desc);
- String name = renameType(ret);
- sb.append(name);
-
- return sb.toString();
- }
-
-
- /**
- * Renames the ClassSignature handled by ClassVisitor.visit
- * or the MethodTypeSignature handled by ClassVisitor.visitMethod.
- */
- String renameTypeSignature(String sig) {
- if (sig == null) {
- return null;
- }
- SignatureReader reader = new SignatureReader(sig);
- SignatureWriter writer = new SignatureWriter();
- reader.accept(new RenameSignatureAdapter(writer));
- sig = writer.toString();
- return sig;
- }
-
-
- /**
- * Renames the FieldTypeSignature handled by ClassVisitor.visitField
- * or MethodVisitor.visitLocalVariable.
- */
- String renameFieldSignature(String sig) {
- return renameTypeSignature(sig);
- }
-
-
- //----------------------------------
- // Methods from the ClassAdapter
-
- @Override
- public void visit(int version, int access, String name, String signature,
- String superName, String[] interfaces) {
- name = renameInternalType(name);
- superName = renameInternalType(superName);
- signature = renameTypeSignature(signature);
- if (interfaces != null) {
- for (int i = 0; i < interfaces.length; ++i) {
- interfaces[i] = renameInternalType(interfaces[i]);
- }
- }
-
- super.visit(version, access, name, signature, superName, interfaces);
- }
-
- @Override
- public void visitInnerClass(String name, String outerName, String innerName, int access) {
- name = renameInternalType(name);
- outerName = renameInternalType(outerName);
- super.visitInnerClass(name, outerName, innerName, access);
- }
-
- @Override
- public void visitOuterClass(String owner, String name, String desc) {
- super.visitOuterClass(renameInternalType(owner), name, renameTypeDesc(desc));
- }
-
- @Override
- public MethodVisitor visitMethod(int access, String name, String desc,
- String signature, String[] exceptions) {
- desc = renameMethodDesc(desc);
- signature = renameTypeSignature(signature);
- MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
- return new RenameMethodAdapter(mw);
- }
-
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- desc = renameTypeDesc(desc);
- return super.visitAnnotation(desc, visible);
- }
-
- @Override
- public FieldVisitor visitField(int access, String name, String desc,
- String signature, Object value) {
- desc = renameTypeDesc(desc);
- return super.visitField(access, name, desc, signature, value);
- }
-
-
- //----------------------------------
-
- /**
- * A method visitor that renames all references from an old class name to a new class name.
- */
- public class RenameMethodAdapter extends MethodVisitor {
-
- /**
- * Creates a method visitor that renames all references from a given old name to a given new
- * name. The method visitor will also rename all inner classes.
- * The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass).
- */
- public RenameMethodAdapter(MethodVisitor mv) {
- super(Main.ASM_VERSION, mv);
- }
-
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- desc = renameTypeDesc(desc);
-
- return super.visitAnnotation(desc, visible);
- }
-
- @Override
- public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
- desc = renameTypeDesc(desc);
-
- return super.visitParameterAnnotation(parameter, desc, visible);
- }
-
- @Override
- public void visitTypeInsn(int opcode, String type) {
- // The type sometimes turns out to be a type descriptor. We try to detect it and fix.
- if (type.indexOf(';') > 0) {
- type = renameTypeDesc(type);
- } else {
- type = renameInternalType(type);
- }
- super.visitTypeInsn(opcode, type);
- }
-
- @Override
- public void visitFieldInsn(int opcode, String owner, String name, String desc) {
- owner = renameInternalType(owner);
- desc = renameTypeDesc(desc);
-
- super.visitFieldInsn(opcode, owner, name, desc);
- }
-
- @Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc,
- boolean itf) {
- // The owner sometimes turns out to be a type descriptor. We try to detect it and fix.
- if (owner.indexOf(';') > 0) {
- owner = renameTypeDesc(owner);
- } else {
- owner = renameInternalType(owner);
- }
- desc = renameMethodDesc(desc);
-
- super.visitMethodInsn(opcode, owner, name, desc, itf);
- }
-
- @Override
- public void visitLdcInsn(Object cst) {
- // If cst is a Type, this means the code is trying to pull the .class constant
- // for this class, so it needs to be renamed too.
- if (cst instanceof Type) {
- cst = renameTypeAsType((Type) cst);
- }
- super.visitLdcInsn(cst);
- }
-
- @Override
- public void visitMultiANewArrayInsn(String desc, int dims) {
- desc = renameTypeDesc(desc);
-
- super.visitMultiANewArrayInsn(desc, dims);
- }
-
- @Override
- public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
- type = renameInternalType(type);
-
- super.visitTryCatchBlock(start, end, handler, type);
- }
-
- @Override
- public void visitLocalVariable(String name, String desc, String signature,
- Label start, Label end, int index) {
- desc = renameTypeDesc(desc);
- signature = renameFieldSignature(signature);
-
- super.visitLocalVariable(name, desc, signature, start, end, index);
- }
-
- }
-
- //----------------------------------
-
- public class RenameSignatureAdapter extends SignatureVisitor {
-
- private final SignatureVisitor mSv;
-
- public RenameSignatureAdapter(SignatureVisitor sv) {
- super(Main.ASM_VERSION);
- mSv = sv;
- }
-
- @Override
- public void visitClassType(String name) {
- name = renameInternalType(name);
- mSv.visitClassType(name);
- }
-
- @Override
- public void visitInnerClassType(String name) {
- name = renameInternalType(name);
- mSv.visitInnerClassType(name);
- }
-
- @Override
- public SignatureVisitor visitArrayType() {
- SignatureVisitor sv = mSv.visitArrayType();
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public void visitBaseType(char descriptor) {
- mSv.visitBaseType(descriptor);
- }
-
- @Override
- public SignatureVisitor visitClassBound() {
- SignatureVisitor sv = mSv.visitClassBound();
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public void visitEnd() {
- mSv.visitEnd();
- }
-
- @Override
- public SignatureVisitor visitExceptionType() {
- SignatureVisitor sv = mSv.visitExceptionType();
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public void visitFormalTypeParameter(String name) {
- mSv.visitFormalTypeParameter(name);
- }
-
- @Override
- public SignatureVisitor visitInterface() {
- SignatureVisitor sv = mSv.visitInterface();
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public SignatureVisitor visitInterfaceBound() {
- SignatureVisitor sv = mSv.visitInterfaceBound();
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public SignatureVisitor visitParameterType() {
- SignatureVisitor sv = mSv.visitParameterType();
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public SignatureVisitor visitReturnType() {
- SignatureVisitor sv = mSv.visitReturnType();
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public SignatureVisitor visitSuperclass() {
- SignatureVisitor sv = mSv.visitSuperclass();
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public void visitTypeArgument() {
- mSv.visitTypeArgument();
- }
-
- @Override
- public SignatureVisitor visitTypeArgument(char wildcard) {
- SignatureVisitor sv = mSv.visitTypeArgument(wildcard);
- return new RenameSignatureAdapter(sv);
- }
-
- @Override
- public void visitTypeVariable(String name) {
- mSv.visitTypeVariable(name);
- }
-
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
deleted file mode 100644
index 11d4c81..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
+++ /dev/null
@@ -1,914 +0,0 @@
-/*
- * Copyright (C) 2008 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.tools.layoutlib.create;
-
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.Attribute;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.signature.SignatureReader;
-import org.objectweb.asm.signature.SignatureVisitor;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.regex.Pattern;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-/**
- * Analyzes the input JAR using the ASM java bytecode manipulation library
- * to list the desired classes and their dependencies.
- */
-public class AsmAnalyzer {
-
- // Note: a bunch of stuff has package-level access for unit tests. Consider it private.
-
- /** Output logger. */
- private final Log mLog;
- /** The input source JAR to parse. */
- private final List<String> mOsSourceJar;
- /** The generator to fill with the class list and dependency list. */
- private final AsmGenerator mGen;
- /** Keep all classes that derive from these one (these included). */
- private final String[] mDeriveFrom;
- /** Glob patterns of classes to keep, e.g. "com.foo.*" */
- private final String[] mIncludeGlobs;
- /** The set of classes to exclude.*/
- private final Set<String> mExcludedClasses;
- /** Glob patterns of files to keep as is. */
- private final String[] mIncludeFileGlobs;
- /** Internal names of classes that contain method calls that need to be rewritten. */
- private final Set<String> mReplaceMethodCallClasses = new HashSet<>();
-
- /**
- * Creates a new analyzer.
- *
- * @param log The log output.
- * @param osJarPath The input source JARs to parse.
- * @param gen The generator to fill with the class list and dependency list.
- * @param deriveFrom Keep all classes that derive from these one (these included).
- * @param includeGlobs Glob patterns of classes to keep, e.g. "com.foo.*"
- * ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is)
- * @param includeFileGlobs Glob patterns of files which are kept as is. This is only for files
- * not ending in .class.
- */
- public AsmAnalyzer(Log log, List<String> osJarPath, AsmGenerator gen,
- String[] deriveFrom, String[] includeGlobs, Set<String> excludeClasses,
- String[] includeFileGlobs) {
- mLog = log;
- mGen = gen;
- mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<String>();
- mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0];
- mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0];
- mExcludedClasses = excludeClasses;
- mIncludeFileGlobs = includeFileGlobs != null ? includeFileGlobs : new String[0];
- }
-
- /**
- * Starts the analysis using parameters from the constructor.
- * Fills the generator with classes & dependencies found.
- */
- public void analyze() throws IOException, LogAbortException {
-
- TreeMap<String, ClassReader> zipClasses = new TreeMap<>();
- Map<String, InputStream> filesFound = new TreeMap<>();
-
- parseZip(mOsSourceJar, zipClasses, filesFound);
- mLog.info("Found %d classes in input JAR%s.", zipClasses.size(),
- mOsSourceJar.size() > 1 ? "s" : "");
-
- Map<String, ClassReader> found = findIncludes(zipClasses);
- Map<String, ClassReader> deps = findDeps(zipClasses, found);
-
- if (mGen != null) {
- mGen.setKeep(found);
- mGen.setDeps(deps);
- mGen.setCopyFiles(filesFound);
- mGen.setRewriteMethodCallClasses(mReplaceMethodCallClasses);
- }
- }
-
- /**
- * Parses a JAR file and adds all the classes found to <code>classes</code>
- * and all other files to <code>filesFound</code>.
- *
- * @param classes The map of class name => ASM ClassReader. Class names are
- * in the form "android.view.View".
- * @param filesFound The map of file name => InputStream. The file name is
- * in the form "android/data/dataFile".
- */
- void parseZip(List<String> jarPathList, Map<String, ClassReader> classes,
- Map<String, InputStream> filesFound) throws IOException {
- if (classes == null || filesFound == null) {
- return;
- }
-
- Pattern[] includeFilePatterns = new Pattern[mIncludeFileGlobs.length];
- for (int i = 0; i < mIncludeFileGlobs.length; ++i) {
- includeFilePatterns[i] = getPatternFromGlob(mIncludeFileGlobs[i]);
- }
-
- for (String jarPath : jarPathList) {
- ZipFile zip = new ZipFile(jarPath);
- Enumeration<? extends ZipEntry> entries = zip.entries();
- ZipEntry entry;
- while (entries.hasMoreElements()) {
- entry = entries.nextElement();
- if (entry.getName().endsWith(".class")) {
- ClassReader cr = new ClassReader(zip.getInputStream(entry));
- String className = classReaderToClassName(cr);
- classes.put(className, cr);
- } else {
- for (Pattern includeFilePattern : includeFilePatterns) {
- if (includeFilePattern.matcher(entry.getName()).matches()) {
- filesFound.put(entry.getName(), zip.getInputStream(entry));
- break;
- }
- }
- }
- }
- }
-
- }
-
- /**
- * Utility that returns the fully qualified binary class name for a ClassReader.
- * E.g. it returns something like android.view.View.
- */
- static String classReaderToClassName(ClassReader classReader) {
- if (classReader == null) {
- return null;
- } else {
- return classReader.getClassName().replace('/', '.');
- }
- }
-
- /**
- * Utility that returns the fully qualified binary class name from a path-like FQCN.
- * E.g. it returns android.view.View from android/view/View.
- */
- static String internalToBinaryClassName(String className) {
- if (className == null) {
- return null;
- } else {
- return className.replace('/', '.');
- }
- }
-
- /**
- * Process the "includes" arrays.
- * <p/>
- * This updates the in_out_found map.
- */
- Map<String, ClassReader> findIncludes(Map<String, ClassReader> zipClasses)
- throws LogAbortException {
- TreeMap<String, ClassReader> found = new TreeMap<>();
-
- mLog.debug("Find classes to include.");
-
- for (String s : mIncludeGlobs) {
- findGlobs(s, zipClasses, found);
- }
- for (String s : mDeriveFrom) {
- findClassesDerivingFrom(s, zipClasses, found);
- }
-
- return found;
- }
-
-
- /**
- * Uses ASM to find the class reader for the given FQCN class name.
- * If found, insert it in the in_out_found map.
- * Returns the class reader object.
- */
- ClassReader findClass(String className, Map<String, ClassReader> zipClasses,
- Map<String, ClassReader> inOutFound) throws LogAbortException {
- ClassReader classReader = zipClasses.get(className);
- if (classReader == null) {
- throw new LogAbortException("Class %s not found by ASM in %s",
- className, mOsSourceJar);
- }
-
- inOutFound.put(className, classReader);
- return classReader;
- }
-
- /**
- * Insert in the inOutFound map all classes found in zipClasses that match the
- * given glob pattern.
- * <p/>
- * The glob pattern is not a regexp. It only accepts the "*" keyword to mean
- * "anything but a period". The "." and "$" characters match themselves.
- * The "**" keyword means everything including ".".
- * <p/>
- * Examples:
- * <ul>
- * <li>com.foo.* matches all classes in the package com.foo but NOT sub-packages.
- * <li>com.foo*.*$Event matches all internal Event classes in a com.foo*.* class.
- * </ul>
- */
- void findGlobs(String globPattern, Map<String, ClassReader> zipClasses,
- Map<String, ClassReader> inOutFound) throws LogAbortException {
-
- Pattern regexp = getPatternFromGlob(globPattern);
-
- for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
- String class_name = entry.getKey();
- if (regexp.matcher(class_name).matches() &&
- !mExcludedClasses.contains(getOuterClassName(class_name))) {
- findClass(class_name, zipClasses, inOutFound);
- }
- }
- }
-
- Pattern getPatternFromGlob(String globPattern) {
- // transforms the glob pattern in a regexp:
- // - escape "." with "\."
- // - replace "*" by "[^.]*"
- // - escape "$" with "\$"
- // - add end-of-line match $
- globPattern = globPattern.replaceAll("\\$", "\\\\\\$");
- globPattern = globPattern.replaceAll("\\.", "\\\\.");
- // prevent ** from being altered by the next rule, then process the * rule and finally
- // the real ** rule (which is now @)
- globPattern = globPattern.replaceAll("\\*\\*", "@");
- globPattern = globPattern.replaceAll("\\*", "[^.]*");
- globPattern = globPattern.replaceAll("@", ".*");
- globPattern += "$";
-
- return Pattern.compile(globPattern);
- }
-
- /**
- * Checks all the classes defined in the JarClassName instance and uses BCEL to
- * determine if they are derived from the given FQCN super class name.
- * Inserts the super class and all the class objects found in the map.
- */
- void findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses,
- Map<String, ClassReader> inOutFound) throws LogAbortException {
- if (mExcludedClasses.contains(getOuterClassName(super_name))) {
- return;
- }
- findClass(super_name, zipClasses, inOutFound);
-
- for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
- String className = entry.getKey();
- if (super_name.equals(className)) {
- continue;
- }
- ClassReader classReader = entry.getValue();
- ClassReader parent_cr = classReader;
- while (parent_cr != null) {
- String parent_name = internalToBinaryClassName(parent_cr.getSuperName());
- if (parent_name == null) {
- // not found
- break;
- } else if (super_name.equals(parent_name)) {
- inOutFound.put(className, classReader);
- break;
- }
- parent_cr = zipClasses.get(parent_name);
- }
- }
- }
-
- /**
- * Instantiates a new DependencyVisitor. Useful for unit tests.
- */
- DependencyVisitor getVisitor(Map<String, ClassReader> zipClasses,
- Map<String, ClassReader> inKeep,
- Map<String, ClassReader> outKeep,
- Map<String, ClassReader> inDeps,
- Map<String, ClassReader> outDeps) {
- return new DependencyVisitor(zipClasses, inKeep, outKeep, inDeps, outDeps);
- }
-
- /**
- * Finds all dependencies for all classes in keepClasses which are also
- * listed in zipClasses. Returns a map of all the dependencies found.
- */
- Map<String, ClassReader> findDeps(Map<String, ClassReader> zipClasses,
- Map<String, ClassReader> inOutKeepClasses) {
-
- TreeMap<String, ClassReader> deps = new TreeMap<>();
- TreeMap<String, ClassReader> new_deps = new TreeMap<>();
- TreeMap<String, ClassReader> new_keep = new TreeMap<>();
- TreeMap<String, ClassReader> temp = new TreeMap<>();
-
- DependencyVisitor visitor = getVisitor(zipClasses,
- inOutKeepClasses, new_keep,
- deps, new_deps);
-
- for (ClassReader cr : inOutKeepClasses.values()) {
- visitor.setClassName(cr.getClassName());
- cr.accept(visitor, 0 /* flags */);
- }
-
- while (new_deps.size() > 0 || new_keep.size() > 0) {
- deps.putAll(new_deps);
- inOutKeepClasses.putAll(new_keep);
-
- temp.clear();
- temp.putAll(new_deps);
- temp.putAll(new_keep);
- new_deps.clear();
- new_keep.clear();
- mLog.debug("Found %1$d to keep, %2$d dependencies.",
- inOutKeepClasses.size(), deps.size());
-
- for (ClassReader cr : temp.values()) {
- visitor.setClassName(cr.getClassName());
- cr.accept(visitor, 0 /* flags */);
- }
- }
-
- mLog.info("Found %1$d classes to keep, %2$d class dependencies.",
- inOutKeepClasses.size(), deps.size());
-
- return deps;
- }
-
- private String getOuterClassName(String className) {
- int pos = className.indexOf('$');
- if (pos > 0) {
- return className.substring(0, pos);
- }
- return className;
- }
-
- // ----------------------------------
-
- /**
- * Visitor to collect all the type dependencies from a class.
- */
- public class DependencyVisitor extends ClassVisitor {
-
- /** All classes found in the source JAR. */
- private final Map<String, ClassReader> mZipClasses;
- /** Classes from which dependencies are to be found. */
- private final Map<String, ClassReader> mInKeep;
- /** Dependencies already known. */
- private final Map<String, ClassReader> mInDeps;
- /** New dependencies found by this visitor. */
- private final Map<String, ClassReader> mOutDeps;
- /** New classes to keep as-is found by this visitor. */
- private final Map<String, ClassReader> mOutKeep;
-
- private String mClassName;
-
- /**
- * Creates a new visitor that will find all the dependencies for the visited class.
- * Types which are already in the zipClasses, keepClasses or inDeps are not marked.
- * New dependencies are marked in outDeps.
- *
- * @param zipClasses All classes found in the source JAR.
- * @param inKeep Classes from which dependencies are to be found.
- * @param inDeps Dependencies already known.
- * @param outDeps New dependencies found by this visitor.
- */
- public DependencyVisitor(Map<String, ClassReader> zipClasses,
- Map<String, ClassReader> inKeep,
- Map<String, ClassReader> outKeep,
- Map<String,ClassReader> inDeps,
- Map<String,ClassReader> outDeps) {
- super(Main.ASM_VERSION);
- mZipClasses = zipClasses;
- mInKeep = inKeep;
- mOutKeep = outKeep;
- mInDeps = inDeps;
- mOutDeps = outDeps;
- }
-
- private void setClassName(String className) {
- mClassName = className;
- }
-
- /**
- * Considers the given class name as a dependency.
- * If it does, add to the mOutDeps map.
- */
- public void considerName(String className) {
- if (className == null) {
- return;
- }
-
- className = internalToBinaryClassName(className);
-
- // exclude classes that have already been found or are marked to be excluded
- if (mInKeep.containsKey(className) ||
- mOutKeep.containsKey(className) ||
- mInDeps.containsKey(className) ||
- mOutDeps.containsKey(className) ||
- mExcludedClasses.contains(getOuterClassName(className))) {
- return;
- }
-
- // exclude classes that are not part of the JAR file being examined
- ClassReader cr = mZipClasses.get(className);
- if (cr == null) {
- return;
- }
-
- try {
- // exclude classes that are part of the default JRE (the one executing this program)
- if (className.startsWith("java.") ||
- getClass().getClassLoader().loadClass(className) != null) {
- return;
- }
- } catch (ClassNotFoundException e) {
- // ignore
- }
-
- // accept this class:
- // - android classes are added to dependencies
- // - non-android classes are added to the list of classes to keep as-is (they don't need
- // to be stubbed).
- if (className.contains("android")) { // TODO make configurable
- mOutDeps.put(className, cr);
- } else {
- mOutKeep.put(className, cr);
- }
- }
-
- /**
- * Considers this array of names using considerName().
- */
- public void considerNames(String[] classNames) {
- if (classNames != null) {
- for (String className : classNames) {
- considerName(className);
- }
- }
- }
-
- /**
- * Considers this signature or type signature by invoking the {@link SignatureVisitor}
- * on it.
- */
- public void considerSignature(String signature) {
- if (signature != null) {
- SignatureReader sr = new SignatureReader(signature);
- // SignatureReader.accept will call accessType so we don't really have
- // to differentiate where the signature comes from.
- sr.accept(new MySignatureVisitor());
- }
- }
-
- /**
- * Considers this {@link Type}. For arrays, the element type is considered.
- * If the type is an object, it's internal name is considered.
- */
- public void considerType(Type t) {
- if (t != null) {
- if (t.getSort() == Type.ARRAY) {
- t = t.getElementType();
- }
- if (t.getSort() == Type.OBJECT) {
- considerName(t.getInternalName());
- }
- }
- }
-
- /**
- * Considers a descriptor string. The descriptor is converted to a {@link Type}
- * and then considerType() is invoked.
- */
- public void considerDesc(String desc) {
- if (desc != null) {
- try {
- Type t = Type.getType(desc);
- considerType(t);
- } catch (ArrayIndexOutOfBoundsException e) {
- // ignore, not a valid type.
- }
- }
- }
-
- // ---------------------------------------------------
- // --- ClassVisitor, FieldVisitor
- // ---------------------------------------------------
-
- // Visits a class header
- @Override
- public void visit(int version, int access, String name,
- String signature, String superName, String[] interfaces) {
- // signature is the signature of this class. May be null if the class is not a generic
- // one, and does not extend or implement generic classes or interfaces.
-
- if (signature != null) {
- considerSignature(signature);
- }
-
- // superName is the internal of name of the super class (see getInternalName).
- // For interfaces, the super class is Object. May be null but only for the Object class.
- considerName(superName);
-
- // interfaces is the internal names of the class's interfaces (see getInternalName).
- // May be null.
- considerNames(interfaces);
- }
-
-
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- // desc is the class descriptor of the annotation class.
- considerDesc(desc);
- return new MyAnnotationVisitor();
- }
-
- @Override
- public void visitAttribute(Attribute attr) {
- // pass
- }
-
- // Visits the end of a class
- @Override
- public void visitEnd() {
- // pass
- }
-
- private class MyFieldVisitor extends FieldVisitor {
-
- public MyFieldVisitor() {
- super(Main.ASM_VERSION);
- }
-
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- // desc is the class descriptor of the annotation class.
- considerDesc(desc);
- return new MyAnnotationVisitor();
- }
-
- @Override
- public void visitAttribute(Attribute attr) {
- // pass
- }
-
- // Visits the end of a class
- @Override
- public void visitEnd() {
- // pass
- }
- }
-
- @Override
- public FieldVisitor visitField(int access, String name, String desc,
- String signature, Object value) {
- // desc is the field's descriptor (see Type).
- considerDesc(desc);
-
- // signature is the field's signature. May be null if the field's type does not use
- // generic types.
- considerSignature(signature);
-
- return new MyFieldVisitor();
- }
-
- @Override
- public void visitInnerClass(String name, String outerName, String innerName, int access) {
- // name is the internal name of an inner class (see getInternalName).
- considerName(name);
- }
-
- @Override
- public MethodVisitor visitMethod(int access, String name, String desc,
- String signature, String[] exceptions) {
- // desc is the method's descriptor (see Type).
- considerDesc(desc);
- // signature is the method's signature. May be null if the method parameters, return
- // type and exceptions do not use generic types.
- considerSignature(signature);
-
- return new MyMethodVisitor(mClassName);
- }
-
- @Override
- public void visitOuterClass(String owner, String name, String desc) {
- // pass
- }
-
- @Override
- public void visitSource(String source, String debug) {
- // pass
- }
-
-
- // ---------------------------------------------------
- // --- MethodVisitor
- // ---------------------------------------------------
-
- private class MyMethodVisitor extends MethodVisitor {
-
- private String mOwnerClass;
-
- public MyMethodVisitor(String ownerClass) {
- super(Main.ASM_VERSION);
- mOwnerClass = ownerClass;
- }
-
-
- @Override
- public AnnotationVisitor visitAnnotationDefault() {
- return new MyAnnotationVisitor();
- }
-
- @Override
- public void visitCode() {
- // pass
- }
-
- // field instruction
- @Override
- public void visitFieldInsn(int opcode, String owner, String name, String desc) {
- // owner is the class that declares the field.
- considerName(owner);
- // desc is the field's descriptor (see Type).
- considerDesc(desc);
- }
-
- @Override
- public void visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2) {
- // pass
- }
-
- @Override
- public void visitIincInsn(int var, int increment) {
- // pass -- an IINC instruction
- }
-
- @Override
- public void visitInsn(int opcode) {
- // pass -- a zero operand instruction
- }
-
- @Override
- public void visitIntInsn(int opcode, int operand) {
- // pass -- a single int operand instruction
- }
-
- @Override
- public void visitJumpInsn(int opcode, Label label) {
- // pass -- a jump instruction
- }
-
- @Override
- public void visitLabel(Label label) {
- // pass -- a label target
- }
-
- // instruction to load a constant from the stack
- @Override
- public void visitLdcInsn(Object cst) {
- if (cst instanceof Type) {
- considerType((Type) cst);
- }
- }
-
- @Override
- public void visitLineNumber(int line, Label start) {
- // pass
- }
-
- @Override
- public void visitLocalVariable(String name, String desc,
- String signature, Label start, Label end, int index) {
- // desc is the type descriptor of this local variable.
- considerDesc(desc);
- // signature is the type signature of this local variable. May be null if the local
- // variable type does not use generic types.
- considerSignature(signature);
- }
-
- @Override
- public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
- // pass -- a lookup switch instruction
- }
-
- @Override
- public void visitMaxs(int maxStack, int maxLocals) {
- // pass
- }
-
- // instruction that invokes a method
- @Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc,
- boolean itf) {
-
- // owner is the internal name of the method's owner class
- considerName(owner);
- // desc is the method's descriptor (see Type).
- considerDesc(desc);
-
-
- // Check if method needs to replaced by a call to a different method.
- if (ReplaceMethodCallsAdapter.isReplacementNeeded(owner, name, desc, mOwnerClass)) {
- mReplaceMethodCallClasses.add(mOwnerClass);
- }
- }
-
- // instruction multianewarray, whatever that is
- @Override
- public void visitMultiANewArrayInsn(String desc, int dims) {
-
- // desc an array type descriptor.
- considerDesc(desc);
- }
-
- @Override
- public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
- boolean visible) {
- // desc is the class descriptor of the annotation class.
- considerDesc(desc);
- return new MyAnnotationVisitor();
- }
-
- @Override
- public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
- // pass -- table switch instruction
-
- }
-
- @Override
- public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
- // type is the internal name of the type of exceptions handled by the handler,
- // or null to catch any exceptions (for "finally" blocks).
- considerName(type);
- }
-
- // type instruction
- @Override
- public void visitTypeInsn(int opcode, String type) {
- // type is the operand of the instruction to be visited. This operand must be the
- // internal name of an object or array class.
- considerName(type);
- }
-
- @Override
- public void visitVarInsn(int opcode, int var) {
- // pass -- local variable instruction
- }
- }
-
- private class MySignatureVisitor extends SignatureVisitor {
-
- public MySignatureVisitor() {
- super(Main.ASM_VERSION);
- }
-
- // ---------------------------------------------------
- // --- SignatureVisitor
- // ---------------------------------------------------
-
- private String mCurrentSignatureClass = null;
-
- // Starts the visit of a signature corresponding to a class or interface type
- @Override
- public void visitClassType(String name) {
- mCurrentSignatureClass = name;
- considerName(name);
- }
-
- // Visits an inner class
- @Override
- public void visitInnerClassType(String name) {
- if (mCurrentSignatureClass != null) {
- mCurrentSignatureClass += "$" + name;
- considerName(mCurrentSignatureClass);
- }
- }
-
- @Override
- public SignatureVisitor visitArrayType() {
- return new MySignatureVisitor();
- }
-
- @Override
- public void visitBaseType(char descriptor) {
- // pass -- a primitive type, ignored
- }
-
- @Override
- public SignatureVisitor visitClassBound() {
- return new MySignatureVisitor();
- }
-
- @Override
- public SignatureVisitor visitExceptionType() {
- return new MySignatureVisitor();
- }
-
- @Override
- public void visitFormalTypeParameter(String name) {
- // pass
- }
-
- @Override
- public SignatureVisitor visitInterface() {
- return new MySignatureVisitor();
- }
-
- @Override
- public SignatureVisitor visitInterfaceBound() {
- return new MySignatureVisitor();
- }
-
- @Override
- public SignatureVisitor visitParameterType() {
- return new MySignatureVisitor();
- }
-
- @Override
- public SignatureVisitor visitReturnType() {
- return new MySignatureVisitor();
- }
-
- @Override
- public SignatureVisitor visitSuperclass() {
- return new MySignatureVisitor();
- }
-
- @Override
- public SignatureVisitor visitTypeArgument(char wildcard) {
- return new MySignatureVisitor();
- }
-
- @Override
- public void visitTypeVariable(String name) {
- // pass
- }
-
- @Override
- public void visitTypeArgument() {
- // pass
- }
- }
-
-
- // ---------------------------------------------------
- // --- AnnotationVisitor
- // ---------------------------------------------------
-
- private class MyAnnotationVisitor extends AnnotationVisitor {
-
- public MyAnnotationVisitor() {
- super(Main.ASM_VERSION);
- }
-
- // Visits a primitive value of an annotation
- @Override
- public void visit(String name, Object value) {
- // value is the actual value, whose type must be Byte, Boolean, Character, Short,
- // Integer, Long, Float, Double, String or Type
- if (value instanceof Type) {
- considerType((Type) value);
- }
- }
-
- @Override
- public AnnotationVisitor visitAnnotation(String name, String desc) {
- // desc is the class descriptor of the nested annotation class.
- considerDesc(desc);
- return new MyAnnotationVisitor();
- }
-
- @Override
- public AnnotationVisitor visitArray(String name) {
- return new MyAnnotationVisitor();
- }
-
- @Override
- public void visitEnum(String name, String desc, String value) {
- // desc is the class descriptor of the enumeration class.
- considerDesc(desc);
- }
- }
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
deleted file mode 100644
index d59b419..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ /dev/null
@@ -1,468 +0,0 @@
-/*
- * Copyright (C) 2008 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.tools.layoutlib.create;
-
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.ClassWriter;
-
-import java.io.ByteArrayOutputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.jar.JarEntry;
-import java.util.jar.JarOutputStream;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-/**
- * Class that generates a new JAR from a list of classes, some of which are to be kept as-is
- * and some of which are to be stubbed partially or totally.
- */
-public class AsmGenerator {
-
- /** Output logger. */
- private final Log mLog;
- /** The path of the destination JAR to create. */
- private final String mOsDestJar;
- /** List of classes to inject in the final JAR from _this_ archive. */
- private final Class<?>[] mInjectClasses;
- /** All classes to output as-is, except if they have native methods. */
- private Map<String, ClassReader> mKeep;
- /** All dependencies that must be completely stubbed. */
- private Map<String, ClassReader> mDeps;
- /** All files that are to be copied as-is. */
- private Map<String, InputStream> mCopyFiles;
- /** All classes where certain method calls need to be rewritten. */
- private Set<String> mReplaceMethodCallsClasses;
- /** Counter of number of classes renamed during transform. */
- private int mRenameCount;
- /** FQCN Names of the classes to rename: map old-FQCN => new-FQCN */
- private final HashMap<String, String> mRenameClasses;
- /** FQCN Names of "old" classes that were NOT renamed. This starts with the full list of
- * old-FQCN to rename and they get erased as they get renamed. At the end, classes still
- * left here are not in the code base anymore and thus were not renamed. */
- private HashSet<String> mClassesNotRenamed;
- /** A map { FQCN => set { list of return types to delete from the FQCN } }. */
- private HashMap<String, Set<String>> mDeleteReturns;
- /** A map { FQCN => set { method names } } of methods to rewrite as delegates.
- * The special name {@link DelegateClassAdapter#ALL_NATIVES} can be used as in internal set. */
- private final HashMap<String, Set<String>> mDelegateMethods;
- /** FQCN Names of classes to refactor. All reference to old-FQCN will be updated to new-FQCN.
- * map old-FQCN => new-FQCN */
- private final HashMap<String, String> mRefactorClasses;
- /** Methods to inject. FQCN of class in which method should be injected => runnable that does
- * the injection. */
- private final Map<String, ICreateInfo.InjectMethodRunnable> mInjectedMethodsMap;
- /** A map { FQCN => set { field names } } which should be promoted to public visibility */
- private final Map<String, Set<String>> mPromotedFields;
- /** A list of classes to be promoted to public visibility */
- private final Set<String> mPromotedClasses;
-
- /**
- * Creates a new generator that can generate the output JAR with the stubbed classes.
- *
- * @param log Output logger.
- * @param osDestJar The path of the destination JAR to create.
- * @param createInfo Creation parameters. Must not be null.
- */
- public AsmGenerator(Log log, String osDestJar, ICreateInfo createInfo) {
- mLog = log;
- mOsDestJar = osDestJar;
- ArrayList<Class<?>> injectedClasses =
- new ArrayList<>(Arrays.asList(createInfo.getInjectedClasses()));
- // Search for and add anonymous inner classes also.
- ListIterator<Class<?>> iter = injectedClasses.listIterator();
- while (iter.hasNext()) {
- Class<?> clazz = iter.next();
- try {
- int i = 1;
- while(i < 100) {
- iter.add(Class.forName(clazz.getName() + "$" + i));
- i++;
- }
- } catch (ClassNotFoundException ignored) {
- // Expected.
- }
- }
- mInjectClasses = injectedClasses.toArray(new Class<?>[0]);
-
- // Create the map/set of methods to change to delegates
- mDelegateMethods = new HashMap<>();
- addToMap(createInfo.getDelegateMethods(), mDelegateMethods);
-
- for (String className : createInfo.getDelegateClassNatives()) {
- className = binaryToInternalClassName(className);
- Set<String> methods = mDelegateMethods.get(className);
- if (methods == null) {
- methods = new HashSet<>();
- mDelegateMethods.put(className, methods);
- }
- methods.add(DelegateClassAdapter.ALL_NATIVES);
- }
-
- // Create the map of classes to rename.
- mRenameClasses = new HashMap<>();
- mClassesNotRenamed = new HashSet<>();
- String[] renameClasses = Stream.concat(
- Arrays.stream(createInfo.getRenamedClasses()),
- Arrays.stream(createInfo.getRefactoredClasses()))
- .toArray(String[]::new);
- int n = renameClasses.length;
- for (int i = 0; i < n; i += 2) {
- assert i + 1 < n;
- // The ASM class names uses "/" separators, whereas regular FQCN use "."
- String oldFqcn = binaryToInternalClassName(renameClasses[i]);
- String newFqcn = binaryToInternalClassName(renameClasses[i + 1]);
- mRenameClasses.put(oldFqcn, newFqcn);
- mClassesNotRenamed.add(oldFqcn);
- }
-
- // Create a map of classes to be refactored.
- mRefactorClasses = new HashMap<>();
- String[] refactorClasses = Stream.concat(
- Arrays.stream(createInfo.getJavaPkgClasses()),
- Arrays.stream(createInfo.getRefactoredClasses()))
- .toArray(String[]::new);
- n = refactorClasses.length;
- for (int i = 0; i < n; i += 2) {
- assert i + 1 < n;
- String oldFqcn = binaryToInternalClassName(refactorClasses[i]);
- String newFqcn = binaryToInternalClassName(refactorClasses[i + 1]);
- mRefactorClasses.put(oldFqcn, newFqcn);
- }
-
- // create the map of renamed class -> return type of method to delete.
- mDeleteReturns = new HashMap<>();
- String[] deleteReturns = createInfo.getDeleteReturns();
- Set<String> returnTypes = null;
- String renamedClass = null;
- for (String className : deleteReturns) {
- // if we reach the end of a section, add it to the main map
- if (className == null) {
- if (returnTypes != null) {
- mDeleteReturns.put(renamedClass, returnTypes);
- }
-
- renamedClass = null;
- continue;
- }
-
- // if the renamed class is null, this is the beginning of a section
- if (renamedClass == null) {
- renamedClass = binaryToInternalClassName(className);
- continue;
- }
-
- // just a standard return type, we add it to the list.
- if (returnTypes == null) {
- returnTypes = new HashSet<>();
- }
- returnTypes.add(binaryToInternalClassName(className));
- }
-
- mPromotedFields = new HashMap<>();
- addToMap(createInfo.getPromotedFields(), mPromotedFields);
-
- mInjectedMethodsMap = createInfo.getInjectedMethodsMap();
-
- mPromotedClasses =
- Arrays.stream(createInfo.getPromotedClasses()).collect(Collectors.toSet());
- }
-
- /**
- * For each value in the array, split the value on '#' and add the parts to the map as key
- * and value.
- */
- private void addToMap(String[] entries, Map<String, Set<String>> map) {
- for (String entry : entries) {
- int pos = entry.indexOf('#');
- if (pos <= 0 || pos >= entry.length() - 1) {
- return;
- }
- String className = binaryToInternalClassName(entry.substring(0, pos));
- String methodOrFieldName = entry.substring(pos + 1);
- Set<String> set = map.get(className);
- if (set == null) {
- set = new HashSet<>();
- map.put(className, set);
- }
- set.add(methodOrFieldName);
- }
- }
-
- /**
- * Returns the list of classes that have not been renamed yet.
- * <p/>
- * The names are "internal class names" rather than FQCN, i.e. they use "/" instead "."
- * as package separators.
- */
- public Set<String> getClassesNotRenamed() {
- return mClassesNotRenamed;
- }
-
- /**
- * Utility that returns the internal ASM class name from a fully qualified binary class
- * name. E.g. it returns android/view/View from android.view.View.
- */
- String binaryToInternalClassName(String className) {
- if (className == null) {
- return null;
- } else {
- return className.replace('.', '/');
- }
- }
-
- /** Sets the map of classes to output as-is, except if they have native methods */
- public void setKeep(Map<String, ClassReader> keep) {
- mKeep = keep;
- }
-
- /** Sets the map of dependencies that must be completely stubbed */
- public void setDeps(Map<String, ClassReader> deps) {
- mDeps = deps;
- }
-
- /** Sets the map of files to output as-is. */
- public void setCopyFiles(Map<String, InputStream> copyFiles) {
- mCopyFiles = copyFiles;
- }
-
- public void setRewriteMethodCallClasses(Set<String> rewriteMethodCallClasses) {
- mReplaceMethodCallsClasses = rewriteMethodCallClasses;
- }
-
- /** Generates the final JAR */
- public void generate() throws IOException {
- TreeMap<String, byte[]> all = new TreeMap<>();
-
- for (Class<?> clazz : mInjectClasses) {
- String name = classToEntryPath(clazz);
- InputStream is = ClassLoader.getSystemResourceAsStream(name);
- ClassReader cr = new ClassReader(is);
- byte[] b = transform(cr, true);
- name = classNameToEntryPath(transformName(cr.getClassName()));
- all.put(name, b);
- }
-
- for (Entry<String, ClassReader> entry : mDeps.entrySet()) {
- ClassReader cr = entry.getValue();
- byte[] b = transform(cr, true);
- String name = classNameToEntryPath(transformName(cr.getClassName()));
- all.put(name, b);
- }
-
- for (Entry<String, ClassReader> entry : mKeep.entrySet()) {
- ClassReader cr = entry.getValue();
- byte[] b = transform(cr, true);
- String name = classNameToEntryPath(transformName(cr.getClassName()));
- all.put(name, b);
- }
-
- for (Entry<String, InputStream> entry : mCopyFiles.entrySet()) {
- try {
- byte[] b = inputStreamToByteArray(entry.getValue());
- all.put(entry.getKey(), b);
- } catch (IOException e) {
- // Ignore.
- }
-
- }
- mLog.info("# deps classes: %d", mDeps.size());
- mLog.info("# keep classes: %d", mKeep.size());
- mLog.info("# renamed : %d", mRenameCount);
-
- createJar(new FileOutputStream(mOsDestJar), all);
- mLog.info("Created JAR file %s", mOsDestJar);
- }
-
- /**
- * Writes the JAR file.
- *
- * @param outStream The file output stream were to write the JAR.
- * @param all The map of all classes to output.
- * @throws IOException if an I/O error has occurred
- */
- void createJar(FileOutputStream outStream, Map<String,byte[]> all) throws IOException {
- JarOutputStream jar = new JarOutputStream(outStream);
- for (Entry<String, byte[]> entry : all.entrySet()) {
- String name = entry.getKey();
- JarEntry jar_entry = new JarEntry(name);
- jar.putNextEntry(jar_entry);
- jar.write(entry.getValue());
- jar.closeEntry();
- }
- jar.flush();
- jar.close();
- }
-
- /**
- * Utility method that converts a fully qualified java name into a JAR entry path
- * e.g. for the input "android.view.View" it returns "android/view/View.class"
- */
- String classNameToEntryPath(String className) {
- return className.replace('.', '/').concat(".class");
- }
-
- /**
- * Utility method to get the JAR entry path from a Class name.
- * e.g. it returns something like "com/foo/OuterClass$InnerClass1$InnerClass2.class"
- */
- private String classToEntryPath(Class<?> clazz) {
- return classNameToEntryPath(clazz.getName());
- }
-
- /**
- * Transforms a class.
- * <p/>
- * There are 3 kind of transformations:
- *
- * 1- For "mock" dependencies classes, we want to remove all code from methods and replace
- * by a stub. Native methods must be implemented with this stub too. Abstract methods are
- * left intact. Modified classes must be overridable (non-private, non-final).
- * Native methods must be made non-final, non-private.
- *
- * 2- For "keep" classes, we want to rewrite all native methods as indicated above.
- * If a class has native methods, it must also be made non-private, non-final.
- *
- * Note that unfortunately static methods cannot be changed to non-static (since static and
- * non-static are invoked differently.)
- */
- byte[] transform(ClassReader cr, boolean stubNativesOnly) {
-
- boolean hasNativeMethods = hasNativeMethods(cr);
-
- // Get the class name, as an internal name (e.g. com/android/SomeClass$InnerClass)
- String className = cr.getClassName();
-
- String newName = transformName(className);
- // transformName returns its input argument if there's no need to rename the class
- if (!newName.equals(className)) {
- mRenameCount++;
- // This class is being renamed, so remove it from the list of classes not renamed.
- mClassesNotRenamed.remove(className);
- }
-
- mLog.debug("Transform %s%s%s%s", className,
- newName.equals(className) ? "" : " (renamed to " + newName + ")",
- hasNativeMethods ? " -- has natives" : "",
- stubNativesOnly ? " -- stub natives only" : "");
-
- // Rewrite the new class from scratch, without reusing the constant pool from the
- // original class reader.
- ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
-
- ClassVisitor cv = cw;
-
- // FIXME Generify
- if ("android/content/res/Resources".equals(className)) {
- cv = new FieldInjectorAdapter(cv);
- }
- if (mReplaceMethodCallsClasses.contains(className)) {
- cv = new ReplaceMethodCallsAdapter(cv, className);
- }
-
- cv = new RefactorClassAdapter(cv, mRefactorClasses);
- if (!newName.equals(className)) {
- cv = new RenameClassAdapter(cv, className, newName);
- }
-
- String binaryNewName = newName.replace('/', '.');
- if (mInjectedMethodsMap.keySet().contains(binaryNewName)) {
- cv = new InjectMethodsAdapter(cv, mInjectedMethodsMap.get(binaryNewName));
- }
- cv = new TransformClassAdapter(mLog, Collections.emptySet(), mDeleteReturns.get(className),
- newName, cv, stubNativesOnly);
-
- Set<String> delegateMethods = mDelegateMethods.get(className);
- if (delegateMethods != null && !delegateMethods.isEmpty()) {
- // If delegateMethods only contains one entry ALL_NATIVES and the class is
- // known to have no native methods, just skip this step.
- if (hasNativeMethods ||
- !(delegateMethods.size() == 1 &&
- delegateMethods.contains(DelegateClassAdapter.ALL_NATIVES))) {
- cv = new DelegateClassAdapter(mLog, cv, className, delegateMethods);
- }
- }
-
- Set<String> promoteFields = mPromotedFields.get(className);
- if (promoteFields != null && !promoteFields.isEmpty()) {
- cv = new PromoteFieldClassAdapter(cv, promoteFields);
- }
- if (!mPromotedClasses.isEmpty()) {
- cv = new PromoteClassClassAdapter(cv, mPromotedClasses);
- }
- cr.accept(cv, 0);
-
- return cw.toByteArray();
- }
-
- /**
- * Should this class be renamed, this returns the new name. Otherwise it returns the
- * original name.
- *
- * @param className The internal ASM name of the class that may have to be renamed
- * @return A new transformed name or the original input argument.
- */
- String transformName(String className) {
- String newName = mRenameClasses.get(className);
- if (newName != null) {
- return newName;
- }
- int pos = className.indexOf('$');
- if (pos > 0) {
- // Is this an inner class of a renamed class?
- String base = className.substring(0, pos);
- newName = mRenameClasses.get(base);
- if (newName != null) {
- return newName + className.substring(pos);
- }
- }
-
- return className;
- }
-
- /**
- * Returns true if a class has any native methods.
- */
- boolean hasNativeMethods(ClassReader cr) {
- ClassHasNativeVisitor cv = new ClassHasNativeVisitor();
- cr.accept(cv, 0);
- return cv.hasNativeMethods();
- }
-
- private byte[] inputStreamToByteArray(InputStream is) throws IOException {
- ByteArrayOutputStream buffer = new ByteArrayOutputStream();
- byte[] data = new byte[8192]; // 8KB
- int n;
- while ((n = is.read(data, 0, data.length)) != -1) {
- buffer.write(data, 0, n);
- }
- return buffer.toByteArray();
- }
-
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java
deleted file mode 100644
index 4748a7c..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2008 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.tools.layoutlib.create;
-
-import com.android.tools.layoutlib.annotations.VisibleForTesting;
-import com.android.tools.layoutlib.annotations.VisibleForTesting.Visibility;
-
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.Attribute;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-
-/**
- * Indicates if a class contains any native methods.
- */
-public class ClassHasNativeVisitor extends ClassVisitor {
- public ClassHasNativeVisitor() {
- super(Main.ASM_VERSION);
- }
-
- private boolean mHasNativeMethods = false;
-
- public boolean hasNativeMethods() {
- return mHasNativeMethods;
- }
-
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected void setHasNativeMethods(boolean hasNativeMethods, String methodName) {
- mHasNativeMethods = hasNativeMethods;
- }
-
- @Override
- public void visit(int version, int access, String name, String signature,
- String superName, String[] interfaces) {
- // pass
- }
-
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- // pass
- return null;
- }
-
- @Override
- public void visitAttribute(Attribute attr) {
- // pass
- }
-
- @Override
- public void visitEnd() {
- // pass
- }
-
- @Override
- public FieldVisitor visitField(int access, String name, String desc,
- String signature, Object value) {
- // pass
- return null;
- }
-
- @Override
- public void visitInnerClass(String name, String outerName,
- String innerName, int access) {
- // pass
- }
-
- @Override
- public MethodVisitor visitMethod(int access, String name, String desc,
- String signature, String[] exceptions) {
- if ((access & Opcodes.ACC_NATIVE) != 0) {
- setHasNativeMethods(true, name);
- }
- return null;
- }
-
- @Override
- public void visitOuterClass(String owner, String name, String desc) {
- // pass
- }
-
- @Override
- public void visitSource(String source, String debug) {
- // pass
- }
-
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
deleted file mode 100644
index cb0bc6d..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ /dev/null
@@ -1,363 +0,0 @@
-/*
- * Copyright (C) 2008 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.tools.layoutlib.create;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-import com.android.tools.layoutlib.java.AutoCloseable;
-import com.android.tools.layoutlib.java.Charsets;
-import com.android.tools.layoutlib.java.IntegralToString;
-import com.android.tools.layoutlib.java.LinkedHashMap_Delegate;
-import com.android.tools.layoutlib.java.Objects;
-import com.android.tools.layoutlib.java.System_Delegate;
-import com.android.tools.layoutlib.java.UnsafeByteSequence;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Describes the work to be done by {@link AsmGenerator}.
- */
-public final class CreateInfo implements ICreateInfo {
-
- @Override
- public Class<?>[] getInjectedClasses() {
- return INJECTED_CLASSES;
- }
-
- @Override
- public String[] getDelegateMethods() {
- return DELEGATE_METHODS;
- }
-
- @Override
- public String[] getDelegateClassNatives() {
- return DELEGATE_CLASS_NATIVES;
- }
-
- @Override
- public String[] getRenamedClasses() {
- return RENAMED_CLASSES;
- }
-
- @Override
- public String[] getDeleteReturns() {
- return DELETE_RETURNS;
- }
-
- @Override
- public String[] getJavaPkgClasses() {
- return JAVA_PKG_CLASSES;
- }
-
- @Override
- public String[] getRefactoredClasses() {
- return REFACTOR_CLASSES;
- }
-
- @Override
- public Set<String> getExcludedClasses() {
- String[] refactoredClasses = getJavaPkgClasses();
- int count = refactoredClasses.length / 2 + EXCLUDED_CLASSES.length;
- Set<String> excludedClasses = new HashSet<>(count);
- for (int i = 0; i < refactoredClasses.length; i+=2) {
- excludedClasses.add(refactoredClasses[i]);
- }
- excludedClasses.addAll(Arrays.asList(EXCLUDED_CLASSES));
- return excludedClasses;
- }
-
- @Override
- public String[] getPromotedFields() {
- return PROMOTED_FIELDS;
- }
-
- @Override
- public String[] getPromotedClasses() {
- return PROMOTED_CLASSES;
- }
-
- @Override
- public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
- return INJECTED_METHODS;
- }
-
- //-----
-
- /**
- * The list of class from layoutlib_create to inject in layoutlib.
- */
- private final static Class<?>[] INJECTED_CLASSES = new Class<?>[] {
- OverrideMethod.class,
- MethodListener.class,
- MethodAdapter.class,
- ICreateInfo.class,
- CreateInfo.class,
- LayoutlibDelegate.class,
- InjectMethodRunnable.class,
- InjectMethodRunnables.class,
- /* Java package classes */
- IntegralToString.class,
- UnsafeByteSequence.class,
- System_Delegate.class,
- LinkedHashMap_Delegate.class,
- };
-
- /**
- * The list of methods to rewrite as delegates.
- */
- public final static String[] DELEGATE_METHODS = new String[] {
- "android.app.Fragment#instantiate", //(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/app/Fragment;",
- "android.content.res.Resources#getAnimation",
- "android.content.res.Resources#getBoolean",
- "android.content.res.Resources#getColor",
- "android.content.res.Resources#getColorStateList",
- "android.content.res.Resources#getDimension",
- "android.content.res.Resources#getDimensionPixelOffset",
- "android.content.res.Resources#getDimensionPixelSize",
- "android.content.res.Resources#getDrawable",
- "android.content.res.Resources#getFont",
- "android.content.res.Resources#getIntArray",
- "android.content.res.Resources#getInteger",
- "android.content.res.Resources#getLayout",
- "android.content.res.Resources#getQuantityString",
- "android.content.res.Resources#getQuantityText",
- "android.content.res.Resources#getResourceEntryName",
- "android.content.res.Resources#getResourceName",
- "android.content.res.Resources#getResourcePackageName",
- "android.content.res.Resources#getResourceTypeName",
- "android.content.res.Resources#getString",
- "android.content.res.Resources#getStringArray",
- "android.content.res.Resources#getText",
- "android.content.res.Resources#getTextArray",
- "android.content.res.Resources#getValue",
- "android.content.res.Resources#getXml",
- "android.content.res.Resources#loadXmlResourceParser",
- "android.content.res.Resources#obtainAttributes",
- "android.content.res.Resources#obtainTypedArray",
- "android.content.res.Resources#openRawResource",
- "android.content.res.Resources#openRawResourceFd",
- "android.content.res.Resources$Theme#obtainStyledAttributes",
- "android.content.res.Resources$Theme#resolveAttribute",
- "android.content.res.Resources$Theme#resolveAttributes",
- "android.content.res.AssetManager#newTheme",
- "android.content.res.AssetManager#deleteTheme",
- "android.content.res.AssetManager#getAssignedPackageIdentifiers",
- "android.content.res.TypedArray#getValueAt",
- "android.content.res.TypedArray#obtain",
- "android.graphics.BitmapFactory#finishDecode",
- "android.graphics.BitmapFactory#setDensityFromOptions",
- "android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#useLastSeenTarget",
- "android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#onDraw",
- "android.graphics.drawable.GradientDrawable#buildRing",
- "android.graphics.FontFamily#addFont",
- "android.graphics.Typeface#getSystemFontConfigLocation",
- "android.graphics.Typeface#makeFamilyFromParsed",
- "android.os.Handler#sendMessageAtTime",
- "android.os.HandlerThread#run",
- "android.preference.Preference#getView",
- "android.text.format.DateFormat#is24HourFormat",
- "android.text.Hyphenator#getSystemHyphenatorLocation",
- "android.util.Xml#newPullParser",
- "android.view.Choreographer#getInstance",
- "android.view.Choreographer#getRefreshRate",
- "android.view.Choreographer#scheduleVsyncLocked",
- "android.view.Display#updateDisplayInfoLocked",
- "android.view.Display#getWindowManager",
- "android.view.HandlerActionQueue#postDelayed",
- "android.view.LayoutInflater#rInflate",
- "android.view.LayoutInflater#parseInclude",
- "android.view.View#getWindowToken",
- "android.view.View#isInEditMode",
- "android.view.ViewRootImpl#isInTouchMode",
- "android.view.WindowManagerGlobal#getWindowManagerService",
- "android.view.inputmethod.InputMethodManager#getInstance",
- "android.view.MenuInflater#registerMenu",
- "android.view.RenderNode#getMatrix",
- "android.view.RenderNode#nCreate",
- "android.view.RenderNode#nGetNativeFinalizer",
- "android.view.RenderNode#nSetElevation",
- "android.view.RenderNode#nGetElevation",
- "android.view.RenderNode#nSetTranslationX",
- "android.view.RenderNode#nGetTranslationX",
- "android.view.RenderNode#nSetTranslationY",
- "android.view.RenderNode#nGetTranslationY",
- "android.view.RenderNode#nSetTranslationZ",
- "android.view.RenderNode#nGetTranslationZ",
- "android.view.RenderNode#nSetRotation",
- "android.view.RenderNode#nGetRotation",
- "android.view.RenderNode#nSetLeft",
- "android.view.RenderNode#nSetTop",
- "android.view.RenderNode#nSetRight",
- "android.view.RenderNode#nSetBottom",
- "android.view.RenderNode#nSetLeftTopRightBottom",
- "android.view.RenderNode#nSetPivotX",
- "android.view.RenderNode#nGetPivotX",
- "android.view.RenderNode#nSetPivotY",
- "android.view.RenderNode#nGetPivotY",
- "android.view.RenderNode#nSetScaleX",
- "android.view.RenderNode#nGetScaleX",
- "android.view.RenderNode#nSetScaleY",
- "android.view.RenderNode#nGetScaleY",
- "android.view.RenderNode#nIsPivotExplicitlySet",
- "android.view.PointerIcon#loadResource",
- "android.view.ViewGroup#drawChild",
- "com.android.internal.view.menu.MenuBuilder#createNewMenuItem",
- "com.android.internal.util.XmlUtils#convertValueToInt",
- "dalvik.system.VMRuntime#newUnpaddedArray",
- "libcore.io.MemoryMappedFile#mmapRO",
- "libcore.io.MemoryMappedFile#close",
- "libcore.io.MemoryMappedFile#bigEndianIterator",
- "libcore.util.NativeAllocationRegistry#applyFreeFunction",
- };
-
- /**
- * The list of classes on which to delegate all native methods.
- */
- public final static String[] DELEGATE_CLASS_NATIVES = new String[] {
- "android.animation.PropertyValuesHolder",
- "android.graphics.BaseCanvas",
- "android.graphics.Bitmap",
- "android.graphics.BitmapFactory",
- "android.graphics.BitmapShader",
- "android.graphics.BlurMaskFilter",
- "android.graphics.Canvas",
- "android.graphics.ColorFilter",
- "android.graphics.ColorMatrixColorFilter",
- "android.graphics.ComposePathEffect",
- "android.graphics.ComposeShader",
- "android.graphics.CornerPathEffect",
- "android.graphics.DashPathEffect",
- "android.graphics.DiscretePathEffect",
- "android.graphics.DrawFilter",
- "android.graphics.EmbossMaskFilter",
- "android.graphics.FontFamily",
- "android.graphics.LightingColorFilter",
- "android.graphics.LinearGradient",
- "android.graphics.MaskFilter",
- "android.graphics.Matrix",
- "android.graphics.NinePatch",
- "android.graphics.Paint",
- "android.graphics.PaintFlagsDrawFilter",
- "android.graphics.Path",
- "android.graphics.PathDashPathEffect",
- "android.graphics.PathEffect",
- "android.graphics.PathMeasure",
- "android.graphics.PorterDuffColorFilter",
- "android.graphics.RadialGradient",
- "android.graphics.Region",
- "android.graphics.Shader",
- "android.graphics.SumPathEffect",
- "android.graphics.SweepGradient",
- "android.graphics.Typeface",
- "android.graphics.drawable.AnimatedVectorDrawable",
- "android.graphics.drawable.VectorDrawable",
- "android.os.SystemClock",
- "android.os.SystemProperties",
- "android.text.AndroidBidi",
- "android.text.StaticLayout",
- "android.util.PathParser",
- "android.view.Display",
- "com.android.internal.util.VirtualRefBasePtr",
- "com.android.internal.view.animation.NativeInterpolatorFactoryHelper",
- "libcore.icu.ICU",
- };
-
- /**
- * The list of classes to rename, must be an even list: the binary FQCN
- * of class to replace followed by the new FQCN.
- */
- private final static String[] RENAMED_CLASSES =
- new String[] {
- "android.os.ServiceManager", "android.os._Original_ServiceManager",
- "android.view.textservice.TextServicesManager", "android.view.textservice._Original_TextServicesManager",
- "android.util.LruCache", "android.util._Original_LruCache",
- "android.view.SurfaceView", "android.view._Original_SurfaceView",
- "android.view.accessibility.AccessibilityManager", "android.view.accessibility._Original_AccessibilityManager",
- "android.webkit.WebView", "android.webkit._Original_WebView",
- };
-
- /**
- * The list of class references to update, must be an even list: the binary
- * FQCN of class to replace followed by the new FQCN. The classes to
- * replace are to be excluded from the output.
- */
- private final static String[] JAVA_PKG_CLASSES =
- new String[] {
- "java.nio.charset.Charsets", "java.nio.charset.StandardCharsets",
- "java.lang.IntegralToString", "com.android.tools.layoutlib.java.IntegralToString",
- "java.lang.UnsafeByteSequence", "com.android.tools.layoutlib.java.UnsafeByteSequence",
- // Use android.icu.text versions of DateFormat and SimpleDateFormat since the
- // original ones do not match the Android implementation
- "java.text.DateFormat", "android.icu.text.DateFormat",
- "java.text.SimpleDateFormat", "android.icu.text.SimpleDateFormat",
- };
-
- /**
- * List of classes to refactor. This is similar to combining {@link #getRenamedClasses()} and
- * {@link #getJavaPkgClasses()}.
- * Classes included here will be renamed and then all their references in any other classes
- * will be also modified.
- * FQCN of class to refactor followed by its new FQCN.
- */
- private final static String[] REFACTOR_CLASSES =
- new String[] {
- "android.os.Build", "android.os._Original_Build",
- };
-
- private final static String[] EXCLUDED_CLASSES =
- new String[] {
- "android.preference.PreferenceActivity",
- "org.kxml2.io.KXmlParser",
- };
-
- /**
- * List of fields for which we will update the visibility to be public. This is sometimes
- * needed when access from the delegate classes is needed.
- */
- private final static String[] PROMOTED_FIELDS = new String[] {
- "android.graphics.drawable.VectorDrawable#mVectorState",
- "android.view.Choreographer#mLastFrameTimeNanos",
- "android.graphics.FontFamily#mBuilderPtr",
- "android.graphics.Typeface#sDynamicTypefaceCache"
- };
-
- /**
- * List of classes to be promoted to public visibility. Prefer using PROMOTED_FIELDS to this
- * if possible.
- */
- private final static String[] PROMOTED_CLASSES = new String[] {
- };
-
- /**
- * List of classes for which the methods returning them should be deleted.
- * The array contains a list of null terminated section starting with the name of the class
- * to rename in which the methods are deleted, followed by a list of return types identifying
- * the methods to delete.
- */
- private final static String[] DELETE_RETURNS =
- new String[] {
- null }; // separator, for next class/methods list.
-
- private final static Map<String, InjectMethodRunnable> INJECTED_METHODS =
- new HashMap<String, InjectMethodRunnable>(1) {{
- put("android.content.Context",
- InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER);
- }};
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
deleted file mode 100644
index cbb3a8a..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2010 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.tools.layoutlib.create;
-
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-
-import java.util.Set;
-
-/**
- * A {@link DelegateClassAdapter} can transform some methods from a class into
- * delegates that defer the call to an associated delegate class.
- * <p/>
- * This is used to override specific methods and or all native methods in classes.
- */
-public class DelegateClassAdapter extends ClassVisitor {
-
- /** Suffix added to original methods. */
- private static final String ORIGINAL_SUFFIX = "_Original";
- private static final String CONSTRUCTOR = "<init>";
- private static final String CLASS_INIT = "<clinit>";
-
- public static final String ALL_NATIVES = "<<all_natives>>";
-
- private final String mClassName;
- private final Set<String> mDelegateMethods;
- private final Log mLog;
- private boolean mIsStaticInnerClass;
-
- /**
- * Creates a new {@link DelegateClassAdapter} that can transform some methods
- * from a class into delegates that defer the call to an associated delegate class.
- * <p/>
- * This is used to override specific methods and or all native methods in classes.
- *
- * @param log The logger object. Must not be null.
- * @param cv the class visitor to which this adapter must delegate calls.
- * @param className The internal class name of the class to visit,
- * e.g. <code>com/android/SomeClass$InnerClass</code>.
- * @param delegateMethods The set of method names to modify and/or the
- * special constant {@link #ALL_NATIVES} to convert all native methods.
- */
- public DelegateClassAdapter(Log log,
- ClassVisitor cv,
- String className,
- Set<String> delegateMethods) {
- super(Main.ASM_VERSION, cv);
- mLog = log;
- mClassName = className;
- mDelegateMethods = delegateMethods;
- // If this is an inner class, by default, we assume it's static. If it's not we will detect
- // by looking at the fields (see visitField)
- mIsStaticInnerClass = className.contains("$");
- }
-
- //----------------------------------
- // Methods from the ClassAdapter
-
- @Override
- public FieldVisitor visitField(int access, String name, String desc, String signature,
- Object value) {
- if (mIsStaticInnerClass && "this$0".equals(name)) {
- // Having a "this$0" field, proves that this class is not a static inner class.
- mIsStaticInnerClass = false;
- }
-
- return super.visitField(access, name, desc, signature, value);
- }
-
- @Override
- public MethodVisitor visitMethod(int access, String name, String desc,
- String signature, String[] exceptions) {
-
- boolean isStaticMethod = (access & Opcodes.ACC_STATIC) != 0;
- boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
-
- boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) ||
- mDelegateMethods.contains(name);
-
- if (!useDelegate) {
- // Not creating a delegate for this method, pass it as-is from the reader to the writer.
- return super.visitMethod(access, name, desc, signature, exceptions);
- }
-
- if (CONSTRUCTOR.equals(name) || CLASS_INIT.equals(name)) {
- // We don't currently support generating delegates for constructors.
- throw new UnsupportedOperationException(
- String.format(
- "Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)", //$NON-NLS-1$
- mClassName, name, desc));
- }
-
- if (isNative) {
- // Remove native flag
- access = access & ~Opcodes.ACC_NATIVE;
- MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions);
-
- DelegateMethodAdapter a = new DelegateMethodAdapter(
- mLog, null, mwDelegate, mClassName, name, desc, isStaticMethod,
- mIsStaticInnerClass);
-
- // A native has no code to visit, so we need to generate it directly.
- a.generateDelegateCode();
-
- return mwDelegate;
- }
-
- // Given a non-native SomeClass.MethodName(), we want to generate 2 methods:
- // - A copy of the original method named SomeClass.MethodName_Original().
- // The content is the original method as-is from the reader.
- // - A brand new implementation of SomeClass.MethodName() which calls to a
- // non-existing method named SomeClass_Delegate.MethodName().
- // The implementation of this 'delegate' method is done in layoutlib_bridge.
-
- int accessDelegate = access;
- access = access & ~Opcodes.ACC_PRIVATE; // If private, make it package protected.
-
- MethodVisitor mwOriginal = super.visitMethod(access, name + ORIGINAL_SUFFIX,
- desc, signature, exceptions);
- MethodVisitor mwDelegate = super.visitMethod(accessDelegate, name,
- desc, signature, exceptions);
-
- return new DelegateMethodAdapter(
- mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStaticMethod,
- mIsStaticInnerClass);
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
deleted file mode 100644
index da8babc..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
+++ /dev/null
@@ -1,466 +0,0 @@
-/*
- * Copyright (C) 2010 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.tools.layoutlib.create;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.Attribute;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-
-import java.util.ArrayList;
-
-/**
- * This method adapter generates delegate methods.
- * <p/>
- * Given a method {@code SomeClass.MethodName()}, this generates 1 or 2 methods:
- * <ul>
- * <li> A copy of the original method named {@code SomeClass.MethodName_Original()}.
- * The content is the original method as-is from the reader.
- * This step is omitted if the method is native, since it has no Java implementation.
- * <li> A brand new implementation of {@code SomeClass.MethodName()} which calls to a
- * non-existing method named {@code SomeClass_Delegate.MethodName()}.
- * The implementation of this 'delegate' method is done in layoutlib_brigde.
- * </ul>
- * A method visitor is generally constructed to generate a single method; however
- * here we might want to generate one or two depending on the context. To achieve
- * that, the visitor here generates the 'original' method and acts as a no-op if
- * no such method exists (e.g. when the original is a native method).
- * The delegate method is generated after the {@code visitEnd} of the original method
- * or by having the class adapter <em>directly</em> call {@link #generateDelegateCode()}
- * for native methods.
- * <p/>
- * When generating the 'delegate', the implementation generates a call to a class
- * class named <code><className>_Delegate</code> with static methods matching
- * the methods to be overridden here. The methods have the same return type.
- * The argument type list is the same except the "this" reference is passed first
- * for non-static methods.
- * <p/>
- * A new annotation is added to these 'delegate' methods so that we can easily find them
- * for automated testing.
- * <p/>
- * This class isn't intended to be generic or reusable.
- * It is called by {@link DelegateClassAdapter}, which takes care of properly initializing
- * the two method writers for the original and the delegate class, as needed, with their
- * expected names.
- * <p/>
- * The class adapter also takes care of calling {@link #generateDelegateCode()} directly for
- * a native and use the visitor pattern for non-natives.
- * Note that native methods have, by definition, no code so there's nothing a visitor
- * can visit.
- * <p/>
- * Instances of this class are not re-usable.
- * The class adapter creates a new instance for each method.
- */
-class DelegateMethodAdapter extends MethodVisitor {
-
- /** Suffix added to delegate classes. */
- public static final String DELEGATE_SUFFIX = "_Delegate";
-
- /** The parent method writer to copy of the original method.
- * Null when dealing with a native original method. */
- private MethodVisitor mOrgWriter;
- /** The parent method writer to generate the delegating method. Never null. */
- private MethodVisitor mDelWriter;
- /** The original method descriptor (return type + argument types.) */
- private String mDesc;
- /** True if the original method is static. */
- private final boolean mIsStatic;
- /** True if the method is contained in a static inner class */
- private final boolean mIsStaticInnerClass;
- /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
- private final String mClassName;
- /** The method name. */
- private final String mMethodName;
- /** Logger object. */
- private final Log mLog;
-
- /** Array used to capture the first line number information from the original method
- * and duplicate it in the delegate. */
- private Object[] mDelegateLineNumber;
-
- /**
- * Creates a new {@link DelegateMethodAdapter} that will transform this method
- * into a delegate call.
- * <p/>
- * See {@link DelegateMethodAdapter} for more details.
- *
- * @param log The logger object. Must not be null.
- * @param mvOriginal The parent method writer to copy of the original method.
- * Must be {@code null} when dealing with a native original method.
- * @param mvDelegate The parent method writer to generate the delegating method.
- * Must never be null.
- * @param className The internal class name of the class to visit,
- * e.g. <code>com/android/SomeClass$InnerClass</code>.
- * @param methodName The simple name of the method.
- * @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} +
- * {@link Type#getArgumentTypes(String)})
- * @param isStatic True if the method is declared static.
- */
- public DelegateMethodAdapter(Log log,
- MethodVisitor mvOriginal,
- MethodVisitor mvDelegate,
- String className,
- String methodName,
- String desc,
- boolean isStatic,
- boolean isStaticClass) {
- super(Main.ASM_VERSION);
- mLog = log;
- mOrgWriter = mvOriginal;
- mDelWriter = mvDelegate;
- mClassName = className;
- mMethodName = methodName;
- mDesc = desc;
- mIsStatic = isStatic;
- mIsStaticInnerClass = isStaticClass;
- }
-
- /**
- * Generates the new code for the method.
- * <p/>
- * For native methods, this must be invoked directly by {@link DelegateClassAdapter}
- * (since they have no code to visit).
- * <p/>
- * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to
- * return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern
- * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then
- * this method will be invoked from {@link MethodVisitor#visitEnd()}.
- */
- public void generateDelegateCode() {
- /*
- * The goal is to generate a call to a static delegate method.
- * If this method is non-static, the first parameter will be 'this'.
- * All the parameters must be passed and then the eventual return type returned.
- *
- * Example, let's say we have a method such as
- * public void myMethod(int a, Object b, ArrayList<String> c) { ... }
- *
- * We'll want to create a body that calls a delegate method like this:
- * TheClass_Delegate.myMethod(this, a, b, c);
- *
- * If the method is non-static and the class name is an inner class (e.g. has $ in its
- * last segment), we want to push the 'this' of the outer class first:
- * OuterClass_InnerClass_Delegate.myMethod(
- * OuterClass.this,
- * OuterClass$InnerClass.this,
- * a, b, c);
- *
- * Only one level of inner class is supported right now, for simplicity and because
- * we don't need more.
- *
- * The generated class name is the current class name with "_Delegate" appended to it.
- * One thing to realize is that we don't care about generics -- since generic types
- * are erased at build time, they have no influence on the method name being called.
- */
-
- // Add our annotation
- AnnotationVisitor aw = mDelWriter.visitAnnotation(
- Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(),
- true); // visible at runtime
- if (aw != null) {
- aw.visitEnd();
- }
-
- mDelWriter.visitCode();
-
- if (mDelegateLineNumber != null) {
- Object[] p = mDelegateLineNumber;
- mDelWriter.visitLineNumber((Integer) p[0], (Label) p[1]);
- }
-
- ArrayList<Type> paramTypes = new ArrayList<>();
- String delegateClassName = mClassName + DELEGATE_SUFFIX;
- boolean pushedArg0 = false;
- int maxStack = 0;
-
- // Check if the last segment of the class name has inner an class.
- // Right now we only support one level of inner classes.
- Type outerType = null;
- int slash = mClassName.lastIndexOf('/');
- int dol = mClassName.lastIndexOf('$');
- if (dol != -1 && dol > slash && dol == mClassName.indexOf('$')) {
- String outerClass = mClassName.substring(0, dol);
- outerType = Type.getObjectType(outerClass);
-
- // Change a delegate class name to "com/foo/Outer_Inner_Delegate"
- delegateClassName = delegateClassName.replace('$', '_');
- }
-
- // For an instance method (e.g. non-static), push the 'this' preceded
- // by the 'this' of any outer class, if any.
- if (!mIsStatic) {
-
- if (outerType != null && !mIsStaticInnerClass) {
- // The first-level inner class has a package-protected member called 'this$0'
- // that points to the outer class.
-
- // Push this.getField("this$0") on the call stack.
- mDelWriter.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this
- mDelWriter.visitFieldInsn(Opcodes.GETFIELD,
- mClassName, // class where the field is defined
- "this$0", // field name
- outerType.getDescriptor()); // type of the field
- maxStack++;
- paramTypes.add(outerType);
-
- }
-
- // Push "this" for the instance method, which is always ALOAD 0
- mDelWriter.visitVarInsn(Opcodes.ALOAD, 0);
- maxStack++;
- pushedArg0 = true;
- paramTypes.add(Type.getObjectType(mClassName));
- }
-
- // Push all other arguments. Start at arg 1 if we already pushed 'this' above.
- Type[] argTypes = Type.getArgumentTypes(mDesc);
- int maxLocals = pushedArg0 ? 1 : 0;
- for (Type t : argTypes) {
- int size = t.getSize();
- mDelWriter.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals);
- maxLocals += size;
- maxStack += size;
- paramTypes.add(t);
- }
-
- // Construct the descriptor of the delegate based on the parameters
- // we pushed on the call stack. The return type remains unchanged.
- String desc = Type.getMethodDescriptor(
- Type.getReturnType(mDesc),
- paramTypes.toArray(new Type[paramTypes.size()]));
-
- // Invoke the static delegate
- mDelWriter.visitMethodInsn(Opcodes.INVOKESTATIC,
- delegateClassName,
- mMethodName,
- desc,
- false);
-
- Type returnType = Type.getReturnType(mDesc);
- mDelWriter.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
-
- mDelWriter.visitMaxs(maxStack, maxLocals);
- mDelWriter.visitEnd();
-
- // For debugging now. Maybe we should collect these and store them in
- // a text file for helping create the delegates. We could also compare
- // the text file to a golden and break the build on unsupported changes
- // or regressions. Even better we could fancy-print something that looks
- // like the expected Java method declaration.
- mLog.debug("Delegate: %1$s # %2$s %3$s", delegateClassName, mMethodName, desc);
- }
-
- /* Pass down to visitor writer. In this implementation, either do nothing. */
- @Override
- public void visitCode() {
- if (mOrgWriter != null) {
- mOrgWriter.visitCode();
- }
- }
-
- /*
- * visitMaxs is called just before visitEnd if there was any code to rewrite.
- */
- @Override
- public void visitMaxs(int maxStack, int maxLocals) {
- if (mOrgWriter != null) {
- mOrgWriter.visitMaxs(maxStack, maxLocals);
- }
- }
-
- /** End of visiting. Generate the delegating code. */
- @Override
- public void visitEnd() {
- if (mOrgWriter != null) {
- mOrgWriter.visitEnd();
- }
- generateDelegateCode();
- }
-
- /* Writes all annotation from the original method. */
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- if (mOrgWriter != null) {
- return mOrgWriter.visitAnnotation(desc, visible);
- } else {
- return null;
- }
- }
-
- /* Writes all annotation default values from the original method. */
- @Override
- public AnnotationVisitor visitAnnotationDefault() {
- if (mOrgWriter != null) {
- return mOrgWriter.visitAnnotationDefault();
- } else {
- return null;
- }
- }
-
- @Override
- public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
- boolean visible) {
- if (mOrgWriter != null) {
- return mOrgWriter.visitParameterAnnotation(parameter, desc, visible);
- } else {
- return null;
- }
- }
-
- /* Writes all attributes from the original method. */
- @Override
- public void visitAttribute(Attribute attr) {
- if (mOrgWriter != null) {
- mOrgWriter.visitAttribute(attr);
- }
- }
-
- /*
- * Only writes the first line number present in the original code so that source
- * viewers can direct to the correct method, even if the content doesn't match.
- */
- @Override
- public void visitLineNumber(int line, Label start) {
- // Capture the first line values for the new delegate method
- if (mDelegateLineNumber == null) {
- mDelegateLineNumber = new Object[] { line, start };
- }
- if (mOrgWriter != null) {
- mOrgWriter.visitLineNumber(line, start);
- }
- }
-
- @Override
- public void visitInsn(int opcode) {
- if (mOrgWriter != null) {
- mOrgWriter.visitInsn(opcode);
- }
- }
-
- @Override
- public void visitLabel(Label label) {
- if (mOrgWriter != null) {
- mOrgWriter.visitLabel(label);
- }
- }
-
- @Override
- public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
- if (mOrgWriter != null) {
- mOrgWriter.visitTryCatchBlock(start, end, handler, type);
- }
- }
-
- @Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
- if (mOrgWriter != null) {
- mOrgWriter.visitMethodInsn(opcode, owner, name, desc, itf);
- }
- }
-
- @Override
- public void visitFieldInsn(int opcode, String owner, String name, String desc) {
- if (mOrgWriter != null) {
- mOrgWriter.visitFieldInsn(opcode, owner, name, desc);
- }
- }
-
- @Override
- public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
- if (mOrgWriter != null) {
- mOrgWriter.visitFrame(type, nLocal, local, nStack, stack);
- }
- }
-
- @Override
- public void visitIincInsn(int var, int increment) {
- if (mOrgWriter != null) {
- mOrgWriter.visitIincInsn(var, increment);
- }
- }
-
- @Override
- public void visitIntInsn(int opcode, int operand) {
- if (mOrgWriter != null) {
- mOrgWriter.visitIntInsn(opcode, operand);
- }
- }
-
- @Override
- public void visitJumpInsn(int opcode, Label label) {
- if (mOrgWriter != null) {
- mOrgWriter.visitJumpInsn(opcode, label);
- }
- }
-
- @Override
- public void visitLdcInsn(Object cst) {
- if (mOrgWriter != null) {
- mOrgWriter.visitLdcInsn(cst);
- }
- }
-
- @Override
- public void visitLocalVariable(String name, String desc, String signature,
- Label start, Label end, int index) {
- if (mOrgWriter != null) {
- mOrgWriter.visitLocalVariable(name, desc, signature, start, end, index);
- }
- }
-
- @Override
- public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
- if (mOrgWriter != null) {
- mOrgWriter.visitLookupSwitchInsn(dflt, keys, labels);
- }
- }
-
- @Override
- public void visitMultiANewArrayInsn(String desc, int dims) {
- if (mOrgWriter != null) {
- mOrgWriter.visitMultiANewArrayInsn(desc, dims);
- }
- }
-
- @Override
- public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
- if (mOrgWriter != null) {
- mOrgWriter.visitTableSwitchInsn(min, max, dflt, labels);
- }
- }
-
- @Override
- public void visitTypeInsn(int opcode, String type) {
- if (mOrgWriter != null) {
- mOrgWriter.visitTypeInsn(opcode, type);
- }
- }
-
- @Override
- public void visitVarInsn(int opcode, int var) {
- if (mOrgWriter != null) {
- mOrgWriter.visitVarInsn(opcode, var);
- }
- }
-
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
deleted file mode 100644
index aa68ea0..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java
+++ /dev/null
@@ -1,788 +0,0 @@
-/*
- * Copyright (C) 2012 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.tools.layoutlib.create;
-
-import com.android.tools.layoutlib.annotations.VisibleForTesting;
-import com.android.tools.layoutlib.annotations.VisibleForTesting.Visibility;
-
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.Attribute;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.signature.SignatureReader;
-import org.objectweb.asm.signature.SignatureVisitor;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-/**
- * Analyzes the input JAR using the ASM java bytecode manipulation library
- * to list the classes and their dependencies. A "dependency" is a class
- * used by another class.
- */
-public class DependencyFinder {
-
- // Note: a bunch of stuff has package-level access for unit tests. Consider it private.
-
- /** Output logger. */
- private final Log mLog;
-
- /**
- * Creates a new analyzer.
- *
- * @param log The log output.
- */
- public DependencyFinder(Log log) {
- mLog = log;
- }
-
- /**
- * Starts the analysis using parameters from the constructor.
- *
- * @param osJarPath The input source JARs to parse.
- * @return A pair: [0]: map { class FQCN => set of FQCN class dependencies }.
- * [1]: map { missing class FQCN => set of FQCN class that uses it. }
- */
- public List<Map<String, Set<String>>> findDeps(List<String> osJarPath) throws IOException {
-
- Map<String, ClassReader> zipClasses = parseZip(osJarPath);
- mLog.info("Found %d classes in input JAR%s.",
- zipClasses.size(),
- osJarPath.size() > 1 ? "s" : "");
-
- Map<String, Set<String>> deps = findClassesDeps(zipClasses);
-
- Map<String, Set<String>> missing = findMissingClasses(deps, zipClasses.keySet());
-
- List<Map<String, Set<String>>> result = new ArrayList<>(2);
- result.add(deps);
- result.add(missing);
- return result;
- }
-
- /**
- * Prints dependencies to the current logger, found stuff and missing stuff.
- */
- public void printAllDeps(List<Map<String, Set<String>>> result) {
- assert result.size() == 2;
- Map<String, Set<String>> deps = result.get(0);
- Map<String, Set<String>> missing = result.get(1);
-
- // Print all dependences found in the format:
- // +Found: <FQCN from zip>
- // uses: FQCN
-
- mLog.info("++++++ %d Entries found in source JARs", deps.size());
- mLog.info("");
-
- for (Entry<String, Set<String>> entry : deps.entrySet()) {
- mLog.info( "+Found : %s", entry.getKey());
- for (String dep : entry.getValue()) {
- mLog.info(" uses: %s", dep);
- }
-
- mLog.info("");
- }
-
-
- // Now print all missing dependences in the format:
- // -Missing <FQCN>:
- // used by: <FQCN>
-
- mLog.info("");
- mLog.info("------ %d Entries missing from source JARs", missing.size());
- mLog.info("");
-
- for (Entry<String, Set<String>> entry : missing.entrySet()) {
- mLog.info( "-Missing : %s", entry.getKey());
- for (String dep : entry.getValue()) {
- mLog.info(" used by: %s", dep);
- }
-
- mLog.info("");
- }
- }
-
- /**
- * Prints only a summary of the missing dependencies to the current logger.
- */
- public void printMissingDeps(List<Map<String, Set<String>>> result) {
- assert result.size() == 2;
- @SuppressWarnings("unused") Map<String, Set<String>> deps = result.get(0);
- Map<String, Set<String>> missing = result.get(1);
-
- for (String fqcn : missing.keySet()) {
- mLog.info("%s", fqcn);
- }
- }
-
- // ----------------
-
- /**
- * Parses a JAR file and returns a list of all classes founds using a map
- * class name => ASM ClassReader. Class names are in the form "android.view.View".
- */
- Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException {
- TreeMap<String, ClassReader> classes = new TreeMap<>();
-
- for (String jarPath : jarPathList) {
- ZipFile zip = new ZipFile(jarPath);
- Enumeration<? extends ZipEntry> entries = zip.entries();
- ZipEntry entry;
- while (entries.hasMoreElements()) {
- entry = entries.nextElement();
- if (entry.getName().endsWith(".class")) {
- ClassReader cr = new ClassReader(zip.getInputStream(entry));
- String className = classReaderToClassName(cr);
- classes.put(className, cr);
- }
- }
- }
-
- return classes;
- }
-
- /**
- * Utility that returns the fully qualified binary class name for a ClassReader.
- * E.g. it returns something like android.view.View.
- */
- static String classReaderToClassName(ClassReader classReader) {
- if (classReader == null) {
- return null;
- } else {
- return classReader.getClassName().replace('/', '.');
- }
- }
-
- /**
- * Utility that returns the fully qualified binary class name from a path-like FQCN.
- * E.g. it returns android.view.View from android/view/View.
- */
- static String internalToBinaryClassName(String className) {
- if (className == null) {
- return null;
- } else {
- return className.replace('/', '.');
- }
- }
-
- /**
- * Finds all dependencies for all classes in keepClasses which are also
- * listed in zipClasses. Returns a map of all the dependencies found.
- */
- Map<String, Set<String>> findClassesDeps(Map<String, ClassReader> zipClasses) {
-
- // The dependencies that we'll collect.
- // It's a map Class name => uses class names.
- Map<String, Set<String>> dependencyMap = new TreeMap<>();
-
- DependencyVisitor visitor = getVisitor();
-
- int count = 0;
- try {
- for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
- String name = entry.getKey();
-
- TreeSet<String> set = new TreeSet<>();
- dependencyMap.put(name, set);
- visitor.setDependencySet(set);
-
- ClassReader cr = entry.getValue();
- cr.accept(visitor, 0 /* flags */);
-
- visitor.setDependencySet(null);
-
- mLog.debugNoln("Visited %d classes\r", ++count);
- }
- } finally {
- mLog.debugNoln("\n");
- }
-
- return dependencyMap;
- }
-
- /**
- * Computes which classes FQCN were found as dependencies that are NOT listed
- * in the original JAR classes.
- *
- * @param deps The map { FQCN => dependencies[] } returned by {@link #findClassesDeps(Map)}.
- * @param zipClasses The set of all classes FQCN found in the JAR files.
- * @return A map { FQCN not found in the zipClasses => classes using it }
- */
- private Map<String, Set<String>> findMissingClasses(
- Map<String, Set<String>> deps,
- Set<String> zipClasses) {
- Map<String, Set<String>> missing = new TreeMap<>();
-
- for (Entry<String, Set<String>> entry : deps.entrySet()) {
- String name = entry.getKey();
-
- for (String dep : entry.getValue()) {
- if (!zipClasses.contains(dep)) {
- // This dependency doesn't exist in the zip classes.
- Set<String> set = missing.get(dep);
- if (set == null) {
- set = new TreeSet<>();
- missing.put(dep, set);
- }
- set.add(name);
- }
- }
-
- }
-
- return missing;
- }
-
-
- // ----------------------------------
-
- /**
- * Instantiates a new DependencyVisitor. Useful for unit tests.
- */
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- DependencyVisitor getVisitor() {
- return new DependencyVisitor();
- }
-
- /**
- * Visitor to collect all the type dependencies from a class.
- */
- public class DependencyVisitor extends ClassVisitor {
-
- private Set<String> mCurrentDepSet;
-
- /**
- * Creates a new visitor that will find all the dependencies for the visited class.
- */
- public DependencyVisitor() {
- super(Main.ASM_VERSION);
- }
-
- /**
- * Sets the {@link Set} where to record direct dependencies for this class.
- * This will change before each {@link ClassReader#accept(ClassVisitor, int)} call.
- */
- public void setDependencySet(Set<String> set) {
- mCurrentDepSet = set;
- }
-
- /**
- * Considers the given class name as a dependency.
- */
- public void considerName(String className) {
- if (className == null) {
- return;
- }
-
- className = internalToBinaryClassName(className);
-
- try {
- // exclude classes that are part of the default JRE (the one executing this program)
- // or in java package (we won't be able to load them anyway).
- if (className.startsWith("java.") ||
- getClass().getClassLoader().loadClass(className) != null) {
- return;
- }
- } catch (ClassNotFoundException e) {
- // ignore
- }
-
- // Add it to the dependency set for the currently visited class, as needed.
- assert mCurrentDepSet != null;
- mCurrentDepSet.add(className);
- }
-
- /**
- * Considers this array of names using considerName().
- */
- public void considerNames(String[] classNames) {
- if (classNames != null) {
- for (String className : classNames) {
- considerName(className);
- }
- }
- }
-
- /**
- * Considers this signature or type signature by invoking the {@link SignatureVisitor}
- * on it.
- */
- public void considerSignature(String signature) {
- if (signature != null) {
- SignatureReader sr = new SignatureReader(signature);
- // SignatureReader.accept will call accessType so we don't really have
- // to differentiate where the signature comes from.
- sr.accept(new MySignatureVisitor());
- }
- }
-
- /**
- * Considers this {@link Type}. For arrays, the element type is considered.
- * If the type is an object, it's internal name is considered.
- */
- public void considerType(Type t) {
- if (t != null) {
- if (t.getSort() == Type.ARRAY) {
- t = t.getElementType();
- }
- if (t.getSort() == Type.OBJECT) {
- considerName(t.getInternalName());
- }
- }
- }
-
- /**
- * Considers a descriptor string. The descriptor is converted to a {@link Type}
- * and then considerType() is invoked.
- */
- public boolean considerDesc(String desc) {
- if (desc != null) {
- try {
- if (desc.length() > 0 && desc.charAt(0) == '(') {
- // This is a method descriptor with arguments and a return type.
- Type t = Type.getReturnType(desc);
- considerType(t);
-
- for (Type arg : Type.getArgumentTypes(desc)) {
- considerType(arg);
- }
-
- } else {
- Type t = Type.getType(desc);
- considerType(t);
- }
- return true;
- } catch (ArrayIndexOutOfBoundsException e) {
- // ignore, not a valid type.
- }
- }
- return false;
- }
-
-
- // ---------------------------------------------------
- // --- ClassVisitor, FieldVisitor
- // ---------------------------------------------------
-
- // Visits a class header
- @Override
- public void visit(int version, int access, String name,
- String signature, String superName, String[] interfaces) {
- // signature is the signature of this class. May be null if the class is not a generic
- // one, and does not extend or implement generic classes or interfaces.
-
- if (signature != null) {
- considerSignature(signature);
- }
-
- // superName is the internal of name of the super class (see getInternalName).
- // For interfaces, the super class is Object. May be null but only for the Object class.
- considerName(superName);
-
- // interfaces is the internal names of the class's interfaces (see getInternalName).
- // May be null.
- considerNames(interfaces);
- }
-
-
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- // desc is the class descriptor of the annotation class.
- considerDesc(desc);
- return new MyAnnotationVisitor();
- }
-
- @Override
- public void visitAttribute(Attribute attr) {
- // pass
- }
-
- // Visits the end of a class
- @Override
- public void visitEnd() {
- // pass
- }
-
- private class MyFieldVisitor extends FieldVisitor {
-
- public MyFieldVisitor() {
- super(Main.ASM_VERSION);
- }
-
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- // desc is the class descriptor of the annotation class.
- considerDesc(desc);
- return new MyAnnotationVisitor();
- }
-
- @Override
- public void visitAttribute(Attribute attr) {
- // pass
- }
-
- // Visits the end of a class
- @Override
- public void visitEnd() {
- // pass
- }
- }
-
- @Override
- public FieldVisitor visitField(int access, String name, String desc,
- String signature, Object value) {
- // desc is the field's descriptor (see Type).
- considerDesc(desc);
-
- // signature is the field's signature. May be null if the field's type does not use
- // generic types.
- considerSignature(signature);
-
- return new MyFieldVisitor();
- }
-
- @Override
- public void visitInnerClass(String name, String outerName, String innerName, int access) {
- // name is the internal name of an inner class (see getInternalName).
- // Note: outerName/innerName seems to be null when we're reading the
- // _Original_ClassName classes generated by layoutlib_create.
- if (outerName != null) {
- considerName(name);
- }
- }
-
- @Override
- public MethodVisitor visitMethod(int access, String name, String desc,
- String signature, String[] exceptions) {
- // desc is the method's descriptor (see Type).
- considerDesc(desc);
- // signature is the method's signature. May be null if the method parameters, return
- // type and exceptions do not use generic types.
- considerSignature(signature);
-
- return new MyMethodVisitor();
- }
-
- @Override
- public void visitOuterClass(String owner, String name, String desc) {
- // pass
- }
-
- @Override
- public void visitSource(String source, String debug) {
- // pass
- }
-
-
- // ---------------------------------------------------
- // --- MethodVisitor
- // ---------------------------------------------------
-
- private class MyMethodVisitor extends MethodVisitor {
-
- public MyMethodVisitor() {
- super(Main.ASM_VERSION);
- }
-
-
- @Override
- public AnnotationVisitor visitAnnotationDefault() {
- return new MyAnnotationVisitor();
- }
-
- @Override
- public void visitCode() {
- // pass
- }
-
- // field instruction
- @Override
- public void visitFieldInsn(int opcode, String owner, String name, String desc) {
- // owner is the class that declares the field.
- considerName(owner);
- // desc is the field's descriptor (see Type).
- considerDesc(desc);
- }
-
- @Override
- public void visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2) {
- // pass
- }
-
- @Override
- public void visitIincInsn(int var, int increment) {
- // pass -- an IINC instruction
- }
-
- @Override
- public void visitInsn(int opcode) {
- // pass -- a zero operand instruction
- }
-
- @Override
- public void visitIntInsn(int opcode, int operand) {
- // pass -- a single int operand instruction
- }
-
- @Override
- public void visitJumpInsn(int opcode, Label label) {
- // pass -- a jump instruction
- }
-
- @Override
- public void visitLabel(Label label) {
- // pass -- a label target
- }
-
- // instruction to load a constant from the stack
- @Override
- public void visitLdcInsn(Object cst) {
- if (cst instanceof Type) {
- considerType((Type) cst);
- }
- }
-
- @Override
- public void visitLineNumber(int line, Label start) {
- // pass
- }
-
- @Override
- public void visitLocalVariable(String name, String desc,
- String signature, Label start, Label end, int index) {
- // desc is the type descriptor of this local variable.
- considerDesc(desc);
- // signature is the type signature of this local variable. May be null if the local
- // variable type does not use generic types.
- considerSignature(signature);
- }
-
- @Override
- public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
- // pass -- a lookup switch instruction
- }
-
- @Override
- public void visitMaxs(int maxStack, int maxLocals) {
- // pass
- }
-
- // instruction that invokes a method
- @Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc,
- boolean itf) {
-
- // owner is the internal name of the method's owner class
- if (!considerDesc(owner) && owner.indexOf('/') != -1) {
- considerName(owner);
- }
- // desc is the method's descriptor (see Type).
- considerDesc(desc);
- }
-
- // instruction multianewarray, whatever that is
- @Override
- public void visitMultiANewArrayInsn(String desc, int dims) {
-
- // desc an array type descriptor.
- considerDesc(desc);
- }
-
- @Override
- public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
- boolean visible) {
- // desc is the class descriptor of the annotation class.
- considerDesc(desc);
- return new MyAnnotationVisitor();
- }
-
- @Override
- public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
- // pass -- table switch instruction
-
- }
-
- @Override
- public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
- // type is the internal name of the type of exceptions handled by the handler,
- // or null to catch any exceptions (for "finally" blocks).
- considerName(type);
- }
-
- // type instruction
- @Override
- public void visitTypeInsn(int opcode, String type) {
- // type is the operand of the instruction to be visited. This operand must be the
- // internal name of an object or array class.
- considerName(type);
- }
-
- @Override
- public void visitVarInsn(int opcode, int var) {
- // pass -- local variable instruction
- }
- }
-
- private class MySignatureVisitor extends SignatureVisitor {
-
- public MySignatureVisitor() {
- super(Main.ASM_VERSION);
- }
-
- // ---------------------------------------------------
- // --- SignatureVisitor
- // ---------------------------------------------------
-
- private String mCurrentSignatureClass = null;
-
- // Starts the visit of a signature corresponding to a class or interface type
- @Override
- public void visitClassType(String name) {
- mCurrentSignatureClass = name;
- considerName(name);
- }
-
- // Visits an inner class
- @Override
- public void visitInnerClassType(String name) {
- if (mCurrentSignatureClass != null) {
- mCurrentSignatureClass += "$" + name;
- considerName(mCurrentSignatureClass);
- }
- }
-
- @Override
- public SignatureVisitor visitArrayType() {
- return new MySignatureVisitor();
- }
-
- @Override
- public void visitBaseType(char descriptor) {
- // pass -- a primitive type, ignored
- }
-
- @Override
- public SignatureVisitor visitClassBound() {
- return new MySignatureVisitor();
- }
-
- @Override
- public SignatureVisitor visitExceptionType() {
- return new MySignatureVisitor();
- }
-
- @Override
- public void visitFormalTypeParameter(String name) {
- // pass
- }
-
- @Override
- public SignatureVisitor visitInterface() {
- return new MySignatureVisitor();
- }
-
- @Override
- public SignatureVisitor visitInterfaceBound() {
- return new MySignatureVisitor();
- }
-
- @Override
- public SignatureVisitor visitParameterType() {
- return new MySignatureVisitor();
- }
-
- @Override
- public SignatureVisitor visitReturnType() {
- return new MySignatureVisitor();
- }
-
- @Override
- public SignatureVisitor visitSuperclass() {
- return new MySignatureVisitor();
- }
-
- @Override
- public SignatureVisitor visitTypeArgument(char wildcard) {
- return new MySignatureVisitor();
- }
-
- @Override
- public void visitTypeVariable(String name) {
- // pass
- }
-
- @Override
- public void visitTypeArgument() {
- // pass
- }
- }
-
-
- // ---------------------------------------------------
- // --- AnnotationVisitor
- // ---------------------------------------------------
-
- private class MyAnnotationVisitor extends AnnotationVisitor {
-
- public MyAnnotationVisitor() {
- super(Main.ASM_VERSION);
- }
-
- // Visits a primitive value of an annotation
- @Override
- public void visit(String name, Object value) {
- // value is the actual value, whose type must be Byte, Boolean, Character, Short,
- // Integer, Long, Float, Double, String or Type
- if (value instanceof Type) {
- considerType((Type) value);
- }
- }
-
- @Override
- public AnnotationVisitor visitAnnotation(String name, String desc) {
- // desc is the class descriptor of the nested annotation class.
- considerDesc(desc);
- return new MyAnnotationVisitor();
- }
-
- @Override
- public AnnotationVisitor visitArray(String name) {
- return new MyAnnotationVisitor();
- }
-
- @Override
- public void visitEnum(String name, String desc, String value) {
- // desc is the class descriptor of the enumeration class.
- considerDesc(desc);
- }
- }
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/FieldInjectorAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/FieldInjectorAdapter.java
deleted file mode 100644
index 4608a84..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/FieldInjectorAdapter.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.layoutlib.create;
-
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.Opcodes;
-
-/**
- * Injects fields in a class.
- * <p>
- * TODO: Generify
- */
-public class FieldInjectorAdapter extends ClassVisitor {
- public FieldInjectorAdapter(ClassVisitor cv) {
- super(Opcodes.ASM4, cv);
- }
-
- @Override
- public void visitEnd() {
- super.visitField(Opcodes.ACC_PUBLIC, "mLayoutlibCallback",
- "Lcom/android/ide/common/rendering/api/LayoutlibCallback;", null, null);
- super.visitField(Opcodes.ACC_PUBLIC, "mContext",
- "Lcom/android/layoutlib/bridge/android/BridgeContext;", null, null);
- super.visitEnd();
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
deleted file mode 100644
index eca1c07..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2010 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.tools.layoutlib.create;
-
-import org.objectweb.asm.ClassVisitor;
-
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Interface describing the work to be done by {@link AsmGenerator}.
- */
-public interface ICreateInfo {
-
- /**
- * Returns the list of class from layoutlib_create to inject in layoutlib.
- * The list can be empty but must not be null.
- */
- Class<?>[] getInjectedClasses();
-
- /**
- * Returns the list of methods to rewrite as delegates.
- * The list can be empty but must not be null.
- */
- String[] getDelegateMethods();
-
- /**
- * Returns the list of classes on which to delegate all native methods.
- * The list can be empty but must not be null.
- */
- String[] getDelegateClassNatives();
-
- /**
- * Returns the list of classes to rename, must be an even list: the binary FQCN
- * of class to replace followed by the new FQCN.
- * The list can be empty but must not be null.
- */
- String[] getRenamedClasses();
-
- /**
- * List of classes to refactor. This is similar to combining {@link #getRenamedClasses()} and
- * {@link #getJavaPkgClasses()}.
- * Classes included here will be renamed and then all their references in any other classes
- * will be also modified.
- * FQCN of class to refactor followed by its new FQCN.
- */
- String[] getRefactoredClasses();
-
- /**
- * Returns the list of classes for which the methods returning them should be deleted.
- * The array contains a list of null terminated section starting with the name of the class
- * to rename in which the methods are deleted, followed by a list of return types identifying
- * the methods to delete.
- * The list can be empty but must not be null.
- */
- String[] getDeleteReturns();
-
- /**
- * Returns the list of classes to refactor, must be an even list: the
- * binary FQCN of class to replace followed by the new FQCN. All references
- * to the old class should be updated to the new class.
- * The list can be empty but must not be null.
- */
- String[] getJavaPkgClasses();
-
- Set<String> getExcludedClasses();
-
- /**
- * Returns a list of fields which should be promoted to public visibility. The array values
- * are in the form of the binary FQCN of the class containing the field and the field name
- * separated by a '#'.
- */
- String[] getPromotedFields();
-
- /**
- * Returns a list of classes to be promoted to public visibility.
- */
- String[] getPromotedClasses();
-
- /**
- * Returns a map from binary FQCN className to {@link InjectMethodRunnable} which will be
- * called to inject methods into a class.
- * Can be empty but must not be null.
- */
- Map<String, InjectMethodRunnable> getInjectedMethodsMap();
-
- abstract class InjectMethodRunnable {
- /**
- * @param cv Must be {@link ClassVisitor}. However, the param type is object so that when
- * loading the class, ClassVisitor is not loaded. This is because when injecting
- * CreateInfo in LayoutLib (see {@link #getInjectedClasses()}, we don't want to inject
- * asm classes also, but still keep CreateInfo loadable.
- */
- public abstract void generateMethods(Object cv);
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java
deleted file mode 100644
index 1941ab4..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2015 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.tools.layoutlib.create;
-
-import com.android.tools.layoutlib.create.ICreateInfo.InjectMethodRunnable;
-
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.MethodVisitor;
-
-import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
-import static org.objectweb.asm.Opcodes.ALOAD;
-import static org.objectweb.asm.Opcodes.ARETURN;
-import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
-
-public class InjectMethodRunnables {
- public static final ICreateInfo.InjectMethodRunnable CONTEXT_GET_FRAMEWORK_CLASS_LOADER
- = new InjectMethodRunnable() {
- @Override
- public void generateMethods(Object classVisitor) {
- assert classVisitor instanceof ClassVisitor;
- ClassVisitor cv = (ClassVisitor) classVisitor;
- // generated by compiling the class:
- // class foo { public ClassLoader getFrameworkClassLoader() { return getClass().getClassLoader(); } }
- // and then running ASMifier on it:
- // java -classpath asm-debug-all-5.0.2.jar:. org.objectweb.asm.util.ASMifier foo
- MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "getFrameworkClassLoader",
- "()Ljava/lang/ClassLoader;", null, null);
- mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass",
- "()Ljava/lang/Class;", false);
- mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader",
- "()Ljava/lang/ClassLoader;", false);
- mv.visitInsn(ARETURN);
- mv.visitMaxs(1, 1);
- mv.visitEnd();
- // generated code ends.
- }
- };
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java
deleted file mode 100644
index c834808..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 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.tools.layoutlib.create;
-
-import com.android.tools.layoutlib.create.ICreateInfo.InjectMethodRunnable;
-
-import org.objectweb.asm.ClassVisitor;
-
-/**
- * Injects methods into some classes.
- */
-public class InjectMethodsAdapter extends ClassVisitor {
-
- private final ICreateInfo.InjectMethodRunnable mRunnable;
-
- public InjectMethodsAdapter(ClassVisitor cv, InjectMethodRunnable runnable) {
- super(Main.ASM_VERSION, cv);
- mRunnable = runnable;
- }
-
- @Override
- public void visitEnd() {
- mRunnable.generateMethods(this);
- super.visitEnd();
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Log.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Log.java
deleted file mode 100644
index c3ba591..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Log.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2008 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.tools.layoutlib.create;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-public class Log {
-
- private boolean mVerbose = false;
-
- public void setVerbose(boolean verbose) {
- mVerbose = verbose;
- }
-
- public void debug(String format, Object... args) {
- if (mVerbose) {
- info(format, args);
- }
- }
-
- /** Similar to debug() but doesn't do a \n automatically. */
- public void debugNoln(String format, Object... args) {
- if (mVerbose) {
- String s = String.format(format, args);
- System.out.print(s);
- }
- }
-
- public void info(String format, Object... args) {
- String s = String.format(format, args);
- outPrintln(s);
- }
-
- public void error(String format, Object... args) {
- String s = String.format(format, args);
- errPrintln(s);
- }
-
- public void exception(Throwable t, String format, Object... args) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- t.printStackTrace(pw);
- pw.flush();
- error(format + "\n" + sw.toString(), args);
- }
-
- /** for unit testing */
- protected void errPrintln(String msg) {
- System.err.println(msg);
- }
-
- /** for unit testing */
- protected void outPrintln(String msg) {
- System.out.println(msg);
- }
-
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/LogAbortException.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/LogAbortException.java
deleted file mode 100644
index dc4b4a7..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/LogAbortException.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2008 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.tools.layoutlib.create;
-
-public class LogAbortException extends Exception {
-
- private final String mFormat;
- private final Object[] mArgs;
-
- public LogAbortException(String format, Object... args) {
- mFormat = format;
- mArgs = args;
- }
-
- public void error(Log log) {
- log.error(mFormat, mArgs);
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
deleted file mode 100644
index 4b6cf43..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2008 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.tools.layoutlib.create;
-
-import org.objectweb.asm.Opcodes;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-
-/**
- * Entry point for the layoutlib_create tool.
- * <p/>
- * The tool does not currently rely on any external configuration file.
- * Instead the configuration is mostly done via the {@link CreateInfo} class.
- * <p/>
- * For a complete description of the tool and its implementation, please refer to
- * the "README.txt" file at the root of this project.
- * <p/>
- * For a quick test, invoke this as follows:
- * <pre>
- * $ make layoutlib
- * </pre>
- * which does:
- * <pre>
- * $ make layoutlib_create <bunch of framework jars>
- * $ java -jar out/host/linux-x86/framework/layoutlib_create.jar \
- * out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar \
- * out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar \
- * out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar
- * </pre>
- */
-public class Main {
-
- public static class Options {
- public boolean listAllDeps = false;
- public boolean listOnlyMissingDeps = false;
- }
-
- public static final int ASM_VERSION = Opcodes.ASM5;
-
- public static final Options sOptions = new Options();
-
- public static void main(String[] args) {
-
- Log log = new Log();
-
- ArrayList<String> osJarPath = new ArrayList<>();
- String[] osDestJar = { null };
-
- if (!processArgs(log, args, osJarPath, osDestJar)) {
- log.error("Usage: layoutlib_create [-v] output.jar input.jar ...");
- log.error("Usage: layoutlib_create [-v] [--list-deps|--missing-deps] input.jar ...");
- System.exit(1);
- }
-
- if (sOptions.listAllDeps || sOptions.listOnlyMissingDeps) {
- System.exit(listDeps(osJarPath, log));
-
- } else {
- System.exit(createLayoutLib(osDestJar[0], osJarPath, log));
- }
-
-
- System.exit(1);
- }
-
- private static int createLayoutLib(String osDestJar, ArrayList<String> osJarPath, Log log) {
- log.info("Output: %1$s", osDestJar);
- for (String path : osJarPath) {
- log.info("Input : %1$s", path);
- }
-
- try {
- CreateInfo info = new CreateInfo();
- Set<String> excludeClasses = info.getExcludedClasses();
- AsmGenerator agen = new AsmGenerator(log, osDestJar, info);
-
- AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen,
- new String[] { // derived from
- "android.view.View",
- "android.app.Fragment"
- },
- new String[] { // include classes
- "android.*", // for android.R
- "android.util.*",
- "com.android.internal.util.*",
- "android.view.*",
- "android.widget.*",
- "com.android.internal.widget.*",
- "android.text.**",
- "android.graphics.*",
- "android.graphics.drawable.**",
- "android.content.*",
- "android.content.res.*",
- "android.preference.*",
- "org.apache.harmony.xml.*",
- "com.android.internal.R**",
- "android.pim.*", // for datepicker
- "android.os.*", // for android.os.Handler
- "android.database.ContentObserver", // for Digital clock
- "com.android.i18n.phonenumbers.*", // for TextView with autolink attribute
- "android.app.DatePickerDialog", // b.android.com/28318
- "android.app.TimePickerDialog", // b.android.com/61515
- "com.android.internal.view.menu.ActionMenu",
- "android.icu.**", // needed by LayoutLib
- "android.annotation.NonNull", // annotations
- "android.annotation.Nullable", // annotations
- "com.android.internal.transition.EpicenterTranslateClipReveal",
- "com.android.internal.graphics.drawable.AnimationScaleListDrawable",
- },
- excludeClasses,
- new String[] {
- "com/android/i18n/phonenumbers/data/*",
- "android/icu/impl/data/**"
- });
- aa.analyze();
- agen.generate();
-
- // Throw an error if any class failed to get renamed by the generator
- //
- // IMPORTANT: if you're building the platform and you get this error message,
- // it means the renameClasses[] array in AsmGenerator needs to be updated: some
- // class should have been renamed but it was not found in the input JAR files.
- Set<String> notRenamed = agen.getClassesNotRenamed();
- if (notRenamed.size() > 0) {
- // (80-column guide below for error formatting)
- // 01234567890123456789012345678901234567890123456789012345678901234567890123456789
- log.error(
- "ERROR when running layoutlib_create: the following classes are referenced\n" +
- "by tools/layoutlib/create but were not actually found in the input JAR files.\n" +
- "This may be due to some platform classes having been renamed.");
- for (String fqcn : notRenamed) {
- log.error("- Class not found: %s", fqcn.replace('/', '.'));
- }
- for (String path : osJarPath) {
- log.info("- Input JAR : %1$s", path);
- }
- return 1;
- }
-
- return 0;
- } catch (IOException e) {
- log.exception(e, "Failed to load jar");
- } catch (LogAbortException e) {
- e.error(log);
- }
-
- return 1;
- }
-
- private static int listDeps(ArrayList<String> osJarPath, Log log) {
- DependencyFinder df = new DependencyFinder(log);
- try {
- List<Map<String, Set<String>>> result = df.findDeps(osJarPath);
- if (sOptions.listAllDeps) {
- df.printAllDeps(result);
- } else if (sOptions.listOnlyMissingDeps) {
- df.printMissingDeps(result);
- }
- } catch (IOException e) {
- log.exception(e, "Failed to load jar");
- }
-
- return 0;
- }
-
- /**
- * Returns true if args where properly parsed.
- * Returns false if program should exit with command-line usage.
- * <p/>
- * Note: the String[0] is an output parameter wrapped in an array, since there is no
- * "out" parameter support.
- */
- private static boolean processArgs(Log log, String[] args,
- ArrayList<String> osJarPath, String[] osDestJar) {
- boolean needs_dest = true;
- for (String s : args) {
- if (s.equals("-v")) {
- log.setVerbose(true);
- } else if (s.equals("--list-deps")) {
- sOptions.listAllDeps = true;
- needs_dest = false;
- } else if (s.equals("--missing-deps")) {
- sOptions.listOnlyMissingDeps = true;
- needs_dest = false;
- } else if (!s.startsWith("-")) {
- if (needs_dest && osDestJar[0] == null) {
- osDestJar[0] = s;
- } else {
- osJarPath.add(s);
- }
- } else {
- log.error("Unknown argument: %s", s);
- return false;
- }
- }
-
- if (osJarPath.isEmpty()) {
- log.error("Missing parameter: path to input jar");
- return false;
- }
- if (needs_dest && osDestJar[0] == null) {
- log.error("Missing parameter: path to output jar");
- return false;
- }
-
- return true;
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodAdapter.java
deleted file mode 100644
index 7d1e4cf..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodAdapter.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2008 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.tools.layoutlib.create;
-
-
-/**
- * An adapter to make it easier to use {@link MethodListener}.
- * <p/>
- * The adapter calls the void {@link #onInvokeV(String, boolean, Object)} listener
- * for all types (I, L, F, D and A), returning 0 or null as appropriate.
- */
-public class MethodAdapter implements MethodListener {
- /**
- * A stub method is being invoked.
- * <p/>
- * Known limitation: caller arguments are not available.
- *
- * @param signature The signature of the method being invoked, composed of the
- * binary class name followed by the method descriptor (aka argument
- * types). Example: "com/foo/MyClass/InnerClass/printInt(I)V".
- * @param isNative True if the method was a native method.
- * @param caller The calling object. Null for static methods, "this" for instance methods.
- */
- @Override
- public void onInvokeV(String signature, boolean isNative, Object caller) {
- }
-
- /**
- * Same as {@link #onInvokeV(String, boolean, Object)} but returns an integer or similar.
- * @see #onInvokeV(String, boolean, Object)
- * @return an integer, or a boolean, or a short or a byte.
- */
- @Override
- public int onInvokeI(String signature, boolean isNative, Object caller) {
- onInvokeV(signature, isNative, caller);
- return 0;
- }
-
- /**
- * Same as {@link #onInvokeV(String, boolean, Object)} but returns a long.
- * @see #onInvokeV(String, boolean, Object)
- * @return a long.
- */
- @Override
- public long onInvokeL(String signature, boolean isNative, Object caller) {
- onInvokeV(signature, isNative, caller);
- return 0;
- }
-
- /**
- * Same as {@link #onInvokeV(String, boolean, Object)} but returns a float.
- * @see #onInvokeV(String, boolean, Object)
- * @return a float.
- */
- @Override
- public float onInvokeF(String signature, boolean isNative, Object caller) {
- onInvokeV(signature, isNative, caller);
- return 0;
- }
-
- /**
- * Same as {@link #onInvokeV(String, boolean, Object)} but returns a double.
- * @see #onInvokeV(String, boolean, Object)
- * @return a double.
- */
- @Override
- public double onInvokeD(String signature, boolean isNative, Object caller) {
- onInvokeV(signature, isNative, caller);
- return 0;
- }
-
- /**
- * Same as {@link #onInvokeV(String, boolean, Object)} but returns an object.
- * @see #onInvokeV(String, boolean, Object)
- * @return an object.
- */
- @Override
- public Object onInvokeA(String signature, boolean isNative, Object caller) {
- onInvokeV(signature, isNative, caller);
- return null;
- }
-}
-
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java
deleted file mode 100644
index faba4d7..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2008 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.tools.layoutlib.create;
-
-
-/**
- * Interface to allow a method invocation to be listened upon.
- * <p/>
- * This is used by {@link OverrideMethod} to register a listener for methods that
- * have been stubbed by the {@link AsmGenerator}. At runtime the stub will call either a
- * default global listener or a specific listener based on the method signature.
- */
-public interface MethodListener {
- /**
- * A stub method is being invoked.
- * <p/>
- * Known limitation: caller arguments are not available.
- *
- * @param signature The signature of the method being invoked, composed of the
- * binary class name followed by the method descriptor (aka argument
- * types). Example: "com/foo/MyClass/InnerClass/printInt(I)V".
- * @param isNative True if the method was a native method.
- * @param caller The calling object. Null for static methods, "this" for instance methods.
- */
- void onInvokeV(String signature, boolean isNative, Object caller);
-
- /**
- * Same as {@link #onInvokeV(String, boolean, Object)} but returns an integer or similar.
- * @see #onInvokeV(String, boolean, Object)
- * @return an integer, or a boolean, or a short or a byte.
- */
- int onInvokeI(String signature, boolean isNative, Object caller);
-
- /**
- * Same as {@link #onInvokeV(String, boolean, Object)} but returns a long.
- * @see #onInvokeV(String, boolean, Object)
- * @return a long.
- */
- long onInvokeL(String signature, boolean isNative, Object caller);
-
- /**
- * Same as {@link #onInvokeV(String, boolean, Object)} but returns a float.
- * @see #onInvokeV(String, boolean, Object)
- * @return a float.
- */
- float onInvokeF(String signature, boolean isNative, Object caller);
-
- /**
- * Same as {@link #onInvokeV(String, boolean, Object)} but returns a double.
- * @see #onInvokeV(String, boolean, Object)
- * @return a double.
- */
- double onInvokeD(String signature, boolean isNative, Object caller);
-
- /**
- * Same as {@link #onInvokeV(String, boolean, Object)} but returns an object.
- * @see #onInvokeV(String, boolean, Object)
- * @return an object.
- */
- Object onInvokeA(String signature, boolean isNative, Object caller);
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java
deleted file mode 100644
index 7ccafc3..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2008 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.tools.layoutlib.create;
-
-import java.util.HashMap;
-
-/**
- * Allows stub methods from LayoutLib to be overriden at runtime.
- * <p/>
- * Implementation note: all types required by this class(inner/outer classes & interfaces)
- * must be referenced by the injectClass argument to {@link AsmGenerator} in Main.java;
- * Otherwise they won't be accessible in layoutlib.jar at runtime.
- */
-public final class OverrideMethod {
-
- /** Map of method overridden. */
- private static HashMap<String, MethodListener> sMethods = new HashMap<>();
- /** Default listener for all method not listed in sMethods. Nothing if null. */
- private static MethodListener sDefaultListener = null;
-
- /**
- * Sets the default listener for all methods not specifically handled.
- * Null means to do nothing.
- */
- @SuppressWarnings("UnusedDeclaration") // Used by Bridge by reflection for debug purposes.
- public static void setDefaultListener(MethodListener listener) {
- sDefaultListener = listener;
- }
-
- /**
- * Defines or reset a listener for the given method signature.
- *
- * @param signature The signature of the method being invoked, composed of the
- * binary class name followed by the method descriptor (aka argument
- * types). Example: "com/foo/MyClass/InnerClass/printInt(I)V"
- * @param listener The new listener. Removes it if null.
- */
- public static void setMethodListener(String signature, MethodListener listener) {
- if (listener == null) {
- sMethods.remove(signature);
- } else {
- sMethods.put(signature, listener);
- }
- }
-
- /**
- * Invokes the specific listener for the given signature or the default one if defined.
- * <p/>
- * This version invokes the method listener for the void return type.
- * <p/>
- * Note: this is not intended to be used by the LayoutLib Bridge. It is intended to be called
- * by the stubbed methods generated by the LayoutLib_create tool.
- *
- * @param signature The signature of the method being invoked, composed of the
- * binary class name followed by the method descriptor (aka argument
- * types). Example: "com/foo/MyClass/InnerClass/printInt(I)V".
- * @param isNative True if the method was a native method.
- * @param caller The calling object. Null for static methods, "this" for instance methods.
- */
- public static void invokeV(String signature, boolean isNative, Object caller) {
- MethodListener i = sMethods.get(signature);
- if (i != null) {
- i.onInvokeV(signature, isNative, caller);
- } else if (sDefaultListener != null) {
- sDefaultListener.onInvokeV(signature, isNative, caller);
- }
- }
-
- /**
- * Invokes the specific listener for the int return type.
- * @see #invokeV(String, boolean, Object)
- */
- public static int invokeI(String signature, boolean isNative, Object caller) {
- MethodListener i = sMethods.get(signature);
- if (i != null) {
- return i.onInvokeI(signature, isNative, caller);
- } else if (sDefaultListener != null) {
- return sDefaultListener.onInvokeI(signature, isNative, caller);
- }
- return 0;
- }
-
- /**
- * Invokes the specific listener for the long return type.
- * @see #invokeV(String, boolean, Object)
- */
- public static long invokeL(String signature, boolean isNative, Object caller) {
- MethodListener i = sMethods.get(signature);
- if (i != null) {
- return i.onInvokeL(signature, isNative, caller);
- } else if (sDefaultListener != null) {
- return sDefaultListener.onInvokeL(signature, isNative, caller);
- }
- return 0;
- }
-
- /**
- * Invokes the specific listener for the float return type.
- * @see #invokeV(String, boolean, Object)
- */
- public static float invokeF(String signature, boolean isNative, Object caller) {
- MethodListener i = sMethods.get(signature);
- if (i != null) {
- return i.onInvokeF(signature, isNative, caller);
- } else if (sDefaultListener != null) {
- return sDefaultListener.onInvokeF(signature, isNative, caller);
- }
- return 0;
- }
-
- /**
- * Invokes the specific listener for the double return type.
- * @see #invokeV(String, boolean, Object)
- */
- public static double invokeD(String signature, boolean isNative, Object caller) {
- MethodListener i = sMethods.get(signature);
- if (i != null) {
- return i.onInvokeD(signature, isNative, caller);
- } else if (sDefaultListener != null) {
- return sDefaultListener.onInvokeD(signature, isNative, caller);
- }
- return 0;
- }
-
- /**
- * Invokes the specific listener for the object return type.
- * @see #invokeV(String, boolean, Object)
- */
- public static Object invokeA(String signature, boolean isNative, Object caller) {
- MethodListener i = sMethods.get(signature);
- if (i != null) {
- return i.onInvokeA(signature, isNative, caller);
- } else if (sDefaultListener != null) {
- return sDefaultListener.onInvokeA(signature, isNative, caller);
- }
- return null;
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteClassClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteClassClassAdapter.java
deleted file mode 100644
index 99e3089..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteClassClassAdapter.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.layoutlib.create;
-
-import org.objectweb.asm.ClassVisitor;
-
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
-import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
-import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
-
-/**
- * Promotes given classes to public visibility.
- */
-public class PromoteClassClassAdapter extends ClassVisitor {
-
- private final Set<String> mClassNames;
- private static final int CLEAR_PRIVATE_MASK = ~(ACC_PRIVATE | ACC_PROTECTED);
-
- public PromoteClassClassAdapter(ClassVisitor cv, Set<String> classNames) {
- super(Main.ASM_VERSION, cv);
- mClassNames =
- classNames.stream().map(name -> name.replace(".", "/")).collect(Collectors.toSet());
- }
-
- @Override
- public void visit(int version, int access, String name, String signature, String superName,
- String[] interfaces) {
- if (mClassNames.contains(name)) {
- if ((access & ACC_PUBLIC) == 0) {
- access = (access & CLEAR_PRIVATE_MASK) | ACC_PUBLIC;
- }
- }
-
- super.visit(version, access, name, signature, superName, interfaces);
- }
-
- @Override
- public void visitInnerClass(String name, String outerName, String innerName, int access) {
- if (mClassNames.contains(name)) {
- if ((access & ACC_PUBLIC) == 0) {
- access = (access & CLEAR_PRIVATE_MASK) | ACC_PUBLIC;
- }
- }
-
- super.visitInnerClass(name, outerName, innerName, access);
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java
deleted file mode 100644
index ba77860..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2015 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.tools.layoutlib.create;
-
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.FieldVisitor;
-
-import java.util.Set;
-
-import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
-import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
-import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
-
-/**
- * Promotes given fields to public visibility.
- */
-public class PromoteFieldClassAdapter extends ClassVisitor {
-
- private final Set<String> mFieldNames;
- private static final int CLEAR_PRIVATE_MASK = ~(ACC_PRIVATE | ACC_PROTECTED);
-
- public PromoteFieldClassAdapter(ClassVisitor cv, Set<String> fieldNames) {
- super(Main.ASM_VERSION, cv);
- mFieldNames = fieldNames;
- }
-
- @Override
- public FieldVisitor visitField(int access, String name, String desc, String signature,
- Object value) {
- if (mFieldNames.contains(name)) {
- if ((access & ACC_PUBLIC) == 0) {
- access = (access & CLEAR_PRIVATE_MASK) | ACC_PUBLIC;
- }
- }
- return super.visitField(access, name, desc, signature, value);
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java
deleted file mode 100644
index 024e32f..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RefactorClassAdapter.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2013 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.tools.layoutlib.create;
-
-import java.util.Arrays;
-import java.util.HashMap;
-
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.MethodVisitor;
-
-public class RefactorClassAdapter extends AbstractClassAdapter {
-
- private final HashMap<String, String> mRefactorClasses;
-
- RefactorClassAdapter(ClassVisitor cv, HashMap<String, String> refactorClasses) {
- super(cv);
- mRefactorClasses = refactorClasses;
- }
-
- @Override
- public MethodVisitor visitMethod(int access, String name, String desc, String signature,
- String[] exceptions) {
- MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
-
- return new RefactorStackMapAdapter(mw);
- }
-
- @Override
- protected String renameInternalType(String oldClassName) {
- if (oldClassName != null) {
- String newName = mRefactorClasses.get(oldClassName);
- if (newName != null) {
- return newName;
- }
- int pos = oldClassName.indexOf('$');
- if (pos > 0) {
- newName = mRefactorClasses.get(oldClassName.substring(0, pos));
- if (newName != null) {
- return newName + oldClassName.substring(pos);
- }
- }
- }
- return oldClassName;
- }
-
- /**
- * A method visitor that renames all references from an old class name to a new class name in
- * the stackmap of the method.
- */
- private class RefactorStackMapAdapter extends MethodVisitor {
-
- private RefactorStackMapAdapter(MethodVisitor mv) {
- super(Main.ASM_VERSION, mv);
- }
-
-
- private Object[] renameFrame(Object[] elements) {
- if (elements == null) {
- return null;
- }
-
- // The input array cannot be modified. We only copy the source array on write
- boolean copied = false;
- for (int i = 0; i < elements.length; i++) {
- if (!(elements[i] instanceof String)) {
- continue;
- }
-
- if (!copied) {
- elements = Arrays.copyOf(elements, elements.length);
- copied = true;
- }
-
- String type = (String)elements[i];
- if (type.indexOf(';') > 0) {
- elements[i] = renameTypeDesc(type);
- } else {
- elements[i] = renameInternalType(type);
- }
- }
-
- return elements;
- }
-
- @Override
- public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
- super.visitFrame(type, nLocal, renameFrame(local), nStack, renameFrame(stack));
- }
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java
deleted file mode 100644
index 40bd126..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2008 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.tools.layoutlib.create;
-
-import org.objectweb.asm.ClassVisitor;
-
-/**
- * This class visitor renames a class from a given old name to a given new name.
- * The class visitor will also rename all inner classes and references in the methods.
- * <p/>
- *
- * For inner classes, this handles only the case where the outer class name changes.
- * The inner class name should remain the same.
- */
-public class RenameClassAdapter extends AbstractClassAdapter {
-
-
- private final String mOldName;
- private final String mNewName;
- private String mOldBase;
- private String mNewBase;
-
- /**
- * Creates a class visitor that renames a class from a given old name to a given new name.
- * The class visitor will also rename all inner classes and references in the methods.
- * The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass).
- */
- public RenameClassAdapter(ClassVisitor cv, String oldName, String newName) {
- super(cv);
- mOldBase = mOldName = oldName;
- mNewBase = mNewName = newName;
-
- int pos = mOldName.indexOf('$');
- if (pos > 0) {
- mOldBase = mOldName.substring(0, pos);
- }
- pos = mNewName.indexOf('$');
- if (pos > 0) {
- mNewBase = mNewName.substring(0, pos);
- }
-
- assert (mOldBase == null && mNewBase == null) || (mOldBase != null && mNewBase != null);
- }
-
- /**
- * Renames an internal type name, e.g. "com.package.MyClass".
- * If the type doesn't need to be renamed, returns the input string as-is.
- * <p/>
- * The internal type of some of the MethodVisitor turns out to be a type
- descriptor sometimes so descriptors are renamed too.
- */
- @Override
- protected String renameInternalType(String type) {
- if (type == null) {
- return null;
- }
-
- if (type.equals(mOldName)) {
- return mNewName;
- }
-
- if (!mOldBase.equals(mOldName) && type.equals(mOldBase)) {
- return mNewBase;
- }
-
- int pos = type.indexOf('$');
- if (pos == mOldBase.length() && type.startsWith(mOldBase)) {
- return mNewBase + type.substring(pos);
- }
- return type;
- }
-
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
deleted file mode 100644
index bf94415..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Copyright (C) 2014 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.tools.layoutlib.create;
-
-import com.android.tools.layoutlib.java.LinkedHashMap_Delegate;
-import com.android.tools.layoutlib.java.System_Delegate;
-
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Replaces calls to certain methods that do not exist in the Desktop VM. Useful for methods in the
- * "java" package.
- */
-public class ReplaceMethodCallsAdapter extends ClassVisitor {
-
- /**
- * Descriptors for specialized versions {@link System#arraycopy} that are not present on the
- * Desktop VM.
- */
- private static Set<String> ARRAYCOPY_DESCRIPTORS = new HashSet<>(Arrays.asList(
- "([CI[CII)V", "([BI[BII)V", "([SI[SII)V", "([II[III)V",
- "([JI[JII)V", "([FI[FII)V", "([DI[DII)V", "([ZI[ZII)V"));
-
- private static final List<MethodReplacer> METHOD_REPLACERS = new ArrayList<>(5);
-
- private static final String ANDROID_LOCALE_CLASS =
- "com/android/layoutlib/bridge/android/AndroidLocale";
-
- private static final String JAVA_LOCALE_CLASS = Type.getInternalName(java.util.Locale.class);
- private static final Type STRING = Type.getType(String.class);
-
- private static final String JAVA_LANG_SYSTEM = Type.getInternalName(System.class);
-
- // Static initialization block to initialize METHOD_REPLACERS.
- static {
- // Case 1: java.lang.System.arraycopy()
- METHOD_REPLACERS.add(new MethodReplacer() {
- @Override
- public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
- return JAVA_LANG_SYSTEM.equals(owner) && "arraycopy".equals(name) &&
- ARRAYCOPY_DESCRIPTORS.contains(desc);
- }
-
- @Override
- public void replace(MethodInformation mi) {
- mi.desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
- }
- });
-
- // Case 2: java.util.Locale.toLanguageTag() and java.util.Locale.getScript()
- METHOD_REPLACERS.add(new MethodReplacer() {
-
- private final String LOCALE_TO_STRING =
- Type.getMethodDescriptor(STRING, Type.getType(Locale.class));
-
- @Override
- public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
- return JAVA_LOCALE_CLASS.equals(owner) && "()Ljava/lang/String;".equals(desc) &&
- ("toLanguageTag".equals(name) || "getScript".equals(name));
- }
-
- @Override
- public void replace(MethodInformation mi) {
- mi.opcode = Opcodes.INVOKESTATIC;
- mi.owner = ANDROID_LOCALE_CLASS;
- mi.desc = LOCALE_TO_STRING;
- }
- });
-
- // Case 3: java.util.Locale.adjustLanguageCode() or java.util.Locale.forLanguageTag() or
- // java.util.Locale.getDefault()
- METHOD_REPLACERS.add(new MethodReplacer() {
-
- private final String STRING_TO_STRING = Type.getMethodDescriptor(STRING, STRING);
- private final String STRING_TO_LOCALE = Type.getMethodDescriptor(
- Type.getType(Locale.class), STRING);
- private final String VOID_TO_LOCALE =
- Type.getMethodDescriptor(Type.getType(Locale.class));
-
- @Override
- public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
- return JAVA_LOCALE_CLASS.equals(owner) &&
- ("adjustLanguageCode".equals(name) && desc.equals(STRING_TO_STRING) ||
- "forLanguageTag".equals(name) && desc.equals(STRING_TO_LOCALE) ||
- "getDefault".equals(name) && desc.equals(VOID_TO_LOCALE));
- }
-
- @Override
- public void replace(MethodInformation mi) {
- mi.owner = ANDROID_LOCALE_CLASS;
- }
- });
-
- // Case 4: java.lang.System.log?()
- METHOD_REPLACERS.add(new MethodReplacer() {
- @Override
- public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
- return JAVA_LANG_SYSTEM.equals(owner) && name.length() == 4
- && name.startsWith("log");
- }
-
- @Override
- public void replace(MethodInformation mi) {
- assert mi.desc.equals("(Ljava/lang/String;Ljava/lang/Throwable;)V")
- || mi.desc.equals("(Ljava/lang/String;)V");
- mi.name = "log";
- mi.owner = Type.getInternalName(System_Delegate.class);
- }
- });
-
- // Case 5: java.lang.System time calls
- METHOD_REPLACERS.add(new MethodReplacer() {
- @Override
- public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
- return JAVA_LANG_SYSTEM.equals(owner) && name.equals("nanoTime");
- }
-
- @Override
- public void replace(MethodInformation mi) {
- mi.name = "nanoTime";
- mi.owner = Type.getInternalName(System_Delegate.class);
- }
- });
- METHOD_REPLACERS.add(new MethodReplacer() {
- @Override
- public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
- return JAVA_LANG_SYSTEM.equals(owner) && name.equals("currentTimeMillis");
- }
-
- @Override
- public void replace(MethodInformation mi) {
- mi.name = "currentTimeMillis";
- mi.owner = Type.getInternalName(System_Delegate.class);
- }
- });
-
- // Case 6: java.util.LinkedHashMap.eldest()
- METHOD_REPLACERS.add(new MethodReplacer() {
-
- private final String VOID_TO_MAP_ENTRY =
- Type.getMethodDescriptor(Type.getType(Map.Entry.class));
- private final String LINKED_HASH_MAP = Type.getInternalName(LinkedHashMap.class);
-
- @Override
- public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
- return LINKED_HASH_MAP.equals(owner) &&
- "eldest".equals(name) &&
- VOID_TO_MAP_ENTRY.equals(desc);
- }
-
- @Override
- public void replace(MethodInformation mi) {
- mi.opcode = Opcodes.INVOKESTATIC;
- mi.owner = Type.getInternalName(LinkedHashMap_Delegate.class);
- mi.desc = Type.getMethodDescriptor(
- Type.getType(Map.Entry.class), Type.getType(LinkedHashMap.class));
- }
- });
-
- // Case 7: android.content.Context.getClassLoader() in LayoutInflater
- METHOD_REPLACERS.add(new MethodReplacer() {
- // When LayoutInflater asks for a class loader, we must return the class loader that
- // cannot return app's custom views/classes. This is so that in case of any failure
- // or exception when instantiating the views, the IDE can replace it with a mock view
- // and have proper error handling. However, if a custom view asks for the class
- // loader, we must return a class loader that can find app's custom views as well.
- // Thus, we rewrite the call to get class loader in LayoutInflater to
- // getFrameworkClassLoader and inject a new method in Context. This leaves the normal
- // method: Context.getClassLoader() free to be used by the apps.
- private final String VOID_TO_CLASS_LOADER =
- Type.getMethodDescriptor(Type.getType(ClassLoader.class));
-
- @Override
- public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
- return owner.equals("android/content/Context") &&
- sourceClass.equals("android/view/LayoutInflater") &&
- name.equals("getClassLoader") &&
- desc.equals(VOID_TO_CLASS_LOADER);
- }
-
- @Override
- public void replace(MethodInformation mi) {
- mi.name = "getFrameworkClassLoader";
- }
- });
- }
-
- /**
- * If a method some.package.Class.Method(args) is called from some.other.Class,
- * @param owner some/package/Class
- * @param name Method
- * @param desc (args)returnType
- * @param sourceClass some/other/Class
- * @return if the method invocation needs to be replaced by some other class.
- */
- public static boolean isReplacementNeeded(String owner, String name, String desc,
- String sourceClass) {
- for (MethodReplacer replacer : METHOD_REPLACERS) {
- if (replacer.isNeeded(owner, name, desc, sourceClass)) {
- return true;
- }
- }
- return false;
- }
-
- private final String mOriginalClassName;
-
- public ReplaceMethodCallsAdapter(ClassVisitor cv, String originalClassName) {
- super(Main.ASM_VERSION, cv);
- mOriginalClassName = originalClassName;
- }
-
- @Override
- public MethodVisitor visitMethod(int access, String name, String desc, String signature,
- String[] exceptions) {
- return new MyMethodVisitor(super.visitMethod(access, name, desc, signature, exceptions));
- }
-
- private class MyMethodVisitor extends MethodVisitor {
-
- public MyMethodVisitor(MethodVisitor mv) {
- super(Main.ASM_VERSION, mv);
- }
-
- @Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc,
- boolean itf) {
- for (MethodReplacer replacer : METHOD_REPLACERS) {
- if (replacer.isNeeded(owner, name, desc, mOriginalClassName)) {
- MethodInformation mi = new MethodInformation(opcode, owner, name, desc);
- replacer.replace(mi);
- opcode = mi.opcode;
- owner = mi.owner;
- name = mi.name;
- desc = mi.desc;
- break;
- }
- }
- super.visitMethodInsn(opcode, owner, name, desc, itf);
- }
- }
-
- private static class MethodInformation {
- public int opcode;
- public String owner;
- public String name;
- public String desc;
-
- public MethodInformation(int opcode, String owner, String name, String desc) {
- this.opcode = opcode;
- this.owner = owner;
- this.name = name;
- this.desc = desc;
- }
- }
-
- private interface MethodReplacer {
- boolean isNeeded(String owner, String name, String desc, String sourceClass);
-
- /**
- * Updates the MethodInformation with the new values of the method attributes -
- * opcode, owner, name and desc.
- */
- void replace(MethodInformation mi);
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
deleted file mode 100644
index 4ba7237..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
+++ /dev/null
@@ -1,383 +0,0 @@
-/*
- * Copyright (C) 2008 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.tools.layoutlib.create;
-
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.Attribute;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-
-/**
- * This method adapter rewrites a method by discarding the original code and generating
- * a stub depending on the return type. Original annotations are passed along unchanged.
- */
-class StubMethodAdapter extends MethodVisitor {
-
- private static final String CONSTRUCTOR = "<init>";
- private static final String CLASS_INIT = "<clinit>";
-
- /** The parent method writer */
- private MethodVisitor mParentVisitor;
- /** The method return type. Can be null. */
- private Type mReturnType;
- /** Message to be printed by stub methods. */
- private String mInvokeSignature;
- /** Flag to output the first line number. */
- private boolean mOutputFirstLineNumber = true;
- /** Flag that is true when implementing a constructor, to accept all original
- * code calling the original super constructor. */
- private boolean mIsInitMethod = false;
-
- private boolean mMessageGenerated;
- private final boolean mIsStatic;
- private final boolean mIsNative;
-
- public StubMethodAdapter(MethodVisitor mv, String methodName, Type returnType,
- String invokeSignature, boolean isStatic, boolean isNative) {
- super(Main.ASM_VERSION);
- mParentVisitor = mv;
- mReturnType = returnType;
- mInvokeSignature = invokeSignature;
- mIsStatic = isStatic;
- mIsNative = isNative;
-
- if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
- mIsInitMethod = true;
- }
- }
-
- private void generateInvoke() {
- /* Generates the code:
- * OverrideMethod.invoke("signature", mIsNative ? true : false, null or this);
- */
- mParentVisitor.visitLdcInsn(mInvokeSignature);
- // push true or false
- mParentVisitor.visitInsn(mIsNative ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
- // push null or this
- if (mIsStatic) {
- mParentVisitor.visitInsn(Opcodes.ACONST_NULL);
- } else {
- mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0);
- }
-
- int sort = mReturnType != null ? mReturnType.getSort() : Type.VOID;
- switch(sort) {
- case Type.VOID:
- mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
- "com/android/tools/layoutlib/create/OverrideMethod",
- "invokeV",
- "(Ljava/lang/String;ZLjava/lang/Object;)V",
- false);
- mParentVisitor.visitInsn(Opcodes.RETURN);
- break;
- case Type.BOOLEAN:
- case Type.CHAR:
- case Type.BYTE:
- case Type.SHORT:
- case Type.INT:
- mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
- "com/android/tools/layoutlib/create/OverrideMethod",
- "invokeI",
- "(Ljava/lang/String;ZLjava/lang/Object;)I",
- false);
- switch(sort) {
- case Type.BOOLEAN:
- Label l1 = new Label();
- mParentVisitor.visitJumpInsn(Opcodes.IFEQ, l1);
- mParentVisitor.visitInsn(Opcodes.ICONST_1);
- mParentVisitor.visitInsn(Opcodes.IRETURN);
- mParentVisitor.visitLabel(l1);
- mParentVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
- mParentVisitor.visitInsn(Opcodes.ICONST_0);
- break;
- case Type.CHAR:
- mParentVisitor.visitInsn(Opcodes.I2C);
- break;
- case Type.BYTE:
- mParentVisitor.visitInsn(Opcodes.I2B);
- break;
- case Type.SHORT:
- mParentVisitor.visitInsn(Opcodes.I2S);
- break;
- }
- mParentVisitor.visitInsn(Opcodes.IRETURN);
- break;
- case Type.LONG:
- mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
- "com/android/tools/layoutlib/create/OverrideMethod",
- "invokeL",
- "(Ljava/lang/String;ZLjava/lang/Object;)J",
- false);
- mParentVisitor.visitInsn(Opcodes.LRETURN);
- break;
- case Type.FLOAT:
- mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
- "com/android/tools/layoutlib/create/OverrideMethod",
- "invokeF",
- "(Ljava/lang/String;ZLjava/lang/Object;)F",
- false);
- mParentVisitor.visitInsn(Opcodes.FRETURN);
- break;
- case Type.DOUBLE:
- mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
- "com/android/tools/layoutlib/create/OverrideMethod",
- "invokeD",
- "(Ljava/lang/String;ZLjava/lang/Object;)D",
- false);
- mParentVisitor.visitInsn(Opcodes.DRETURN);
- break;
- case Type.ARRAY:
- case Type.OBJECT:
- mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
- "com/android/tools/layoutlib/create/OverrideMethod",
- "invokeA",
- "(Ljava/lang/String;ZLjava/lang/Object;)Ljava/lang/Object;",
- false);
- mParentVisitor.visitTypeInsn(Opcodes.CHECKCAST, mReturnType.getInternalName());
- mParentVisitor.visitInsn(Opcodes.ARETURN);
- break;
- }
-
- }
-
- private void generatePop() {
- /* Pops the stack, depending on the return type.
- */
- switch(mReturnType != null ? mReturnType.getSort() : Type.VOID) {
- case Type.VOID:
- break;
- case Type.BOOLEAN:
- case Type.CHAR:
- case Type.BYTE:
- case Type.SHORT:
- case Type.INT:
- case Type.FLOAT:
- case Type.ARRAY:
- case Type.OBJECT:
- mParentVisitor.visitInsn(Opcodes.POP);
- break;
- case Type.LONG:
- case Type.DOUBLE:
- mParentVisitor.visitInsn(Opcodes.POP2);
- break;
- }
- }
-
- /* Pass down to visitor writer. In this implementation, either do nothing. */
- @Override
- public void visitCode() {
- mParentVisitor.visitCode();
- }
-
- /*
- * visitMaxs is called just before visitEnd if there was any code to rewrite.
- * For non-constructor, generate the messaging code and the return statement
- * if it hasn't been done before.
- */
- @Override
- public void visitMaxs(int maxStack, int maxLocals) {
- if (!mIsInitMethod && !mMessageGenerated) {
- generateInvoke();
- mMessageGenerated = true;
- }
- mParentVisitor.visitMaxs(maxStack, maxLocals);
- }
-
- /**
- * End of visiting.
- * For non-constructor, generate the messaging code and the return statement
- * if it hasn't been done before.
- */
- @Override
- public void visitEnd() {
- if (!mIsInitMethod && !mMessageGenerated) {
- generateInvoke();
- mMessageGenerated = true;
- mParentVisitor.visitMaxs(1, 1);
- }
- mParentVisitor.visitEnd();
- }
-
- /* Writes all annotation from the original method. */
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- return mParentVisitor.visitAnnotation(desc, visible);
- }
-
- /* Writes all annotation default values from the original method. */
- @Override
- public AnnotationVisitor visitAnnotationDefault() {
- return mParentVisitor.visitAnnotationDefault();
- }
-
- @Override
- public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
- boolean visible) {
- return mParentVisitor.visitParameterAnnotation(parameter, desc, visible);
- }
-
- /* Writes all attributes from the original method. */
- @Override
- public void visitAttribute(Attribute attr) {
- mParentVisitor.visitAttribute(attr);
- }
-
- /*
- * Only writes the first line number present in the original code so that source
- * viewers can direct to the correct method, even if the content doesn't match.
- */
- @Override
- public void visitLineNumber(int line, Label start) {
- if (mIsInitMethod || mOutputFirstLineNumber) {
- mParentVisitor.visitLineNumber(line, start);
- mOutputFirstLineNumber = false;
- }
- }
-
- /**
- * For non-constructor, rewrite existing "return" instructions to write the message.
- */
- @Override
- public void visitInsn(int opcode) {
- if (mIsInitMethod) {
- switch (opcode) {
- case Opcodes.RETURN:
- case Opcodes.ARETURN:
- case Opcodes.DRETURN:
- case Opcodes.FRETURN:
- case Opcodes.IRETURN:
- case Opcodes.LRETURN:
- // Pop the last word from the stack since invoke will generate its own return.
- generatePop();
- generateInvoke();
- mMessageGenerated = true;
- //$FALL-THROUGH$
- default:
- mParentVisitor.visitInsn(opcode);
- }
- }
- }
-
- @Override
- public void visitLabel(Label label) {
- if (mIsInitMethod) {
- mParentVisitor.visitLabel(label);
- }
- }
-
- @Override
- public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
- if (mIsInitMethod) {
- mParentVisitor.visitTryCatchBlock(start, end, handler, type);
- }
- }
-
- @Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
- if (mIsInitMethod) {
- mParentVisitor.visitMethodInsn(opcode, owner, name, desc, itf);
- }
- }
-
- @Override
- public void visitFieldInsn(int opcode, String owner, String name, String desc) {
- if (mIsInitMethod) {
- mParentVisitor.visitFieldInsn(opcode, owner, name, desc);
- }
- }
-
- @Override
- public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
- if (mIsInitMethod) {
- mParentVisitor.visitFrame(type, nLocal, local, nStack, stack);
- }
- }
-
- @Override
- public void visitIincInsn(int var, int increment) {
- if (mIsInitMethod) {
- mParentVisitor.visitIincInsn(var, increment);
- }
- }
-
- @Override
- public void visitIntInsn(int opcode, int operand) {
- if (mIsInitMethod) {
- mParentVisitor.visitIntInsn(opcode, operand);
- }
- }
-
- @Override
- public void visitJumpInsn(int opcode, Label label) {
- if (mIsInitMethod) {
- mParentVisitor.visitJumpInsn(opcode, label);
- }
- }
-
- @Override
- public void visitLdcInsn(Object cst) {
- if (mIsInitMethod) {
- mParentVisitor.visitLdcInsn(cst);
- }
- }
-
- @Override
- public void visitLocalVariable(String name, String desc, String signature,
- Label start, Label end, int index) {
- if (mIsInitMethod) {
- mParentVisitor.visitLocalVariable(name, desc, signature, start, end, index);
- }
- }
-
- @Override
- public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
- if (mIsInitMethod) {
- mParentVisitor.visitLookupSwitchInsn(dflt, keys, labels);
- }
- }
-
- @Override
- public void visitMultiANewArrayInsn(String desc, int dims) {
- if (mIsInitMethod) {
- mParentVisitor.visitMultiANewArrayInsn(desc, dims);
- }
- }
-
- @Override
- public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
- if (mIsInitMethod) {
- mParentVisitor.visitTableSwitchInsn(min, max, dflt, labels);
- }
- }
-
- @Override
- public void visitTypeInsn(int opcode, String type) {
- if (mIsInitMethod) {
- mParentVisitor.visitTypeInsn(opcode, type);
- }
- }
-
- @Override
- public void visitVarInsn(int opcode, int var) {
- if (mIsInitMethod) {
- mParentVisitor.visitVarInsn(opcode, var);
- }
- }
-
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
deleted file mode 100644
index a28ae69..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2008 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.tools.layoutlib.create;
-
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-
-import java.util.Set;
-
-/**
- * Class adapter that can stub some or all of the methods of the class.
- */
-class TransformClassAdapter extends ClassVisitor {
-
- /** True if all methods should be stubbed, false if only native ones must be stubbed. */
- private final boolean mStubAll;
- /** True if the class is an interface. */
- private boolean mIsInterface;
- private final String mClassName;
- private final Log mLog;
- private final Set<String> mStubMethods;
- private Set<String> mDeleteReturns;
-
- /**
- * Creates a new class adapter that will stub some or all methods.
- * @param stubMethods list of method signatures to always stub out
- * @param deleteReturns list of types that trigger the deletion of methods returning them.
- * @param className The name of the class being modified
- * @param cv The parent class writer visitor
- * @param stubNativesOnly True if only native methods should be stubbed. False if all
- * methods should be stubbed.
- */
- public TransformClassAdapter(Log logger, Set<String> stubMethods,
- Set<String> deleteReturns, String className, ClassVisitor cv,
- boolean stubNativesOnly) {
- super(Main.ASM_VERSION, cv);
- mLog = logger;
- mStubMethods = stubMethods;
- mClassName = className;
- mStubAll = !stubNativesOnly;
- mIsInterface = false;
- mDeleteReturns = deleteReturns;
- }
-
- /* Visits the class header. */
- @Override
- public void visit(int version, int access, String name,
- String signature, String superName, String[] interfaces) {
-
- // This class might be being renamed.
- name = mClassName;
-
- // remove final
- access = access & ~Opcodes.ACC_FINAL;
- // note: leave abstract classes as such
- // don't try to implement stub for interfaces
-
- mIsInterface = ((access & Opcodes.ACC_INTERFACE) != 0);
- super.visit(version, access, name, signature, superName, interfaces);
- }
-
- /* Visits the header of an inner class. */
- @Override
- public void visitInnerClass(String name, String outerName, String innerName, int access) {
- // remove final
- access = access & ~Opcodes.ACC_FINAL;
- // note: leave abstract classes as such
- // don't try to implement stub for interfaces
-
- super.visitInnerClass(name, outerName, innerName, access);
- }
-
- /* Visits a method. */
- @Override
- public MethodVisitor visitMethod(int access, String name, String desc,
- String signature, String[] exceptions) {
-
- if (mDeleteReturns != null) {
- Type t = Type.getReturnType(desc);
- if (t.getSort() == Type.OBJECT) {
- String returnType = t.getInternalName();
- if (returnType != null) {
- if (mDeleteReturns.contains(returnType)) {
- return null;
- }
- }
- }
- }
-
- String methodSignature = mClassName.replace('/', '.') + "#" + name;
-
- // remove final
- access = access & ~Opcodes.ACC_FINAL;
-
- // stub this method if they are all to be stubbed or if it is a native method
- // and don't try to stub interfaces nor abstract non-native methods.
- if (!mIsInterface &&
- ((access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) != Opcodes.ACC_ABSTRACT) &&
- (mStubAll ||
- (access & Opcodes.ACC_NATIVE) != 0) ||
- mStubMethods.contains(methodSignature)) {
-
- boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
- boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
-
- // remove abstract, final and native
- access = access & ~(Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL | Opcodes.ACC_NATIVE);
-
- String invokeSignature = methodSignature + desc;
- mLog.debug(" Stub: %s (%s)", invokeSignature, isNative ? "native" : "");
-
- MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
- return new StubMethodAdapter(mw, name, returnType(desc), invokeSignature,
- isStatic, isNative);
-
- } else {
- mLog.debug(" Keep: %s %s", name, desc);
- return super.visitMethod(access, name, desc, signature, exceptions);
- }
- }
-
- /**
- * Extracts the return {@link Type} of this descriptor.
- */
- Type returnType(String desc) {
- if (desc != null) {
- try {
- return Type.getReturnType(desc);
- } catch (ArrayIndexOutOfBoundsException e) {
- // ignore, not a valid type.
- }
- }
- return null;
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java
deleted file mode 100644
index 7d6c4ec..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/AutoCloseable.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2013 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.tools.layoutlib.java;
-
-/**
- * Defines the same interface as the java.lang.AutoCloseable which was added in
- * Java 7. This hack makes it possible to run the Android code which uses Java 7
- * features (API 18 and beyond) to run on Java 6.
- * <p/>
- * Extracted from API level 18, file:
- * platform/libcore/luni/src/main/java/java/lang/AutoCloseable.java
- */
-public interface AutoCloseable {
- /**
- * Closes the object and release any system resources it holds.
- */
- void close() throws Exception;
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/Charsets.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/Charsets.java
deleted file mode 100644
index f73b06b..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/Charsets.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2013 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.tools.layoutlib.java;
-
-import java.nio.CharBuffer;
-import java.nio.charset.Charset;
-
-/**
- * Defines the same class as the java.nio.charset.Charsets which was added in
- * Dalvik VM. This hack, provides a replacement for that class which can't be
- * loaded in the standard JVM since it's in the java package and standard JVM
- * doesn't have it. An implementation of the native methods in the original
- * class has been added.
- * <p/>
- * Extracted from API level 18, file:
- * platform/libcore/luni/src/main/java/java/nio/charset/Charsets
- */
-public final class Charsets {
- /**
- * A cheap and type-safe constant for the ISO-8859-1 Charset.
- */
- public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
-
- /**
- * A cheap and type-safe constant for the US-ASCII Charset.
- */
- public static final Charset US_ASCII = Charset.forName("US-ASCII");
-
- /**
- * A cheap and type-safe constant for the UTF-8 Charset.
- */
- public static final Charset UTF_8 = Charset.forName("UTF-8");
-
- /**
- * Returns a new byte array containing the bytes corresponding to the given characters,
- * encoded in US-ASCII. Unrepresentable characters are replaced by (byte) '?'.
- */
- public static byte[] toAsciiBytes(char[] chars, int offset, int length) {
- CharBuffer cb = CharBuffer.allocate(length);
- cb.put(chars, offset, length);
- return US_ASCII.encode(cb).array();
- }
-
- /**
- * Returns a new byte array containing the bytes corresponding to the given characters,
- * encoded in ISO-8859-1. Unrepresentable characters are replaced by (byte) '?'.
- */
- public static byte[] toIsoLatin1Bytes(char[] chars, int offset, int length) {
- CharBuffer cb = CharBuffer.allocate(length);
- cb.put(chars, offset, length);
- return ISO_8859_1.encode(cb).array();
- }
-
- /**
- * Returns a new byte array containing the bytes corresponding to the given characters,
- * encoded in UTF-8. All characters are representable in UTF-8.
- */
- public static byte[] toUtf8Bytes(char[] chars, int offset, int length) {
- CharBuffer cb = CharBuffer.allocate(length);
- cb.put(chars, offset, length);
- return UTF_8.encode(cb).array();
- }
-
- /**
- * Returns a new byte array containing the bytes corresponding to the given characters,
- * encoded in UTF-16BE. All characters are representable in UTF-16BE.
- */
- public static byte[] toBigEndianUtf16Bytes(char[] chars, int offset, int length) {
- byte[] result = new byte[length * 2];
- int end = offset + length;
- int resultIndex = 0;
- for (int i = offset; i < end; ++i) {
- char ch = chars[i];
- result[resultIndex++] = (byte) (ch >> 8);
- result[resultIndex++] = (byte) ch;
- }
- return result;
- }
-
- /**
- * Decodes the given US-ASCII bytes into the given char[]. Equivalent to but faster than:
- *
- * for (int i = 0; i < count; ++i) {
- * char ch = (char) (data[start++] & 0xff);
- * value[i] = (ch <= 0x7f) ? ch : REPLACEMENT_CHAR;
- * }
- */
- public static void asciiBytesToChars(byte[] bytes, int offset, int length, char[] chars) {
- if (bytes == null || chars == null) {
- return;
- }
- final char REPLACEMENT_CHAR = (char)0xffd;
- int start = offset;
- for (int i = 0; i < length; ++i) {
- char ch = (char) (bytes[start++] & 0xff);
- chars[i] = (ch <= 0x7f) ? ch : REPLACEMENT_CHAR;
- }
- }
-
- /**
- * Decodes the given ISO-8859-1 bytes into the given char[]. Equivalent to but faster than:
- *
- * for (int i = 0; i < count; ++i) {
- * value[i] = (char) (data[start++] & 0xff);
- * }
- */
- public static void isoLatin1BytesToChars(byte[] bytes, int offset, int length, char[] chars) {
- if (bytes == null || chars == null) {
- return;
- }
- int start = offset;
- for (int i = 0; i < length; ++i) {
- chars[i] = (char) (bytes[start++] & 0xff);
- }
- }
-
- private Charsets() {
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/IntegralToString.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/IntegralToString.java
deleted file mode 100644
index e6dd00a..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/IntegralToString.java
+++ /dev/null
@@ -1,537 +0,0 @@
-/*
- * Copyright (C) 2013 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.tools.layoutlib.java;
-
-/**
- * Defines the same class as the java.lang.IntegralToString which was added in
- * Dalvik VM. This hack, provides a replacement for that class which can't be
- * loaded in the standard JVM since it's in the java package and standard JVM
- * doesn't have it. Since it's no longer in java.lang, access to package
- * private methods and classes has been replaced by the closes matching public
- * implementation.
- * <p/>
- * Extracted from API level 18, file:
- * platform/libcore/luni/src/main/java/java/lang/IntegralToString.java
- */
-public final class IntegralToString {
- /**
- * When appending to an AbstractStringBuilder, this thread-local char[] lets us avoid
- * allocation of a temporary array. (We can't write straight into the AbstractStringBuilder
- * because it's almost as expensive to work out the exact length of the result as it is to
- * do the formatting. We could try being conservative and "delete"-ing the unused space
- * afterwards, but then we'd need to duplicate convertInt and convertLong rather than share
- * the code.)
- */
- private static final ThreadLocal<char[]> BUFFER = new ThreadLocal<char[]>() {
- @Override protected char[] initialValue() {
- return new char[20]; // Maximum length of a base-10 long.
- }
- };
-
- /**
- * These tables are used to special-case toString computation for
- * small values. This serves three purposes: it reduces memory usage;
- * it increases performance for small values; and it decreases the
- * number of comparisons required to do the length computation.
- * Elements of this table are lazily initialized on first use.
- * No locking is necessary, i.e., we use the non-volatile, racy
- * single-check idiom.
- */
- private static final String[] SMALL_NONNEGATIVE_VALUES = new String[100];
- private static final String[] SMALL_NEGATIVE_VALUES = new String[100];
-
- /** TENS[i] contains the tens digit of the number i, 0 <= i <= 99. */
- private static final char[] TENS = {
- '0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
- '1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
- '2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
- '3', '3', '3', '3', '3', '3', '3', '3', '3', '3',
- '4', '4', '4', '4', '4', '4', '4', '4', '4', '4',
- '5', '5', '5', '5', '5', '5', '5', '5', '5', '5',
- '6', '6', '6', '6', '6', '6', '6', '6', '6', '6',
- '7', '7', '7', '7', '7', '7', '7', '7', '7', '7',
- '8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
- '9', '9', '9', '9', '9', '9', '9', '9', '9', '9'
- };
-
- /** Ones [i] contains the tens digit of the number i, 0 <= i <= 99. */
- private static final char[] ONES = {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- };
-
- /**
- * Table for MOD / DIV 10 computation described in Section 10-21
- * of Hank Warren's "Hacker's Delight" online addendum.
- * http://www.hackersdelight.org/divcMore.pdf
- */
- private static final char[] MOD_10_TABLE = {
- 0, 1, 2, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 8, 9, 0
- };
-
- /**
- * The digits for every supported radix.
- */
- private static final char[] DIGITS = {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
- 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
- 'u', 'v', 'w', 'x', 'y', 'z'
- };
-
- private static final char[] UPPER_CASE_DIGITS = {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
- 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
- 'U', 'V', 'W', 'X', 'Y', 'Z'
- };
-
- private IntegralToString() {
- }
-
- /**
- * Equivalent to Integer.toString(i, radix).
- */
- public static String intToString(int i, int radix) {
- if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
- radix = 10;
- }
- if (radix == 10) {
- return intToString(i);
- }
-
- /*
- * If i is positive, negate it. This is the opposite of what one might
- * expect. It is necessary because the range of the negative values is
- * strictly larger than that of the positive values: there is no
- * positive value corresponding to Integer.MIN_VALUE.
- */
- boolean negative = false;
- if (i < 0) {
- negative = true;
- } else {
- i = -i;
- }
-
- int bufLen = radix < 8 ? 33 : 12; // Max chars in result (conservative)
- char[] buf = new char[bufLen];
- int cursor = bufLen;
-
- do {
- int q = i / radix;
- buf[--cursor] = DIGITS[radix * q - i];
- i = q;
- } while (i != 0);
-
- if (negative) {
- buf[--cursor] = '-';
- }
-
- return new String(buf, cursor, bufLen - cursor);
- }
-
- /**
- * Equivalent to Integer.toString(i).
- */
- public static String intToString(int i) {
- return convertInt(null, i);
- }
-
- /**
- * Equivalent to sb.append(Integer.toString(i)).
- */
- public static void appendInt(StringBuilder sb, int i) {
- convertInt(sb, i);
- }
-
- /**
- * Returns the string representation of i and leaves sb alone if sb is null.
- * Returns null and appends the string representation of i to sb if sb is non-null.
- */
- private static String convertInt(StringBuilder sb, int i) {
- boolean negative = false;
- String quickResult = null;
- if (i < 0) {
- negative = true;
- i = -i;
- if (i < 100) {
- if (i < 0) {
- // If -n is still negative, n is Integer.MIN_VALUE
- quickResult = "-2147483648";
- } else {
- quickResult = SMALL_NEGATIVE_VALUES[i];
- if (quickResult == null) {
- SMALL_NEGATIVE_VALUES[i] = quickResult =
- i < 10 ? stringOf('-', ONES[i]) : stringOf('-', TENS[i], ONES[i]);
- }
- }
- }
- } else {
- if (i < 100) {
- quickResult = SMALL_NONNEGATIVE_VALUES[i];
- if (quickResult == null) {
- SMALL_NONNEGATIVE_VALUES[i] = quickResult =
- i < 10 ? stringOf(ONES[i]) : stringOf(TENS[i], ONES[i]);
- }
- }
- }
- if (quickResult != null) {
- if (sb != null) {
- sb.append(quickResult);
- return null;
- }
- return quickResult;
- }
-
- int bufLen = 11; // Max number of chars in result
- char[] buf = (sb != null) ? BUFFER.get() : new char[bufLen];
- int cursor = bufLen;
-
- // Calculate digits two-at-a-time till remaining digits fit in 16 bits
- while (i >= (1 << 16)) {
- // Compute q = n/100 and r = n % 100 as per "Hacker's Delight" 10-8
- int q = (int) ((0x51EB851FL * i) >>> 37);
- int r = i - 100*q;
- buf[--cursor] = ONES[r];
- buf[--cursor] = TENS[r];
- i = q;
- }
-
- // Calculate remaining digits one-at-a-time for performance
- while (i != 0) {
- // Compute q = n/10 and r = n % 10 as per "Hacker's Delight" 10-8
- int q = (0xCCCD * i) >>> 19;
- int r = i - 10*q;
- buf[--cursor] = DIGITS[r];
- i = q;
- }
-
- if (negative) {
- buf[--cursor] = '-';
- }
-
- if (sb != null) {
- sb.append(buf, cursor, bufLen - cursor);
- return null;
- } else {
- return new String(buf, cursor, bufLen - cursor);
- }
- }
-
- /**
- * Equivalent to Long.toString(v, radix).
- */
- public static String longToString(long v, int radix) {
- int i = (int) v;
- if (i == v) {
- return intToString(i, radix);
- }
-
- if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
- radix = 10;
- }
- if (radix == 10) {
- return longToString(v);
- }
-
- /*
- * If v is positive, negate it. This is the opposite of what one might
- * expect. It is necessary because the range of the negative values is
- * strictly larger than that of the positive values: there is no
- * positive value corresponding to Integer.MIN_VALUE.
- */
- boolean negative = false;
- if (v < 0) {
- negative = true;
- } else {
- v = -v;
- }
-
- int bufLen = radix < 8 ? 65 : 23; // Max chars in result (conservative)
- char[] buf = new char[bufLen];
- int cursor = bufLen;
-
- do {
- long q = v / radix;
- buf[--cursor] = DIGITS[(int) (radix * q - v)];
- v = q;
- } while (v != 0);
-
- if (negative) {
- buf[--cursor] = '-';
- }
-
- return new String(buf, cursor, bufLen - cursor);
- }
-
- /**
- * Equivalent to Long.toString(l).
- */
- public static String longToString(long l) {
- return convertLong(null, l);
- }
-
- /**
- * Equivalent to sb.append(Long.toString(l)).
- */
- public static void appendLong(StringBuilder sb, long l) {
- convertLong(sb, l);
- }
-
- /**
- * Returns the string representation of n and leaves sb alone if sb is null.
- * Returns null and appends the string representation of n to sb if sb is non-null.
- */
- private static String convertLong(StringBuilder sb, long n) {
- int i = (int) n;
- if (i == n) {
- return convertInt(sb, i);
- }
-
- boolean negative = (n < 0);
- if (negative) {
- n = -n;
- if (n < 0) {
- // If -n is still negative, n is Long.MIN_VALUE
- String quickResult = "-9223372036854775808";
- if (sb != null) {
- sb.append(quickResult);
- return null;
- }
- return quickResult;
- }
- }
-
- int bufLen = 20; // Maximum number of chars in result
- char[] buf = (sb != null) ? BUFFER.get() : new char[bufLen];
-
- int low = (int) (n % 1000000000); // Extract low-order 9 digits
- int cursor = intIntoCharArray(buf, bufLen, low);
-
- // Zero-pad Low order part to 9 digits
- while (cursor != (bufLen - 9)) {
- buf[--cursor] = '0';
- }
-
- /*
- * The remaining digits are (n - low) / 1,000,000,000. This
- * "exact division" is done as per the online addendum to Hank Warren's
- * "Hacker's Delight" 10-20, http://www.hackersdelight.org/divcMore.pdf
- */
- n = ((n - low) >>> 9) * 0x8E47CE423A2E9C6DL;
-
- /*
- * If the remaining digits fit in an int, emit them using a
- * single call to intIntoCharArray. Otherwise, strip off the
- * low-order digit, put it in buf, and then call intIntoCharArray
- * on the remaining digits (which now fit in an int).
- */
- if ((n & (-1L << 32)) == 0) {
- cursor = intIntoCharArray(buf, cursor, (int) n);
- } else {
- /*
- * Set midDigit to n % 10
- */
- int lo32 = (int) n;
- int hi32 = (int) (n >>> 32);
-
- // midDigit = ((unsigned) low32) % 10, per "Hacker's Delight" 10-21
- int midDigit = MOD_10_TABLE[(0x19999999 * lo32 + (lo32 >>> 1) + (lo32 >>> 3)) >>> 28];
-
- // Adjust midDigit for hi32. (assert hi32 == 1 || hi32 == 2)
- midDigit -= hi32 << 2; // 1L << 32 == -4 MOD 10
- if (midDigit < 0) {
- midDigit += 10;
- }
- buf[--cursor] = DIGITS[midDigit];
-
- // Exact division as per Warren 10-20
- int rest = ((int) ((n - midDigit) >>> 1)) * 0xCCCCCCCD;
- cursor = intIntoCharArray(buf, cursor, rest);
- }
-
- if (negative) {
- buf[--cursor] = '-';
- }
- if (sb != null) {
- sb.append(buf, cursor, bufLen - cursor);
- return null;
- } else {
- return new String(buf, cursor, bufLen - cursor);
- }
- }
-
- /**
- * Inserts the unsigned decimal integer represented by n into the specified
- * character array starting at position cursor. Returns the index after
- * the last character inserted (i.e., the value to pass in as cursor the
- * next time this method is called). Note that n is interpreted as a large
- * positive integer (not a negative integer) if its sign bit is set.
- */
- private static int intIntoCharArray(char[] buf, int cursor, int n) {
- // Calculate digits two-at-a-time till remaining digits fit in 16 bits
- while ((n & 0xffff0000) != 0) {
- /*
- * Compute q = n/100 and r = n % 100 as per "Hacker's Delight" 10-8.
- * This computation is slightly different from the corresponding
- * computation in intToString: the shifts before and after
- * multiply can't be combined, as that would yield the wrong result
- * if n's sign bit were set.
- */
- int q = (int) ((0x51EB851FL * (n >>> 2)) >>> 35);
- int r = n - 100*q;
- buf[--cursor] = ONES[r];
- buf[--cursor] = TENS[r];
- n = q;
- }
-
- // Calculate remaining digits one-at-a-time for performance
- while (n != 0) {
- // Compute q = n / 10 and r = n % 10 as per "Hacker's Delight" 10-8
- int q = (0xCCCD * n) >>> 19;
- int r = n - 10*q;
- buf[--cursor] = DIGITS[r];
- n = q;
- }
- return cursor;
- }
-
- public static String intToBinaryString(int i) {
- int bufLen = 32; // Max number of binary digits in an int
- char[] buf = new char[bufLen];
- int cursor = bufLen;
-
- do {
- buf[--cursor] = DIGITS[i & 1];
- } while ((i >>>= 1) != 0);
-
- return new String(buf, cursor, bufLen - cursor);
- }
-
- public static String longToBinaryString(long v) {
- int i = (int) v;
- if (v >= 0 && i == v) {
- return intToBinaryString(i);
- }
-
- int bufLen = 64; // Max number of binary digits in a long
- char[] buf = new char[bufLen];
- int cursor = bufLen;
-
- do {
- buf[--cursor] = DIGITS[((int) v) & 1];
- } while ((v >>>= 1) != 0);
-
- return new String(buf, cursor, bufLen - cursor);
- }
-
- public static StringBuilder appendByteAsHex(StringBuilder sb, byte b, boolean upperCase) {
- char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
- sb.append(digits[(b >> 4) & 0xf]);
- sb.append(digits[b & 0xf]);
- return sb;
- }
-
- public static String byteToHexString(byte b, boolean upperCase) {
- char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
- char[] buf = new char[2]; // We always want two digits.
- buf[0] = digits[(b >> 4) & 0xf];
- buf[1] = digits[b & 0xf];
- return new String(buf, 0, 2);
- }
-
- public static String bytesToHexString(byte[] bytes, boolean upperCase) {
- char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
- char[] buf = new char[bytes.length * 2];
- int c = 0;
- for (byte b : bytes) {
- buf[c++] = digits[(b >> 4) & 0xf];
- buf[c++] = digits[b & 0xf];
- }
- return new String(buf);
- }
-
- public static String intToHexString(int i, boolean upperCase, int minWidth) {
- int bufLen = 8; // Max number of hex digits in an int
- char[] buf = new char[bufLen];
- int cursor = bufLen;
-
- char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
- do {
- buf[--cursor] = digits[i & 0xf];
- } while ((i >>>= 4) != 0 || (bufLen - cursor < minWidth));
-
- return new String(buf, cursor, bufLen - cursor);
- }
-
- public static String longToHexString(long v) {
- int i = (int) v;
- if (v >= 0 && i == v) {
- return intToHexString(i, false, 0);
- }
-
- int bufLen = 16; // Max number of hex digits in a long
- char[] buf = new char[bufLen];
- int cursor = bufLen;
-
- do {
- buf[--cursor] = DIGITS[((int) v) & 0xF];
- } while ((v >>>= 4) != 0);
-
- return new String(buf, cursor, bufLen - cursor);
- }
-
- public static String intToOctalString(int i) {
- int bufLen = 11; // Max number of octal digits in an int
- char[] buf = new char[bufLen];
- int cursor = bufLen;
-
- do {
- buf[--cursor] = DIGITS[i & 7];
- } while ((i >>>= 3) != 0);
-
- return new String(buf, cursor, bufLen - cursor);
- }
-
- public static String longToOctalString(long v) {
- int i = (int) v;
- if (v >= 0 && i == v) {
- return intToOctalString(i);
- }
- int bufLen = 22; // Max number of octal digits in a long
- char[] buf = new char[bufLen];
- int cursor = bufLen;
-
- do {
- buf[--cursor] = DIGITS[((int) v) & 7];
- } while ((v >>>= 3) != 0);
-
- return new String(buf, cursor, bufLen - cursor);
- }
-
- private static String stringOf(char... args) {
- return new String(args, 0, args.length);
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/LinkedHashMap_Delegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/LinkedHashMap_Delegate.java
deleted file mode 100644
index 59cc75f..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/LinkedHashMap_Delegate.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2015 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.tools.layoutlib.java;
-
-import com.android.tools.layoutlib.create.ReplaceMethodCallsAdapter;
-
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-
-/**
- * Provides alternate implementation to java.util.LinkedHashMap#eldest(), which is present as a
- * non-public method in the Android VM, but not present on the host VM. This is injected in the
- * layoutlib using {@link ReplaceMethodCallsAdapter}.
- */
-public class LinkedHashMap_Delegate {
- public static <K,V> Map.Entry<K,V> eldest(LinkedHashMap<K,V> map) {
- Iterator<Entry<K, V>> iterator = map.entrySet().iterator();
- return iterator.hasNext() ? iterator.next() : null;
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/Objects.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/Objects.java
deleted file mode 100644
index eb1ef72..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/Objects.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2013 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.tools.layoutlib.java;
-
-import java.util.Arrays;
-import java.util.Comparator;
-
-/**
- * Defines the same class as the java.util.Objects which is added in Java 7.
- * This hack makes it possible to run the Android code which uses Java 7 features
- * (API 18 and beyond) to run on Java 6.
- * <p/>
- * Extracted from API level 19, file:
- * platform/libcore/luni/src/main/java/java/util/Objects.java
- */
-public final class Objects {
- private Objects() {}
-
- /**
- * Returns 0 if {@code a == b}, or {@code c.compare(a, b)} otherwise.
- * That is, this makes {@code c} null-safe.
- */
- public static <T> int compare(T a, T b, Comparator<? super T> c) {
- if (a == b) {
- return 0;
- }
- return c.compare(a, b);
- }
-
- /**
- * Returns true if both arguments are null,
- * the result of {@link Arrays#equals} if both arguments are primitive arrays,
- * the result of {@link Arrays#deepEquals} if both arguments are arrays of reference types,
- * and the result of {@link #equals} otherwise.
- */
- public static boolean deepEquals(Object a, Object b) {
- if (a == null || b == null) {
- return a == b;
- } else if (a instanceof Object[] && b instanceof Object[]) {
- return Arrays.deepEquals((Object[]) a, (Object[]) b);
- } else if (a instanceof boolean[] && b instanceof boolean[]) {
- return Arrays.equals((boolean[]) a, (boolean[]) b);
- } else if (a instanceof byte[] && b instanceof byte[]) {
- return Arrays.equals((byte[]) a, (byte[]) b);
- } else if (a instanceof char[] && b instanceof char[]) {
- return Arrays.equals((char[]) a, (char[]) b);
- } else if (a instanceof double[] && b instanceof double[]) {
- return Arrays.equals((double[]) a, (double[]) b);
- } else if (a instanceof float[] && b instanceof float[]) {
- return Arrays.equals((float[]) a, (float[]) b);
- } else if (a instanceof int[] && b instanceof int[]) {
- return Arrays.equals((int[]) a, (int[]) b);
- } else if (a instanceof long[] && b instanceof long[]) {
- return Arrays.equals((long[]) a, (long[]) b);
- } else if (a instanceof short[] && b instanceof short[]) {
- return Arrays.equals((short[]) a, (short[]) b);
- }
- return a.equals(b);
- }
-
- /**
- * Null-safe equivalent of {@code a.equals(b)}.
- */
- public static boolean equals(Object a, Object b) {
- return (a == null) ? (b == null) : a.equals(b);
- }
-
- /**
- * Convenience wrapper for {@link Arrays#hashCode}, adding varargs.
- * This can be used to compute a hash code for an object's fields as follows:
- * {@code Objects.hash(a, b, c)}.
- */
- public static int hash(Object... values) {
- return Arrays.hashCode(values);
- }
-
- /**
- * Returns 0 for null or {@code o.hashCode()}.
- */
- public static int hashCode(Object o) {
- return (o == null) ? 0 : o.hashCode();
- }
-
- /**
- * Returns {@code o} if non-null, or throws {@code NullPointerException}.
- */
- public static <T> T requireNonNull(T o) {
- if (o == null) {
- throw new NullPointerException();
- }
- return o;
- }
-
- /**
- * Returns {@code o} if non-null, or throws {@code NullPointerException}
- * with the given detail message.
- */
- public static <T> T requireNonNull(T o, String message) {
- if (o == null) {
- throw new NullPointerException(message);
- }
- return o;
- }
-
- /**
- * Returns "null" for null or {@code o.toString()}.
- */
- public static String toString(Object o) {
- return (o == null) ? "null" : o.toString();
- }
-
- /**
- * Returns {@code nullString} for null or {@code o.toString()}.
- */
- public static String toString(Object o, String nullString) {
- return (o == null) ? nullString : o.toString();
- }
-}
\ No newline at end of file
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java
deleted file mode 100644
index be93744..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2014 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.tools.layoutlib.java;
-
-import com.android.tools.layoutlib.create.ReplaceMethodCallsAdapter;
-
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * Provides dummy implementation of methods that don't exist on the host VM.
- * This also providers a time control that allows to set a specific system time.
- *
- * @see ReplaceMethodCallsAdapter
- */
-@SuppressWarnings("unused")
-public class System_Delegate {
- // Current system time
- private static AtomicLong mNanosTime = new AtomicLong(System.nanoTime());
- // Time that the system booted up in nanos
- private static AtomicLong mBootNanosTime = new AtomicLong(System.nanoTime());
-
- public static void log(String message) {
- // ignore.
- }
-
- public static void log(String message, Throwable th) {
- // ignore.
- }
-
- public static void setNanosTime(long nanos) {
- mNanosTime.set(nanos);
- }
-
- public static void setBootTimeNanos(long nanos) {
- mBootNanosTime.set(nanos);
- }
-
- public static long nanoTime() {
- return mNanosTime.get();
- }
-
- public static long currentTimeMillis() {
- return TimeUnit.NANOSECONDS.toMillis(mNanosTime.get());
- }
-
- public static long bootTime() {
- return mBootNanosTime.get();
- }
-
- public static long bootTimeMillis() {
- return TimeUnit.NANOSECONDS.toMillis(mBootNanosTime.get());
- }
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/UnsafeByteSequence.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/UnsafeByteSequence.java
deleted file mode 100644
index 0e09080..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/UnsafeByteSequence.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2013 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.tools.layoutlib.java;
-
-import java.nio.charset.Charset;
-
-/**
- * Defines the same class as the java.lang.UnsafeByteSequence which was added in
- * Dalvik VM. This hack, provides a replacement for that class which can't be
- * loaded in the standard JVM since it's in the java package and standard JVM
- * doesn't have it.
- * <p/>
- * Extracted from API level 18, file:
- * platform/libcore/luni/src/main/java/java/lang/UnsafeByteSequence.java
- */
-public class UnsafeByteSequence {
- private byte[] bytes;
- private int count;
-
- public UnsafeByteSequence(int initialCapacity) {
- this.bytes = new byte[initialCapacity];
- }
-
- public int size() {
- return count;
- }
-
- /**
- * Moves the write pointer back to the beginning of the sequence,
- * but without resizing or reallocating the buffer.
- */
- public void rewind() {
- count = 0;
- }
-
- public void write(byte[] buffer, int offset, int length) {
- if (count + length >= bytes.length) {
- byte[] newBytes = new byte[(count + length) * 2];
- System.arraycopy(bytes, 0, newBytes, 0, count);
- bytes = newBytes;
- }
- System.arraycopy(buffer, offset, bytes, count, length);
- count += length;
- }
-
- public void write(int b) {
- if (count == bytes.length) {
- byte[] newBytes = new byte[count * 2];
- System.arraycopy(bytes, 0, newBytes, 0, count);
- bytes = newBytes;
- }
- bytes[count++] = (byte) b;
- }
-
- public byte[] toByteArray() {
- if (count == bytes.length) {
- return bytes;
- }
- byte[] result = new byte[count];
- System.arraycopy(bytes, 0, result, 0, count);
- return result;
- }
-
- public String toString(Charset cs) {
- return new String(bytes, 0, count, cs);
- }
-}
diff --git a/tools/layoutlib/create/tests/Android.mk b/tools/layoutlib/create/tests/Android.mk
deleted file mode 100644
index 488d7d6..0000000
--- a/tools/layoutlib/create/tests/Android.mk
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright (C) 2014 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)
-
-# Only compile source java files in this lib.
-LOCAL_SRC_FILES := $(call all-java-files-under, com)
-
-LOCAL_JAVA_RESOURCE_DIRS := data mock_data
-
-LOCAL_MODULE := layoutlib-create-tests
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_JAVA_LIBRARIES := layoutlib_create junit-host
-LOCAL_STATIC_JAVA_LIBRARIES := asm-5.2
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-# Copy the jar to DIST_DIR for sdk builds
-$(call dist-for-goals, sdk win_sdk, $(LOCAL_INSTALLED_MODULE))
-
-# Build all sub-directories
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
deleted file mode 100644
index f86917a..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2008 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.tools.layoutlib.create;
-
-import com.android.tools.layoutlib.create.AsmAnalyzer.DependencyVisitor;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.objectweb.asm.ClassReader;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-/**
- * Unit tests for some methods of {@link AsmAnalyzer}.
- */
-public class AsmAnalyzerTest {
-
- private MockLog mLog;
- private ArrayList<String> mOsJarPath;
- private AsmAnalyzer mAa;
-
- @Before
- public void setUp() throws Exception {
- mLog = new MockLog();
- URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
-
- mOsJarPath = new ArrayList<>();
- //noinspection ConstantConditions
- mOsJarPath.add(url.getFile());
-
- Set<String> excludeClasses = Collections.singleton("java.lang.JavaClass");
-
- String[] includeFiles = new String[]{"mock_android/data/data*"};
- mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */, null /* deriveFrom */,
- null /* includeGlobs */, excludeClasses, includeFiles);
- }
-
- @Test
- public void testParseZip() throws IOException {
-
- Map<String, ClassReader> map = new TreeMap<>();
- Map<String, InputStream> filesFound = new TreeMap<>();
-
- mAa.parseZip(mOsJarPath, map, filesFound);
-
- assertArrayEquals(new String[] {
- "java.lang.JavaClass",
- "mock_android.dummy.InnerTest",
- "mock_android.dummy.InnerTest$DerivingClass",
- "mock_android.dummy.InnerTest$MyGenerics1",
- "mock_android.dummy.InnerTest$MyIntEnum",
- "mock_android.dummy.InnerTest$MyStaticInnerClass",
- "mock_android.dummy.InnerTest$NotStaticInner1",
- "mock_android.dummy.InnerTest$NotStaticInner2",
- "mock_android.util.EmptyArray",
- "mock_android.view.View",
- "mock_android.view.ViewGroup",
- "mock_android.view.ViewGroup$LayoutParams",
- "mock_android.view.ViewGroup$MarginLayoutParams",
- "mock_android.widget.LinearLayout",
- "mock_android.widget.LinearLayout$LayoutParams",
- "mock_android.widget.TableLayout",
- "mock_android.widget.TableLayout$LayoutParams"
- },
- map.keySet().toArray());
- assertArrayEquals(new String[] {"mock_android/data/dataFile"},
- filesFound.keySet().toArray());
- }
-
- @Test
- public void testFindClass() throws IOException, LogAbortException {
-
- Map<String, ClassReader> zipClasses = new TreeMap<>();
- Map<String, InputStream> filesFound = new TreeMap<>();
-
- mAa.parseZip(mOsJarPath, zipClasses, filesFound);
- TreeMap<String, ClassReader> found = new TreeMap<>();
-
- ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams",
- zipClasses, found);
-
- assertNotNull(cr);
- assertEquals("mock_android/view/ViewGroup$LayoutParams", cr.getClassName());
- assertArrayEquals(new String[] { "mock_android.view.ViewGroup$LayoutParams" },
- found.keySet().toArray());
- assertArrayEquals(new ClassReader[] { cr }, found.values().toArray());
- }
-
- @Test
- public void testFindGlobs() throws IOException, LogAbortException {
-
- Map<String, ClassReader> zipClasses = new TreeMap<>();
- Map<String, InputStream> filesFound = new TreeMap<>();
-
- mAa.parseZip(mOsJarPath, zipClasses, filesFound);
- TreeMap<String, ClassReader> found = new TreeMap<>();
-
- // this matches classes, a package match returns nothing
- found.clear();
- mAa.findGlobs("mock_android.view", zipClasses, found);
-
- assertArrayEquals(new String[] { },
- found.keySet().toArray());
-
- // a complex glob search. * is a search pattern that matches names, not dots
- mAa.findGlobs("mock_android.*.*Group$*Layout*", zipClasses, found);
-
- assertArrayEquals(new String[] {
- "mock_android.view.ViewGroup$LayoutParams",
- "mock_android.view.ViewGroup$MarginLayoutParams"
- },
- found.keySet().toArray());
-
- // a complex glob search. ** is a search pattern that matches names including dots
- mAa.findGlobs("mock_android.**Group*", zipClasses, found);
-
- assertArrayEquals(new String[] {
- "mock_android.view.ViewGroup",
- "mock_android.view.ViewGroup$LayoutParams",
- "mock_android.view.ViewGroup$MarginLayoutParams"
- },
- found.keySet().toArray());
-
- // matches a single class
- found.clear();
- mAa.findGlobs("mock_android.view.View", zipClasses, found);
-
- assertArrayEquals(new String[] {
- "mock_android.view.View"
- },
- found.keySet().toArray());
-
- // matches everyting inside the given package but not sub-packages
- found.clear();
- mAa.findGlobs("mock_android.view.*", zipClasses, found);
-
- assertArrayEquals(new String[] {
- "mock_android.view.View",
- "mock_android.view.ViewGroup",
- "mock_android.view.ViewGroup$LayoutParams",
- "mock_android.view.ViewGroup$MarginLayoutParams"
- },
- found.keySet().toArray());
-
- for (String key : found.keySet()) {
- ClassReader value = found.get(key);
- assertNotNull("No value for " + key, value);
- assertEquals(key, AsmAnalyzer.classReaderToClassName(value));
- }
- }
-
- @Test
- public void testFindClassesDerivingFrom() throws LogAbortException, IOException {
-
- Map<String, ClassReader> zipClasses = new TreeMap<>();
- Map<String, InputStream> filesFound = new TreeMap<>();
-
- mAa.parseZip(mOsJarPath, zipClasses, filesFound);
- TreeMap<String, ClassReader> found = new TreeMap<>();
-
- mAa.findClassesDerivingFrom("mock_android.view.View", zipClasses, found);
-
- assertArrayEquals(new String[] {
- "mock_android.view.View",
- "mock_android.view.ViewGroup",
- "mock_android.widget.LinearLayout",
- "mock_android.widget.TableLayout",
- },
- found.keySet().toArray());
-
- for (String key : found.keySet()) {
- ClassReader value = found.get(key);
- assertNotNull("No value for " + key, value);
- assertEquals(key, AsmAnalyzer.classReaderToClassName(value));
- }
- }
-
- @Test
- public void testDependencyVisitor() throws IOException, LogAbortException {
-
- Map<String, ClassReader> zipClasses = new TreeMap<>();
- Map<String, InputStream> filesFound = new TreeMap<>();
-
- mAa.parseZip(mOsJarPath, zipClasses, filesFound);
- TreeMap<String, ClassReader> keep = new TreeMap<>();
- TreeMap<String, ClassReader> new_keep = new TreeMap<>();
- TreeMap<String, ClassReader> in_deps = new TreeMap<>();
- TreeMap<String, ClassReader> out_deps = new TreeMap<>();
-
- ClassReader cr = mAa.findClass("mock_android.widget.LinearLayout", zipClasses, keep);
- DependencyVisitor visitor = mAa.getVisitor(zipClasses, keep, new_keep, in_deps, out_deps);
-
- // get first level dependencies
- cr.accept(visitor, 0 /* flags */);
-
- assertArrayEquals(new String[] {
- "mock_android.util.EmptyArray",
- "mock_android.view.ViewGroup",
- "mock_android.widget.LinearLayout$LayoutParams",
- },
- out_deps.keySet().toArray());
-
- in_deps.putAll(out_deps);
- out_deps.clear();
-
- // get second level dependencies
- for (ClassReader cr2 : in_deps.values()) {
- cr2.accept(visitor, 0 /* flags */);
- }
-
- assertArrayEquals(new String[] {
- "mock_android.view.View",
- "mock_android.view.ViewGroup$LayoutParams",
- "mock_android.view.ViewGroup$MarginLayoutParams",
- },
- out_deps.keySet().toArray());
-
- in_deps.putAll(out_deps);
- out_deps.clear();
-
- // get third level dependencies (there are none)
- for (ClassReader cr2 : in_deps.values()) {
- cr2.accept(visitor, 0 /* flags */);
- }
- keep.putAll(new_keep);
-
- assertArrayEquals(new String[] { }, out_deps.keySet().toArray());
- assertArrayEquals(new String[] {
- "mock_android.widget.LinearLayout",
- }, keep.keySet().toArray());
- }
-}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
deleted file mode 100644
index e718fb9..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ /dev/null
@@ -1,431 +0,0 @@
-/*
- * Copyright (C) 2008 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.tools.layoutlib.create;
-
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Type;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Unit tests for some methods of {@link AsmGenerator}.
- */
-public class AsmGeneratorTest {
- private MockLog mLog;
- private ArrayList<String> mOsJarPath;
- private String mOsDestJar;
- private File mTempFile;
-
- // ASM internal name for the the class in java package that should be refactored.
- private static final String JAVA_CLASS_NAME = "java/lang/JavaClass";
-
- @Before
- public void setUp() throws Exception {
- mLog = new MockLog();
- URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
-
- mOsJarPath = new ArrayList<>();
- //noinspection ConstantConditions
- mOsJarPath.add(url.getFile());
-
- mTempFile = File.createTempFile("mock", ".jar");
- mOsDestJar = mTempFile.getAbsolutePath();
- mTempFile.deleteOnExit();
- }
-
- @After
- public void tearDown() throws Exception {
- if (mTempFile != null) {
- //noinspection ResultOfMethodCallIgnored
- mTempFile.delete();
- mTempFile = null;
- }
- }
-
- @Test
- public void testClassRenaming() throws IOException, LogAbortException {
-
- ICreateInfo ci = new CreateInfoAdapter() {
- @Override
- public String[] getRenamedClasses() {
- // classes to rename (so that we can replace them)
- return new String[] {
- "mock_android.view.View", "mock_android.view._Original_View",
- "not.an.actual.ClassName", "anoter.fake.NewClassName",
- };
- }
- };
-
- AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
-
- AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
- null, // derived from
- new String[] { // include classes
- "**"
- },
- Collections.emptySet() /* excluded classes */,
- new String[]{} /* include files */);
- aa.analyze();
- agen.generate();
-
- Set<String> notRenamed = agen.getClassesNotRenamed();
- assertArrayEquals(new String[] { "not/an/actual/ClassName" }, notRenamed.toArray());
-
- }
-
- @Test
- public void testJavaClassRefactoring() throws IOException, LogAbortException {
- ICreateInfo ci = new CreateInfoAdapter() {
- @Override
- public Class<?>[] getInjectedClasses() {
- // classes to inject in the final JAR
- return new Class<?>[] {
- com.android.tools.layoutlib.create.dataclass.JavaClass.class
- };
- }
-
- @Override
- public String[] getJavaPkgClasses() {
- // classes to refactor (so that we can replace them)
- return new String[] {
- "java.lang.JavaClass", "com.android.tools.layoutlib.create.dataclass.JavaClass",
- };
- }
-
- @Override
- public Set<String> getExcludedClasses() {
- return Collections.singleton("java.lang.JavaClass");
- }
- };
-
- AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
-
- AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
- null, // derived from
- new String[] { // include classes
- "**"
- },
- Collections.emptySet(),
- new String[] { /* include files */
- "mock_android/data/data*"
- });
- aa.analyze();
- agen.generate();
- Map<String, ClassReader> output = new TreeMap<>();
- Map<String, InputStream> filesFound = new TreeMap<>();
- parseZip(mOsDestJar, output, filesFound);
- RecordingClassVisitor cv = new RecordingClassVisitor();
- for (ClassReader cr: output.values()) {
- cr.accept(cv, 0);
- }
- assertTrue(cv.mVisitedClasses.contains(
- "com/android/tools/layoutlib/create/dataclass/JavaClass"));
- assertFalse(cv.mVisitedClasses.contains(
- JAVA_CLASS_NAME));
- assertArrayEquals(new String[] {"mock_android/data/dataFile"},
- filesFound.keySet().toArray());
- }
-
- @Test
- public void testClassRefactoring() throws IOException, LogAbortException {
- ICreateInfo ci = new CreateInfoAdapter() {
- @Override
- public Class<?>[] getInjectedClasses() {
- // classes to inject in the final JAR
- return new Class<?>[] {
- com.android.tools.layoutlib.create.dataclass.JavaClass.class
- };
- }
-
- @Override
- public String[] getRefactoredClasses() {
- // classes to refactor (so that we can replace them)
- return new String[] {
- "mock_android.view.View", "mock_android.view._Original_View",
- };
- }
- };
-
- AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
-
- AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
- null, // derived from
- new String[] { // include classes
- "**"
- },
- Collections.emptySet(),
- new String[] {});
- aa.analyze();
- agen.generate();
- Map<String, ClassReader> output = new TreeMap<>();
- parseZip(mOsDestJar, output, new TreeMap<>());
- RecordingClassVisitor cv = new RecordingClassVisitor();
- for (ClassReader cr: output.values()) {
- cr.accept(cv, 0);
- }
- assertTrue(cv.mVisitedClasses.contains(
- "mock_android/view/_Original_View"));
- assertFalse(cv.mVisitedClasses.contains(
- "mock_android/view/View"));
- }
-
- @Test
- public void testClassExclusion() throws IOException, LogAbortException {
- ICreateInfo ci = new CreateInfoAdapter() {
- @Override
- public Set<String> getExcludedClasses() {
- Set<String> set = new HashSet<>(2);
- set.add("mock_android.dummy.InnerTest");
- set.add("java.lang.JavaClass");
- return set;
- }
- };
-
- AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
- Set<String> excludedClasses = ci.getExcludedClasses();
- AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
- null, // derived from
- new String[] { // include classes
- "**"
- },
- excludedClasses,
- new String[] { /* include files */
- "mock_android/data/data*"
- });
- aa.analyze();
- agen.generate();
- Map<String, ClassReader> output = new TreeMap<>();
- Map<String, InputStream> filesFound = new TreeMap<>();
- parseZip(mOsDestJar, output, filesFound);
- for (String s : output.keySet()) {
- assertFalse(excludedClasses.contains(s));
- }
- assertArrayEquals(new String[] {"mock_android/data/dataFile"},
- filesFound.keySet().toArray());
- }
-
- @Test
- public void testMethodInjection() throws IOException, LogAbortException,
- ClassNotFoundException, IllegalAccessException, InstantiationException,
- NoSuchMethodException, InvocationTargetException {
- ICreateInfo ci = new CreateInfoAdapter() {
- @Override
- public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
- return Collections.singletonMap("mock_android.util.EmptyArray",
- InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER);
- }
- };
-
- AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
- AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
- null, // derived from
- new String[] { // include classes
- "**"
- },
- ci.getExcludedClasses(),
- new String[] { /* include files */
- "mock_android/data/data*"
- });
- aa.analyze();
- agen.generate();
- Map<String, ClassReader> output = new TreeMap<>();
- Map<String, InputStream> filesFound = new TreeMap<>();
- parseZip(mOsDestJar, output, filesFound);
- final String modifiedClass = "mock_android.util.EmptyArray";
- final String modifiedClassPath = modifiedClass.replace('.', '/').concat(".class");
- ZipFile zipFile = new ZipFile(mOsDestJar);
- ZipEntry entry = zipFile.getEntry(modifiedClassPath);
- assertNotNull(entry);
- final byte[] bytes;
- try (InputStream inputStream = zipFile.getInputStream(entry)) {
- bytes = getByteArray(inputStream);
- }
- ClassLoader classLoader = new ClassLoader(getClass().getClassLoader()) {
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- if (name.equals(modifiedClass)) {
- return defineClass(null, bytes, 0, bytes.length);
- }
- throw new ClassNotFoundException(name + " not found.");
- }
- };
- Class<?> emptyArrayClass = classLoader.loadClass(modifiedClass);
- Object emptyArrayInstance = emptyArrayClass.newInstance();
- Method method = emptyArrayClass.getMethod("getFrameworkClassLoader");
- Object cl = method.invoke(emptyArrayInstance);
- assertEquals(classLoader, cl);
- }
-
- private static byte[] getByteArray(InputStream stream) throws IOException {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- byte[] buffer = new byte[1024];
- int read;
- while ((read = stream.read(buffer, 0, buffer.length)) > -1) {
- bos.write(buffer, 0, read);
- }
- return bos.toByteArray();
- }
-
- private void parseZip(String jarPath,
- Map<String, ClassReader> classes,
- Map<String, InputStream> filesFound) throws IOException {
-
- ZipFile zip = new ZipFile(jarPath);
- Enumeration<? extends ZipEntry> entries = zip.entries();
- ZipEntry entry;
- while (entries.hasMoreElements()) {
- entry = entries.nextElement();
- if (entry.getName().endsWith(".class")) {
- ClassReader cr = new ClassReader(zip.getInputStream(entry));
- String className = classReaderToClassName(cr);
- classes.put(className, cr);
- } else {
- filesFound.put(entry.getName(), zip.getInputStream(entry));
- }
- }
-
- }
-
- private String classReaderToClassName(ClassReader classReader) {
- if (classReader == null) {
- return null;
- } else {
- return classReader.getClassName().replace('/', '.');
- }
- }
-
- /**
- * {@link ClassVisitor} that records every class that sees.
- */
- private static class RecordingClassVisitor extends ClassVisitor {
- private Set<String> mVisitedClasses = new HashSet<>();
-
- private RecordingClassVisitor() {
- super(Main.ASM_VERSION);
- }
-
- private void addClass(String className) {
- if (className == null) {
- return;
- }
-
- int pos = className.indexOf('$');
- if (pos > 0) {
- // For inner classes, add also the base class
- mVisitedClasses.add(className.substring(0, pos));
- }
- mVisitedClasses.add(className);
- }
-
- @Override
- public void visit(int version, int access, String name, String signature, String superName,
- String[] interfaces) {
- addClass(superName);
- Arrays.stream(interfaces).forEach(this::addClass);
- }
-
- private void processType(Type type) {
- switch (type.getSort()) {
- case Type.OBJECT:
- addClass(type.getInternalName());
- break;
- case Type.ARRAY:
- addClass(type.getElementType().getInternalName());
- break;
- case Type.METHOD:
- processType(type.getReturnType());
- Arrays.stream(type.getArgumentTypes()).forEach(this::processType);
- break;
- }
- }
-
- @Override
- public FieldVisitor visitField(int access, String name, String desc, String signature,
- Object value) {
- processType(Type.getType(desc));
- return super.visitField(access, name, desc, signature, value);
- }
-
- @Override
- public MethodVisitor visitMethod(int access, String name, String desc, String signature,
- String[] exceptions) {
- MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
- return new MethodVisitor(Main.ASM_VERSION, mv) {
-
- @Override
- public void visitFieldInsn(int opcode, String owner, String name, String desc) {
- addClass(owner);
- processType(Type.getType(desc));
- super.visitFieldInsn(opcode, owner, name, desc);
- }
-
- @Override
- public void visitLdcInsn(Object cst) {
- if (cst instanceof Type) {
- processType((Type) cst);
- }
- super.visitLdcInsn(cst);
- }
-
- @Override
- public void visitTypeInsn(int opcode, String type) {
- addClass(type);
- super.visitTypeInsn(opcode, type);
- }
-
- @Override
- public void visitMethodInsn(int opcode, String owner, String name, String desc,
- boolean itf) {
- addClass(owner);
- processType(Type.getType(desc));
- super.visitMethodInsn(opcode, owner, name, desc, itf);
- }
-
- };
- }
- }
-}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
deleted file mode 100644
index 0cdcdc0..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2010 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.tools.layoutlib.create;
-
-import static org.junit.Assert.*;
-
-import org.junit.Test;
-import org.objectweb.asm.ClassReader;
-
-import java.io.IOException;
-import java.util.ArrayList;
-
-
-/**
- * Tests {@link ClassHasNativeVisitor}.
- */
-public class ClassHasNativeVisitorTest {
-
- @Test
- public void testHasNative() throws IOException {
- MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
- String className =
- this.getClass().getCanonicalName() + "$" + ClassWithNative.class.getSimpleName();
- ClassReader cr = new ClassReader(className);
-
- cr.accept(cv, 0 /* flags */);
- assertArrayEquals(new String[] { "native_method" }, cv.getMethodsFound());
- assertTrue(cv.hasNativeMethods());
- }
-
- @Test
- public void testHasNoNative() throws IOException {
- MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
- String className =
- this.getClass().getCanonicalName() + "$" + ClassWithoutNative.class.getSimpleName();
- ClassReader cr = new ClassReader(className);
-
- cr.accept(cv, 0 /* flags */);
- assertArrayEquals(new String[0], cv.getMethodsFound());
- assertFalse(cv.hasNativeMethods());
- }
-
- //-------
-
- /**
- * Overrides {@link ClassHasNativeVisitor} to collec the name of the native methods found.
- */
- private static class MockClassHasNativeVisitor extends ClassHasNativeVisitor {
- private ArrayList<String> mMethodsFound = new ArrayList<>();
-
- public String[] getMethodsFound() {
- return mMethodsFound.toArray(new String[mMethodsFound.size()]);
- }
-
- @Override
- protected void setHasNativeMethods(boolean hasNativeMethods, String methodName) {
- if (hasNativeMethods) {
- mMethodsFound.add(methodName);
- }
- super.setHasNativeMethods(hasNativeMethods, methodName);
- }
- }
-
- /**
- * Dummy test class with a native method.
- */
- public static class ClassWithNative {
- public ClassWithNative() {
- }
-
- public void callTheNativeMethod() {
- native_method();
- }
-
- private native void native_method();
- }
-
- /**
- * Dummy test class with no native method.
- */
- public static class ClassWithoutNative {
- public ClassWithoutNative() {
- }
-
- public void someMethod() {
- }
- }
-}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/CreateInfoAdapter.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/CreateInfoAdapter.java
deleted file mode 100644
index ad7cb9a..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/CreateInfoAdapter.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.layoutlib.create;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-
-class CreateInfoAdapter implements ICreateInfo {
- private static final String[] EMPTY_STRING_ARRAY = new String[0];
-
- @Override
- public Class<?>[] getInjectedClasses() {
- return new Class<?>[0];
- }
-
- @Override
- public String[] getDelegateMethods() {
- return EMPTY_STRING_ARRAY;
- }
-
- @Override
- public String[] getDelegateClassNatives() {
- return EMPTY_STRING_ARRAY;
- }
-
- @Override
- public String[] getRenamedClasses() {
- return EMPTY_STRING_ARRAY;
- }
-
- @Override
- public String[] getRefactoredClasses() {
- return EMPTY_STRING_ARRAY;
- }
-
- @Override
- public String[] getDeleteReturns() {
- return EMPTY_STRING_ARRAY;
- }
-
- @Override
- public String[] getJavaPkgClasses() {
- return EMPTY_STRING_ARRAY;
- }
-
- @Override
- public Set<String> getExcludedClasses() {
- return Collections.emptySet();
- }
-
- @Override
- public String[] getPromotedFields() {
- return EMPTY_STRING_ARRAY;
- }
-
- @Override
- public String[] getPromotedClasses() {
- return EMPTY_STRING_ARRAY;
- }
-
- @Override
- public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
- return Collections.emptyMap();
- }
-}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
deleted file mode 100644
index afaa399..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
+++ /dev/null
@@ -1,535 +0,0 @@
-/*
- * Copyright (C) 2010 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.tools.layoutlib.create;
-
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.android.tools.layoutlib.create.dataclass.ClassWithNative;
-import com.android.tools.layoutlib.create.dataclass.OuterClass;
-import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass;
-import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.ClassWriter;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-public class DelegateClassAdapterTest {
-
- private MockLog mLog;
-
- private static final String NATIVE_CLASS_NAME = ClassWithNative.class.getName();
- private static final String OUTER_CLASS_NAME = OuterClass.class.getName();
- private static final String INNER_CLASS_NAME = InnerClass.class.getName();
- private static final String STATIC_INNER_CLASS_NAME = StaticInnerClass.class.getName();
-
- @Before
- public void setUp() throws Exception {
- mLog = new MockLog();
- mLog.setVerbose(true); // capture debug error too
- }
-
- /**
- * Tests that a class not being modified still works.
- */
- @Test
- public void testNoOp() throws Throwable {
- // create an instance of the class that will be modified
- // (load the class in a distinct class loader so that we can trash its definition later)
- ClassLoader cl1 = new ClassLoader(this.getClass().getClassLoader()) { };
- @SuppressWarnings("unchecked")
- Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(NATIVE_CLASS_NAME);
- ClassWithNative instance1 = clazz1.newInstance();
- assertEquals(42, instance1.add(20, 22));
- try {
- instance1.callNativeInstance(10, 3.1415, new Object[0] );
- fail("Test should have failed to invoke callTheNativeMethod [1]");
- } catch (UnsatisfiedLinkError e) {
- // This is expected to fail since the native method is not implemented.
- }
-
- // Now process it but tell the delegate to not modify any method
- ClassWriter cw = new ClassWriter(0 /*flags*/);
-
- HashSet<String> delegateMethods = new HashSet<>();
- String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
- DelegateClassAdapter cv = new DelegateClassAdapter(
- mLog, cw, internalClassName, delegateMethods);
-
- ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
- cr.accept(cv, 0 /* flags */);
-
- // Load the generated class in a different class loader and try it again
-
- ClassLoader2 cl2 = null;
- try {
- cl2 = new ClassLoader2() {
- @Override
- public void testModifiedInstance() throws Exception {
- Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME);
- Object i2 = clazz2.newInstance();
- assertNotNull(i2);
- assertEquals(42, callAdd(i2, 20, 22));
-
- try {
- callCallNativeInstance(i2, 10, 3.1415, new Object[0]);
- fail("Test should have failed to invoke callTheNativeMethod [2]");
- } catch (InvocationTargetException e) {
- // This is expected to fail since the native method has NOT been
- // overridden here.
- assertEquals(UnsatisfiedLinkError.class, e.getCause().getClass());
- }
-
- // Check that the native method does NOT have the new annotation
- Method[] m = clazz2.getDeclaredMethods();
- Method nativeInstanceMethod = null;
- for (Method method : m) {
- if ("native_instance".equals(method.getName())) {
- nativeInstanceMethod = method;
- break;
- }
- }
- assertNotNull(nativeInstanceMethod);
- assertTrue(Modifier.isNative(nativeInstanceMethod.getModifiers()));
- Annotation[] a = nativeInstanceMethod.getAnnotations();
- assertEquals(0, a.length);
- }
- };
- cl2.add(NATIVE_CLASS_NAME, cw);
- cl2.testModifiedInstance();
- } catch (Throwable t) {
- throw dumpGeneratedClass(t, cl2);
- }
- }
-
- /**
- * {@link DelegateMethodAdapter} does not support overriding constructors yet,
- * so this should fail with an {@link UnsupportedOperationException}.
- *
- * Although not tested here, the message of the exception should contain the
- * constructor signature.
- */
- @Test(expected=UnsupportedOperationException.class)
- public void testConstructorsNotSupported() throws IOException {
- ClassWriter cw = new ClassWriter(0 /*flags*/);
-
- String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
-
- HashSet<String> delegateMethods = new HashSet<>();
- delegateMethods.add("<init>");
- DelegateClassAdapter cv = new DelegateClassAdapter(
- mLog, cw, internalClassName, delegateMethods);
-
- ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
- cr.accept(cv, 0 /* flags */);
- }
-
- @Test
- public void testDelegateNative() throws Throwable {
- ClassWriter cw = new ClassWriter(0 /*flags*/);
- String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
-
- HashSet<String> delegateMethods = new HashSet<>();
- delegateMethods.add(DelegateClassAdapter.ALL_NATIVES);
- DelegateClassAdapter cv = new DelegateClassAdapter(
- mLog, cw, internalClassName, delegateMethods);
-
- ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
- cr.accept(cv, 0 /* flags */);
-
- // Load the generated class in a different class loader and try it
- ClassLoader2 cl2 = null;
- try {
- cl2 = new ClassLoader2() {
- @Override
- public void testModifiedInstance() throws Exception {
- Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME);
- Object i2 = clazz2.newInstance();
- assertNotNull(i2);
-
- // Use reflection to access inner methods
- assertEquals(42, callAdd(i2, 20, 22));
-
- Object[] objResult = new Object[] { null };
- int result = callCallNativeInstance(i2, 10, 3.1415, objResult);
- assertEquals((int)(10 + 3.1415), result);
- assertSame(i2, objResult[0]);
-
- // Check that the native method now has the new annotation and is not native
- Method[] m = clazz2.getDeclaredMethods();
- Method nativeInstanceMethod = null;
- for (Method method : m) {
- if ("native_instance".equals(method.getName())) {
- nativeInstanceMethod = method;
- break;
- }
- }
- assertNotNull(nativeInstanceMethod);
- assertFalse(Modifier.isNative(nativeInstanceMethod.getModifiers()));
- Annotation[] a = nativeInstanceMethod.getAnnotations();
- assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName());
- }
- };
- cl2.add(NATIVE_CLASS_NAME, cw);
- cl2.testModifiedInstance();
- } catch (Throwable t) {
- throw dumpGeneratedClass(t, cl2);
- }
- }
-
- @Test
- public void testDelegateInner() throws Throwable {
- // We'll delegate the "get" method of both the inner and outer class.
- HashSet<String> delegateMethods = new HashSet<>();
- delegateMethods.add("get");
- delegateMethods.add("privateMethod");
-
- // Generate the delegate for the outer class.
- ClassWriter cwOuter = new ClassWriter(0 /*flags*/);
- String outerClassName = OUTER_CLASS_NAME.replace('.', '/');
- DelegateClassAdapter cvOuter = new DelegateClassAdapter(
- mLog, cwOuter, outerClassName, delegateMethods);
- ClassReader cr = new ClassReader(OUTER_CLASS_NAME);
- cr.accept(cvOuter, 0 /* flags */);
-
- // Generate the delegate for the inner class.
- ClassWriter cwInner = new ClassWriter(0 /*flags*/);
- String innerClassName = INNER_CLASS_NAME.replace('.', '/');
- DelegateClassAdapter cvInner = new DelegateClassAdapter(
- mLog, cwInner, innerClassName, delegateMethods);
- cr = new ClassReader(INNER_CLASS_NAME);
- cr.accept(cvInner, 0 /* flags */);
-
- // Load the generated classes in a different class loader and try them
- ClassLoader2 cl2 = null;
- try {
- cl2 = new ClassLoader2() {
- @Override
- public void testModifiedInstance() throws Exception {
-
- // Check the outer class
- Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME);
- Object o2 = outerClazz2.newInstance();
- assertNotNull(o2);
-
- // The original Outer.get returns 1+10+20,
- // but the delegate makes it return 4+10+20
- assertEquals(4+10+20, callGet(o2, 10, 20));
- assertEquals(1+10+20, callGet_Original(o2, 10, 20));
-
- // The original Outer has a private method,
- // so by default we can't access it.
- boolean gotIllegalAccessException = false;
- try {
- callMethod(o2, "privateMethod", false /*makePublic*/);
- } catch(IllegalAccessException e) {
- gotIllegalAccessException = true;
- }
- assertTrue(gotIllegalAccessException);
-
- // The private method from original Outer has been
- // delegated. The delegate generated should have the
- // same access.
- gotIllegalAccessException = false;
- try {
- assertEquals("outerPrivateMethod",
- callMethod(o2, "privateMethod_Original", false /*makePublic*/));
- } catch (IllegalAccessException e) {
- gotIllegalAccessException = true;
- }
- assertTrue(gotIllegalAccessException);
-
- // Check the inner class. Since it's not a static inner class, we need
- // to use the hidden constructor that takes the outer class as first parameter.
- Class<?> innerClazz2 = loadClass(INNER_CLASS_NAME);
- Constructor<?> innerCons = innerClazz2.getConstructor(outerClazz2);
- Object i2 = innerCons.newInstance(o2);
- assertNotNull(i2);
-
- // The original Inner.get returns 3+10+20,
- // but the delegate makes it return 6+10+20
- assertEquals(6+10+20, callGet(i2, 10, 20));
- assertEquals(3+10+20, callGet_Original(i2, 10, 20));
- }
- };
- cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray());
- cl2.add(INNER_CLASS_NAME, cwInner.toByteArray());
- cl2.testModifiedInstance();
- } catch (Throwable t) {
- throw dumpGeneratedClass(t, cl2);
- }
- }
-
- @Test
- public void testDelegateStaticInner() throws Throwable {
- // We'll delegate the "get" method of both the inner and outer class.
- HashSet<String> delegateMethods = new HashSet<>();
- delegateMethods.add("get");
-
- // Generate the delegate for the outer class.
- ClassWriter cwOuter = new ClassWriter(0 /*flags*/);
- String outerClassName = OUTER_CLASS_NAME.replace('.', '/');
- DelegateClassAdapter cvOuter = new DelegateClassAdapter(
- mLog, cwOuter, outerClassName, delegateMethods);
- ClassReader cr = new ClassReader(OUTER_CLASS_NAME);
- cr.accept(cvOuter, 0 /* flags */);
-
- // Generate the delegate for the static inner class.
- ClassWriter cwInner = new ClassWriter(0 /*flags*/);
- String innerClassName = STATIC_INNER_CLASS_NAME.replace('.', '/');
- DelegateClassAdapter cvInner = new DelegateClassAdapter(
- mLog, cwInner, innerClassName, delegateMethods);
- cr = new ClassReader(STATIC_INNER_CLASS_NAME);
- cr.accept(cvInner, 0 /* flags */);
-
- // Load the generated classes in a different class loader and try them
- ClassLoader2 cl2 = null;
- try {
- cl2 = new ClassLoader2() {
- @Override
- public void testModifiedInstance() throws Exception {
-
- // Check the outer class
- Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME);
- Object o2 = outerClazz2.newInstance();
- assertNotNull(o2);
-
- // Check the inner class. Since it's not a static inner class, we need
- // to use the hidden constructor that takes the outer class as first parameter.
- Class<?> innerClazz2 = loadClass(STATIC_INNER_CLASS_NAME);
- Constructor<?> innerCons = innerClazz2.getConstructor();
- Object i2 = innerCons.newInstance();
- assertNotNull(i2);
-
- // The original StaticInner.get returns 100+10+20,
- // but the delegate makes it return 6+10+20
- assertEquals(6+10+20, callGet(i2, 10, 20));
- assertEquals(100+10+20, callGet_Original(i2, 10, 20));
- }
- };
- cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray());
- cl2.add(STATIC_INNER_CLASS_NAME, cwInner.toByteArray());
- cl2.testModifiedInstance();
- } catch (Throwable t) {
- throw dumpGeneratedClass(t, cl2);
- }
- }
-
- //-------
-
- /**
- * A class loader than can define and instantiate our modified classes.
- * <p/>
- * The trick here is that this class loader will test our <em>modified</em> version
- * of the classes, the one with the delegate calls.
- * <p/>
- * Trying to do so in the original class loader generates all sort of link issues because
- * there are 2 different definitions of the same class name. This class loader will
- * define and load the class when requested by name and provide helpers to access the
- * instance methods via reflection.
- */
- private abstract class ClassLoader2 extends ClassLoader {
-
- private final Map<String, byte[]> mClassDefs = new HashMap<>();
-
- public ClassLoader2() {
- super(null);
- }
-
- public ClassLoader2 add(String className, byte[] definition) {
- mClassDefs.put(className, definition);
- return this;
- }
-
- public ClassLoader2 add(String className, ClassWriter rewrittenClass) {
- mClassDefs.put(className, rewrittenClass.toByteArray());
- return this;
- }
-
- private Set<Entry<String, byte[]>> getByteCode() {
- return mClassDefs.entrySet();
- }
-
- @SuppressWarnings("unused")
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- try {
- return super.findClass(name);
- } catch (ClassNotFoundException e) {
-
- byte[] def = mClassDefs.get(name);
- if (def != null) {
- // Load the modified ClassWithNative from its bytes representation.
- return defineClass(name, def, 0, def.length);
- }
-
- try {
- // Load everything else from the original definition into the new class loader.
- ClassReader cr = new ClassReader(name);
- ClassWriter cw = new ClassWriter(0);
- cr.accept(cw, 0);
- byte[] bytes = cw.toByteArray();
- return defineClass(name, bytes, 0, bytes.length);
-
- } catch (IOException ioe) {
- throw new RuntimeException(ioe);
- }
- }
- }
-
- /**
- * Accesses {@link OuterClass#get} or {@link InnerClass#get}via reflection.
- */
- public int callGet(Object instance, int a, long b) throws Exception {
- Method m = instance.getClass().getMethod("get",
- int.class, long.class);
-
- Object result = m.invoke(instance, a, b);
- return (Integer) result;
- }
-
- /**
- * Accesses the "_Original" methods for {@link OuterClass#get}
- * or {@link InnerClass#get}via reflection.
- */
- public int callGet_Original(Object instance, int a, long b) throws Exception {
- Method m = instance.getClass().getMethod("get_Original",
- int.class, long.class);
-
- Object result = m.invoke(instance, a, b);
- return (Integer) result;
- }
-
- /**
- * Accesses the any declared method that takes no parameter via reflection.
- */
- @SuppressWarnings("unchecked")
- public <T> T callMethod(Object instance, String methodName, boolean makePublic) throws Exception {
- Method m = instance.getClass().getDeclaredMethod(methodName, (Class<?>[])null);
-
- boolean wasAccessible = m.isAccessible();
- if (makePublic && !wasAccessible) {
- m.setAccessible(true);
- }
-
- Object result = m.invoke(instance, (Object[])null);
-
- if (makePublic && !wasAccessible) {
- m.setAccessible(false);
- }
-
- return (T) result;
- }
-
- /**
- * Accesses {@link ClassWithNative#add(int, int)} via reflection.
- */
- public int callAdd(Object instance, int a, int b) throws Exception {
- Method m = instance.getClass().getMethod("add",
- int.class, int.class);
-
- Object result = m.invoke(instance, a, b);
- return (Integer) result;
- }
-
- /**
- * Accesses {@link ClassWithNative#callNativeInstance(int, double, Object[])}
- * via reflection.
- */
- public int callCallNativeInstance(Object instance, int a, double d, Object[] o)
- throws Exception {
- Method m = instance.getClass().getMethod("callNativeInstance",
- int.class, double.class, Object[].class);
-
- Object result = m.invoke(instance, a, d, o);
- return (Integer) result;
- }
-
- public abstract void testModifiedInstance() throws Exception;
- }
-
- /**
- * For debugging, it's useful to dump the content of the generated classes
- * along with the exception that was generated.
- *
- * However to make it work you need to pull in the org.objectweb.asm.util.TraceClassVisitor
- * class and associated utilities which are found in the ASM source jar. Since we don't
- * want that dependency in the source code, we only put it manually for development and
- * access the TraceClassVisitor via reflection if present.
- *
- * @param t The exception thrown by {@link ClassLoader2#testModifiedInstance()}
- * @param cl2 The {@link ClassLoader2} instance with the generated bytecode.
- * @return Either original {@code t} or a new wrapper {@link Throwable}
- */
- private Throwable dumpGeneratedClass(Throwable t, ClassLoader2 cl2) {
- try {
- // For debugging, dump the bytecode of the class in case of unexpected error
- // if we can find the TraceClassVisitor class.
- Class<?> tcvClass = Class.forName("org.objectweb.asm.util.TraceClassVisitor");
-
- StringBuilder sb = new StringBuilder();
- sb.append('\n').append(t.getClass().getCanonicalName());
- if (t.getMessage() != null) {
- sb.append(": ").append(t.getMessage());
- }
-
- for (Entry<String, byte[]> entry : cl2.getByteCode()) {
- String className = entry.getKey();
- byte[] bytes = entry.getValue();
-
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- // next 2 lines do: TraceClassVisitor tcv = new TraceClassVisitor(pw);
- Constructor<?> cons = tcvClass.getConstructor(pw.getClass());
- Object tcv = cons.newInstance(pw);
- ClassReader cr2 = new ClassReader(bytes);
- cr2.accept((ClassVisitor) tcv, 0 /* flags */);
-
- sb.append("\nBytecode dump: <").append(className).append(">:\n")
- .append(sw.toString());
- }
-
- // Re-throw exception with new message
- return new RuntimeException(sb.toString(), t);
- } catch (Throwable ignore) {
- // In case of problem, just throw the original exception as-is.
- return t;
- }
- }
-
-}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
deleted file mode 100644
index 1a5f653..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2008 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.tools.layoutlib.create;
-
-import static org.junit.Assert.*;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class LogTest {
-
- private MockLog mLog;
-
- @Before
- public void setUp() throws Exception {
- mLog = new MockLog();
- }
-
- @After
- public void tearDown() throws Exception {
- // pass
- }
-
- @Test
- public void testDebug() {
- assertEquals("", mLog.getOut());
- assertEquals("", mLog.getErr());
-
- mLog.setVerbose(false);
- mLog.debug("Test %d", 42);
- assertEquals("", mLog.getOut());
-
- mLog.setVerbose(true);
- mLog.debug("Test %d", 42);
-
- assertEquals("Test 42\n", mLog.getOut());
- assertEquals("", mLog.getErr());
- }
-
- @Test
- public void testInfo() {
- assertEquals("", mLog.getOut());
- assertEquals("", mLog.getErr());
-
- mLog.info("Test %d", 43);
-
- assertEquals("Test 43\n", mLog.getOut());
- assertEquals("", mLog.getErr());
- }
-
- @Test
- public void testError() {
- assertEquals("", mLog.getOut());
- assertEquals("", mLog.getErr());
-
- mLog.error("Test %d", 44);
-
- assertEquals("", mLog.getOut());
- assertEquals("Test 44\n", mLog.getErr());
- }
-
- @Test
- public void testException() {
- assertEquals("", mLog.getOut());
- assertEquals("", mLog.getErr());
-
- Exception e = new Exception("My Exception");
- mLog.exception(e, "Test %d", 44);
-
- assertEquals("", mLog.getOut());
- assertTrue(mLog.getErr().startsWith("Test 44\njava.lang.Exception: My Exception"));
- }
-}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java
deleted file mode 100644
index de750a3..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2010 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.tools.layoutlib.create;
-
-
-public class MockLog extends Log {
- StringBuilder mOut = new StringBuilder();
- StringBuilder mErr = new StringBuilder();
-
- public String getOut() {
- return mOut.toString();
- }
-
- public String getErr() {
- return mErr.toString();
- }
-
- @Override
- protected void outPrintln(String msg) {
- mOut.append(msg);
- mOut.append('\n');
- }
-
- @Override
- protected void errPrintln(String msg) {
- mErr.append(msg);
- mErr.append('\n');
- }
-}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/PromoteClassClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/PromoteClassClassAdapterTest.java
deleted file mode 100644
index eeb0b10..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/PromoteClassClassAdapterTest.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.layoutlib.create;
-
-import org.junit.Test;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.Opcodes;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.StringJoiner;
-
-import static org.junit.Assert.assertTrue;
-
-/**
- * {@link ClassVisitor} that logs all the calls to the different visit methods so they can be later
- * inspected.
- */
-class LoggingClassVisitor extends ClassVisitor {
- List<String> mLog = new LinkedList<String>();
-
- public LoggingClassVisitor() {
- super(Main.ASM_VERSION);
- }
-
- public LoggingClassVisitor(ClassVisitor cv) {
- super(Main.ASM_VERSION, cv);
- }
-
- private static String formatAccess(int access) {
- StringJoiner modifiers = new StringJoiner(",");
-
- if ((access & Opcodes.ACC_PUBLIC) != 0) {
- modifiers.add("public");
- }
- if ((access & Opcodes.ACC_PRIVATE) != 0) {
- modifiers.add("private");
- }
- if ((access & Opcodes.ACC_PROTECTED) != 0) {
- modifiers.add("protected");
- }
- if ((access & Opcodes.ACC_STATIC) != 0) {
- modifiers.add("static");
- }
- if ((access & Opcodes.ACC_FINAL) != 0) {
- modifiers.add("static");
- }
-
- return "[" + modifiers.toString() + "]";
- }
-
- private void log(String method, String format, Object...args) {
- mLog.add(
- String.format("[%s] - %s", method, String.format(format, (Object[]) args))
- );
- }
-
- @Override
- public void visitOuterClass(String owner, String name, String desc) {
- log(
- "visitOuterClass",
- "owner=%s, name=%s, desc=%s",
- owner, name, desc
- );
-
- super.visitOuterClass(owner, name, desc);
- }
-
- @Override
- public void visitInnerClass(String name, String outerName, String innerName, int access) {
- log(
- "visitInnerClass",
- "name=%s, outerName=%s, innerName=%s, access=%s",
- name, outerName, innerName, formatAccess(access)
- );
-
- super.visitInnerClass(name, outerName, innerName, access);
- }
-
- @Override
- public void visit(int version, int access, String name, String signature, String superName,
- String[] interfaces) {
- log(
- "visit",
- "version=%d, access=%s, name=%s, signature=%s, superName=%s, interfaces=%s",
- version, formatAccess(access), name, signature, superName, Arrays.toString(interfaces)
- );
-
- super.visit(version, access, name, signature, superName, interfaces);
- }
-}
-
-class PackageProtectedClass {}
-
-public class PromoteClassClassAdapterTest {
- private static class PrivateClass {}
- private static class ClassWithPrivateInnerClass {
- private class InnerPrivateClass {}
- }
-
- @Test
- public void testInnerClassPromotion() throws IOException {
- ClassReader reader = new ClassReader(PrivateClass.class.getName());
- LoggingClassVisitor log = new LoggingClassVisitor();
-
- PromoteClassClassAdapter adapter = new PromoteClassClassAdapter(log, new HashSet<String>() {
- {
- add("com.android.tools.layoutlib.create.PromoteClassClassAdapterTest$PrivateClass");
- add("com.android.tools.layoutlib.create" +
- ".PromoteClassClassAdapterTest$ClassWithPrivateInnerClass$InnerPrivateClass");
- }
- });
- reader.accept(adapter, 0);
- assertTrue(log.mLog.contains(
- "[visitInnerClass] - " +
- "name=com/android/tools/layoutlib/create" +
- "/PromoteClassClassAdapterTest$PrivateClass, " +
- "outerName=com/android/tools/layoutlib/create" +
- "/PromoteClassClassAdapterTest, innerName=PrivateClass, access=[public,static]"));
-
- // Test inner of inner class
- log.mLog.clear();
- reader = new ClassReader(ClassWithPrivateInnerClass.class.getName());
- reader.accept(adapter, 0);
-
- assertTrue(log.mLog.contains("[visitInnerClass] - " +
- "name=com/android/tools/layoutlib/create" +
- "/PromoteClassClassAdapterTest$ClassWithPrivateInnerClass$InnerPrivateClass, " +
- "outerName=com/android/tools/layoutlib/create" +
- "/PromoteClassClassAdapterTest$ClassWithPrivateInnerClass, " +
- "innerName=InnerPrivateClass, access=[public]"));
-
- }
-
- @Test
- public void testProtectedClassPromotion() throws IOException {
- ClassReader reader = new ClassReader(PackageProtectedClass.class.getName());
- LoggingClassVisitor log = new LoggingClassVisitor();
-
- PromoteClassClassAdapter adapter = new PromoteClassClassAdapter(log, new HashSet<String>() {
- {
- add("com.android.tools.layoutlib.create.PackageProtectedClass");
- }
- });
-
- reader.accept(adapter, 0);
- assertTrue(log.mLog.contains("[visit] - version=52, access=[public], " +
- "name=com/android/tools/layoutlib/create/PackageProtectedClass, signature=null, " +
- "superName=java/lang/Object, interfaces=[]"));
-
- }
-}
\ No newline at end of file
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java
deleted file mode 100644
index 6211e73..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2008 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.tools.layoutlib.create;
-
-import static org.junit.Assert.*;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- *
- */
-public class RenameClassAdapterTest {
-
- private RenameClassAdapter mOuter;
- private RenameClassAdapter mInner;
-
- @Before
- public void setUp() throws Exception {
- mOuter = new RenameClassAdapter(null, // cv
- "com.pack.Old",
- "org.blah.New");
-
- mInner = new RenameClassAdapter(null, // cv
- "com.pack.Old$Inner",
- "org.blah.New$Inner");
- }
-
- @After
- public void tearDown() throws Exception {
- }
-
- /**
- * Renames a type, e.g. "Lcom.package.My;"
- * If the type doesn't need to be renamed, returns the input string as-is.
- */
- @Test
- public void testRenameTypeDesc() {
-
- // primitive types are left untouched
- assertEquals("I", mOuter.renameTypeDesc("I"));
- assertEquals("D", mOuter.renameTypeDesc("D"));
- assertEquals("V", mOuter.renameTypeDesc("V"));
-
- // object types that need no renaming are left untouched
- assertEquals("Lcom.package.MyClass;", mOuter.renameTypeDesc("Lcom.package.MyClass;"));
- assertEquals("Lcom.package.MyClass;", mInner.renameTypeDesc("Lcom.package.MyClass;"));
-
- // object types that match the requirements
- assertEquals("Lorg.blah.New;", mOuter.renameTypeDesc("Lcom.pack.Old;"));
- assertEquals("Lorg.blah.New$Inner;", mInner.renameTypeDesc("Lcom.pack.Old$Inner;"));
- // inner classes match the base type which is being renamed
- assertEquals("Lorg.blah.New$Other;", mOuter.renameTypeDesc("Lcom.pack.Old$Other;"));
- assertEquals("Lorg.blah.New$Other;", mInner.renameTypeDesc("Lcom.pack.Old$Other;"));
-
- // arrays
- assertEquals("[Lorg.blah.New;", mOuter.renameTypeDesc("[Lcom.pack.Old;"));
- assertEquals("[[Lorg.blah.New;", mOuter.renameTypeDesc("[[Lcom.pack.Old;"));
-
- assertEquals("[Lorg.blah.New;", mInner.renameTypeDesc("[Lcom.pack.Old;"));
- assertEquals("[[Lorg.blah.New;", mInner.renameTypeDesc("[[Lcom.pack.Old;"));
- }
-
- /**
- * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an
- * object element, e.g. "[Lcom.package.MyClass;"
- * If the type doesn't need to be renamed, returns the internal name of the input type.
- */
- @Test
- public void testRenameType() {
- // Skip. This is actually tested by testRenameTypeDesc above.
- }
-
- /**
- * Renames an internal type name, e.g. "com.package.MyClass".
- * If the type doesn't need to be renamed, returns the input string as-is.
- */
- @Test
- public void testRenameInternalType() {
- // an actual FQCN
- assertEquals("org.blah.New", mOuter.renameInternalType("com.pack.Old"));
- assertEquals("org.blah.New$Inner", mOuter.renameInternalType("com.pack.Old$Inner"));
-
- assertEquals("org.blah.New$Other", mInner.renameInternalType("com.pack.Old$Other"));
- assertEquals("org.blah.New$Other", mInner.renameInternalType("com.pack.Old$Other"));
- }
-
- /**
- * Renames a method descriptor, i.e. applies renameType to all arguments and to the
- * return value.
- */
- @Test
- public void testRenameMethodDesc() {
- assertEquals("(IDLorg.blah.New;[Lorg.blah.New$Inner;)Lorg.blah.New$Other;",
- mOuter.renameMethodDesc("(IDLcom.pack.Old;[Lcom.pack.Old$Inner;)Lcom.pack.Old$Other;"));
- }
-
-
-
-}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/StubMethodAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/StubMethodAdapterTest.java
deleted file mode 100644
index 3db3e23..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/StubMethodAdapterTest.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.layoutlib.create;
-
-import com.android.tools.layoutlib.create.dataclass.StubClass;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-
-import java.lang.reflect.Method;
-import java.util.function.BiPredicate;
-import java.util.function.Consumer;
-
-import static org.junit.Assert.*;
-
-public class StubMethodAdapterTest {
-
- private static final String STUB_CLASS_NAME = StubClass.class.getName();
-
- /**
- * Load a dummy class, stub one of its method and ensure that the modified class works as
- * intended.
- */
- @Test
- public void testBoolean() throws Exception {
- final String methodName = "returnTrue";
- // First don't change the method and assert that it returns true
- testBoolean((name, type) -> false, Assert::assertTrue, methodName);
- // Change the method now and assert that it returns false.
- testBoolean((name, type) -> methodName.equals(name) &&
- Type.BOOLEAN_TYPE.equals(type.getReturnType()), Assert::assertFalse, methodName);
- }
-
- /**
- * @param methodPredicate tests if the method should be replaced
- */
- private void testBoolean(BiPredicate<String, Type> methodPredicate, Consumer<Boolean> assertion,
- String methodName) throws Exception {
- ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
- // Always rename the class to avoid conflict with the original class.
- String newClassName = STUB_CLASS_NAME + '_';
- new ClassReader(STUB_CLASS_NAME).accept(
- new ClassAdapter(newClassName, writer, methodPredicate), 0);
- MyClassLoader myClassLoader = new MyClassLoader(newClassName, writer.toByteArray());
- Class<?> aClass = myClassLoader.loadClass(newClassName);
- assertTrue("StubClass not loaded by the classloader. Likely a bug in the test.",
- myClassLoader.findClassCalled);
- Method method = aClass.getMethod(methodName);
- Object o = aClass.newInstance();
- assertion.accept((Boolean) method.invoke(o));
- }
-
- private static class ClassAdapter extends ClassVisitor {
-
- private final String mClassName;
- private final BiPredicate<String, Type> mMethodPredicate;
-
- private ClassAdapter(String className, ClassVisitor cv,
- BiPredicate<String, Type> methodPredicate) {
- super(Main.ASM_VERSION, cv);
- mClassName = className.replace('.', '/');
- mMethodPredicate = methodPredicate;
- }
-
- @Override
- public void visit(int version, int access, String name, String signature, String superName,
- String[] interfaces) {
- super.visit(version, access, mClassName, signature, superName,
- interfaces);
- }
-
- @Override
- public MethodVisitor visitMethod(int access, String name, String desc, String signature,
- String[] exceptions) {
- // Copied partly from
- // com.android.tools.layoutlib.create.DelegateClassAdapter.visitMethod()
- // but not generating the _Original method.
- boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
- boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
- MethodVisitor originalMethod =
- super.visitMethod(access, name, desc, signature, exceptions);
- Type descriptor = Type.getMethodType(desc);
- if (mMethodPredicate.test(name, descriptor)) {
- String methodSignature = mClassName + "#" + name;
- String invokeSignature = methodSignature + desc;
- return new StubMethodAdapter(originalMethod, name, descriptor.getReturnType(),
- invokeSignature, isStatic, isNative);
- }
- return originalMethod;
- }
- }
-
- private static class MyClassLoader extends ClassLoader {
- private final String mName;
- private final byte[] mBytes;
- private boolean findClassCalled;
-
- private MyClassLoader(String name, byte[] bytes) {
- mName = name;
- mBytes = bytes;
- }
-
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- if (name.equals(mName)) {
- findClassCalled = true;
- return defineClass(name, mBytes, 0, mBytes.length);
- }
- return super.findClass(name);
- }
- }
-}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java
deleted file mode 100644
index c314853..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2010 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.tools.layoutlib.create.dataclass;
-
-import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
-
-/**
- * Dummy test class with a native method.
- * The native method is not defined and any attempt to invoke it will
- * throw an {@link UnsatisfiedLinkError}.
- *
- * Used by {@link DelegateClassAdapterTest}.
- */
-public class ClassWithNative {
- public ClassWithNative() {
- }
-
- public int add(int a, int b) {
- return a + b;
- }
-
- // Note: it's good to have a long or double for testing parameters since they take
- // 2 slots in the stack/locals maps.
-
- public int callNativeInstance(int a, double d, Object[] o) {
- return native_instance(a, d, o);
- }
-
- private native int native_instance(int a, double d, Object[] o);
-}
-
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java
deleted file mode 100644
index a3d4dc6..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2010 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.tools.layoutlib.create.dataclass;
-
-import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
-
-/**
- * The delegate that receives the call to {@link ClassWithNative_Delegate}'s overridden methods.
- *
- * Used by {@link DelegateClassAdapterTest}.
- */
-public class ClassWithNative_Delegate {
- public static int native_instance(ClassWithNative instance, int a, double d, Object[] o) {
- if (o != null && o.length > 0) {
- o[0] = instance;
- }
- return (int)(a + d);
- }
-}
-
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/JavaClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/JavaClass.java
deleted file mode 100644
index 9b5a918..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/JavaClass.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.tools.layoutlib.create.dataclass;
-
-public final class JavaClass {
-
- public static final String test = "test";
-}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
deleted file mode 100644
index 6dfb816..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2011 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.tools.layoutlib.create.dataclass;
-
-import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
-
-/**
- * Test class with an inner class.
- *
- * Used by {@link DelegateClassAdapterTest}.
- */
-public class OuterClass {
- private int mOuterValue = 1;
- public OuterClass() {
- }
-
- // Outer.get returns 1 + a + b
- // Note: it's good to have a long or double for testing parameters since they take
- // 2 slots in the stack/locals maps.
- public int get(int a, long b) {
- return mOuterValue + a + (int) b;
- }
-
- public class InnerClass {
- public InnerClass() {
- }
-
- // Inner.get returns 2 + 1 + a + b
- public int get(int a, long b) {
- return 2 + mOuterValue + a + (int) b;
- }
- }
-
- public static class StaticInnerClass {
- public StaticInnerClass() {
- }
-
- // StaticInnerClass.get returns 100 + a + b
- public int get(int a, long b) {
- return 100 + a + (int) b;
- }
- }
-
- @SuppressWarnings("unused")
- private String privateMethod() {
- return "outerPrivateMethod";
- }
-}
-
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java
deleted file mode 100644
index 774be8e..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2011 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.tools.layoutlib.create.dataclass;
-
-import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
-
-/**
- * Used by {@link DelegateClassAdapterTest}.
- */
-public class OuterClass_Delegate {
- // The delegate override of Outer.get returns 4 + a + b
- public static int get(OuterClass instance, int a, long b) {
- return 4 + a + (int) b;
- }
-
- public static String privateMethod(OuterClass instance) {
- return "outerPrivate_Delegate";
- }
-}
-
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java
deleted file mode 100644
index b472220..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2011 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.tools.layoutlib.create.dataclass;
-
-import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
-import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass;
-
-/**
- * Used by {@link DelegateClassAdapterTest}.
- */
-public class OuterClass_InnerClass_Delegate {
- // The delegate override of Inner.get return 6 + a + b
- public static int get(OuterClass outer, InnerClass inner, int a, long b) {
- return 6 + a + (int) b;
- }
-}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java
deleted file mode 100644
index a29439e..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2015 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.tools.layoutlib.create.dataclass;
-
-import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
-import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass;
-
-/**
- * Used by {@link DelegateClassAdapterTest}.
- */
-public class OuterClass_StaticInnerClass_Delegate {
- // The delegate override of Inner.get return 6 + a + b
- public static int get(StaticInnerClass inner, int a, long b) {
- return 6 + a + (int) b;
- }
-}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/StubClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/StubClass.java
deleted file mode 100644
index 3ae8e47..0000000
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/StubClass.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tools.layoutlib.create.dataclass;
-
-import com.android.tools.layoutlib.create.StubMethodAdapterTest;
-
-/**
- * Used by {@link StubMethodAdapterTest}
- */
-@SuppressWarnings("unused")
-public class StubClass {
-
- public boolean returnTrue() {
- return true;
- }
-}
diff --git a/tools/layoutlib/create/tests/data/mock_android.jar b/tools/layoutlib/create/tests/data/mock_android.jar
deleted file mode 100644
index c6ca3c4..0000000
--- a/tools/layoutlib/create/tests/data/mock_android.jar
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/create/tests/mock_data/java/lang/JavaClass.java b/tools/layoutlib/create/tests/mock_data/java/lang/JavaClass.java
deleted file mode 100644
index 59612e9..0000000
--- a/tools/layoutlib/create/tests/mock_data/java/lang/JavaClass.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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 java.lang;
-
-public class JavaClass {
-
- public static String test = "test";
-}
diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/data/anotherDataFile b/tools/layoutlib/create/tests/mock_data/mock_android/data/anotherDataFile
deleted file mode 100644
index ab29fbe..0000000
--- a/tools/layoutlib/create/tests/mock_data/mock_android/data/anotherDataFile
+++ /dev/null
@@ -1 +0,0 @@
-A simple data file that should *not* be copied to the output jar.
\ No newline at end of file
diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/data/dataFile b/tools/layoutlib/create/tests/mock_data/mock_android/data/dataFile
deleted file mode 100644
index 9b01893..0000000
--- a/tools/layoutlib/create/tests/mock_data/mock_android/data/dataFile
+++ /dev/null
@@ -1 +0,0 @@
-A simple data file that should be copied to the output jar unchanged.
\ No newline at end of file
diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/dummy/InnerTest.java b/tools/layoutlib/create/tests/mock_data/mock_android/dummy/InnerTest.java
deleted file mode 100644
index d3a1d05..0000000
--- a/tools/layoutlib/create/tests/mock_data/mock_android/dummy/InnerTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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 mock_android.dummy;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-
-public class InnerTest {
-
- private int mSomeField;
- private MyStaticInnerClass mInnerInstance;
- private MyIntEnum mTheIntEnum;
- private MyGenerics1<int[][], InnerTest, MyIntEnum, float[]> mGeneric1;
-
- public class NotStaticInner2 extends NotStaticInner1 {
-
- }
-
- public class NotStaticInner1 {
-
- public void someThing() {
- mSomeField = 2;
- mInnerInstance = null;
- }
-
- }
-
- private static class MyStaticInnerClass {
-
- }
-
- private static class DerivingClass extends InnerTest {
-
- }
-
- // enums are a kind of inner static class
- public enum MyIntEnum {
- VALUE0(0),
- VALUE1(1),
- VALUE2(2);
-
- MyIntEnum(int myInt) {
- this.myInt = myInt;
- }
- final int myInt;
- }
-
- public static class MyGenerics1<T, U, V, W> {
- public MyGenerics1() {
- int a = 1;
- }
- }
-
- public <X> void genericMethod1(X a, X[] b) {
- }
-
- public <X, Y> void genericMethod2(X a, List<Y> b) {
- }
-
- public <X, Y extends InnerTest> void genericMethod3(X a, List<Y> b) {
- }
-
- public <T extends InnerTest> void genericMethod4(T[] a, Collection<T> b, Collection<?> c) {
- Iterator<T> i = b.iterator();
- }
-
- public void someMethod(InnerTest self) {
- mSomeField = self.mSomeField;
- MyStaticInnerClass m = new MyStaticInnerClass();
- mInnerInstance = m;
- mTheIntEnum = null;
- mGeneric1 = new MyGenerics1();
- genericMethod4(new DerivingClass[0], new ArrayList<DerivingClass>(), new ArrayList<InnerTest>());
- }
-}
diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/util/EmptyArray.java b/tools/layoutlib/create/tests/mock_data/mock_android/util/EmptyArray.java
deleted file mode 100644
index aaeebf6..0000000
--- a/tools/layoutlib/create/tests/mock_data/mock_android/util/EmptyArray.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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 mock_android.util;
-
-import java.lang.JavaClass;
-
-public class EmptyArray {
-
- public static final Object[] OBJECT = new Object[0];
-}
diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/view/View.java b/tools/layoutlib/create/tests/mock_data/mock_android/view/View.java
deleted file mode 100644
index 84ec8a9..0000000
--- a/tools/layoutlib/create/tests/mock_data/mock_android/view/View.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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 mock_android.view;
-
-import java.lang.JavaClass;
-
-public class View {
-
- String x = JavaClass.test;
-
-}
diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/view/ViewGroup.java b/tools/layoutlib/create/tests/mock_data/mock_android/view/ViewGroup.java
deleted file mode 100644
index 466470f..0000000
--- a/tools/layoutlib/create/tests/mock_data/mock_android/view/ViewGroup.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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 mock_android.view;
-
-public class ViewGroup extends View {
-
- public class MarginLayoutParams extends LayoutParams {
-
- }
-
- public class LayoutParams {
-
- }
-
-}
diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java b/tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java
deleted file mode 100644
index af56c4b..0000000
--- a/tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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 mock_android.widget;
-
-import mock_android.util.EmptyArray;
-import mock_android.view.ViewGroup;
-
-public class LinearLayout extends ViewGroup {
-
- Object[] mObjects = EmptyArray.OBJECT;
- public class LayoutParams extends MarginLayoutParams {
-
- }
-
-}
diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/widget/TableLayout.java b/tools/layoutlib/create/tests/mock_data/mock_android/widget/TableLayout.java
deleted file mode 100644
index e455e7d..0000000
--- a/tools/layoutlib/create/tests/mock_data/mock_android/widget/TableLayout.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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 mock_android.widget;
-
-import mock_android.view.ViewGroup;
-
-public class TableLayout extends ViewGroup {
-
- public class LayoutParams extends MarginLayoutParams {
-
- }
-
-}
diff --git a/tools/layoutlib/legacy/Android.mk b/tools/layoutlib/legacy/Android.mk
deleted file mode 100644
index 5855f89..0000000
--- a/tools/layoutlib/legacy/Android.mk
+++ /dev/null
@@ -1,30 +0,0 @@
-#
-# Copyright (C) 2008 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 := $(call all-java-files-under,src)
-
-LOCAL_JAVA_LIBRARIES := \
- layoutlib_api-prebuilt
-
-LOCAL_MODULE := layoutlib-legacy
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-# Build all sub-directories
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/tools/layoutlib/legacy/legacy.iml b/tools/layoutlib/legacy/legacy.iml
deleted file mode 100644
index a167a75..0000000
--- a/tools/layoutlib/legacy/legacy.iml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
- </content>
- <orderEntry type="inheritedJdk" />
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="library" name="layoutlib_api-prebuilt" level="project" />
- </component>
-</module>
\ No newline at end of file
diff --git a/tools/layoutlib/legacy/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/legacy/src/com/android/layoutlib/bridge/Bridge.java
deleted file mode 100644
index 0cfc181..0000000
--- a/tools/layoutlib/legacy/src/com/android/layoutlib/bridge/Bridge.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge;import com.android.ide.common.rendering.api.RenderSession;
-import com.android.ide.common.rendering.api.Result;
-import com.android.ide.common.rendering.api.Result.Status;
-import com.android.ide.common.rendering.api.SessionParams;
-
-import java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
-
-/**
- * Legacy Bridge used in the SDK version of layoutlib
- */
-public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
- private static final String SDK_NOT_SUPPORTED = "The SDK layoutlib version is not supported";
- private static final Result NOT_SUPPORTED_RESULT =
- Status.NOT_IMPLEMENTED.createResult(SDK_NOT_SUPPORTED);
- private static BufferedImage sImage;
-
- private static class BridgeRenderSession extends RenderSession {
-
- @Override
- public synchronized BufferedImage getImage() {
- if (sImage == null) {
- sImage = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB);
- Graphics2D g = sImage.createGraphics();
- g.clearRect(0, 0, 500, 500);
- g.drawString(SDK_NOT_SUPPORTED, 20, 20);
- g.dispose();
- }
-
- return sImage;
- }
-
- @Override
- public Result render(long timeout, boolean forceMeasure) {
- return NOT_SUPPORTED_RESULT;
- }
-
- @Override
- public Result measure(long timeout) {
- return NOT_SUPPORTED_RESULT;
- }
-
- @Override
- public Result getResult() {
- return NOT_SUPPORTED_RESULT;
- }
- }
-
-
- @Override
- public RenderSession createSession(SessionParams params) {
- return new BridgeRenderSession();
- }
-
- @Override
- public int getApiLevel() {
- return 0;
- }
-}
diff --git a/tools/layoutlib/rename_font/README b/tools/layoutlib/rename_font/README
deleted file mode 100644
index 600b756..0000000
--- a/tools/layoutlib/rename_font/README
+++ /dev/null
@@ -1,9 +0,0 @@
-This tool is used to rename the PS name encoded inside the ttf font that we ship
-with the SDK. There is bug in Java that returns incorrect results for
-java.awt.Font#layoutGlyphVector() if two fonts with same name but differnt
-versions are loaded. As a workaround, we rename all the fonts that we ship with
-the SDK by appending the font version to its name.
-
-
-The build_font.py copies all files from input_dir to output_dir while renaming
-the font files (*.ttf) in the process.
diff --git a/tools/layoutlib/rename_font/Roboto-Regular.ttf b/tools/layoutlib/rename_font/Roboto-Regular.ttf
deleted file mode 100644
index 7469063..0000000
--- a/tools/layoutlib/rename_font/Roboto-Regular.ttf
+++ /dev/null
Binary files differ
diff --git a/tools/layoutlib/rename_font/build_font.py b/tools/layoutlib/rename_font/build_font.py
deleted file mode 100755
index 9713a4c..0000000
--- a/tools/layoutlib/rename_font/build_font.py
+++ /dev/null
@@ -1,227 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (C) 2014 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.
-
-"""
-Rename the PS name of all fonts in the input directories and copy them to the
-output directory.
-
-Usage: build_font.py /path/to/input_fonts1/ /path/to/input_fonts2/ /path/to/output_fonts/
-
-"""
-
-import glob
-from multiprocessing import Pool
-import os
-import re
-import shutil
-import sys
-import xml.etree.ElementTree as etree
-
-# Prevent .pyc files from being created.
-sys.dont_write_bytecode = True
-
-# fontTools is available at platform/external/fonttools
-from fontTools import ttx
-
-# global variable
-dest_dir = '/tmp'
-
-
-class FontInfo(object):
- family = None
- style = None
- version = None
- ends_in_regular = False
- fullname = None
-
-
-class InvalidFontException(Exception):
- pass
-
-
-# These constants represent the value of nameID parameter in the namerecord for
-# different information.
-# see http://scripts.sil.org/cms/scripts/page.php?item_id=IWS-Chapter08#3054f18b
-NAMEID_FAMILY = 1
-NAMEID_STYLE = 2
-NAMEID_FULLNAME = 4
-NAMEID_VERSION = 5
-
-
-def main(argv):
- if len(argv) < 2:
- sys.exit('Usage: build_font.py /path/to/input_fonts/ /path/to/out/dir/')
- for directory in argv:
- if not os.path.isdir(directory):
- sys.exit(directory + ' is not a valid directory')
- global dest_dir
- dest_dir = argv[-1]
- src_dirs = argv[:-1]
- cwd = os.getcwd()
- os.chdir(dest_dir)
- files = glob.glob('*')
- for filename in files:
- os.remove(filename)
- os.chdir(cwd)
- input_fonts = list()
- for src_dir in src_dirs:
- for dirname, dirnames, filenames in os.walk(src_dir):
- for filename in filenames:
- input_path = os.path.join(dirname, filename)
- extension = os.path.splitext(filename)[1].lower()
- if extension == '.ttf':
- input_fonts.append(input_path)
- elif extension == '.xml':
- shutil.copy(input_path, dest_dir)
- if '.git' in dirnames:
- # don't go into any .git directories.
- dirnames.remove('.git')
- # Create as many threads as the number of CPUs
- pool = Pool(processes=None)
- pool.map(convert_font, input_fonts)
-
-
-def convert_font(input_path):
- filename = os.path.basename(input_path)
- print 'Converting font: ' + filename
- # the path to the output file. The file name is the fontfilename.ttx
- ttx_path = os.path.join(dest_dir, filename)
- ttx_path = ttx_path[:-1] + 'x'
- try:
- # run ttx to generate an xml file in the output folder which represents all
- # its info
- ttx_args = ['-q', '-d', dest_dir, input_path]
- ttx.main(ttx_args)
- # now parse the xml file to change its PS name.
- tree = etree.parse(ttx_path)
- root = tree.getroot()
- for name in root.iter('name'):
- update_tag(name, get_font_info(name))
- tree.write(ttx_path, xml_declaration=True, encoding='utf-8')
- # generate the udpated font now.
- ttx_args = ['-q', '-d', dest_dir, ttx_path]
- ttx.main(ttx_args)
- except InvalidFontException:
- # In case of invalid fonts, we exit.
- print filename + ' is not a valid font'
- raise
- except Exception as e:
- print 'Error converting font: ' + filename
- print e
- # Some fonts are too big to be handled by the ttx library.
- # Just copy paste them.
- shutil.copy(input_path, dest_dir)
- try:
- # delete the temp ttx file is it exists.
- os.remove(ttx_path)
- except OSError:
- pass
-
-
-def get_font_info(tag):
- """ Returns a list of FontInfo representing the various sets of namerecords
- found in the name table of the font. """
- fonts = []
- font = None
- last_name_id = sys.maxint
- for namerecord in tag.iter('namerecord'):
- if 'nameID' in namerecord.attrib:
- name_id = int(namerecord.attrib['nameID'])
- # A new font should be created for each platform, encoding and language
- # id. But, since the nameIDs are sorted, we use the easy approach of
- # creating a new one when the nameIDs reset.
- if name_id <= last_name_id and font is not None:
- fonts.append(font)
- font = None
- last_name_id = name_id
- if font is None:
- font = FontInfo()
- if name_id == NAMEID_FAMILY:
- font.family = namerecord.text.strip()
- if name_id == NAMEID_STYLE:
- font.style = namerecord.text.strip()
- if name_id == NAMEID_FULLNAME:
- font.ends_in_regular = ends_in_regular(namerecord.text)
- font.fullname = namerecord.text.strip()
- if name_id == NAMEID_VERSION:
- font.version = get_version(namerecord.text)
- if font is not None:
- fonts.append(font)
- return fonts
-
-
-def update_tag(tag, fonts):
- last_name_id = sys.maxint
- fonts_iterator = fonts.__iter__()
- font = None
- for namerecord in tag.iter('namerecord'):
- if 'nameID' in namerecord.attrib:
- name_id = int(namerecord.attrib['nameID'])
- if name_id <= last_name_id:
- font = fonts_iterator.next()
- font = update_font_name(font)
- last_name_id = name_id
- if name_id == NAMEID_FAMILY:
- namerecord.text = font.family
- if name_id == NAMEID_FULLNAME:
- namerecord.text = font.fullname
-
-
-def update_font_name(font):
- """ Compute the new font family name and font fullname. If the font has a
- valid version, it's sanitized and appended to the font family name. The
- font fullname is then created by joining the new family name and the
- style. If the style is 'Regular', it is appended only if the original font
- had it. """
- if font.family is None or font.style is None:
- raise InvalidFontException('Font doesn\'t have proper family name or style')
- if font.version is not None:
- new_family = font.family + font.version
- else:
- new_family = font.family
- if font.style is 'Regular' and not font.ends_in_regular:
- font.fullname = new_family
- else:
- font.fullname = new_family + ' ' + font.style
- font.family = new_family
- return font
-
-
-def ends_in_regular(string):
- """ According to the specification, the font fullname should not end in
- 'Regular' for plain fonts. However, some fonts don't obey this rule. We
- keep the style info, to minimize the diff. """
- string = string.strip().split()[-1]
- return string is 'Regular'
-
-
-def get_version(string):
- string = string.strip()
- # The spec says that the version string should start with "Version ". But not
- # all fonts do. So, we return the complete string if it doesn't start with
- # the prefix, else we return the rest of the string after sanitizing it.
- prefix = 'Version '
- if string.startswith(prefix):
- string = string[len(prefix):]
- return sanitize(string)
-
-
-def sanitize(string):
- """ Remove non-standard chars. """
- return re.sub(r'[^\w-]+', '', string)
-
-if __name__ == '__main__':
- main(sys.argv[1:])
diff --git a/tools/layoutlib/rename_font/build_font_single.py b/tools/layoutlib/rename_font/build_font_single.py
deleted file mode 100755
index 4245cdc..0000000
--- a/tools/layoutlib/rename_font/build_font_single.py
+++ /dev/null
@@ -1,211 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (C) 2014 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.
-
-"""
-Rename the PS name of the input font.
-
-OpenType fonts (*.otf) are not currently supported. They are copied to the destination without renaming.
-XML files are also copied in case they are passed there by mistake.
-
-Usage: build_font_single.py /path/to/input_font.ttf /path/to/output_font.ttf
-
-"""
-
-import glob
-import os
-import re
-import shutil
-import sys
-import xml.etree.ElementTree as etree
-
-# Prevent .pyc files from being created.
-sys.dont_write_bytecode = True
-
-# fontTools is available at platform/external/fonttools
-from fontTools import ttx
-
-
-class FontInfo(object):
- family = None
- style = None
- version = None
- ends_in_regular = False
- fullname = None
-
-
-class InvalidFontException(Exception):
- pass
-
-
-# A constant to copy the font without modifying. This is useful when running
-# locally and speed up the time to build the SDK.
-COPY_ONLY = False
-
-# These constants represent the value of nameID parameter in the namerecord for
-# different information.
-# see http://scripts.sil.org/cms/scripts/page.php?item_id=IWS-Chapter08#3054f18b
-NAMEID_FAMILY = 1
-NAMEID_STYLE = 2
-NAMEID_FULLNAME = 4
-NAMEID_VERSION = 5
-
-# A list of extensions to process.
-EXTENSIONS = ['.ttf', '.otf', '.xml']
-
-def main(argv):
- if len(argv) < 2:
- print 'Incorrect usage: ' + str(argv)
- sys.exit('Usage: build_font_single.py /path/to/input/font.ttf /path/to/out/font.ttf')
- dest_path = argv[-1]
- input_path = argv[0]
- extension = os.path.splitext(input_path)[1].lower()
- if extension in EXTENSIONS:
- if not COPY_ONLY and extension == '.ttf':
- convert_font(input_path, dest_path)
- return
- shutil.copy(input_path, dest_path)
-
-
-def convert_font(input_path, dest_path):
- filename = os.path.basename(input_path)
- print 'Converting font: ' + filename
- # the path to the output file. The file name is the fontfilename.ttx
- ttx_path = dest_path[:-1] + 'x'
- try:
- # run ttx to generate an xml file in the output folder which represents all
- # its info
- ttx_args = ['-q', '-o', ttx_path, input_path]
- ttx.main(ttx_args)
- # now parse the xml file to change its PS name.
- tree = etree.parse(ttx_path)
- root = tree.getroot()
- for name in root.iter('name'):
- update_tag(name, get_font_info(name))
- tree.write(ttx_path, xml_declaration=True, encoding='utf-8')
- # generate the udpated font now.
- ttx_args = ['-q', '-o', dest_path, ttx_path]
- ttx.main(ttx_args)
- except InvalidFontException:
- # In case of invalid fonts, we exit.
- print filename + ' is not a valid font'
- raise
- except Exception as e:
- print 'Error converting font: ' + filename
- print e
- # Some fonts are too big to be handled by the ttx library.
- # Just copy paste them.
- shutil.copy(input_path, dest_path)
- try:
- # delete the temp ttx file is it exists.
- os.remove(ttx_path)
- except OSError:
- pass
-
-
-def get_font_info(tag):
- """ Returns a list of FontInfo representing the various sets of namerecords
- found in the name table of the font. """
- fonts = []
- font = None
- last_name_id = sys.maxint
- for namerecord in tag.iter('namerecord'):
- if 'nameID' in namerecord.attrib:
- name_id = int(namerecord.attrib['nameID'])
- # A new font should be created for each platform, encoding and language
- # id. But, since the nameIDs are sorted, we use the easy approach of
- # creating a new one when the nameIDs reset.
- if name_id <= last_name_id and font is not None:
- fonts.append(font)
- font = None
- last_name_id = name_id
- if font is None:
- font = FontInfo()
- if name_id == NAMEID_FAMILY:
- font.family = namerecord.text.strip()
- if name_id == NAMEID_STYLE:
- font.style = namerecord.text.strip()
- if name_id == NAMEID_FULLNAME:
- font.ends_in_regular = ends_in_regular(namerecord.text)
- font.fullname = namerecord.text.strip()
- if name_id == NAMEID_VERSION:
- font.version = get_version(namerecord.text)
- if font is not None:
- fonts.append(font)
- return fonts
-
-
-def update_tag(tag, fonts):
- last_name_id = sys.maxint
- fonts_iterator = fonts.__iter__()
- font = None
- for namerecord in tag.iter('namerecord'):
- if 'nameID' in namerecord.attrib:
- name_id = int(namerecord.attrib['nameID'])
- if name_id <= last_name_id:
- font = fonts_iterator.next()
- font = update_font_name(font)
- last_name_id = name_id
- if name_id == NAMEID_FAMILY:
- namerecord.text = font.family
- if name_id == NAMEID_FULLNAME:
- namerecord.text = font.fullname
-
-
-def update_font_name(font):
- """ Compute the new font family name and font fullname. If the font has a
- valid version, it's sanitized and appended to the font family name. The
- font fullname is then created by joining the new family name and the
- style. If the style is 'Regular', it is appended only if the original font
- had it. """
- if font.family is None or font.style is None:
- raise InvalidFontException('Font doesn\'t have proper family name or style')
- if font.version is not None:
- new_family = font.family + font.version
- else:
- new_family = font.family
- if font.style is 'Regular' and not font.ends_in_regular:
- font.fullname = new_family
- else:
- font.fullname = new_family + ' ' + font.style
- font.family = new_family
- return font
-
-
-def ends_in_regular(string):
- """ According to the specification, the font fullname should not end in
- 'Regular' for plain fonts. However, some fonts don't obey this rule. We
- keep the style info, to minimize the diff. """
- string = string.strip().split()[-1]
- return string is 'Regular'
-
-
-def get_version(string):
- string = string.strip()
- # The spec says that the version string should start with "Version ". But not
- # all fonts do. So, we return the complete string if it doesn't start with
- # the prefix, else we return the rest of the string after sanitizing it.
- prefix = 'Version '
- if string.startswith(prefix):
- string = string[len(prefix):]
- return sanitize(string)
-
-
-def sanitize(string):
- """ Remove non-standard chars. """
- return re.sub(r'[^\w-]+', '', string)
-
-if __name__ == '__main__':
- main(sys.argv[1:])
diff --git a/tools/layoutlib/rename_font/test.py b/tools/layoutlib/rename_font/test.py
deleted file mode 100755
index 2ffddf4..0000000
--- a/tools/layoutlib/rename_font/test.py
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env python
-
-"""Tests build_font.py by renaming a font.
-
-The test copies Roboto-Regular.ttf to a tmp directory and ask build_font.py to rename it and put in another dir.
-We then use ttx to dump the new font to its xml and check if rename was successful
-
-To test locally, use:
-PYTHONPATH="$PYTHONPATH:/path/to/android/checkout/external/fonttools/Lib" ./test.py
-"""
-
-import unittest
-import build_font
-
-from fontTools import ttx
-import os
-import xml.etree.ElementTree as etree
-import shutil
-import tempfile
-
-class MyTest(unittest.TestCase):
- def test(self):
- font_name = "Roboto-Regular.ttf"
- srcdir = tempfile.mkdtemp()
- print "srcdir: " + srcdir
- shutil.copy(font_name, srcdir)
- destdir = tempfile.mkdtemp()
- print "destdir: " + destdir
- self.assertTrue(build_font.main([srcdir, destdir]) is None)
- out_path = os.path.join(destdir, font_name)
- ttx.main([out_path])
- ttx_path = out_path[:-1] + "x"
- tree = etree.parse(ttx_path)
- root = tree.getroot()
- name_tag = root.find('name')
- fonts = build_font.get_font_info(name_tag)
- shutil.rmtree(srcdir)
- shutil.rmtree(destdir)
- self.assertEqual(fonts[0].family, "Roboto1200310")
- self.assertEqual(fonts[0].fullname, "Roboto1200310 Regular")
-
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ErrorCatcher.java b/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ErrorCatcher.java
deleted file mode 100644
index ecf39b3..0000000
--- a/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ErrorCatcher.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2015 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.tools.idea.editors.theme.widgets;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * {@link ViewGroup} that wraps another view and catches any possible exceptions that the child view
- * might generate.
- * This is used by the theme editor to stop custom views from breaking the preview.
- */
-// TODO: This view is just a temporary solution that will be replaced by adding a try / catch
-// for custom views in the ClassConverter
-public class ErrorCatcher extends ViewGroup {
- public ErrorCatcher(Context context) {
- super(context);
- }
-
- public ErrorCatcher(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public ErrorCatcher(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public ErrorCatcher(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- assert getChildCount() == 1 : "ErrorCatcher can only have one child";
-
- View child = getChildAt(0);
- try {
- measureChild(child, widthMeasureSpec, heightMeasureSpec);
-
- setMeasuredDimension(resolveSize(child.getMeasuredWidth(), widthMeasureSpec),
- resolveSize(child.getMeasuredHeight(), heightMeasureSpec));
- } catch (Throwable t) {
- Bridge.getLog().warning(LayoutLog.TAG_BROKEN, "Failed to do onMeasure for view " +
- child.getClass().getCanonicalName(), t);
- setMeasuredDimension(resolveSize(0, widthMeasureSpec),
- resolveSize(0, heightMeasureSpec));
- }
- }
-
- @Override
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- try {
- return super.drawChild(canvas, child, drawingTime);
- } catch (Throwable t) {
- Bridge.getLog().warning(LayoutLog.TAG_BROKEN, "Failed to draw for view " +
- child.getClass().getCanonicalName(), t);
- }
-
- return false;
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- assert getChildCount() == 1 : "ErrorCatcher can only have one child";
-
- View child = getChildAt(0);
- try {
- child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
- } catch (Throwable e) {
- Bridge.getLog().warning(LayoutLog.TAG_BROKEN, "Failed to do onLayout for view " +
- child.getClass().getCanonicalName(), e);
- }
- }
-}
diff --git a/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/PressedButton.java b/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/PressedButton.java
deleted file mode 100644
index 4320157..0000000
--- a/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/PressedButton.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2015 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.tools.idea.editors.theme.widgets;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.Button;
-
-@SuppressWarnings("unused")
-public class PressedButton extends Button {
- public PressedButton(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- setPressed(true);
- jumpDrawablesToCurrentState();
- }
-}
diff --git a/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ThemePreviewLayout.java b/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ThemePreviewLayout.java
deleted file mode 100644
index af89910..0000000
--- a/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ThemePreviewLayout.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2015 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.tools.idea.editors.theme.widgets;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Custom layout used in the theme editor to display the component preview. It arranges the child
- * Views as a grid of cards.
- * <p/>
- * The Views are measured and the maximum width and height are used to dimension all the child
- * components. Any margin attributes from the children are ignored and only the item_margin element
- * is used.
- */
-@SuppressWarnings("unused")
-public class ThemePreviewLayout extends ViewGroup {
- private final int mMaxColumns;
- private final int mMaxColumnWidth;
- private final int mMinColumnWidth;
- private final int mItemHorizontalMargin;
- private final int mItemVerticalMargin;
-
- /** Item width to use for every card component. This includes margins. */
- private int mItemWidth;
- /** Item height to use for every card component. This includes margins. */
- private int mItemHeight;
-
- /** Calculated number of columns */
- private int mNumColumns;
-
- public ThemePreviewLayout(Context context) {
- this(context, null);
- }
-
- public ThemePreviewLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ThemePreviewLayout(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
-
- if (attrs == null) {
- mMaxColumnWidth = Integer.MAX_VALUE;
- mMinColumnWidth = 0;
- mMaxColumns = Integer.MAX_VALUE;
- mItemHorizontalMargin = 0;
- mItemVerticalMargin = 0;
- return;
- }
-
- DisplayMetrics dm = getResources().getDisplayMetrics();
- int maxColumnWidth = attrs.getAttributeIntValue(null, "max_column_width", Integer
- .MAX_VALUE);
- int minColumnWidth = attrs.getAttributeIntValue(null, "min_column_width", 0);
- int itemHorizontalMargin = attrs.getAttributeIntValue(null, "item_horizontal_margin", 0);
- int itemVerticalMargin = attrs.getAttributeIntValue(null, "item_vertical_margin", 0);
-
- mMaxColumnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- maxColumnWidth,
- dm);
- mMinColumnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- minColumnWidth,
- dm);
- mItemHorizontalMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- itemHorizontalMargin,
- dm);
- mItemVerticalMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- itemVerticalMargin,
- dm);
- mMaxColumns = attrs.getAttributeIntValue(null, "max_columns", Integer.MAX_VALUE);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // Measure the column size.
- // The column has a minimum width that will be used to calculate the maximum number of
- // columns that we can fit in the available space.
- //
- // Once we have the maximum number of columns, we will span all columns width evenly to fill
- // all the available space.
- int wSize = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
-
- // Calculate the desired width of all columns and take the maximum.
- // This step can be skipped if we have a fixed column height so we do not have to
- // dynamically calculate it.
- int childWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- int itemWidth = 0;
- int itemHeight = 0;
- for (int i = 0; i < getChildCount(); i++) {
- View v = getChildAt(i);
-
- if (v.getVisibility() == GONE) {
- continue;
- }
-
- measureChild(v, childWidthSpec, childHeightSpec);
-
- itemWidth = Math.max(itemWidth, v.getMeasuredWidth());
- itemHeight = Math.max(itemHeight, v.getMeasuredHeight());
- }
-
- itemWidth = Math.min(Math.max(itemWidth, mMinColumnWidth), mMaxColumnWidth);
- mNumColumns = Math.min((int) Math.ceil((double) wSize / itemWidth), mMaxColumns);
-
- // Check how much space this distribution would take taking into account the margins.
- // If it's bigger than what we have, remove one column.
- int wSizeNeeded = mNumColumns * itemWidth + (mNumColumns - 1) * mItemHorizontalMargin;
- if (wSizeNeeded > wSize && mNumColumns > 1) {
- mNumColumns--;
- }
-
- if (getChildCount() < mNumColumns) {
- mNumColumns = getChildCount();
- }
- if (mNumColumns == 0) {
- mNumColumns = 1;
- }
-
- // Inform each child of the measurement
- childWidthSpec = MeasureSpec.makeMeasureSpec(itemWidth, MeasureSpec.EXACTLY);
- childHeightSpec = MeasureSpec.makeMeasureSpec(itemHeight, MeasureSpec.EXACTLY);
- for (int i = 0; i < getChildCount(); i++) {
- View v = getChildAt(i);
-
- if (v.getVisibility() == GONE) {
- continue;
- }
-
- measureChild(v, childWidthSpec, childHeightSpec);
- }
-
- // Calculate the height of the first column to measure our own size
- int firstColumnItems = getChildCount() / mNumColumns + ((getChildCount() % mNumColumns) > 0
- ? 1 : 0);
-
- int horizontalMarginsTotalWidth = (mNumColumns - 1) * mItemHorizontalMargin;
- int verticalMarginsTotalHeight = (firstColumnItems - 1) * mItemVerticalMargin;
- int totalWidth = mNumColumns * itemWidth + horizontalMarginsTotalWidth +
- mPaddingRight + mPaddingLeft;
- int totalHeight = firstColumnItems * itemHeight + verticalMarginsTotalHeight +
- mPaddingBottom + mPaddingTop;
-
- setMeasuredDimension(resolveSize(totalWidth, widthMeasureSpec),
- resolveSize(totalHeight, heightMeasureSpec));
-
- mItemWidth = itemWidth;
- mItemHeight = itemHeight;
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int itemsPerColumn = getChildCount() / mNumColumns;
- // The remainder items are distributed one per column.
- int remainderItems = getChildCount() % mNumColumns;
-
- int x = mPaddingLeft;
- int y = mPaddingTop;
- int position = 1;
- for (int i = 0; i < getChildCount(); i++) {
- View v = getChildAt(i);
- v.layout(x,
- y,
- x + mItemWidth,
- y + mItemHeight);
-
- if (position == itemsPerColumn + (remainderItems > 0 ? 1 : 0)) {
- // Break column
- position = 1;
- remainderItems--;
- x += mItemWidth + mItemHorizontalMargin;
- y = mPaddingTop;
- } else {
- position++;
- y += mItemHeight + mItemVerticalMargin;
- }
- }
- }
-}
-
-
diff --git a/tools/layoutlib/studio-custom-widgets/studio-android-widgets.iml b/tools/layoutlib/studio-custom-widgets/studio-android-widgets.iml
deleted file mode 100644
index b0363d7..0000000
--- a/tools/layoutlib/studio-custom-widgets/studio-android-widgets.iml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" inherit-compiler-output="true">
- <exclude-output />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
- </content>
- <orderEntry type="inheritedJdk" />
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="library" name="layoutlib_api-prebuilt" level="project" />
- <orderEntry type="library" name="framework.jar" level="project" />
- <orderEntry type="module" module-name="bridge" />
- </component>
-</module>
\ No newline at end of file
diff --git a/tools/obbtool/Android.mk b/tools/obbtool/Android.mk
index 1b1f63e..6dc306e 100644
--- a/tools/obbtool/Android.mk
+++ b/tools/obbtool/Android.mk
@@ -39,7 +39,7 @@
LOCAL_CFLAGS := -Wall -Werror -Wno-mismatched-tags
LOCAL_SRC_FILES := pbkdf2gen.cpp
LOCAL_LDLIBS += -ldl
-LOCAL_STATIC_LIBRARIES := libcrypto_static
+LOCAL_STATIC_LIBRARIES := libcrypto
include $(BUILD_HOST_EXECUTABLE)
diff --git a/wifi/java/android/net/wifi/WifiSsid.java b/wifi/java/android/net/wifi/WifiSsid.java
index 7a3cddf..5deb80a 100644
--- a/wifi/java/android/net/wifi/WifiSsid.java
+++ b/wifi/java/android/net/wifi/WifiSsid.java
@@ -26,6 +26,7 @@
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
+import java.util.Arrays;
import java.util.Locale;
/**
@@ -189,6 +190,23 @@
return out.toString();
}
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof WifiSsid)) {
+ return false;
+ }
+ WifiSsid that = (WifiSsid) thatObject;
+ return Arrays.equals(octets.toByteArray(), that.octets.toByteArray());
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(octets.toByteArray());
+ }
+
private boolean isArrayAllZeroes(byte[] ssidBytes) {
for (int i = 0; i< ssidBytes.length; i++) {
if (ssidBytes[i] != 0) return false;
diff --git a/wifi/java/android/net/wifi/aware/DiscoverySession.java b/wifi/java/android/net/wifi/aware/DiscoverySession.java
index c7e1fc7..06e3867 100644
--- a/wifi/java/android/net/wifi/aware/DiscoverySession.java
+++ b/wifi/java/android/net/wifi/aware/DiscoverySession.java
@@ -130,8 +130,11 @@
@Override
protected void finalize() throws Throwable {
try {
- if (!mTerminated) {
+ if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
+ }
+
+ if (!mTerminated) {
close();
}
} finally {
@@ -339,8 +342,8 @@
*/
public NetworkSpecifier createNetworkSpecifierPassphrase(
@Nullable PeerHandle peerHandle, @NonNull String passphrase) {
- if (passphrase == null || passphrase.length() == 0) {
- throw new IllegalArgumentException("Passphrase must not be null or empty");
+ if (!WifiAwareUtils.validatePassphrase(passphrase)) {
+ throw new IllegalArgumentException("Passphrase must meet length requirements");
}
if (mTerminated) {
@@ -402,8 +405,8 @@
@SystemApi
public NetworkSpecifier createNetworkSpecifierPmk(@Nullable PeerHandle peerHandle,
@NonNull byte[] pmk) {
- if (pmk == null || pmk.length == 0) {
- throw new IllegalArgumentException("PMK must not be null or empty");
+ if (!WifiAwareUtils.validatePmk(pmk)) {
+ throw new IllegalArgumentException("PMK must 32 bytes");
}
if (mTerminated) {
diff --git a/wifi/java/android/net/wifi/aware/DiscoverySessionCallback.java b/wifi/java/android/net/wifi/aware/DiscoverySessionCallback.java
index d8667e6..115b86d 100644
--- a/wifi/java/android/net/wifi/aware/DiscoverySessionCallback.java
+++ b/wifi/java/android/net/wifi/aware/DiscoverySessionCallback.java
@@ -100,7 +100,12 @@
* @param serviceSpecificInfo The service specific information (arbitrary
* byte array) provided by the peer as part of its discovery
* configuration.
- * @param matchFilter The filter which resulted in this service discovery.
+ * @param matchFilter The filter which resulted in this service discovery. For
+ * {@link PublishConfig#PUBLISH_TYPE_UNSOLICITED},
+ * {@link SubscribeConfig#SUBSCRIBE_TYPE_PASSIVE} discovery sessions this is the publisher's
+ * match filter. For {@link PublishConfig#PUBLISH_TYPE_SOLICITED},
+ * {@link SubscribeConfig#SUBSCRIBE_TYPE_ACTIVE} discovery sessions this
+ * is the subscriber's match filter.
*/
public void onServiceDiscovered(PeerHandle peerHandle,
byte[] serviceSpecificInfo, List<byte[]> matchFilter) {
diff --git a/wifi/java/android/net/wifi/aware/PublishConfig.java b/wifi/java/android/net/wifi/aware/PublishConfig.java
index 0f1e992..d018620 100644
--- a/wifi/java/android/net/wifi/aware/PublishConfig.java
+++ b/wifi/java/android/net/wifi/aware/PublishConfig.java
@@ -93,12 +93,17 @@
@Override
public String toString() {
- return "PublishConfig [mServiceName='" + mServiceName + ", mServiceSpecificInfo='" + (
- (mServiceSpecificInfo == null) ? "null" : HexEncoding.encode(mServiceSpecificInfo))
- + ", mMatchFilter=" + (new TlvBufferUtils.TlvIterable(0, 1,
- mMatchFilter)).toString() + ", mPublishType=" + mPublishType
- + ", mTtlSec=" + mTtlSec + ", mEnableTerminateNotification="
- + mEnableTerminateNotification + "]";
+ return "PublishConfig [mServiceName='" + (mServiceName == null ? "<null>" : String.valueOf(
+ HexEncoding.encode(mServiceName))) + ", mServiceName.length=" + (
+ mServiceName == null ? 0 : mServiceName.length) + ", mServiceSpecificInfo='" + (
+ (mServiceSpecificInfo == null) ? "<null>" : String.valueOf(
+ HexEncoding.encode(mServiceSpecificInfo)))
+ + ", mServiceSpecificInfo.length=" + (mServiceSpecificInfo == null ? 0
+ : mServiceSpecificInfo.length) + ", mMatchFilter="
+ + (new TlvBufferUtils.TlvIterable(0, 1, mMatchFilter)).toString()
+ + ", mMatchFilter.length=" + (mMatchFilter == null ? 0 : mMatchFilter.length)
+ + ", mPublishType=" + mPublishType + ", mTtlSec=" + mTtlSec
+ + ", mEnableTerminateNotification=" + mEnableTerminateNotification + "]";
}
@Override
diff --git a/wifi/java/android/net/wifi/aware/SubscribeConfig.java b/wifi/java/android/net/wifi/aware/SubscribeConfig.java
index 31e7e8e..4bf2fb6 100644
--- a/wifi/java/android/net/wifi/aware/SubscribeConfig.java
+++ b/wifi/java/android/net/wifi/aware/SubscribeConfig.java
@@ -92,13 +92,17 @@
@Override
public String toString() {
- return "SubscribeConfig [mServiceName='" + mServiceName + ", mServiceSpecificInfo='" + (
- (mServiceSpecificInfo == null) ? "null" : HexEncoding.encode(mServiceSpecificInfo))
- + ", mMatchFilter=" + (new TlvBufferUtils.TlvIterable(0, 1,
- mMatchFilter)).toString() + ", mSubscribeType=" + mSubscribeType
- + ", mTtlSec=" + mTtlSec
- + ", mEnableTerminateNotification=" + mEnableTerminateNotification
- + "]";
+ return "SubscribeConfig [mServiceName='" + (mServiceName == null ? "<null>"
+ : String.valueOf(HexEncoding.encode(mServiceName))) + ", mServiceName.length=" + (
+ mServiceName == null ? 0 : mServiceName.length) + ", mServiceSpecificInfo='" + (
+ (mServiceSpecificInfo == null) ? "<null>" : String.valueOf(
+ HexEncoding.encode(mServiceSpecificInfo)))
+ + ", mServiceSpecificInfo.length=" + (mServiceSpecificInfo == null ? 0
+ : mServiceSpecificInfo.length) + ", mMatchFilter="
+ + (new TlvBufferUtils.TlvIterable(0, 1, mMatchFilter)).toString()
+ + ", mMatchFilter.length=" + (mMatchFilter == null ? 0 : mMatchFilter.length)
+ + ", mSubscribeType=" + mSubscribeType + ", mTtlSec=" + mTtlSec
+ + ", mEnableTerminateNotification=" + mEnableTerminateNotification + "]";
}
@Override
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index c22daaf..df0d9d2 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -32,6 +32,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
@@ -453,7 +454,8 @@
peerHandle != null ? peerHandle.peerId : 0, // 0 is an invalid peer ID
null, // peerMac (not used in this method)
pmk,
- passphrase);
+ passphrase,
+ Process.myUid());
}
/** @hide */
@@ -490,7 +492,8 @@
0, // 0 is an invalid peer ID
peer,
pmk,
- passphrase);
+ passphrase,
+ Process.myUid());
}
private static class WifiAwareEventCallbackProxy extends IWifiAwareEventCallback.Stub {
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
index 5993480..eeabbfa 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
@@ -19,6 +19,7 @@
import android.net.NetworkSpecifier;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
import java.util.Arrays;
import java.util.Objects;
@@ -115,9 +116,18 @@
*/
public final String passphrase;
+ /**
+ * The UID of the process initializing this network specifier. Validated by receiver using
+ * checkUidIfNecessary() and is used by satisfiedBy() to determine whether matches the
+ * offered network.
+ *
+ * @hide
+ */
+ public final int requestorUid;
+
/** @hide */
public WifiAwareNetworkSpecifier(int type, int role, int clientId, int sessionId, int peerId,
- byte[] peerMac, byte[] pmk, String passphrase) {
+ byte[] peerMac, byte[] pmk, String passphrase, int requestorUid) {
this.type = type;
this.role = role;
this.clientId = clientId;
@@ -126,6 +136,7 @@
this.peerMac = peerMac;
this.pmk = pmk;
this.passphrase = passphrase;
+ this.requestorUid = requestorUid;
}
public static final Creator<WifiAwareNetworkSpecifier> CREATOR =
@@ -140,7 +151,8 @@
in.readInt(), // peerId
in.createByteArray(), // peerMac
in.createByteArray(), // pmk
- in.readString()); // passphrase
+ in.readString(), // passphrase
+ in.readInt()); // requestorUid
}
@Override
@@ -149,6 +161,16 @@
}
};
+ /**
+ * Indicates whether the network specifier specifies an OOB (out-of-band) data-path - i.e. a
+ * data-path created without a corresponding Aware discovery session.
+ *
+ * @hide
+ */
+ public boolean isOutOfBand() {
+ return type == NETWORK_SPECIFIER_TYPE_OOB || type == NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -164,6 +186,7 @@
dest.writeByteArray(peerMac);
dest.writeByteArray(pmk);
dest.writeString(passphrase);
+ dest.writeInt(requestorUid);
}
/** @hide */
@@ -186,6 +209,7 @@
result = 31 * result + Arrays.hashCode(peerMac);
result = 31 * result + Arrays.hashCode(pmk);
result = 31 * result + Objects.hashCode(passphrase);
+ result = 31 * result + requestorUid;
return result;
}
@@ -210,7 +234,8 @@
&& peerId == lhs.peerId
&& Arrays.equals(peerMac, lhs.peerMac)
&& Arrays.equals(pmk, lhs.pmk)
- && Objects.equals(passphrase, lhs.passphrase);
+ && Objects.equals(passphrase, lhs.passphrase)
+ && requestorUid == lhs.requestorUid;
}
/** @hide */
@@ -228,7 +253,16 @@
.append(", pmk=").append((pmk == null) ? "<null>" : "<non-null>")
// masking PII
.append(", passphrase=").append((passphrase == null) ? "<null>" : "<non-null>")
+ .append(", requestorUid=").append(requestorUid)
.append("]");
return sb.toString();
}
+
+ /** @hide */
+ @Override
+ public void assertValidFromUid(int requestorUid) {
+ if (this.requestorUid != requestorUid) {
+ throw new SecurityException("mismatched UIDs");
+ }
+ }
}
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareSession.java b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
index 4e060d5..babbed0 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareSession.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
@@ -83,8 +83,11 @@
@Override
protected void finalize() throws Throwable {
try {
- if (!mTerminated) {
+ if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
+ }
+
+ if (!mTerminated) {
close();
}
} finally {
@@ -266,9 +269,10 @@
Log.e(TAG, "createNetworkSpecifierPassphrase: called after termination");
return null;
}
- if (passphrase == null || passphrase.length() == 0) {
- throw new IllegalArgumentException("Passphrase must not be null or empty");
+ if (!WifiAwareUtils.validatePassphrase(passphrase)) {
+ throw new IllegalArgumentException("Passphrase must meet length requirements");
}
+
return mgr.createNetworkSpecifier(mClientId, role, peer, null, passphrase);
}
@@ -316,8 +320,8 @@
Log.e(TAG, "createNetworkSpecifierPmk: called after termination");
return null;
}
- if (pmk == null || pmk.length == 0) {
- throw new IllegalArgumentException("PMK must not be null or empty");
+ if (!WifiAwareUtils.validatePmk(pmk)) {
+ throw new IllegalArgumentException("PMK must 32 bytes");
}
return mgr.createNetworkSpecifier(mClientId, role, peer, pmk, null);
}
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareUtils.java b/wifi/java/android/net/wifi/aware/WifiAwareUtils.java
index 4083388..fda7a9a 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareUtils.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareUtils.java
@@ -16,6 +16,8 @@
package android.net.wifi.aware;
+import android.hardware.wifi.V1_0.Constants;
+
/**
* Provides utilities for the Wifi Aware manager/service.
*
@@ -51,4 +53,35 @@
++index;
}
}
+
+ /**
+ * Validates that the passphrase is a non-null string of the right size (per the HAL min/max
+ * length parameters).
+ *
+ * @param passphrase Passphrase to test
+ * @return true if passphrase is valid, false if not
+ */
+ public static boolean validatePassphrase(String passphrase) {
+ if (passphrase == null
+ || passphrase.length() < Constants.NanParamSizeLimits.MIN_PASSPHRASE_LENGTH
+ || passphrase.length() > Constants.NanParamSizeLimits.MAX_PASSPHRASE_LENGTH) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Validates that the PMK is a non-null byte array of the right size (32 bytes per spec).
+ *
+ * @param pmk PMK to test
+ * @return true if PMK is valid, false if not
+ */
+ public static boolean validatePmk(byte[] pmk) {
+ if (pmk == null || pmk.length != 32) {
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/wifi/java/android/net/wifi/hotspot2/OsuProvider.aidl b/wifi/java/android/net/wifi/hotspot2/OsuProvider.aidl
new file mode 100644
index 0000000..23d0f22
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/OsuProvider.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.net.wifi.hotspot2;
+
+parcelable OsuProvider;
diff --git a/wifi/java/android/net/wifi/hotspot2/OsuProvider.java b/wifi/java/android/net/wifi/hotspot2/OsuProvider.java
new file mode 100644
index 0000000..25dcdd8
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/OsuProvider.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.net.wifi.hotspot2;
+
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.net.wifi.WifiSsid;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Contained information for a Hotspot 2.0 OSU (Online Sign-Up provider).
+ *
+ * @hide
+ */
+public final class OsuProvider implements Parcelable {
+ /**
+ * OSU (Online Sign-Up) method: OMA DM (Open Mobile Alliance Device Management).
+ * For more info, refer to Section 8.3 of the Hotspot 2.0 Release 2 Technical Specification.
+ */
+ public static final int METHOD_OMA_DM = 0;
+
+ /**
+ * OSU (Online Sign-Up) method: SOAP XML SPP (Subscription Provisioning Protocol).
+ * For more info, refer to Section 8.4 of the Hotspot 2.0 Release 2 Technical Specification.
+ */
+ public static final int METHOD_SOAP_XML_SPP = 1;
+
+ /**
+ * SSID of the network to connect for service sign-up.
+ */
+ private final WifiSsid mOsuSsid;
+
+ /**
+ * Friendly name of the OSU provider.
+ */
+ private final String mFriendlyName;
+
+ /**
+ * Description of the OSU provider.
+ */
+ private final String mServiceDescription;
+
+ /**
+ * URI to browse to for service sign-up.
+ */
+ private final Uri mServerUri;
+
+ /**
+ * Network Access Identifier used for authenticating with the OSU network when OSEN is used.
+ */
+ private final String mNetworkAccessIdentifier;
+
+ /**
+ * List of OSU (Online Sign-Up) method supported.
+ */
+ private final List<Integer> mMethodList;
+
+ /**
+ * Icon data for the OSU (Online Sign-Up) provider.
+ */
+ private final Icon mIcon;
+
+ public OsuProvider(WifiSsid osuSsid, String friendlyName, String serviceDescription,
+ Uri serverUri, String nai, List<Integer> methodList, Icon icon) {
+ mOsuSsid = osuSsid;
+ mFriendlyName = friendlyName;
+ mServiceDescription = serviceDescription;
+ mServerUri = serverUri;
+ mNetworkAccessIdentifier = nai;
+ if (methodList == null) {
+ mMethodList = new ArrayList<>();
+ } else {
+ mMethodList = new ArrayList<>(methodList);
+ }
+ mIcon = icon;
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ */
+ public OsuProvider(OsuProvider source) {
+ if (source == null) {
+ mOsuSsid = null;
+ mFriendlyName = null;
+ mServiceDescription = null;
+ mServerUri = null;
+ mNetworkAccessIdentifier = null;
+ mMethodList = new ArrayList<>();
+ mIcon = null;
+ return;
+ }
+
+ mOsuSsid = source.mOsuSsid;
+ mFriendlyName = source.mFriendlyName;
+ mServiceDescription = source.mServiceDescription;
+ mServerUri = source.mServerUri;
+ mNetworkAccessIdentifier = source.mNetworkAccessIdentifier;
+ if (source.mMethodList == null) {
+ mMethodList = new ArrayList<>();
+ } else {
+ mMethodList = new ArrayList<>(source.mMethodList);
+ }
+ mIcon = source.mIcon;
+ }
+
+ public WifiSsid getOsuSsid() {
+ return mOsuSsid;
+ }
+
+ public String getFriendlyName() {
+ return mFriendlyName;
+ }
+
+ public String getServiceDescription() {
+ return mServiceDescription;
+ }
+
+ public Uri getServerUri() {
+ return mServerUri;
+ }
+
+ public String getNetworkAccessIdentifier() {
+ return mNetworkAccessIdentifier;
+ }
+
+ public List<Integer> getMethodList() {
+ return Collections.unmodifiableList(mMethodList);
+ }
+
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mOsuSsid, flags);
+ dest.writeString(mFriendlyName);
+ dest.writeString(mServiceDescription);
+ dest.writeParcelable(mServerUri, flags);
+ dest.writeString(mNetworkAccessIdentifier);
+ dest.writeList(mMethodList);
+ dest.writeParcelable(mIcon, flags);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof OsuProvider)) {
+ return false;
+ }
+ OsuProvider that = (OsuProvider) thatObject;
+ return (mOsuSsid == null ? that.mOsuSsid == null : mOsuSsid.equals(that.mOsuSsid))
+ && TextUtils.equals(mFriendlyName, that.mFriendlyName)
+ && TextUtils.equals(mServiceDescription, that.mServiceDescription)
+ && (mServerUri == null ? that.mServerUri == null
+ : mServerUri.equals(that.mServerUri))
+ && TextUtils.equals(mNetworkAccessIdentifier, that.mNetworkAccessIdentifier)
+ && (mMethodList == null ? that.mMethodList == null
+ : mMethodList.equals(that.mMethodList))
+ && (mIcon == null ? that.mIcon == null : mIcon.sameAs(that.mIcon));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mOsuSsid, mFriendlyName, mServiceDescription, mServerUri,
+ mNetworkAccessIdentifier, mMethodList, mIcon);
+ }
+
+ @Override
+ public String toString() {
+ return "OsuProvider{mOsuSsid=" + mOsuSsid
+ + " mFriendlyName=" + mFriendlyName
+ + " mServiceDescription=" + mServiceDescription
+ + " mServerUri=" + mServerUri
+ + " mNetworkAccessIdentifier=" + mNetworkAccessIdentifier
+ + " mMethodList=" + mMethodList
+ + " mIcon=" + mIcon;
+ }
+
+ public static final Creator<OsuProvider> CREATOR =
+ new Creator<OsuProvider>() {
+ @Override
+ public OsuProvider createFromParcel(Parcel in) {
+ WifiSsid osuSsid = (WifiSsid) in.readParcelable(null);
+ String friendlyName = in.readString();
+ String serviceDescription = in.readString();
+ Uri serverUri = (Uri) in.readParcelable(null);
+ String nai = in.readString();
+ List<Integer> methodList = new ArrayList<>();
+ in.readList(methodList, null);
+ Icon icon = (Icon) in.readParcelable(null);
+ return new OsuProvider(osuSsid, friendlyName, serviceDescription, serverUri,
+ nai, methodList, icon);
+ }
+
+ @Override
+ public OsuProvider[] newArray(int size) {
+ return new OsuProvider[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl b/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
index 8b1cfae..bfdd45d 100644
--- a/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
+++ b/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
@@ -25,8 +25,9 @@
*/
interface IWifiP2pManager
{
- Messenger getMessenger();
+ Messenger getMessenger(in IBinder binder);
Messenger getP2pStateMachineMessenger();
+ oneway void close(in IBinder binder);
void setMiracastMode(int mode);
void checkConfigureWifiDisplayPermission();
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 95d0a79..7f085f7 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -28,6 +28,7 @@
import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceInfo;
import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceResponse;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -290,6 +291,7 @@
"android.net.wifi.p2p.CALLING_PACKAGE";
IWifiP2pManager mService;
+ private final Map<Channel, Binder> mBinders = new HashMap<>();
private static final int BASE = Protocol.BASE_WIFI_P2P_MANAGER;
@@ -889,7 +891,10 @@
* @return Channel instance that is necessary for performing any further p2p operations
*/
public Channel initialize(Context srcContext, Looper srcLooper, ChannelListener listener) {
- return initalizeChannel(srcContext, srcLooper, listener, getMessenger());
+ Binder binder = new Binder();
+ Channel channel = initalizeChannel(srcContext, srcLooper, listener, getMessenger(binder));
+ mBinders.put(channel, binder);
+ return channel;
}
/**
@@ -1385,12 +1390,14 @@
* Get a reference to WifiP2pService handler. This is used to establish
* an AsyncChannel communication with WifiService
*
+ * @param binder A binder for the service to associate with this client.
+ *
* @return Messenger pointing to the WifiP2pService handler
* @hide
*/
- public Messenger getMessenger() {
+ public Messenger getMessenger(Binder binder) {
try {
- return mService.getMessenger();
+ return mService.getMessenger(binder);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1412,6 +1419,23 @@
}
/**
+ * Close the current P2P connection and clean-up any configuration requested by the
+ * current app. Takes same action as taken when the app dies.
+ *
+ * @param c is the channel created at {@link #initialize}
+ *
+ * @hide
+ */
+ public void close(Channel c) {
+ try {
+ mService.close(mBinders.get(c));
+ mBinders.remove(c);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Get a handover request message for use in WFA NFC Handover transfer.
* @hide
*/
diff --git a/wifi/tests/AndroidTest.xml b/wifi/tests/AndroidTest.xml
index c30dcea..764eb2b 100644
--- a/wifi/tests/AndroidTest.xml
+++ b/wifi/tests/AndroidTest.xml
@@ -20,7 +20,7 @@
<option name="test-suite-tag" value="apct" />
<option name="test-tag" value="FrameworksWifiApiTests" />
- <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.net.wifi.test" />
<option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
</test>
diff --git a/wifi/tests/src/android/net/wifi/WifiSsidTest.java b/wifi/tests/src/android/net/wifi/WifiSsidTest.java
index c7bdb7b..e5794c5 100644
--- a/wifi/tests/src/android/net/wifi/WifiSsidTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiSsidTest.java
@@ -16,25 +16,43 @@
package android.net.wifi;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import org.junit.Test;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
/**
* Unit tests for {@link android.net.wifi.WifiSsid}.
*/
public class WifiSsidTest {
- private static final byte[] TEST_SSID =
- new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+ private static final String TEST_SSID = "Test SSID";
+ private static final byte[] TEST_SSID_BYTES = TEST_SSID.getBytes(StandardCharsets.US_ASCII);
+
/**
* Check that createFromByteArray() works.
*/
@Test
public void testCreateFromByteArray() {
- WifiSsid wifiSsid = WifiSsid.createFromByteArray(TEST_SSID);
+ WifiSsid wifiSsid = WifiSsid.createFromByteArray(TEST_SSID_BYTES);
assertTrue(wifiSsid != null);
- assertEquals(new String(TEST_SSID), wifiSsid.toString());
+ assertEquals(TEST_SSID, wifiSsid.toString());
+ }
+
+ /**
+ * Verify that SSID created from byte array and string with the same content are equal.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testEquals() throws Exception {
+ WifiSsid fromBytes = WifiSsid.createFromByteArray(TEST_SSID_BYTES);
+ WifiSsid fromString = WifiSsid.createFromAsciiEncoded(TEST_SSID);
+ assertTrue(fromBytes != null);
+ assertTrue(fromString != null);
+ assertEquals(fromBytes, fromString);
}
}
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index 694b911..d9433c5 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -32,6 +32,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Parcel;
+import android.os.RemoteException;
import android.os.test.TestLooper;
import android.test.suitebuilder.annotation.SmallTest;
@@ -938,7 +939,7 @@
final int sessionId = 123;
final PeerHandle peerHandle = new PeerHandle(123412);
final int role = WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
- final byte[] pmk = "Some arbitrary byte array".getBytes();
+ final byte[] pmk = "01234567890123456789012345678901".getBytes();
final String passphrase = "A really bad password";
final ConfigRequest configRequest = new ConfigRequest.Builder().build();
final PublishConfig publishConfig = new PublishConfig.Builder().build();
@@ -1019,7 +1020,7 @@
final ConfigRequest configRequest = new ConfigRequest.Builder().build();
final byte[] someMac = HexEncoding.decode("000102030405".toCharArray(), false);
final int role = WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR;
- final byte[] pmk = "Some arbitrary pmk data".getBytes();
+ final byte[] pmk = "01234567890123456789012345678901".getBytes();
final String passphrase = "A really bad password";
ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
@@ -1070,4 +1071,162 @@
verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
mockPublishSession, mockRttListener);
}
+
+ /**
+ * Validate that a null PMK triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierWithClientNullPmk() throws Exception {
+ executeNetworkSpecifierWithClient(true, null, null);
+ }
+
+ /**
+ * Validate that a non-32-bytes PMK triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierWithClientIncorrectLengthPmk() throws Exception {
+ executeNetworkSpecifierWithClient(true, "012".getBytes(), null);
+ }
+
+ /**
+ * Validate that a null Passphrase triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierWithClientNullPassphrase() throws Exception {
+ executeNetworkSpecifierWithClient(false, null, null);
+ }
+
+ /**
+ * Validate that a too short Passphrase triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierWithClientShortPassphrase() throws Exception {
+ executeNetworkSpecifierWithClient(false, null, "012");
+ }
+
+ /**
+ * Validate that a too long Passphrase triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierWithClientLongPassphrase() throws Exception {
+ executeNetworkSpecifierWithClient(false, null,
+ "0123456789012345678901234567890123456789012345678901234567890123456789");
+ }
+
+ private void executeNetworkSpecifierWithClient(boolean doPmk, byte[] pmk, String passphrase)
+ throws Exception {
+ final int clientId = 4565;
+ final int sessionId = 123;
+ final PeerHandle peerHandle = new PeerHandle(123412);
+ final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+ final PublishConfig publishConfig = new PublishConfig.Builder().build();
+
+ ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
+ WifiAwareSession.class);
+ ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
+ .forClass(IWifiAwareEventCallback.class);
+ ArgumentCaptor<IWifiAwareDiscoverySessionCallback> sessionProxyCallback = ArgumentCaptor
+ .forClass(IWifiAwareDiscoverySessionCallback.class);
+ ArgumentCaptor<PublishDiscoverySession> publishSession = ArgumentCaptor
+ .forClass(PublishDiscoverySession.class);
+
+ InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService,
+ mockPublishSession, mockRttListener);
+
+ // (1) connect successfully
+ mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
+ inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+ eq(configRequest), eq(false));
+ clientProxyCallback.getValue().onConnectSuccess(clientId);
+ mMockLooper.dispatchAll();
+ inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
+ WifiAwareSession session = sessionCaptor.getValue();
+
+ // (2) publish successfully
+ session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
+ inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
+ sessionProxyCallback.capture());
+ sessionProxyCallback.getValue().onSessionStarted(sessionId);
+ mMockLooper.dispatchAll();
+ inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
+
+ // (3) create network specifier
+ if (doPmk) {
+ publishSession.getValue().createNetworkSpecifierPmk(peerHandle, pmk);
+ } else {
+ publishSession.getValue().createNetworkSpecifierPassphrase(peerHandle, passphrase);
+ }
+ }
+
+ /**
+ * Validate that a null PMK triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierDirectNullPmk() throws Exception {
+ executeNetworkSpecifierDirect(true, null, null);
+ }
+
+ /**
+ * Validate that a non-32-bytes PMK triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierDirectIncorrectLengthPmk() throws Exception {
+ executeNetworkSpecifierDirect(true, "012".getBytes(), null);
+ }
+
+ /**
+ * Validate that a null Passphrase triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierDirectNullPassphrase() throws Exception {
+ executeNetworkSpecifierDirect(false, null, null);
+ }
+
+ /**
+ * Validate that a too short Passphrase triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierDirectShortPassphrase() throws Exception {
+ executeNetworkSpecifierDirect(false, null, "012");
+ }
+
+ /**
+ * Validate that a too long Passphrase triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierDirectLongPassphrase() throws Exception {
+ executeNetworkSpecifierDirect(false, null,
+ "0123456789012345678901234567890123456789012345678901234567890123456789");
+ }
+
+ private void executeNetworkSpecifierDirect(boolean doPmk, byte[] pmk, String passphrase)
+ throws Exception {
+ final int clientId = 134;
+ final byte[] someMac = HexEncoding.decode("000102030405".toCharArray(), false);
+ final int role = WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR;
+ final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+ ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
+ WifiAwareSession.class);
+ ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
+ .forClass(IWifiAwareEventCallback.class);
+
+ InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService,
+ mockPublishSession, mockRttListener);
+
+ // (1) connect successfully
+ mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
+ inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+ eq(configRequest), eq(false));
+ clientProxyCallback.getValue().onConnectSuccess(clientId);
+ mMockLooper.dispatchAll();
+ inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
+
+ // (2) create network specifier
+ if (doPmk) {
+ sessionCaptor.getValue().createNetworkSpecifierPmk(role, someMac, pmk);
+ } else {
+ sessionCaptor.getValue().createNetworkSpecifierPassphrase(role, someMac, passphrase);
+ }
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/OsuProviderTest.java b/wifi/tests/src/android/net/wifi/hotspot2/OsuProviderTest.java
new file mode 100644
index 0000000..9670bfa
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/OsuProviderTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.net.wifi.hotspot2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.net.wifi.WifiSsid;
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.OsuProvider}.
+ */
+@SmallTest
+public class OsuProviderTest {
+ private static final WifiSsid TEST_SSID =
+ WifiSsid.createFromByteArray("TEST SSID".getBytes(StandardCharsets.UTF_8));
+ private static final String TEST_FRIENDLY_NAME = "Friendly Name";
+ private static final String TEST_SERVICE_DESCRIPTION = "Dummy Service";
+ private static final Uri TEST_SERVER_URI = Uri.parse("https://test.com");
+ private static final String TEST_NAI = "test.access.com";
+ private static final List<Integer> TEST_METHOD_LIST =
+ Arrays.asList(OsuProvider.METHOD_SOAP_XML_SPP);
+ private static final Icon TEST_ICON = Icon.createWithData(new byte[10], 0, 10);
+
+ /**
+ * Verify parcel write and read consistency for the given {@link OsuProvider}.
+ *
+ * @param writeInfo The {@link OsuProvider} to verify
+ * @throws Exception
+ */
+ private static void verifyParcel(OsuProvider writeInfo) throws Exception {
+ Parcel parcel = Parcel.obtain();
+ writeInfo.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ OsuProvider readInfo = OsuProvider.CREATOR.createFromParcel(parcel);
+ assertEquals(writeInfo, readInfo);
+ }
+
+ /**
+ * Verify parcel read/write for an OSU provider containing no information.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithEmptyProviderInfo() throws Exception {
+ verifyParcel(new OsuProvider(null, null, null, null, null, null, null));
+ }
+
+ /**
+ * Verify parcel read/write for an OSU provider containing full information.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithFullProviderInfo() throws Exception {
+ verifyParcel(new OsuProvider(TEST_SSID, TEST_FRIENDLY_NAME, TEST_SERVICE_DESCRIPTION,
+ TEST_SERVER_URI, TEST_NAI, TEST_METHOD_LIST, TEST_ICON));
+ }
+
+ /**
+ * Verify copy constructor with a null source.
+ * @throws Exception
+ */
+ @Test
+ public void verifyCopyConstructorWithNullSource() throws Exception {
+ OsuProvider expected = new OsuProvider(null, null, null, null, null, null, null);
+ assertEquals(expected, new OsuProvider(null));
+ }
+
+ /**
+ * Verify copy constructor with a valid source.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyCopyConstructorWithValidSource() throws Exception {
+ OsuProvider source = new OsuProvider(TEST_SSID, TEST_FRIENDLY_NAME, TEST_SERVICE_DESCRIPTION,
+ TEST_SERVER_URI, TEST_NAI, TEST_METHOD_LIST, TEST_ICON);
+ assertEquals(source, new OsuProvider(source));
+ }
+
+ /**
+ * Verify getter methods.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyGetters() throws Exception {
+ OsuProvider provider = new OsuProvider(TEST_SSID, TEST_FRIENDLY_NAME,
+ TEST_SERVICE_DESCRIPTION, TEST_SERVER_URI, TEST_NAI, TEST_METHOD_LIST, TEST_ICON);
+ assertTrue(TEST_SSID.equals(provider.getOsuSsid()));
+ assertTrue(TEST_FRIENDLY_NAME.equals(provider.getFriendlyName()));
+ assertTrue(TEST_SERVICE_DESCRIPTION.equals(provider.getServiceDescription()));
+ assertTrue(TEST_SERVER_URI.equals(provider.getServerUri()));
+ assertTrue(TEST_NAI.equals(provider.getNetworkAccessIdentifier()));
+ assertTrue(TEST_METHOD_LIST.equals(provider.getMethodList()));
+ assertTrue(TEST_ICON.sameAs(provider.getIcon()));
+ }
+}